Better darkmode and chart.js start

This commit is contained in:
krzysztof Płaczek
2026-01-01 10:34:28 +01:00
parent 59c76c8414
commit 1535d450f3
16 changed files with 553 additions and 1685 deletions

34
src/chart.js Executable file
View File

@@ -0,0 +1,34 @@
import Chart from 'chart.js/auto'
const data = [
{year: 2009, count: 10},
{year: 2010, count: NaN},
{year: 2011, count: 20},
{year: 2012, count: 15},
{year: 2013, count: 25},
{year: 2014, count: 22},
{year: 2015, count: 30},
{year: 2016, count: 28},
];
// linechart.data.labels.push(label);
// linechart.data.datasets.forEach((dataset) => {
// dataset.data.push(newData);
// });
// linechart.update();
window.linechart = new Chart(
document.getElementById('acquisitions'),
{
type: 'bar',
data: {
labels: data.map(row => row.year),
datasets: [
{
label: 'Acquisitions by year',
data: data.map(row => row.count)
}
]
}
}
);

6
src/darkmodetoggle.js Executable file
View File

@@ -0,0 +1,6 @@
export default () => ({
mode(modeName) {
localStorage.theme = modeName;
document.documentElement.classList.toggle("dark", modeName==='dark');
}
});

844
src/generated.css Normal file → Executable file

File diff suppressed because one or more lines are too long

200
src/main.js Normal file → Executable file
View File

@@ -2,12 +2,17 @@ import './generated.css'
import Alpine from 'alpinejs'
import persist from '@alpinejs/persist'
import Chart from 'chart.js/auto'
import darkmodetoggle from './darkmodetoggle';
window.Alpine = Alpine;
Alpine.plugin(persist)
Alpine.data('darkmodetoggle', darkmodetoggle);
Alpine.data('bookmeterList', function () {
return {
chart: null,
chartPoints: null,
chartPublisher: null,
items: {},
filteredItems: [],
username: '',
@@ -17,15 +22,19 @@ Alpine.data('bookmeterList', function () {
nameFilter: '',
years: [2023, 2024, 2025],
year: this.$persist(['2025']).as('year-filter'),
orderColumn: ['order', 1],
orderColumn: ['order', 0],
advancedSearch: this.$persist(0).as('advanced-search'),
async init() {
this.$watch('nameFilter', () => this.updateFilter());
this.$watch('username', () => this.updateFilter());
this.$watch('title', () => this.updateFilter());
this.$watch('author', () => this.updateFilter());
this.$watch('publisher', () => this.updateFilter());
this.$watch('year', () => this.updateFilter());
this.$watch(() => JSON.stringify([
this.nameFilter,
this.username,
this.title,
this.author,
this.publisher,
this.year,
this.advancedSearch
]), () => this.updateFilter());
let qp = new URLSearchParams(window.location.search);
if(qp.get('filter')) this.nameFilter = qp.get('filter');
@@ -37,10 +46,129 @@ Alpine.data('bookmeterList', function () {
])
this.items = [...data1, ...data2, ...data3]
.filter(i => i.book.title.length > 0)
.map(i => ({
...i,
_year: i.created_at.substring(0, 4),
_username: i.username.toLowerCase(),
_title: i.book.title.toLowerCase(),
_author: i.book.author.toLowerCase(),
_publisher: i.book.publisher.toLowerCase(),
_search: (
i.username +
' ' + i.book.title +
' ' + i.book.author +
' ' + i.book.publisher
).toLowerCase()
}));
this.updateFilter();
// this.initChart();
},
updatechartnew(){
this.chart.data.datasets[0].data[0] = Math.random()*50;
Alpine.raw(this.chart).update('none');
this.updateChart()
},
// initChart() {
// const ctx = document.getElementById('statsChart').getContext('2d')
//
// this.chart = Alpine.raw(new Chart(ctx, {
// type: 'bar',
// data: {
// labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
// datasets: [{
// label: '# of Votes',
// data: [12, 19, 3, 5, 2, 3],
// borderWidth: 1
// }]
// }
// }));
//
// const chartPoints = document.getElementById('chartPoints').getContext('2d')
//
// const bubbleData = this.filteredItems.map(item => ({
// x: item.book.pages,
// y: item.book.rating,
// r: Math.sqrt(item.likes || 1) * 2, // scale likes
// format: item.book.format
// }));
//
//
// this.chartPoints = Alpine.raw(new Chart(chartPoints, {
// type: 'bubble',
// data: {
// labels: [],
// datasets: [{
// label: 'points',
// // data: this.pointsChartData,
// data: bubbleData,
// // backgroundColor: 'rgb(255, 99, 132)'
// backgroundColor: bubbleData.map(d =>
// d.format === 'audiobook' ? 'rgba(255,99,132,0.5)' :
// d.format === 'ebook' ? 'rgba(54,162,235,0.5)' :
// 'rgba(75,192,192,0.5)'
// )
// }]
// },
// options: {
// scales: {
// x: { title: { display: true, text: 'Pages' } },
// y: { title: { display: true, text: 'Rating' }, min: 0, max: 10 }
// }
// }
// }));
//
// },
get pointsChartData(){
let data = [];
for(let i in this.filteredItems){
// console.info(Alpine.raw(this.filteredItems[i]));
if(!(this.filteredItems[i]['book']['rating'] in data)){
data[this.filteredItems[i]['book']['rating']] = []
}
if(!(this.filteredItems[i]['likes'] in data[this.filteredItems[i]['book']['rating']])){
data[this.filteredItems[i]['book']['rating']][this.filteredItems[i]['likes']] = 0;
}
data[this.filteredItems[i]['book']['rating']][this.filteredItems[i]['likes']]++;
}
let data2 = [];
for(let i in data){
for(let j in data[i]){
data2.push({x: j, y: i, r: data[i][j], label: 'label'});
}
}
return data2;
},
updateChart() {
if (!this.chart) {
return;
}
// console.info( Alpine.raw(this.filteredItems));
const topItems = this.filteredItems.slice(0, 20).map(item => ({
title: item.book?.title || 'Unknown',
likes: item.likes || 0,
// date: (new Date(item.created_at)).getFullYear()+'-'+((new Date(item.created_at)).getMonth()+1)
}));
let counts = {};
// topItems.forEach(item => counts[item.date] = 1 + ())
console.info(topItems);
this.chart.data.labels = topItems.map(i => i.title)
this.chart.data.datasets[0].data = topItems.map(i => i.likes)
Alpine.raw(this.chart).update('none');
},
orderBy(property) {
let direction = (this.orderColumn[0] === property) ? this.orderColumn[1] * -1 : 1;
let direction = (this.orderColumn[0] === property) ? ((this.orderColumn[1] === -1) ? 0 : this.orderColumn[1] * -1) : 1;
if (!direction) {
property = 'order';
}
this.orderColumn = [property, direction];
this.sort();
},
@@ -59,6 +187,10 @@ Alpine.data('bookmeterList', function () {
};
},
sort() {
if (this.orderColumn[1] === 0) {
this.initFilteredData();
return;
}
let sortFunction = ({
username: (a,b) => ((a.username < b.username) ? -1 : (a.username > b.username) ? 1 : 0) * this.orderColumn[1],
title: (a,b) => ((a.book.title < b.book.title) ? -1 : (a.book.title > b.book.title) ? 1 : 0) * this.orderColumn[1],
@@ -70,29 +202,41 @@ Alpine.data('bookmeterList', function () {
likes: (a,b) => ((a.likes < b.likes) ? -1 : (a.likes > b.likes) ? 1 : 0) * this.orderColumn[1],
})[this.orderColumn[0]];
this.filteredItems = this.filteredItems.sort(sortFunction);
if (this.orderColumn[0] === 'order') {
this.initFilteredData();
}
},
updateFilter(){
initFilteredData(){
let filter = this.nameFilter.toLowerCase();
this.filteredItems = this.items.filter(i => this.year.includes(i.created_at.substring(0,4)))
.filter(i => {
if(this.advancedSearch) {
return (
(!this.username || i.username.toLowerCase().includes(this.username.toLowerCase())) &&
(!this.title || i.book.title.toLowerCase().includes(this.title.toLowerCase())) &&
(!this.author || i.book.author.toLowerCase().includes(this.author.toLowerCase())) &&
(!this.publisher || i.book.publisher.toLowerCase().includes(this.publisher.toLowerCase()))
);
} else {
if(filter.startsWith('@') && i.username.toLowerCase().includes(filter.substring(1))) return true;
if (i.username.toLowerCase().includes(filter) ||
i.book.title.toLowerCase().includes(filter) || i.book.author.toLowerCase().includes(filter) || i.book.publisher.toLowerCase().includes(filter)) return true;
return false;
}
});
this.sort();
this.updateURL();
let username = this.username.toLowerCase().trim();
let author = this.author.toLowerCase().trim();
let publisher = this.publisher.toLowerCase().trim();
let title = this.title.toLowerCase().trim();
this.filteredItems = this.items
.filter(i => this.year.includes(i._year))
.filter(i => {
if (this.advancedSearch) {
return (
(!this.username || i._username.includes(this.username)) &&
(!this.title || i._title.includes(title)) &&
(!this.author || i._author.includes(author)) &&
(!this.publisher || i._publisher.includes(publisher))
);
} else {
if (filter.startsWith('@')) {
return i.username.toLowerCase().includes(filter.slice(1));
}
return i._search.includes(filter);
}
});
},
updateFilter: Alpine.debounce(function () {
this.initFilteredData();
this.sort();
this.updateURL();
}, 250),
updateURL() {
let qp = new URLSearchParams();
if(this.nameFilter !== '') qp.set('filter', this.nameFilter);

1
src/style.css Normal file → Executable file
View File

@@ -1,6 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
[x-cloak] {
display: none;