Testing page generation

This commit is contained in:
ZXY101
2023-08-03 10:08:04 +02:00
parent 00f438a290
commit 07e007aaa7
26 changed files with 564 additions and 92 deletions

15
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "z-reader", "name": "z-reader",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@zip.js/zip.js": "^2.7.20",
"panzoom": "^9.4.3" "panzoom": "^9.4.3"
}, },
"devDependencies": { "devDependencies": {
@@ -885,6 +886,15 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@zip.js/zip.js": {
"version": "2.7.20",
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.20.tgz",
"integrity": "sha512-rh5cby/bBOYC+hcK9qElDzbiIDeL3nhHPUTIE6/FQZR8mhY7azpthPdYbSNMOYBfv0AM188RNJ2yjtXsUfbAuQ==",
"engines": {
"deno": ">=1.0.0",
"node": ">=16.5.0"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.10.0", "version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
@@ -3698,6 +3708,11 @@
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
} }
}, },
"@zip.js/zip.js": {
"version": "2.7.20",
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.20.tgz",
"integrity": "sha512-rh5cby/bBOYC+hcK9qElDzbiIDeL3nhHPUTIE6/FQZR8mhY7azpthPdYbSNMOYBfv0AM188RNJ2yjtXsUfbAuQ=="
},
"acorn": { "acorn": {
"version": "8.10.0", "version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",

View File

@@ -29,6 +29,7 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@zip.js/zip.js": "^2.7.20",
"panzoom": "^9.4.3" "panzoom": "^9.4.3"
} }
} }

View File

@@ -13,6 +13,7 @@
button { button {
border: none; border: none;
font-family: var(--font-family);
background-color: transparent; background-color: transparent;
color: white; color: white;
} }

BIN
src/lib/assets/001.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

BIN
src/lib/assets/049.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@@ -1,4 +1,5 @@
import type { Manga } from "$lib/types/catalog"; import type { Manga, Volume } from "$lib/types/catalog";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
export const currentManga = writable<Manga | undefined>(undefined); export const currentManga = writable<Manga | undefined>(undefined);
export const currentVolume = writable<Volume | undefined>(undefined);

View File

@@ -41,6 +41,7 @@
style:--color={color} style:--color={color}
style:--background-color={backgroundColor} style:--background-color={backgroundColor}
style:--active={activeColor} style:--active={activeColor}
{...$$restProps}
on:click on:click
> >
<slot /> <slot />

View File

@@ -11,32 +11,92 @@
{ {
title: 'Manga name', title: 'Manga name',
cover: image1, cover: image1,
currentPage: 26, volumes: [
{
cover: image1,
title: 'Volume 1',
currentPage: 0,
totalPages: 100 totalPages: 100
}, },
{
cover: image1,
title: 'Volume 2',
currentPage: 0,
totalPages: 100
}
]
},
{ {
title: 'Another', title: 'Another',
cover: image2, cover: image2,
volumes: [
{
cover: image2,
title: 'Volume 1',
currentPage: 0, currentPage: 0,
totalPages: 120 totalPages: 100
},
{
cover: image2,
title: 'Volume 2',
currentPage: 0,
totalPages: 100
}
]
}, },
{ {
title: 'Awooo', title: 'Awooo',
cover: image3, cover: image3,
currentPage: 69, volumes: [
totalPages: 96 {
cover: image3,
title: 'Volume 1',
currentPage: 0,
totalPages: 100
},
{
cover: image3,
title: 'Volume 2',
currentPage: 0,
totalPages: 100
}
]
}, },
{ {
title: 'Title', title: 'Title',
cover: image4, cover: image4,
currentPage: 9, volumes: [
totalPages: 59 {
cover: image4,
title: 'Volume 1',
currentPage: 0,
totalPages: 100
},
{
cover: image4,
title: 'Volume 2',
currentPage: 0,
totalPages: 100
}
]
}, },
{ {
title: 'sdhfjksdh', title: 'sdhfjksdh',
cover: image5, cover: image5,
currentPage: 19, volumes: [
totalPages: 200 {
cover: image5,
title: 'Volume 1',
currentPage: 0,
totalPages: 100
},
{
cover: image5,
title: 'Volume 2',
currentPage: 0,
totalPages: 100
}
]
} }
]; ];
</script> </script>

View File

@@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation';
import { currentManga } from '$lib/catalog'; import { currentManga } from '$lib/catalog';
import type { Manga } from '$lib/types/catalog'; import type { Manga } from '$lib/types/catalog';
import Button from './Button.svelte'; import { navbarTitle } from './NavBar.svelte';
export let manga: Manga; export let manga: Manga;
const { cover, currentPage, title, totalPages } = manga; const { cover, title } = manga;
function onClick() { function onClick() {
console.log('bruh');
navbarTitle.set(title);
currentManga.set(manga); currentManga.set(manga);
} }
</script> </script>
@@ -16,7 +17,6 @@
<div class="content"> <div class="content">
{title} {title}
<img src={cover} alt={title} /> <img src={cover} alt={title} />
{currentPage} / {totalPages}
</div> </div>
</a> </a>

View File

@@ -0,0 +1,56 @@
<script lang="ts">
import type { Entry } from '@zip.js/zip.js';
import Button from './Button.svelte';
import { colors } from '$lib/theme';
export let accept: string | null | undefined = undefined;
export let files: FileList | null | undefined = undefined;
export let multiple: boolean | null | undefined = true;
export let onUpload: ((files: FileList) => void) | undefined = undefined;
let input: HTMLInputElement;
function handleChange() {
if (files && onUpload) {
onUpload(files);
}
}
function onClick() {
input.click();
}
function onDrop(event: DragEvent) {
const items = event.dataTransfer?.items;
// TODO
}
</script>
<input type="file" {multiple} {accept} bind:files bind:this={input} on:change={handleChange} />
<button
on:click={onClick}
style:--border={colors.secondaryColor}
on:dragover|preventDefault
on:drop|preventDefault|stopPropagation={onDrop}
>
<p style:color={colors.secondaryColor}><slot>Upload</slot></p>
</button>
<style>
input {
display: none;
}
button {
width: 500px;
height: 100px;
border-radius: 12px;
border: 2px dashed var(--border);
}
p {
font-weight: bold;
font-size: 16px;
}
</style>

View File

@@ -1,16 +1,51 @@
<script lang="ts"> <script lang="ts" context="module">
import { colors } from '$lib/theme'; export let navbarTitle = writable<string | undefined>(undefined);
</script>
export let title: string | undefined = undefined; <script lang="ts">
import { afterNavigate } from '$app/navigation';
import { page } from '$app/stores';
import { currentManga, currentVolume } from '$lib/catalog';
import { colors } from '$lib/theme';
import { writable } from 'svelte/store';
let title: string | undefined = 'Mokuro';
let back: string | undefined = undefined;
afterNavigate(() => {
console.log($page?.route.id);
window.document.body.classList.remove('reader');
switch ($page?.route.id) {
case '/[manga]':
title = $currentManga?.title;
back = '/';
break;
case '/[manga]/[volume]':
window.document.body.classList.add('reader');
title = $currentVolume?.title;
back = '/manga';
break;
case '/upload':
title = 'Upload';
back = '/';
break;
default:
title = 'Mokuro';
back = undefined;
break;
}
});
</script> </script>
<nav> <nav>
<div style:background-color={colors.primaryColor}> <div style:background-color={colors.primaryColor}>
{#if title} {#if back}
<a href="/"><h2>Back</h2></a> <a href={back}><h2>Back</h2></a>
<h2>{title}</h2> <h2>{title}</h2>
{:else} {:else}
<a href="/"><h2>Mokuro</h2></a> <a href="/"><h2>{title}</h2></a>
{/if} {/if}
</div> </div>
</nav> </nav>

View File

@@ -0,0 +1,195 @@
<script lang="ts">
import img from '$lib/assets/001.jpg';
type Block = {
box: number[];
vertical: boolean;
fontSize: number;
lines: string[];
};
type Page = {
version?: string;
imgWidth: number;
imgHeight: number;
blocks: Block[];
imgPath: string;
};
let page: Page = {
version: '0.1.6',
imgWidth: 1078,
imgHeight: 1530,
blocks: [
{
box: [924, 167, 948, 380],
vertical: true,
fontSize: 21.0,
lines: ['私も頑張らなくちゃ!']
},
{
box: [584, 242, 610, 397],
vertical: true,
fontSize: 26.0,
lines: ['リョウ先輩!']
},
{
box: [895, 474, 943, 687],
vertical: true,
fontSize: 20.0,
lines: ['私のギターの練習', 'みてもらえませんか!']
},
{
box: [898, 787, 947, 979],
vertical: true,
fontSize: 21.0,
lines: ['ぼっちに教えて', 'もらってるじゃん?']
},
{
box: [708, 796, 783, 936],
vertical: true,
fontSize: 20.0,
lines: ['〜〜っ', 'もっと練習', 'したいんです!!']
},
{
box: [663, 832, 701, 971],
vertical: true,
fontSize: 20.0,
lines: ['後藤さんだって', '?の練習あるし...']
},
{
box: [568, 800, 616, 1011],
vertical: true,
fontSize: 20.0,
lines: ['ちょっと喜多ちゃん', '急にどうしちゃったの']
},
{
box: [874, 1122, 951, 1380],
vertical: true,
fontSize: 22.0,
lines: ['まさか結束パンドの', 'ギター同士で血で血を洗う', 'パート争いを...']
},
{
box: [636, 1135, 682, 1256],
vertical: true,
fontSize: 20.0,
lines: ['ちょっと', '結束してよ〜']
},
{
box: [560, 1240, 590, 1381],
vertical: true,
fontSize: 27.0,
lines: ['違います!!']
}
],
imgPath: '049.jpg'
};
let bold = false;
$: fontWeight = bold ? 'bold' : '400';
console.log(bold);
function clamp(x: number, min: number, max: number) {
return Math.min(Math.max(x, min), max);
}
const { blocks, imgHeight, imgPath, imgWidth } = page;
const area = imgWidth * imgHeight;
const textBoxes = blocks.map((block) => {
const { box, fontSize, lines, vertical } = block;
let [_xmin, _ymin, _xmax, _ymax] = box;
const xmin = clamp(_xmin, 0, imgWidth);
const ymin = clamp(_ymin, 0, imgHeight);
const xmax = clamp(_xmax, 0, imgWidth);
const ymax = clamp(_ymax, 0, imgHeight);
const width = xmax - xmin;
const height = ymax - ymin;
const textBox = {
left: `${xmin}px`,
top: `${ymin}px`,
width: `${width}px`,
height: `${height}px`,
fontSize: `${fontSize}px`,
writingMode: vertical ? 'vertical-rl' : 'horizontal-tb',
lines
};
console.log(textBox);
return textBox;
});
let src = `/src/lib/assets/${imgPath}`;
</script>
<div style:--bold={fontWeight}>
<div class="bold">
<label>Bold</label>
<input bind:checked={bold} type="checkbox" placeholder="????" />
</div>
<img draggable="false" {src} alt="Page 1" />
{#each textBoxes as { left, top, width, height, lines, fontSize, writingMode }}
<div
class="text-box"
style:width
style:height
style:left
style:top
style:font-size={fontSize}
style:writing-mode={writingMode}
>
{#each lines as line}
<p>{line}</p>
{/each}
</div>
<div />
{/each}
</div>
<style>
div {
position: relative;
margin: 0 auto;
}
.bold {
position: absolute;
color: #000;
right: 10px;
bottom: 10px;
z-index: 99;
}
.text-box {
color: black;
padding: 0;
position: absolute;
line-height: 1.1em;
font-size: 16pt;
white-space: nowrap;
border: 1px solid rgba(0, 0, 0, 0);
}
.text-box:hover {
background: rgb(255, 255, 255);
border: 1px solid rgba(0, 0, 0, 0);
z-index: 999 !important;
}
.text-box p {
display: none;
white-space: nowrap;
letter-spacing: 0.1em;
line-height: 1.1em;
margin: 0;
background-color: rgb(255, 255, 255);
font-weight: var(--bold);
}
.text-box:hover p {
display: table;
}
</style>

View File

@@ -0,0 +1,26 @@
<script lang="ts">
import { currentManga, currentVolume } from '$lib/catalog';
import Button from '$lib/components/Button.svelte';
import { Panzoom, zoomOriginal } from '$lib/panzoom';
import MangaPage from './MangaPage.svelte';
const volume = $currentVolume;
</script>
{#if volume}
<div>
<Button on:click={zoomOriginal}>Reset Zoom</Button>
</div>
<Panzoom>
<MangaPage />
</Panzoom>
{/if}
<style>
div {
position: absolute;
bottom: 10px;
left: 10px;
z-index: 1;
}
</style>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { currentManga, currentVolume } from '$lib/catalog';
import type { Volume } from '$lib/types/catalog';
export let volume: Volume;
const { cover, title, currentPage, totalPages } = volume;
function onClick() {
currentVolume.set(volume);
}
</script>
<a href={`${$page.params.manga}/${title}`} on:click={onClick}>
<div class="content">
{title}
<img src={cover} alt={title} />
{currentPage} / {totalPages}
</div>
</a>
<style>
img {
width: 250px;
height: 350px;
object-fit: contain;
}
.content {
display: flex;
flex-direction: column;
gap: 5px;
text-align: center;
}
</style>

View File

@@ -65,8 +65,6 @@ export function panAlign(alignX: PanX, alignY: PanY) {
break; break;
} }
console.log(x, y);
pz?.moveTo(x, y) pz?.moveTo(x, y)
} }

View File

@@ -1,6 +1,12 @@
export type Manga = { export type Volume = {
title: string; title: string;
cover: string; cover: string;
currentPage: number; currentPage: number;
totalPages: number; totalPages: number;
}
export type Manga = {
title: string;
cover: string;
volumes: Volume[];
}; };

8
src/lib/upload/index.ts Normal file
View File

@@ -0,0 +1,8 @@
import { BlobReader, ZipReader } from "@zip.js/zip.js";
export async function unzipManga(file: File) {
const zipFileReader = new BlobReader(file);
const zipReader = new ZipReader(zipFileReader);
return await zipReader.getEntries()
}

View File

@@ -1,23 +0,0 @@
<script lang="ts">
import { afterNavigate } from '$app/navigation';
import { navigating } from '$app/stores';
import { currentManga } from '$lib/catalog';
import Button from '$lib/components/Button.svelte';
import NavBar from '$lib/components/NavBar.svelte';
import '../../app.css';
afterNavigate(({}) => {
window.document.body.classList.remove('reader');
});
</script>
<NavBar />
<div>
<slot />
</div>
<style>
div {
padding: 10px;
}
</style>

View File

@@ -1,5 +0,0 @@
<script lang="ts">
import Catalog from '$lib/components/Catalog.svelte';
</script>
<Catalog />

16
src/routes/+layout.svelte Normal file
View File

@@ -0,0 +1,16 @@
<script lang="ts">
import NavBar from '$lib/components/NavBar.svelte';
import '../app.css';
</script>
<NavBar />
<div>
<slot />
</div>
<style>
div {
padding: 10px;
}
</style>

12
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { goto } from '$app/navigation';
import Button from '$lib/components/Button.svelte';
import Catalog from '$lib/components/Catalog.svelte';
function onClick() {
goto('/upload');
}
</script>
<Button on:click={onClick}>Upload</Button>
<Catalog />

View File

@@ -1,27 +0,0 @@
<script lang="ts">
import { currentManga } from '$lib/catalog';
import NavBar from '$lib/components/NavBar.svelte';
import { onMount } from 'svelte';
import { afterNavigate, goto } from '$app/navigation';
import '../../app.css';
const manga = $currentManga;
onMount(() => {
if (!manga) {
goto('/');
window.document.body.classList.remove('reader');
} else {
window.document.body.classList.add('reader');
}
});
</script>
<NavBar title={manga?.title} />
<slot />
<style>
:global(body.reader) {
overflow: hidden;
}
</style>

View File

@@ -4,24 +4,30 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import Button from '$lib/components/Button.svelte'; import Button from '$lib/components/Button.svelte';
import { panAlign, Panzoom, zoomOriginal } from '$lib/panzoom'; import { panAlign, Panzoom, zoomOriginal } from '$lib/panzoom';
import VolumeItem from '$lib/components/VolumeItem.svelte';
const manga = $currentManga; const manga = $currentManga;
onMount(() => {
if (!manga) {
goto('/');
}
});
</script> </script>
{#if manga} <div>
<div> {#if manga}
<Button on:click={zoomOriginal}>Reset Zoom</Button> {#each manga.volumes as volume}
</div> <VolumeItem {volume} />
<Panzoom> {/each}
<img draggable="false" src={manga.cover} alt={manga.title} /> {/if}
</Panzoom> </div>
{/if}
<style> <style>
div { div {
position: absolute; display: flex;
bottom: 10px; flex-direction: row;
left: 10px; gap: 20px;
z-index: 1; flex-wrap: wrap;
} }
</style> </style>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { currentVolume } from '$lib/catalog';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
const volume = $currentVolume;
onMount(() => {
if (!volume) {
goto('/');
} else {
console.log('???');
window.document.body.classList.add('reader');
}
});
</script>
<slot />
<style>
:global(body.reader) {
overflow: hidden !important;
}
</style>

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import Reader from '$lib/components/Reader/Reader.svelte';
</script>
<Reader />

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import FileUpload from '$lib/components/FileUpload.svelte';
import { unzipManga } from '$lib/upload';
import type { Entry } from '@zip.js/zip.js';
let promise: Promise<Entry[]> | undefined;
async function onUpload(files: FileList) {
const [file] = files;
promise = unzipManga(file);
}
</script>
<FileUpload {onUpload} accept=".mokuro,.zip,.cbz,.rar" />
{#if promise}
{#await promise}
<p>Loading...</p>
{:then entries}
{#each entries as entry}
<p>{entry.filename}</p>
{/each}
{/await}
{/if}