Handle mobile viewer

This commit is contained in:
ZXY101
2023-10-02 12:05:26 +02:00
parent 83ae19f510
commit 310febe53c
7 changed files with 162 additions and 40 deletions

View File

@@ -1,8 +1,14 @@
<script lang="ts"> <script lang="ts">
import { catalog } from '$lib/catalog'; import { catalog } from '$lib/catalog';
import { Panzoom, toggleFullScreen, zoomDefault } from '$lib/panzoom'; import {
Panzoom,
panzoomStore,
toggleFullScreen,
zoomDefault,
zoomFitToScreen
} from '$lib/panzoom';
import { progress, settings, updateProgress } from '$lib/settings'; import { progress, settings, updateProgress } from '$lib/settings';
import { clamp } from '$lib/util'; import { clamp, debounce } from '$lib/util';
import { Input, Popover, Range, Spinner } from 'flowbite-svelte'; import { Input, Popover, Range, Spinner } from 'flowbite-svelte';
import MangaPage from './MangaPage.svelte'; import MangaPage from './MangaPage.svelte';
import { import {
@@ -181,9 +187,64 @@
return maxCharCount; return maxCharCount;
} }
let startX = 0;
let startY = 0;
function handleTouchStart(event: TouchEvent) {
if ($settings.mobile) {
const { clientX, clientY } = event.touches[0];
startX = clientX;
startY = clientY;
}
}
function handlePointerUp(event: TouchEvent) {
if ($settings.mobile) {
debounce(() => {
if (event.touches.length === 0) {
const { clientX, clientY } = event.changedTouches[0];
const distanceX = clientX - startX;
const distanceY = clientY - startY;
const isSwipe = distanceY < 200 && distanceY > 200 * -1;
if (isSwipe) {
const swipeThreshold = Math.abs(($settings.swipeThreshold / 100) * window.innerWidth);
if (distanceX > swipeThreshold) {
left(event, true);
} else if (distanceX < swipeThreshold * -1) {
right(event, true);
}
}
}
});
}
}
function onDoubleTap(event: MouseEvent) {
if ($panzoomStore && $settings.mobile) {
const { clientX, clientY } = event;
const { scale } = $panzoomStore.getTransform();
if (scale < 0.5) {
$panzoomStore.zoomTo(clientX, clientY, 3);
} else {
zoomFitToScreen();
}
}
}
</script> </script>
<svelte:window on:resize={zoomDefault} on:keyup|preventDefault={handleShortcuts} /> <svelte:window
on:resize={zoomDefault}
on:keyup|preventDefault={handleShortcuts}
on:touchstart={handleTouchStart}
on:touchend={handlePointerUp}
/>
<svelte:head> <svelte:head>
<title>{volume?.mokuroData.volume || 'Volume'}</title> <title>{volume?.mokuroData.volume || 'Volume'}</title>
</svelte:head> </svelte:head>
@@ -227,7 +288,6 @@
max={pages.length} max={pages.length}
bind:value={manualPage} bind:value={manualPage}
on:change={onManualPageChange} on:change={onManualPageChange}
cla
defaultClass="" defaultClass=""
/> />
</div> </div>
@@ -239,8 +299,9 @@
</button> </button>
<div class="flex" style:background-color={$settings.backgroundColor}> <div class="flex" style:background-color={$settings.backgroundColor}>
<Panzoom> <Panzoom>
{#if !$settings.mobile}
<button <button
class="h-full fixed -left-1/2 z-10 w-1/2 hover:bg-slate-400 opacity-[0.01] justify-items-center" class="h-full fixed -left-1/2 z-10 w-1/2 hover:bg-slate-400 opacity-[0.01]"
on:mousedown={mouseDown} on:mousedown={mouseDown}
on:mouseup={left} on:mouseup={left}
/> />
@@ -249,7 +310,24 @@
on:mousedown={mouseDown} on:mousedown={mouseDown}
on:mouseup={right} on:mouseup={right}
/> />
<div class="flex flex-row" class:flex-row-reverse={!$settings.rightToLeft}> {:else}
<button
class="h-screen fixed top-full left-0 z-10 w-1/2 hover:bg-slate-400 opacity-[0.01]"
on:mousedown={mouseDown}
on:mouseup={left}
/>
<button
class="h-screen fixed top-full right-0 z-10 w-1/2 hover:bg-slate-400 opacity-[0.01]"
on:mousedown={mouseDown}
on:mouseup={right}
/>
{/if}
<div
class="flex flex-row"
class:flex-row-reverse={!$settings.rightToLeft}
on:dblclick={onDoubleTap}
role="none"
>
{#if showSecondPage()} {#if showSecondPage()}
<MangaPage page={pages[index + 1]} src={Object.values(volume?.files)[index + 1]} /> <MangaPage page={pages[index + 1]} src={Object.values(volume?.files)[index + 1]} />
{/if} {/if}
@@ -257,16 +335,18 @@
</div> </div>
</Panzoom> </Panzoom>
</div> </div>
{#if !$settings.mobile}
<button <button
on:mousedown={mouseDown} on:mousedown={mouseDown}
on:mouseup={left} on:mouseup={left}
class="left-0 top-0 absolute h-full w-10 hover:bg-slate-400 opacity-[0.01]" class="left-0 top-0 absolute h-full w-16 hover:bg-slate-400 opacity-[0.01]"
/> />
<button <button
on:mousedown={mouseDown} on:mousedown={mouseDown}
on:mouseup={right} on:mouseup={right}
class="right-0 top-0 absolute h-full w-10 hover:bg-slate-400 opacity-[0.01]" class="right-0 top-0 absolute h-full w-16 hover:bg-slate-400 opacity-[0.01]"
/> />
{/if}
{:else} {:else}
<div class="fixed z-50 left-1/2 top-1/2"> <div class="fixed z-50 left-1/2 top-1/2">
<Spinner /> <Spinner />

View File

@@ -55,7 +55,7 @@
</script> </script>
{#each textBoxes as { fontSize, height, left, lines, top, width, writingMode }, index (`text-box-${index}`)} {#each textBoxes as { fontSize, height, left, lines, top, width, writingMode }, index (`text-box-${index}`)}
<button <div
class="text-box" class="text-box"
style:width style:width
style:height style:height
@@ -65,15 +65,15 @@
style:font-weight={fontWeight} style:font-weight={fontWeight}
style:display style:display
style:border style:border
style:writing-mode={writingMode}
role="none"
on:dblclick={() => onUpdateCard(lines)} on:dblclick={() => onUpdateCard(lines)}
{contenteditable} {contenteditable}
> >
<div style:writing-mode={writingMode}>
{#each lines as line} {#each lines as line}
<p>{line}</p> <p>{line}</p>
{/each} {/each}
</div> </div>
</button>
{/each} {/each}
<style> <style>
@@ -85,6 +85,7 @@
font-size: 16pt; font-size: 16pt;
white-space: nowrap; white-space: nowrap;
border: 1px solid rgba(0, 0, 0, 0); border: 1px solid rgba(0, 0, 0, 0);
z-index: 11;
} }
.text-box:focus, .text-box:focus,
@@ -101,6 +102,7 @@
margin: 0; margin: 0;
background-color: rgb(255, 255, 255); background-color: rgb(255, 255, 255);
font-weight: var(--bold); font-weight: var(--bold);
z-index: 11;
} }
.text-box:focus p, .text-box:focus p,

View File

@@ -1,8 +1,14 @@
<script lang="ts"> <script lang="ts">
import { AccordionItem } from 'flowbite-svelte'; import { AccordionItem, Label, Range } from 'flowbite-svelte';
import ReaderSelects from './ReaderSelects.svelte'; import ReaderSelects from './ReaderSelects.svelte';
import ReaderToggles from './ReaderToggles.svelte'; import ReaderToggles from './ReaderToggles.svelte';
import { isReader } from '$lib/util'; import { isReader } from '$lib/util';
import { settings, updateSetting } from '$lib/settings';
let value = $settings.swipeThreshold;
function onChange() {
updateSetting('swipeThreshold', value);
}
</script> </script>
<AccordionItem open={isReader()}> <AccordionItem open={isReader()}>
@@ -11,5 +17,9 @@
<ReaderSelects /> <ReaderSelects />
<hr class="border-gray-100 opacity-10" /> <hr class="border-gray-100 opacity-10" />
<ReaderToggles /> <ReaderToggles />
<div>
<Label>Swipe threshold</Label>
<Range on:change={onChange} min={20} max={90} disabled={!$settings.mobile} bind:value />
</div>
</div> </div>
</AccordionItem> </AccordionItem>

View File

@@ -167,14 +167,23 @@ export function keepInBounds() {
let minY = innerHeight - height - marginY; let minY = innerHeight - height - marginY;
let maxY = marginY; let maxY = marginY;
let forceCenterY = false;
if (width + 2 * marginX <= innerWidth) { if (width + 2 * marginX <= innerWidth) {
minX = marginX; minX = marginX;
maxX = innerWidth - width - marginX; maxX = innerWidth - width - marginX;
} else {
minX = innerWidth - width - marginX;
maxX = marginX;
} }
if (height + 2 * marginY <= innerHeight) { if (height + 2 * marginY <= innerHeight) {
minY = marginY; minY = marginY;
maxY = innerHeight - height - marginY; maxY = innerHeight - height - marginY;
forceCenterY = true;
} else {
minY = innerHeight - height - marginY;
maxY = marginY;
} }
if (x < minX) { if (x < minX) {
@@ -185,6 +194,9 @@ export function keepInBounds() {
transform.x = maxX; transform.x = maxX;
} }
if (forceCenterY) {
transform.y = innerHeight / 2 - height / 2;
} else {
if (y < minY) { if (y < minY) {
transform.y = minY; transform.y = minY;
} }
@@ -192,6 +204,7 @@ export function keepInBounds() {
transform.y = maxY; transform.y = maxY;
} }
} }
}
export function toggleFullScreen() { export function toggleFullScreen() {
if (!document.fullscreenElement) { if (!document.fullscreenElement) {

View File

@@ -37,6 +37,7 @@ export type Settings = {
hasCover: boolean; hasCover: boolean;
mobile: boolean; mobile: boolean;
backgroundColor: string; backgroundColor: string;
swipeThreshold: number;
fontSize: FontSize; fontSize: FontSize;
zoomDefault: ZoomModes; zoomDefault: ZoomModes;
ankiConnectSettings: AnkiConnectSettings; ankiConnectSettings: AnkiConnectSettings;
@@ -67,6 +68,7 @@ const defaultSettings: Settings = {
charCount: false, charCount: false,
mobile: false, mobile: false,
backgroundColor: '#030712', backgroundColor: '#030712',
swipeThreshold: 50,
fontSize: 'auto', fontSize: 'auto',
zoomDefault: 'zoomFitToScreen', zoomDefault: 'zoomFitToScreen',
ankiConnectSettings: { ankiConnectSettings: {

View File

@@ -32,7 +32,7 @@ export async function unzipManga(file: File) {
function getDetails(file: File) { function getDetails(file: File) {
const { webkitRelativePath, name } = file const { webkitRelativePath, name } = file
const split = name.split('.'); const split = name.split('.');
const ext = split.pop() const ext = split.pop();
const filename = split.join('.'); const filename = split.join('.');
let path = filename let path = filename
@@ -143,7 +143,7 @@ export async function processFiles(files: File[]) {
continue; continue;
} }
if (zipTypes.includes(ext)) { if (ext && zipTypes.includes(ext)) {
const unzippedFiles = await unzipManga(file); const unzippedFiles = await unzipManga(file);
volumes[path] = { volumes[path] = {

View File

@@ -8,3 +8,18 @@ export function clamp(num: number, min: number, max: number) {
export function isReader() { export function isReader() {
return get(page).route.id === '/[manga]/[volume]' return get(page).route.id === '/[manga]/[volume]'
} }
let timer: any;
export function debounce(func: () => void, timeout = 50) {
if (!timer) {
timer = setTimeout(() => {
func();
clearTimeout(timer);
timer = undefined;
}, timeout);
} else {
clearTimeout(timer);
timer = undefined;
}
}