init
This commit is contained in:
2
view/src/routes/index.ts
Executable file
2
view/src/routes/index.ts
Executable file
@ -0,0 +1,2 @@
|
||||
export { default as main } from "./main.svelte"
|
||||
export { default as list } from "./list.svelte"
|
24
view/src/routes/list.svelte
Normal file
24
view/src/routes/list.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { store as imageStore } from "../stores/images";
|
||||
|
||||
let images = [];
|
||||
const urlCreator = window.URL || window.webkitURL;
|
||||
|
||||
$: if ($imageStore.length) {
|
||||
images = $imageStore.map((img) => {
|
||||
const blob = new Blob([img.data], { type: img.type });
|
||||
const imageUrl = urlCreator.createObjectURL(blob);
|
||||
return {
|
||||
...img,
|
||||
imageUrl,
|
||||
};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each images as img}
|
||||
<p>{img.filename}</p>
|
||||
<img src={img.imageUrl} />
|
||||
{/each}
|
||||
|
||||
<h3>List</h3>
|
168
view/src/routes/main.svelte
Executable file
168
view/src/routes/main.svelte
Executable file
@ -0,0 +1,168 @@
|
||||
<script lang="ts">
|
||||
import DropZone from "../components/DropZone/index.svelte";
|
||||
import AnimatedNumber from "../components/AnimatedNumber.svelte";
|
||||
import Cross from "../icons/Cross.svelte";
|
||||
import Toast from "../components/Toast";
|
||||
import { route as currentRoute, images } from "../stores";
|
||||
let acceptedFiles: File[] = [];
|
||||
let hovering = false;
|
||||
|
||||
async function handleFilesSelect(e) {
|
||||
if (e.detail.acceptedFiles) {
|
||||
const addedFiles: File[] = e.detail.acceptedFiles;
|
||||
|
||||
const newFiles: File[] = [];
|
||||
const dupes: File[] = [];
|
||||
|
||||
addedFiles.forEach((f) => {
|
||||
const isNew = !acceptedFiles.find((_f) => {
|
||||
return (
|
||||
_f.lastModified === f.lastModified &&
|
||||
_f.name === f.name &&
|
||||
_f.type === f.type &&
|
||||
_f.size === f.size
|
||||
);
|
||||
});
|
||||
|
||||
if (isNew) {
|
||||
newFiles.push(f);
|
||||
} else {
|
||||
dupes.push(f);
|
||||
}
|
||||
});
|
||||
|
||||
acceptedFiles = [...acceptedFiles, ...newFiles];
|
||||
|
||||
if (dupes.length) {
|
||||
const loadDupes = await Toast.confirm(
|
||||
`Add <b> ${dupes.length}</b> duplicate file${
|
||||
dupes.length > 1 ? "s" : ""
|
||||
}?`
|
||||
);
|
||||
|
||||
if (loadDupes) {
|
||||
acceptedFiles = [...acceptedFiles, ...dupes];
|
||||
}
|
||||
}
|
||||
}
|
||||
hovering = false;
|
||||
}
|
||||
|
||||
async function handleLoadingImages(e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
|
||||
const img = await Promise.all(
|
||||
acceptedFiles.map(async (f) => {
|
||||
return {
|
||||
name: f.name,
|
||||
filename: f.name,
|
||||
type: f.type,
|
||||
lastModified: f.lastModified,
|
||||
data: await f.arrayBuffer(),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
images.add(img);
|
||||
|
||||
await currentRoute.set("list");
|
||||
}
|
||||
|
||||
function handleClearingImages(e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
acceptedFiles = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<DropZone
|
||||
accept="image/*"
|
||||
on:drop={handleFilesSelect}
|
||||
on:dragenter={() => (hovering = true)}
|
||||
on:dragleave={() => (hovering = false)}
|
||||
on:dragenter={() => (hovering = false)}
|
||||
>
|
||||
<div class="inner-wrapper">
|
||||
<div class="icon-wrapper" class:hovering>
|
||||
<Cross />
|
||||
</div>
|
||||
<p>Drop an image here to get started</p>
|
||||
|
||||
<div class="button-wrapper" class:visible={acceptedFiles.length > 0}>
|
||||
<button on:click={handleLoadingImages}>
|
||||
load <b><AnimatedNumber num={acceptedFiles.length} /></b>
|
||||
</button>
|
||||
<button on:click={handleClearingImages}> clear </button>
|
||||
</div>
|
||||
</div>
|
||||
</DropZone>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.inner-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: Roboto;
|
||||
margin-left: 20px;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.button-wrapper > button {
|
||||
border-radius: 0px;
|
||||
background: none;
|
||||
height: 100%;
|
||||
border: none;
|
||||
color: black;
|
||||
outline: solid thin black;
|
||||
}
|
||||
|
||||
.button-wrapper > button:first-child {
|
||||
background-color: rgb(131, 255, 131);
|
||||
}
|
||||
.button-wrapper > button:last-child {
|
||||
background-color: rgb(255, 126, 126);
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
transform: translateY(100%);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s cubic-bezier(0.57, 0.21, 0.69, 1.25);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.icon-wrapper.hovering {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.button-wrapper.visible {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user