Compare commits

...

2 commits

5 changed files with 114 additions and 13 deletions

View file

@ -10,7 +10,7 @@ export default component$(() => {
const loaded = useNanostore$<boolean>(areFilesLoaded); const loaded = useNanostore$<boolean>(areFilesLoaded);
const files = useNanostore$<StereoFile[]>(dashboardFiles); const files = useNanostore$<StereoFile[]>(dashboardFiles);
const fileInputRef = useSignal<HTMLInputElement>(); const fileInputRef = useSignal<HTMLInputElement>();
const uploadingFile = useSignal<NoSerialize<File> | undefined>(); const uploadingFiles = useSignal<NoSerialize<File[]> | undefined>();
const now = useSignal(new Date()); const now = useSignal(new Date());
useVisibleTask$(() => { useVisibleTask$(() => {
@ -20,18 +20,22 @@ export default component$(() => {
return () => clearInterval(interval); return () => clearInterval(interval);
}); });
const uploadFile = $(async () => { const uploadFiles = $(async () => {
if (!uploadingFile.value) { if (!uploadingFiles.value) {
console.error("No file selected for upload."); console.error("No file(s) selected for upload.");
return; return;
} }
try { try {
const unsafe = uploadingFile.value as File; const ufiles = uploadingFiles.value as File[];
const name = unsafe.name.replace(/[^a-zA-Z0-9_.-]/g, "_");
const f = new File([unsafe], name, { type: unsafe.type }); for (const file of ufiles) {
const name = file.name.replace(/[^a-zA-Z0-9_.-]/g, "_");
const f = new File([file], name, { type: file.type });
await api.upload(f); await api.upload(f);
}
files.value = await api.list(); files.value = await api.list();
} catch (error) { } catch (error) {
console.error("Error uploading file:", error); console.error("Error uploading file:", error);
@ -44,9 +48,10 @@ export default component$(() => {
ref={fileInputRef} ref={fileInputRef}
style="display: none;" style="display: none;"
onChange$={async (e: Event) => { onChange$={async (e: Event) => {
uploadingFile.value = noSerialize((e.target as HTMLInputElement).files![0]); uploadingFiles.value = noSerialize(Object.values((e.target as HTMLInputElement).files || {}));
await uploadFile(); await uploadFiles();
}} }}
multiple
/> />
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">

View file

@ -0,0 +1,62 @@
import { component$, useVisibleTask$ } from "@builder.io/qwik";
import { useDropzone } from "~/hooks/dropzone";
import { useNanostore$ } from "~/hooks/nanostores";
import { api } from "~/lib/api";
import { dashboardFiles } from "~/lib/stores";
import { StereoFile } from "~/lib/types";
export default component$(() => {
const dashfiles = useNanostore$<StereoFile[]>(dashboardFiles);
const {
highlight,
onDragOver,
onDragLeave,
onInputChange,
triggerFileInput,
fileInputRef,
} = useDropzone();
useVisibleTask$(() => {
const dropzone = document.getElementById("dropzone");
if (!dropzone) return;
const handler = async (e: DragEvent) => {
e.preventDefault();
highlight.value = false;
const files = Array.from(e.dataTransfer?.files || []);
for (const file of files) {
const name = file.name.replace(/[^a-zA-Z0-9_.-]/g, "_");
const f = new File([file], name, { type: file.type });
await api.upload(f);
dashfiles.value = await api.list();
}
};
dropzone.addEventListener("drop", handler);
return () => dropzone.removeEventListener("drop", handler);
});
return (
<div
id="dropzone"
preventdefault:drop
class={{
"relative z-10 border-2 border-dashed rounded-lg p-10 transition-colors": true,
"border-white bg-black/20": highlight.value,
"border-neutral-800": !highlight.value,
}}
onDragOver$={onDragOver}
onDragLeave$={onDragLeave}
onClick$={triggerFileInput}
>
<p class="text-center text-neutral-500 pointer-events-none">drop file ehre</p>
<input
type="file"
ref={fileInputRef}
multiple
accept="image/*"
class="hidden"
onChange$={onInputChange}
/>
</div>
);
});

View file

@ -65,14 +65,18 @@ export default component$(({ file }: FileProps) => {
return ( return (
<div class="rounded-xl bg-neutral-900 flex flex-col group overflow-hidden hover:bg-neutral-800 transition-all duration-200"> <div class="rounded-xl bg-neutral-900 flex flex-col group overflow-hidden hover:bg-neutral-800 transition-all duration-200">
<div class="relative"> <div class="relative">
<a href={`/api/${file.Owner}/${file.Name}`} target="_blank"> <a
href={`/api/${file.Owner}/${file.Name}`}
target="_blank"
class="block w-full h-60 overflow-clip"
>
{ (file.Name.endsWith(".png") || file.Name.endsWith(".jpg") || file.Name.endsWith(".jpeg")) && ( { (file.Name.endsWith(".png") || file.Name.endsWith(".jpg") || file.Name.endsWith(".jpeg")) && (
<img <img
width={400} width={400}
height={300} height={300}
src={`/api/${file.Owner}/${file.Name}`} src={`/api/${file.Owner}/${file.Name}`}
alt={file.Name} alt={file.Name}
class="w-full h-60 object-cover bg-neutral-800 flex-grow" class="w-full h-60 object-cover bg-neutral-800 flex-grow group-hover:scale-110 group-hover:saturate-150 transition-all duration-500"
/> />
)} )}
</a> </a>

28
src/hooks/dropzone.tsx Normal file
View file

@ -0,0 +1,28 @@
import { $, useSignal } from '@builder.io/qwik';
export const useDropzone = () => {
const highlight = useSignal(false);
const onInputChange = $(async (e: Event) => {
e.preventDefault();
});
const fileInputRef = useSignal<HTMLInputElement | undefined>(undefined);
return {
highlight,
onDragOver: $((e: DragEvent) => {
e.preventDefault();
highlight.value = true;
}),
onDragLeave: $((e: DragEvent) => {
e.preventDefault();
highlight.value = false;
}),
onInputChange,
triggerFileInput: $(() => {
fileInputRef.value?.click();
}),
fileInputRef,
};
}

View file

@ -1,6 +1,7 @@
import { component$, useVisibleTask$ } from "@builder.io/qwik"; import { component$, useVisibleTask$ } from "@builder.io/qwik";
import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city"; import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city";
import Controlbar from "~/components/Controlbar"; import Controlbar from "~/components/Controlbar";
// import Dropzone from "~/components/Dropzone";
import File from "~/components/File"; import File from "~/components/File";
import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/Icons"; import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/Icons";
import { useNanostore$ } from "~/hooks/nanostores"; import { useNanostore$ } from "~/hooks/nanostores";
@ -28,6 +29,7 @@ export default component$(() => {
return ( return (
<> <>
{/* <Dropzone /> */}
<Controlbar /> <Controlbar />
{!loaded.value ? ( {!loaded.value ? (
<div class="absolute w-full h-screen flex justify-center items-center flex-col"> <div class="absolute w-full h-screen flex justify-center items-center flex-col">
@ -44,7 +46,7 @@ export default component$(() => {
</div> </div>
) )
: ( : (
<div class="grid grid-cols-4 gap-4 p-4 mb-18"> <div class="grid grid-cols-4 gap-4 p-4 mb-16">
{files.value.map((file) => ( {files.value.map((file) => (
<File key={file.Name} file={file} /> <File key={file.Name} file={file} />
))} ))}