More stats and cleanup
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Page } from '$lib/types';
|
import type { Page } from '$lib/types';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import TextBoxes from './TextBoxes.svelte';
|
import TextBoxes from './TextBoxes.svelte';
|
||||||
|
import { zoomDefault } from '$lib/panzoom';
|
||||||
|
|
||||||
export let page: Page;
|
export let page: Page;
|
||||||
export let src: File;
|
export let src: File;
|
||||||
|
|
||||||
$: url = src ? `url(${URL.createObjectURL(src)})` : '';
|
$: url = src ? `url(${URL.createObjectURL(src)})` : '';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
zoomDefault();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
ChevronLeftSolid,
|
ChevronLeftSolid,
|
||||||
ChevronRightSolid
|
ChevronRightSolid
|
||||||
} from 'flowbite-svelte-icons';
|
} from 'flowbite-svelte-icons';
|
||||||
import { afterUpdate, onMount } from 'svelte';
|
|
||||||
import Cropper from './Cropper.svelte';
|
import Cropper from './Cropper.svelte';
|
||||||
import { page as pageStore } from '$app/stores';
|
import { page as pageStore } from '$app/stores';
|
||||||
import SettingsButton from './SettingsButton.svelte';
|
import SettingsButton from './SettingsButton.svelte';
|
||||||
@@ -98,17 +97,6 @@
|
|||||||
|
|
||||||
$: charDisplay = `${charCount} / ${maxCharCount}`;
|
$: charDisplay = `${charCount} / ${maxCharCount}`;
|
||||||
|
|
||||||
let hasCoverSetting = volumeSettings.hasCover;
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (volumeSettings.hasCover !== hasCoverSetting) {
|
|
||||||
hasCoverSetting = volumeSettings.hasCover;
|
|
||||||
if (page > 1 && !volumeSettings.singlePageView) {
|
|
||||||
page--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onInputClick(this: any) {
|
function onInputClick(this: any) {
|
||||||
this.select();
|
this.select();
|
||||||
}
|
}
|
||||||
@@ -117,10 +105,6 @@
|
|||||||
changePage(manualPage, true);
|
changePage(manualPage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
afterUpdate(() => {
|
|
||||||
zoomDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleShortcuts(event: KeyboardEvent & { currentTarget: EventTarget & Window }) {
|
function handleShortcuts(event: KeyboardEvent & { currentTarget: EventTarget & Window }) {
|
||||||
const action = event.code || event.key;
|
const action = event.code || event.key;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,13 @@
|
|||||||
return total;
|
return total;
|
||||||
}, 0)
|
}, 0)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
$: minutesRead = $volumes
|
||||||
|
? Object.values($volumes).reduce((total: number, { timeReadInMinutes }) => {
|
||||||
|
total += timeReadInMinutes;
|
||||||
|
return total;
|
||||||
|
}, 0)
|
||||||
|
: 0;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
@@ -32,5 +39,6 @@
|
|||||||
<p>Completed volumes: {completed}</p>
|
<p>Completed volumes: {completed}</p>
|
||||||
<p>Pages read: {pagesRead}</p>
|
<p>Pages read: {pagesRead}</p>
|
||||||
<p>Characters read: {charsRead}</p>
|
<p>Characters read: {charsRead}</p>
|
||||||
|
<p>Minutes read: {minutesRead}</p>
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { toggleFullScreen } from '$lib/panzoom';
|
import { toggleFullScreen, zoomDefault } from '$lib/panzoom';
|
||||||
import { updateVolumeSetting, volumeSettings, type VolumeSettingsKey } from '$lib/settings';
|
import {
|
||||||
|
updateProgress,
|
||||||
|
updateVolumeSetting,
|
||||||
|
volumes,
|
||||||
|
volumeSettings,
|
||||||
|
type VolumeSettingsKey
|
||||||
|
} from '$lib/settings';
|
||||||
import { AccordionItem, Button, Helper, Toggle } from 'flowbite-svelte';
|
import { AccordionItem, Button, Helper, Toggle } from 'flowbite-svelte';
|
||||||
|
|
||||||
|
const volumeId = $page.params.volume;
|
||||||
|
|
||||||
$: settings = $volumeSettings[$page.params.volume];
|
$: settings = $volumeSettings[$page.params.volume];
|
||||||
|
|
||||||
$: toggles = [
|
$: toggles = [
|
||||||
@@ -11,6 +19,15 @@
|
|||||||
{ key: 'singlePageView', text: 'Single page view', value: settings.singlePageView },
|
{ key: 'singlePageView', text: 'Single page view', value: settings.singlePageView },
|
||||||
{ key: 'hasCover', text: 'First page is cover', value: settings.hasCover }
|
{ key: 'hasCover', text: 'First page is cover', value: settings.hasCover }
|
||||||
] as { key: VolumeSettingsKey; text: string; value: any }[];
|
] as { key: VolumeSettingsKey; text: string; value: any }[];
|
||||||
|
|
||||||
|
function onChange(key: VolumeSettingsKey, value: any) {
|
||||||
|
updateVolumeSetting(volumeId, key, !value);
|
||||||
|
if (key === 'hasCover') {
|
||||||
|
const pageClamped = Math.max($volumes[volumeId].progress - 1, 1);
|
||||||
|
updateProgress(volumeId, pageClamped);
|
||||||
|
zoomDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AccordionItem open>
|
<AccordionItem open>
|
||||||
@@ -18,11 +35,7 @@
|
|||||||
<div class="flex flex-col gap-5">
|
<div class="flex flex-col gap-5">
|
||||||
<Helper>These settings only apply to this volume</Helper>
|
<Helper>These settings only apply to this volume</Helper>
|
||||||
{#each toggles as { key, text, value }}
|
{#each toggles as { key, text, value }}
|
||||||
<Toggle
|
<Toggle size="small" checked={value} on:change={() => onChange(key, value)}>{text}</Toggle>
|
||||||
size="small"
|
|
||||||
checked={value}
|
|
||||||
on:change={() => updateVolumeSetting($page.params.volume, key, !value)}>{text}</Toggle
|
|
||||||
>
|
|
||||||
{/each}
|
{/each}
|
||||||
<Button color="alternative" on:click={toggleFullScreen}>Toggle fullscreen</Button>
|
<Button color="alternative" on:click={toggleFullScreen}>Toggle fullscreen</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ type Progress = Record<string, number> | undefined;
|
|||||||
type VolumeData = {
|
type VolumeData = {
|
||||||
progress: number;
|
progress: number;
|
||||||
chars: number;
|
chars: number;
|
||||||
settings: VolumeSettings;
|
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
|
timeReadInMinutes: number,
|
||||||
|
settings: VolumeSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Volumes = Record<string, VolumeData>;
|
type Volumes = Record<string, VolumeData>;
|
||||||
@@ -37,6 +38,7 @@ export function initializeVolume(volume: string) {
|
|||||||
chars: 0,
|
chars: 0,
|
||||||
completed: false,
|
completed: false,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
|
timeReadInMinutes: 0,
|
||||||
settings: {
|
settings: {
|
||||||
hasCover,
|
hasCover,
|
||||||
rightToLeft,
|
rightToLeft,
|
||||||
@@ -58,20 +60,34 @@ export function clearVolumes() {
|
|||||||
volumes.set({});
|
volumes.set({});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateProgress(volume: string, progress: number, chars: number, completed = false) {
|
export function updateProgress(volume: string, progress: number, chars?: number, completed = false) {
|
||||||
volumes.update((prev) => {
|
volumes.update((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
[volume]: {
|
[volume]: {
|
||||||
...prev?.[volume],
|
...prev?.[volume],
|
||||||
progress,
|
progress,
|
||||||
chars,
|
chars: chars || prev?.[volume].chars,
|
||||||
completed
|
completed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function startCount(volume: string) {
|
||||||
|
return setInterval(() => {
|
||||||
|
volumes.update((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[volume]: {
|
||||||
|
...prev?.[volume],
|
||||||
|
timeReadInMinutes: prev?.[volume].timeReadInMinutes + 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, 60 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
volumes.subscribe((volumes) => {
|
volumes.subscribe((volumes) => {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
window.localStorage.setItem('volumes', volumes ? JSON.stringify(volumes) : '');
|
window.localStorage.setItem('volumes', volumes ? JSON.stringify(volumes) : '');
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { promptConfirmation } from '$lib/util';
|
import { promptConfirmation } 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 } from '$lib/settings';
|
import { deleteVolume, 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) {
|
||||||
@@ -21,6 +21,23 @@
|
|||||||
|
|
||||||
$: manga = $catalog?.find((item) => item.id === $page.params.manga)?.manga.sort(sortManga);
|
$: manga = $catalog?.find((item) => item.id === $page.params.manga)?.manga.sort(sortManga);
|
||||||
|
|
||||||
|
$: stats = 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 }
|
||||||
|
);
|
||||||
|
|
||||||
async function confirmDelete() {
|
async function confirmDelete() {
|
||||||
const title = manga?.[0].mokuroData.title_uuid;
|
const title = manga?.[0].mokuroData.title_uuid;
|
||||||
manga?.forEach((vol) => {
|
manga?.forEach((vol) => {
|
||||||
@@ -43,11 +60,17 @@
|
|||||||
{#if manga}
|
{#if manga}
|
||||||
<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">
|
<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>
|
||||||
<p>Volumes: {manga.length}</p>
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button color="alternative" on:click={onDelete}>Remove manga</Button>
|
||||||
</div>
|
</div>
|
||||||
<div><Button color="alternative" on:click={onDelete}>Remove manga</Button></div>
|
|
||||||
</div>
|
</div>
|
||||||
<Listgroup items={manga} let:item active class="flex-1 h-full w-full">
|
<Listgroup items={manga} let:item active class="flex-1 h-full w-full">
|
||||||
<VolumeItem {item} />
|
<VolumeItem {item} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<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, volumeSettings, volumes } from '$lib/settings';
|
import { initializeVolume, startCount, volumeSettings, volumes } from '$lib/settings';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
const volumeId = $page.params.volume;
|
const volumeId = $page.params.volume;
|
||||||
@@ -10,6 +10,12 @@
|
|||||||
if (!$volumes?.[volumeId]) {
|
if (!$volumes?.[volumeId]) {
|
||||||
initializeVolume(volumeId);
|
initializeVolume(volumeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const count = startCount(volumeId);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(count);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user