Refactor stats, add derived catalog stores, improve timer
This commit is contained in:
@@ -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 { derived, type Readable } from 'svelte/store';
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
12
src/lib/components/Reader/Timer.svelte
Normal file
12
src/lib/components/Reader/Timer.svelte
Normal 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>
|
||||
@@ -9,7 +9,8 @@
|
||||
{ key: 'boldFont', text: 'Bold font', value: $settings.boldFont },
|
||||
{ key: 'pageNum', text: 'Show page number', value: $settings.pageNum },
|
||||
{ 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 }[];
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,44 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { volumes } from '$lib/settings';
|
||||
import { AccordionItem, P } 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;
|
||||
import { totalStats } from '$lib/settings';
|
||||
import { AccordionItem } from 'flowbite-svelte';
|
||||
</script>
|
||||
|
||||
<AccordionItem>
|
||||
<span slot="header">Stats</span>
|
||||
<div>
|
||||
<p>Completed volumes: {completed}</p>
|
||||
<p>Pages read: {pagesRead}</p>
|
||||
<p>Characters read: {charsRead}</p>
|
||||
<p>Minutes read: {minutesRead}</p>
|
||||
<p>Completed volumes: {$totalStats?.completed || 0}</p>
|
||||
<p>Pages read: {$totalStats?.pagesRead || 0}</p>
|
||||
<p>Characters read: {$totalStats?.charsRead || 0}</p>
|
||||
<p>Minutes read: {$totalStats?.minutesRead || 0}</p>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
@@ -49,6 +49,7 @@ export type Settings = {
|
||||
mobile: boolean;
|
||||
backgroundColor: string;
|
||||
swipeThreshold: number;
|
||||
showTimer: boolean;
|
||||
fontSize: FontSize;
|
||||
zoomDefault: ZoomModes;
|
||||
volumeDefaults: VolumeDefaults;
|
||||
@@ -71,6 +72,7 @@ const defaultSettings: Settings = {
|
||||
mobile: false,
|
||||
backgroundColor: '#030712',
|
||||
swipeThreshold: 50,
|
||||
showTimer: false,
|
||||
fontSize: 'auto',
|
||||
zoomDefault: 'zoomFitToScreen',
|
||||
volumeDefaults: {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { browser } from '$app/environment';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import { settings } from './settings';
|
||||
import { zoomDefault } from '$lib/panzoom';
|
||||
import { page } from '$app/stores';
|
||||
import { manga, volume } from '$lib/catalog';
|
||||
|
||||
export type VolumeSettings = {
|
||||
rightToLeft: boolean;
|
||||
@@ -21,6 +23,13 @@ type VolumeData = {
|
||||
settings: VolumeSettings;
|
||||
}
|
||||
|
||||
type TotalStats = {
|
||||
completed: number;
|
||||
pagesRead: number;
|
||||
charsRead: number;
|
||||
minutesRead: number;
|
||||
}
|
||||
|
||||
type Volumes = Record<string, VolumeData>;
|
||||
|
||||
|
||||
@@ -132,4 +141,51 @@ export function updateVolumeSetting(volume: string, key: VolumeSettingsKey, valu
|
||||
};
|
||||
});
|
||||
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 }
|
||||
}
|
||||
});
|
||||
@@ -7,7 +7,7 @@
|
||||
import { promptConfirmation, zipManga } from '$lib/util';
|
||||
import { page } from '$app/stores';
|
||||
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) {
|
||||
if (a.volumeName < b.volumeName) {
|
||||
@@ -66,15 +66,15 @@
|
||||
<svelte:head>
|
||||
<title>{manga?.[0].mokuroData.title || 'Manga'}</title>
|
||||
</svelte:head>
|
||||
{#if manga}
|
||||
{#if manga && $mangaStats}
|
||||
<div class="p-2 flex flex-col gap-5">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="font-bold">{manga[0].mokuroData.title}</h3>
|
||||
<div class="flex flex-col gap-0 sm:flex-row sm:gap-5">
|
||||
<p>Volumes: {stats.completed} / {manga.length}</p>
|
||||
<p>Characters read: {stats.chars}</p>
|
||||
<p>Minutes read: {stats.timeReadInMinutes}</p>
|
||||
<p>Volumes: {$mangaStats.completed} / {manga.length}</p>
|
||||
<p>Characters read: {$mangaStats.chars}</p>
|
||||
<p>Minutes read: {$mangaStats.timeReadInMinutes}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:block flex-col flex gap-2">
|
||||
|
||||
@@ -1,24 +1,73 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
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';
|
||||
|
||||
const volumeId = $page.params.volume;
|
||||
let count: undefined | number = undefined;
|
||||
let inactiveTimer: undefined | number = undefined;
|
||||
let inactive = false;
|
||||
|
||||
onMount(() => {
|
||||
if (!$volumes?.[volumeId]) {
|
||||
initializeVolume(volumeId);
|
||||
}
|
||||
|
||||
const count = startCount(volumeId);
|
||||
count = startCount(volumeId);
|
||||
|
||||
return () => {
|
||||
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>
|
||||
|
||||
<svelte:window
|
||||
on:blur={onBlur}
|
||||
on:focus={onFocus}
|
||||
on:load={resetInactiveTimer}
|
||||
on:mousemove={resetInactiveTimer}
|
||||
on:keydown={resetInactiveTimer}
|
||||
/>
|
||||
|
||||
{#if $volumeSettings[volumeId]}
|
||||
{#if $settings.showTimer}
|
||||
<Timer active={Boolean(count)} />
|
||||
{/if}
|
||||
<Reader volumeSettings={$volumeSettings[volumeId]} />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user