style: format web/src with oxfmt defaults (via vp fmt)
One-time mechanical sweep, isolated from any functional change. - oxfmt defaults: double quotes, semicolons, 2-space indent, LF, printWidth 100, trailing commas, bracket spacing, arrow parens. - vite.config.ts gains an fmt.ignorePatterns block for web/static: vendored/minified assets (htmx.min.js, compiled css) must never be reformatted. - Verified: rebuild after the sweep produces byte-identical JS/CSS bundles; only sourcemaps changed (they embed sourcesContent).
This commit is contained in:
1 parent
8aeecaa0ef
commit
77cd268f4e
37 files changed
+1151
-1853
No files matched your search
+67
-28
@@ -137,9 +137,13 @@ let libraryDataLoaded = false;
|
||||
|
||||
// Page-scoped helpers -----------------------------------------------------
|
||||
|
||||
async function chartCtor(): Promise<new (el: HTMLCanvasElement, cfg: ChartConfiguration) => ChartLike> {
|
||||
async function chartCtor(): Promise<
|
||||
new (el: HTMLCanvasElement, cfg: ChartConfiguration) => ChartLike
|
||||
> {
|
||||
const mod = await import("chart.js/auto");
|
||||
return (mod as unknown as { default: new (el: HTMLCanvasElement, cfg: ChartConfiguration) => ChartLike }).default;
|
||||
return (
|
||||
mod as unknown as { default: new (el: HTMLCanvasElement, cfg: ChartConfiguration) => ChartLike }
|
||||
).default;
|
||||
}
|
||||
|
||||
function text(id: string, value: string): void {
|
||||
@@ -302,10 +306,14 @@ function renderSummary(summary: Summary): void {
|
||||
const hasData = summary.total_minutes > 0 || summary.session_count > 0;
|
||||
document.getElementById("stats-empty")?.classList.toggle("hidden", hasData);
|
||||
const windowLabel = document.getElementById("stats-window-label");
|
||||
if (windowLabel) windowLabel.textContent = `${summary.start_date} → ${summary.end_date} (${summary.days} days)`;
|
||||
if (windowLabel)
|
||||
windowLabel.textContent = `${summary.start_date} → ${summary.end_date} (${summary.days} days)`;
|
||||
|
||||
const mins = summary.total_minutes;
|
||||
text("stats-total-minutes", mins >= 60 ? `${Math.floor(mins / 60)} h ${mins % 60} min` : `${mins} min`);
|
||||
text(
|
||||
"stats-total-minutes",
|
||||
mins >= 60 ? `${Math.floor(mins / 60)} h ${mins % 60} min` : `${mins} min`,
|
||||
);
|
||||
renderPagesCard(summary);
|
||||
text(
|
||||
"stats-streaks",
|
||||
@@ -315,7 +323,9 @@ function renderSummary(summary: Summary): void {
|
||||
);
|
||||
text(
|
||||
"stats-books-finished",
|
||||
summary.books_with_activity > 0 ? `${summary.books_finished} of ${summary.books_with_activity}` : "–",
|
||||
summary.books_with_activity > 0
|
||||
? `${summary.books_finished} of ${summary.books_with_activity}`
|
||||
: "–",
|
||||
);
|
||||
|
||||
const mostActive = document.getElementById("stats-most-active");
|
||||
@@ -357,7 +367,11 @@ async function renderDailyCharts(daily: DailyEntry[]): Promise<void> {
|
||||
},
|
||||
scales: {
|
||||
x: { grid: { display: false }, ticks: { color: secondary, maxTicksLimit: 10 } },
|
||||
y: { grid: { color: "rgba(128,128,128,.15)" }, ticks: { color: secondary }, beginAtZero: true },
|
||||
y: {
|
||||
grid: { color: "rgba(128,128,128,.15)" },
|
||||
ticks: { color: secondary },
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -368,7 +382,9 @@ async function renderDailyCharts(daily: DailyEntry[]): Promise<void> {
|
||||
pagesChart = null;
|
||||
listeningChart = null;
|
||||
|
||||
const minutesCanvas = document.getElementById("stats-daily-minutes-chart") as HTMLCanvasElement | null;
|
||||
const minutesCanvas = document.getElementById(
|
||||
"stats-daily-minutes-chart",
|
||||
) as HTMLCanvasElement | null;
|
||||
if (minutesCanvas) {
|
||||
minutesChart = new ChartCtor(minutesCanvas, {
|
||||
type: "bar",
|
||||
@@ -380,7 +396,9 @@ async function renderDailyCharts(daily: DailyEntry[]): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
const pagesCanvas = document.getElementById("stats-daily-pages-chart") as HTMLCanvasElement | null;
|
||||
const pagesCanvas = document.getElementById(
|
||||
"stats-daily-pages-chart",
|
||||
) as HTMLCanvasElement | null;
|
||||
if (pagesCanvas) {
|
||||
pagesChart = new ChartCtor(pagesCanvas, {
|
||||
type: "line",
|
||||
@@ -404,7 +422,9 @@ async function renderDailyCharts(daily: DailyEntry[]): Promise<void> {
|
||||
// Listening per Day (hrs) — the audio half of the side-by-side pages
|
||||
// split; its panel is hidden by renderPagesCard unless the range has
|
||||
// audio minutes.
|
||||
const listeningCanvas = document.getElementById("stats-daily-listening-chart") as HTMLCanvasElement | null;
|
||||
const listeningCanvas = document.getElementById(
|
||||
"stats-daily-listening-chart",
|
||||
) as HTMLCanvasElement | null;
|
||||
if (listeningCanvas) {
|
||||
listeningChart = new ChartCtor(listeningCanvas, {
|
||||
type: "bar",
|
||||
@@ -426,26 +446,32 @@ async function renderDailyCharts(daily: DailyEntry[]): Promise<void> {
|
||||
function renderLibraries(rows: LibraryRow[]): void {
|
||||
const container = document.getElementById("stats-library-breakdown");
|
||||
if (!container) return;
|
||||
renderBars(container, rows.map((r) => ({
|
||||
label: r.name,
|
||||
sub: `${fmtMinutes(r.minutes)} · ${r.sessions} sessions`,
|
||||
value: r.minutes,
|
||||
})));
|
||||
renderBars(
|
||||
container,
|
||||
rows.map((r) => ({
|
||||
label: r.name,
|
||||
sub: `${fmtMinutes(r.minutes)} · ${r.sessions} sessions`,
|
||||
value: r.minutes,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
function renderDevices(rows: DeviceRow[]): void {
|
||||
const container = document.getElementById("stats-device-breakdown");
|
||||
if (!container) return;
|
||||
renderBars(container, rows.map((r) => ({
|
||||
label: r.name || "Web",
|
||||
sub: `${r.books} ${r.books === 1 ? "book" : "books"} · ${r.sessions} sessions`,
|
||||
value: r.minutes,
|
||||
})));
|
||||
renderBars(
|
||||
container,
|
||||
rows.map((r) => ({
|
||||
label: r.name || "Web",
|
||||
sub: `${r.books} ${r.books === 1 ? "book" : "books"} · ${r.sessions} sessions`,
|
||||
value: r.minutes,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
function fmtMinutes(minutes: number): string {
|
||||
return minutes >= 60
|
||||
? `${Math.floor(minutes / 60)}h${minutes % 60 ? ` ${(minutes % 60)}m` : ""}`
|
||||
? `${Math.floor(minutes / 60)}h${minutes % 60 ? ` ${minutes % 60}m` : ""}`
|
||||
: `${minutes} min`;
|
||||
}
|
||||
|
||||
@@ -522,10 +548,9 @@ async function loadLibraries(): Promise<VisibleLibrary[]> {
|
||||
function fillLibrarySelector(rows: VisibleLibrary[]): void {
|
||||
const select = document.getElementById("stats-library-select") as HTMLSelectElement | null;
|
||||
if (!select) return;
|
||||
select.innerHTML = `<option value="">All libraries</option>` +
|
||||
rows
|
||||
.map((r) => `<option value="${escapeHtml(r.id)}">${escapeHtml(r.name)}</option>`)
|
||||
.join("");
|
||||
select.innerHTML =
|
||||
`<option value="">All libraries</option>` +
|
||||
rows.map((r) => `<option value="${escapeHtml(r.id)}">${escapeHtml(r.name)}</option>`).join("");
|
||||
// Honor a URL-seeded selection initStatsPage may have set.
|
||||
if (currentLibraryID) select.value = currentLibraryID;
|
||||
}
|
||||
@@ -545,9 +570,15 @@ async function loadLibraryUsage(): Promise<void> {
|
||||
"lib-stat-narrators",
|
||||
];
|
||||
targets.forEach((id) => text(id, "–"));
|
||||
for (const id of ["stats-lib-genres", "stats-lib-authors", "stats-lib-longest", "stats-lib-largest"]) {
|
||||
for (const id of [
|
||||
"stats-lib-genres",
|
||||
"stats-lib-authors",
|
||||
"stats-lib-longest",
|
||||
"stats-lib-largest",
|
||||
]) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.innerHTML = `<div class="text-center py-6 text-sm" style="color: var(--text-secondary)">Loading…</div>`;
|
||||
if (el)
|
||||
el.innerHTML = `<div class="text-center py-6 text-sm" style="color: var(--text-secondary)">Loading…</div>`;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -598,7 +629,10 @@ function renderLibraryUsage(payload: LibraryUsage): void {
|
||||
}
|
||||
|
||||
const gb = payload.total_size_bytes / 1e9;
|
||||
text("lib-stat-size", gb >= 0.1 ? gb.toFixed(1) : (payload.total_size_bytes / 1e6).toFixed(1) + " MB");
|
||||
text(
|
||||
"lib-stat-size",
|
||||
gb >= 0.1 ? gb.toFixed(1) : (payload.total_size_bytes / 1e6).toFixed(1) + " MB",
|
||||
);
|
||||
|
||||
const itemTotal = Math.max(payload.items, 1);
|
||||
renderBars(
|
||||
@@ -619,7 +653,12 @@ function renderLibraryUsage(payload: LibraryUsage): void {
|
||||
})),
|
||||
"No authors recorded in this pool yet.",
|
||||
);
|
||||
renderUsageItems("stats-lib-longest", "stats-lib-longest-label", payload.longest_items, payload.hours_mode);
|
||||
renderUsageItems(
|
||||
"stats-lib-longest",
|
||||
"stats-lib-longest-label",
|
||||
payload.longest_items,
|
||||
payload.hours_mode,
|
||||
);
|
||||
renderUsageItems("stats-lib-largest", null, payload.largest_items, payload.hours_mode, true);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user