Mostly get drag and drop working

This commit is contained in:
ZXY101
2023-09-13 16:57:52 +02:00
parent 3c8ab90b06
commit 3a37b62a38
9 changed files with 284 additions and 146 deletions

View File

@@ -3,7 +3,7 @@
import CatalogItem from './CatalogItem.svelte';
</script>
{#if $catalog && $catalog.length > 0}
{#if $catalog}
{#if $catalog.length > 0}
<div class="container">
{#each $catalog as { manga }}
@@ -13,7 +13,6 @@
{:else}
<div class="empty-state">
<p>Your catalog is currently empty.</p>
<a href="upload">Add manga</a>
</div>
{/if}
{:else}

View File

@@ -1,5 +1,7 @@
<script lang="ts">
export let files: FileList | null | undefined = undefined;
import { A, Fileupload, Label } from 'flowbite-svelte';
export let files: FileList | undefined = undefined;
export let onUpload: ((files: FileList) => void) | undefined = undefined;
let input: HTMLInputElement;
@@ -13,38 +15,15 @@
function onClick() {
input.click();
}
function onDrop(event: DragEvent) {
const items = event.dataTransfer?.items;
// TODO
}
</script>
<input type="file" bind:files bind:this={input} on:change={handleChange} {...$$restProps} />
<input
type="file"
bind:files
bind:this={input}
on:change={handleChange}
{...$$restProps}
class="hidden"
/>
<button
on:click={onClick}
on:dragover|preventDefault
on:drop|preventDefault|stopPropagation={onDrop}
>
<p><slot>Upload</slot></p>
</button>
<style lang="scss">
input {
display: none;
}
button {
width: 100%;
height: 100px;
border-radius: 12px;
border: 2px dashed $secondary-color;
}
p {
font-weight: bold;
font-size: 16px;
color: $secondary-color;
}
</style>
<A on:click={onClick}><slot>Upload</slot></A>

View File

@@ -1,66 +1,18 @@
<script lang="ts">
import {
Navbar,
NavBrand,
Modal,
Drawer,
Button,
CloseButton,
Checkbox,
Toggle,
Select,
Input,
Label
} from 'flowbite-svelte';
import {
UserSettingsSolid,
UploadSolid,
InfoCircleSolid,
ArrowRightOutline
} from 'flowbite-svelte-icons';
import FileUpload from './FileUpload.svelte';
import { processFiles } from '$lib/upload';
import { Navbar, NavBrand } from 'flowbite-svelte';
import { UserSettingsSolid, UploadSolid } from 'flowbite-svelte-icons';
import { afterNavigate } from '$app/navigation';
import { page } from '$app/stores';
import { sineIn } from 'svelte/easing';
import { settingsStore, updateSetting } from '$lib/settings';
import Settings from './Settings.svelte';
import UploadModal from './UploadModal.svelte';
let transitionParams = {
x: 320,
duration: 200,
easing: sineIn
};
let promise: Promise<void>;
let modal = false;
let drawer = false;
let isReader = true;
async function onUpload(files: FileList) {
promise = processFiles(files).then(() => {
modal = false;
});
}
let settingsHidden = true;
let uploadModalOpen = false;
let isReader = false;
afterNavigate(() => {
isReader = $page.route.id === '/[manga]/[volume]';
});
let selected = 'us';
let countries = [
{ value: 'us', name: 'auto' },
{ value: 'ca', name: 'Canada' },
{ value: 'fr', name: 'France' }
];
$: toggles = [
{ key: 'rightToLeft', text: 'Right to left', value: $settingsStore.rightToLeft },
{ key: 'singlePageView', text: 'Single page view', value: $settingsStore.singlePageView },
{ key: 'textEditable', text: 'Editable text', value: $settingsStore.textEditable },
{ key: 'textBoxBorders', text: 'Text box borders', value: $settingsStore.textBoxBorders },
{ key: 'displayOCR', text: 'OCR enabled', value: $settingsStore.displayOCR }
];
</script>
<div class="relative z-10">
@@ -69,54 +21,17 @@
<span class="text-xl font-semibold dark:text-white">Mokuro</span>
</NavBrand>
<div class="flex md:order-2 gap-5">
<UserSettingsSolid class="hover:text-primary-700" on:click={() => (drawer = false)} />
<UploadSolid class="hover:text-primary-700" on:click={() => (modal = true)} />
<UserSettingsSolid class="hover:text-primary-700" on:click={() => (settingsHidden = false)} />
<UploadSolid class="hover:text-primary-700" on:click={() => (uploadModalOpen = true)} />
</div>
</Navbar>
{#if isReader}
<UserSettingsSolid
class="hover:text-primary-700 absolute right-5 top-5 opacity-10 hover:opacity-100"
on:click={() => (drawer = false)}
on:click={() => (settingsHidden = false)}
/>
{/if}
</div>
<Drawer
placement="right"
transitionType="fly"
width="lg:w-1/4 md:w-1/2 w-full"
{transitionParams}
bind:hidden={drawer}
id="sidebar1"
>
<div class="flex items-center">
<h5 id="drawer-label" class="inline-flex items-center mb-4 text-base font-semibold">
<UserSettingsSolid class="w-4 h-4 mr-2.5" />Settings
</h5>
<CloseButton on:click={() => (drawer = true)} class="mb-4 dark:text-white" />
</div>
<div class="flex flex-col gap-5">
{#each toggles as { key, text, value }}
<Toggle size="small" checked={value} on:change={() => updateSetting(key, !value)}
>{text}</Toggle
>
{/each}
<div>
<Label>Fontsize:</Label>
<Select items={countries} bind:value={selected} />
</div>
<div>
<Label>Background color:</Label>
<Input type="color" />
</div>
</div>
</Drawer>
<Modal title="Upload" bind:open={modal} outsideclose>
{#await promise}
<h2 class="justify-center flex">Loading...</h2>
{:then}
<FileUpload {onUpload} webkitdirectory>Upload directory</FileUpload>
<FileUpload {onUpload} accept=".mokuro,.zip,.cbz" multiple>Upload files</FileUpload>
{/await}
</Modal>
<Settings bind:hidden={settingsHidden} />
<UploadModal bind:open={uploadModalOpen} />

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import { Drawer, CloseButton, Toggle, Select, Input, Label } from 'flowbite-svelte';
import { UserSettingsSolid } from 'flowbite-svelte-icons';
import { sineIn } from 'svelte/easing';
import { settingsStore, updateSetting } from '$lib/settings';
let transitionParams = {
x: 320,
duration: 200,
easing: sineIn
};
export let hidden = true;
let selected = 'us';
let countries = [
{ value: 'us', name: 'auto' },
{ value: 'ca', name: 'Canada' },
{ value: 'fr', name: 'France' }
];
$: toggles = [
{ key: 'rightToLeft', text: 'Right to left', value: $settingsStore.rightToLeft },
{ key: 'singlePageView', text: 'Single page view', value: $settingsStore.singlePageView },
{ key: 'textEditable', text: 'Editable text', value: $settingsStore.textEditable },
{ key: 'textBoxBorders', text: 'Text box borders', value: $settingsStore.textBoxBorders },
{ key: 'displayOCR', text: 'OCR enabled', value: $settingsStore.displayOCR }
];
</script>
<Drawer
placement="right"
transitionType="fly"
width="lg:w-1/4 md:w-1/2 w-full"
{transitionParams}
bind:hidden
id="sidebar1"
>
<div class="flex items-center">
<h5 id="drawer-label" class="inline-flex items-center mb-4 text-base font-semibold">
<UserSettingsSolid class="w-4 h-4 mr-2.5" />Settings
</h5>
<CloseButton on:click={() => (hidden = true)} class="mb-4 dark:text-white" />
</div>
<div class="flex flex-col gap-5">
{#each toggles as { key, text, value }}
<Toggle size="small" checked={value} on:change={() => updateSetting(key, !value)}
>{text}</Toggle
>
{/each}
<div>
<Label>Fontsize:</Label>
<Select items={countries} bind:value={selected} />
</div>
<div>
<Label>Background color:</Label>
<Input type="color" />
</div>
</div>
</Drawer>

View File

@@ -0,0 +1,144 @@
<script lang="ts">
import { Button, Dropzone, Modal, Spinner } from 'flowbite-svelte';
import FileUpload from './FileUpload.svelte';
import { processFiles } from '$lib/upload';
import { onMount } from 'svelte';
import { formatBytes, scanFiles } from '$lib/util/upload';
export let open = false;
let promise: Promise<void>;
let files: FileList | undefined = undefined;
async function onUpload() {
if (files) {
promise = processFiles([...files]).then(() => {
open = false;
});
} else if (draggedFiles) {
promise = processFiles(draggedFiles).then(() => {
open = false;
});
}
}
function reset() {
files = undefined;
draggedFiles = undefined;
}
let storageSpace = 'Loading...';
onMount(() => {
navigator.storage.estimate().then(({ usage, quota }) => {
if (usage && quota) {
storageSpace = `Storage: ${formatBytes(usage)} / ${formatBytes(quota)}`;
}
});
});
let filePromises: Promise<File>[];
let draggedFiles: File[] | undefined;
const dropHandle = async (event: DragEvent) => {
draggedFiles = [];
filePromises = [];
event.preventDefault();
activeStyle = defaultStyle;
if (event?.dataTransfer?.items) {
for (const item of [...event.dataTransfer.items]) {
const entry = item.webkitGetAsEntry();
if (entry && entry.isDirectory) {
await scanFiles(entry, filePromises);
}
if (item.kind === 'file') {
const file = item.getAsFile();
if (file) {
draggedFiles.push(file);
draggedFiles = draggedFiles;
}
}
}
if (filePromises && filePromises.length > 0) {
const files = await Promise.all(filePromises);
if (files) {
draggedFiles = [...draggedFiles, ...files];
}
}
}
};
let defaultStyle =
'flex flex-col justify-center items-center w-full h-64 bg-gray-50 rounded-lg border-2 border-gray-300 border-dashed cursor-pointer dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600';
let highlightStyle =
'flex flex-col justify-center items-center w-full h-64 bg-gray-50 rounded-lg border-2 border-gray-300 border-dashed cursor-pointer dark:bg-bray-800 dark:bg-gray-700 bg-gray-100 dark:border-gray-600 dark:border-gray-500 dark:bg-gray-600';
let activeStyle = defaultStyle;
</script>
<Modal title="Upload" bind:open outsideclose on:close={reset}>
{#await promise}
<h2 class="justify-center flex">Loading...</h2>
<div class="text-center"><Spinner /></div>
{:then}
<Dropzone
id="dropzone"
on:drop={dropHandle}
on:dragover={(event) => {
event.preventDefault();
activeStyle = highlightStyle;
}}
on:dragleave={(event) => {
event.preventDefault();
activeStyle = defaultStyle;
}}
on:click={(event) => {
event.preventDefault();
}}
defaultClass={activeStyle}
>
<svg
aria-hidden="true"
class="mb-3 w-10 h-10 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/></svg
>
{#if files}
<p class="mb-2 text-sm text-gray-500 dark:text-gray-400">
Upload {files.length}
{files.length > 1 ? 'files' : 'file'}?
</p>
{:else if draggedFiles && draggedFiles.length > 0}
<p class="mb-2 text-sm text-gray-500 dark:text-gray-400">
Upload {draggedFiles.length} hih
{draggedFiles.length > 1 ? 'files' : 'file'}?
</p>
{:else}
<p class="mb-2 text-sm text-gray-500 dark:text-gray-400">
Drag and drop / <FileUpload bind:files accept=".mokuro,.zip,.cbz" multiple
>choose files</FileUpload
> /
<FileUpload bind:files webkitdirectory>choose directory</FileUpload>
</p>
{/if}
</Dropzone>
<p class=" text-sm text-gray-500 dark:text-gray-400 text-center">{storageSpace}</p>
<div class="flex flex-1 flex-col gap-2">
<Button outline on:click={reset} disabled={!files && !draggedFiles} color="dark">Reset</Button
>
<Button outline on:click={onUpload} disabled={!files && !draggedFiles}>Upload</Button>
</div>
{/await}
</Modal>

View File

@@ -35,17 +35,7 @@ function getDetails(file: File) {
}
}
// export async function processVolumes(volumes: Volume[]) {
// for (const { mokuroData, volumeName, archiveFile, files } of volumes) {
// const { title_uuid } = mokuroData
// }
// }
export async function processFiles(fileList: FileList) {
const files = [...fileList]
export async function processFiles(files: File[]) {
const zipTypes = ['zip', 'cbz']
const volumes: Record<string, Volume> = {};
@@ -64,7 +54,10 @@ export async function processFiles(fileList: FileList) {
continue;
}
if (type === 'image/jpeg' || type === 'image/png') {
const mimeType = type || getMimeType(file.name)
if (mimeType === 'image/jpeg' || mimeType === 'image/png') {
if (webkitRelativePath) {
const imageName = webkitRelativePath.split('/').at(-1)
const vol = webkitRelativePath.split('/').at(-2)

View File

@@ -4,3 +4,41 @@ export async function requestPersistentStorage() {
console.log(`Persisted storage granted: ${isPersisted}`);
}
}
export function formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return '0 B';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
}
async function getFile(fileEntry: FileSystemFileEntry) {
try {
return new Promise<File>((resolve, reject) => fileEntry.file(resolve, reject));
} catch (err) {
console.log(err);
}
}
export async function scanFiles(item: FileSystemEntry, files: Promise<File | undefined>[]) {
if (item.isDirectory) {
const directoryReader = (item as FileSystemDirectoryEntry).createReader();
await new Promise<void>((resolve) => {
directoryReader.readEntries((entries) => {
entries.forEach((entry) => {
if (entry.isFile) {
files.push(getFile(entry as FileSystemFileEntry))
resolve()
}
scanFiles(entry, files);
});
});
});
}
}

View File

@@ -7,7 +7,16 @@
import { ExclamationCircleOutline } from 'flowbite-svelte-icons';
import { db } from '$lib/catalog/db';
const manga = $currentManga;
const manga = $currentManga?.sort((a, b) => {
if (a.volumeName < b.volumeName) {
return -1;
}
if (a.volumeName > b.volumeName) {
return 1;
}
return 0;
});
let popupModal = false;
onMount(() => {

View File

@@ -6,7 +6,7 @@
let promise: Promise<void>;
async function onUpload(files: FileList) {
promise = processFiles(files);
promise = processFiles([...files]);
}
</script>