Refactor stats, add derived catalog stores, improve timer

This commit is contained in:
ZXY101
2024-02-05 15:39:22 +02:00
parent 2e28843a07
commit 1c92a749be
8 changed files with 162 additions and 46 deletions

View File

@@ -1,4 +1,30 @@
import { db } from '$lib/catalog/db'; import { page } from '$app/stores';
import { db, type Catalog } from '$lib/catalog/db';
import type { Volume } from '$lib/types';
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
import { derived, type Readable } from 'svelte/store';
export const catalog = liveQuery(() => db.catalog.toArray()); export const catalog = liveQuery(() => db.catalog.toArray());
function sortManga(a: Volume, b: Volume) {
if (a.volumeName < b.volumeName) {
return -1;
}
if (a.volumeName > b.volumeName) {
return 1;
}
return 0;
}
export const manga = derived([page, catalog as unknown as Readable<Catalog[]>], ([$page, $catalog]) => {
if ($page && $catalog) {
return $catalog.find((item) => item.id === $page.params.manga)?.manga.sort(sortManga)
}
});
export const volume = derived(([page, manga]), ([$page, $manga]) => {
if ($page && $manga) {
return $manga.find((item) => item.mokuroData.volume_uuid === $page.params.volume)
}
})

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { volumeStats } from '$lib/settings';
export let active = true;
</script>
<div
class:text-primary-700={!active}
class="mix-blend-difference z-10 fixed opacity-50 right-20 top-5 p-10 m-[-2.5rem]"
>
<p>{active ? 'Timer active' : 'Timer idle'} | Minutes read: {$volumeStats?.timeReadInMinutes}</p>
</div>

View File

@@ -9,7 +9,8 @@
{ key: 'boldFont', text: 'Bold font', value: $settings.boldFont }, { key: 'boldFont', text: 'Bold font', value: $settings.boldFont },
{ key: 'pageNum', text: 'Show page number', value: $settings.pageNum }, { key: 'pageNum', text: 'Show page number', value: $settings.pageNum },
{ key: 'charCount', text: 'Show character count', value: $settings.charCount }, { key: 'charCount', text: 'Show character count', value: $settings.charCount },
{ key: 'mobile', text: 'Mobile', value: $settings.mobile } { key: 'mobile', text: 'Mobile', value: $settings.mobile },
{ key: 'showTimer', text: 'Show timer', value: $settings.showTimer }
] as { key: SettingsKey; text: string; value: any }[]; ] as { key: SettingsKey; text: string; value: any }[];
</script> </script>

View File

@@ -1,44 +1,14 @@
<script lang="ts"> <script lang="ts">
import { volumes } from '$lib/settings'; import { totalStats } from '$lib/settings';
import { AccordionItem, P } from 'flowbite-svelte'; import { AccordionItem } from 'flowbite-svelte';
$: completed = $volumes
? Object.values($volumes).reduce((total: number, { completed }) => {
if (completed) {
total++;
}
return total;
}, 0)
: 0;
$: pagesRead = $volumes
? Object.values($volumes).reduce((total: number, { progress }) => {
total += progress;
return total;
}, 0)
: 0;
$: charsRead = $volumes
? Object.values($volumes).reduce((total: number, { chars }) => {
total += chars;
return total;
}, 0)
: 0;
$: minutesRead = $volumes
? Object.values($volumes).reduce((total: number, { timeReadInMinutes }) => {
total += timeReadInMinutes;
return total;
}, 0)
: 0;
</script> </script>
<AccordionItem> <AccordionItem>
<span slot="header">Stats</span> <span slot="header">Stats</span>
<div> <div>
<p>Completed volumes: {completed}</p> <p>Completed volumes: {$totalStats?.completed || 0}</p>
<p>Pages read: {pagesRead}</p> <p>Pages read: {$totalStats?.pagesRead || 0}</p>
<p>Characters read: {charsRead}</p> <p>Characters read: {$totalStats?.charsRead || 0}</p>
<p>Minutes read: {minutesRead}</p> <p>Minutes read: {$totalStats?.minutesRead || 0}</p>
</div> </div>
</AccordionItem> </AccordionItem>

View File

@@ -49,6 +49,7 @@ export type Settings = {
mobile: boolean; mobile: boolean;
backgroundColor: string; backgroundColor: string;
swipeThreshold: number; swipeThreshold: number;
showTimer: boolean;
fontSize: FontSize; fontSize: FontSize;
zoomDefault: ZoomModes; zoomDefault: ZoomModes;
volumeDefaults: VolumeDefaults; volumeDefaults: VolumeDefaults;
@@ -71,6 +72,7 @@ const defaultSettings: Settings = {
mobile: false, mobile: false,
backgroundColor: '#030712', backgroundColor: '#030712',
swipeThreshold: 50, swipeThreshold: 50,
showTimer: false,
fontSize: 'auto', fontSize: 'auto',
zoomDefault: 'zoomFitToScreen', zoomDefault: 'zoomFitToScreen',
volumeDefaults: { volumeDefaults: {

View File

@@ -2,6 +2,8 @@ import { browser } from '$app/environment';
import { derived, get, writable } from 'svelte/store'; import { derived, get, writable } from 'svelte/store';
import { settings } from './settings'; import { settings } from './settings';
import { zoomDefault } from '$lib/panzoom'; import { zoomDefault } from '$lib/panzoom';
import { page } from '$app/stores';
import { manga, volume } from '$lib/catalog';
export type VolumeSettings = { export type VolumeSettings = {
rightToLeft: boolean; rightToLeft: boolean;
@@ -21,6 +23,13 @@ type VolumeData = {
settings: VolumeSettings; settings: VolumeSettings;
} }
type TotalStats = {
completed: number;
pagesRead: number;
charsRead: number;
minutesRead: number;
}
type Volumes = Record<string, VolumeData>; type Volumes = Record<string, VolumeData>;
@@ -132,4 +141,51 @@ export function updateVolumeSetting(volume: string, key: VolumeSettingsKey, valu
}; };
}); });
zoomDefault(); zoomDefault();
} }
export const totalStats = derived([volumes, page], ([$volumes, $page]) => {
if ($page && $volumes) {
return Object.values($volumes).reduce<TotalStats>((stats, { chars, completed, timeReadInMinutes, progress }) => {
if (completed) {
stats.completed++;
}
stats.pagesRead += progress;
stats.minutesRead += timeReadInMinutes;
stats.charsRead += chars
return stats;
}, {
charsRead: 0,
completed: 0,
pagesRead: 0,
minutesRead: 0
})
}
})
export const mangaStats = derived([manga, volumes], ([$manga, $volumes]) => {
if ($manga && $volumes) {
return $manga.map((vol) => vol.mokuroData.volume_uuid).reduce(
(stats: any, volumeId) => {
const timeReadInMinutes = $volumes[volumeId]?.timeReadInMinutes || 0;
const chars = $volumes[volumeId]?.chars || 0;
const completed = $volumes[volumeId]?.completed || 0;
stats.timeReadInMinutes = stats.timeReadInMinutes + timeReadInMinutes;
stats.chars = stats.chars + chars;
stats.completed = stats.completed + completed;
return stats;
},
{ timeReadInMinutes: 0, chars: 0, completed: 0 }
);
}
});
export const volumeStats = derived([volume, volumes], ([$volume, $volumes]) => {
if ($volume && $volumes) {
const { chars, completed, timeReadInMinutes, progress } = $volumes[$volume.mokuroData.volume_uuid]
return { chars, completed, timeReadInMinutes, progress }
}
});

View File

@@ -7,7 +7,7 @@
import { promptConfirmation, zipManga } from '$lib/util'; import { promptConfirmation, zipManga } from '$lib/util';
import { page } from '$app/stores'; import { page } from '$app/stores';
import type { Volume } from '$lib/types'; import type { Volume } from '$lib/types';
import { deleteVolume, volumes } from '$lib/settings'; import { deleteVolume, mangaStats, volumes } from '$lib/settings';
function sortManga(a: Volume, b: Volume) { function sortManga(a: Volume, b: Volume) {
if (a.volumeName < b.volumeName) { if (a.volumeName < b.volumeName) {
@@ -66,15 +66,15 @@
<svelte:head> <svelte:head>
<title>{manga?.[0].mokuroData.title || 'Manga'}</title> <title>{manga?.[0].mokuroData.title || 'Manga'}</title>
</svelte:head> </svelte:head>
{#if manga} {#if manga && $mangaStats}
<div class="p-2 flex flex-col gap-5"> <div class="p-2 flex flex-col gap-5">
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="font-bold">{manga[0].mokuroData.title}</h3> <h3 class="font-bold">{manga[0].mokuroData.title}</h3>
<div class="flex flex-col gap-0 sm:flex-row sm:gap-5"> <div class="flex flex-col gap-0 sm:flex-row sm:gap-5">
<p>Volumes: {stats.completed} / {manga.length}</p> <p>Volumes: {$mangaStats.completed} / {manga.length}</p>
<p>Characters read: {stats.chars}</p> <p>Characters read: {$mangaStats.chars}</p>
<p>Minutes read: {stats.timeReadInMinutes}</p> <p>Minutes read: {$mangaStats.timeReadInMinutes}</p>
</div> </div>
</div> </div>
<div class="sm:block flex-col flex gap-2"> <div class="sm:block flex-col flex gap-2">

View File

@@ -1,24 +1,73 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import Reader from '$lib/components/Reader/Reader.svelte'; import Reader from '$lib/components/Reader/Reader.svelte';
import { initializeVolume, startCount, volumeSettings, volumes } from '$lib/settings'; import Timer from '$lib/components/Reader/Timer.svelte';
import { initializeVolume, settings, startCount, volumeSettings, volumes } from '$lib/settings';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
const volumeId = $page.params.volume; const volumeId = $page.params.volume;
let count: undefined | number = undefined;
let inactiveTimer: undefined | number = undefined;
let inactive = false;
onMount(() => { onMount(() => {
if (!$volumes?.[volumeId]) { if (!$volumes?.[volumeId]) {
initializeVolume(volumeId); initializeVolume(volumeId);
} }
const count = startCount(volumeId); count = startCount(volumeId);
return () => { return () => {
clearInterval(count); clearInterval(count);
count = undefined;
}; };
}); });
function onBlur() {
// This is an attempt to pause the timer when the page loses focus, but
// keep it going if focus is given to an extension such as yomitan
if (
document.activeElement?.innerHTML.includes('moz-extension') ||
!Boolean(document.activeElement?.innerHTML)
) {
return;
}
clearInterval(count);
count = undefined;
}
function onFocus() {
count = startCount(volumeId);
}
function resetInactiveTimer() {
if (inactive && !count) {
count = startCount(volumeId);
}
clearTimeout(inactiveTimer);
inactive = false;
inactiveTimer = setTimeout(() => {
clearInterval(count);
count = undefined;
inactive = true;
}, 15 * 1000);
}
</script> </script>
<svelte:window
on:blur={onBlur}
on:focus={onFocus}
on:load={resetInactiveTimer}
on:mousemove={resetInactiveTimer}
on:keydown={resetInactiveTimer}
/>
{#if $volumeSettings[volumeId]} {#if $volumeSettings[volumeId]}
{#if $settings.showTimer}
<Timer active={Boolean(count)} />
{/if}
<Reader volumeSettings={$volumeSettings[volumeId]} /> <Reader volumeSettings={$volumeSettings[volumeId]} />
{/if} {/if}