file upload
This commit is contained in:
parent
1599371df5
commit
aaaebc20a5
5 changed files with 62 additions and 17 deletions
|
@ -1,6 +1,9 @@
|
||||||
import { component$ } from "@builder.io/qwik";
|
/* eslint-disable qwik/jsx-a */
|
||||||
|
import { $, component$, useSignal } from "@builder.io/qwik";
|
||||||
import { useNanostore$ } from "~/hooks/nanostores";
|
import { useNanostore$ } from "~/hooks/nanostores";
|
||||||
import { isSettingsOpen } from "~/lib/stores";
|
import { api } from "~/lib/api";
|
||||||
|
import { isSettingsOpen, loadedFiles } from "~/lib/stores";
|
||||||
|
import { StereoFile } from "~/lib/types";
|
||||||
import { SolarLibraryLinear, SolarQuestionCircleLinear, SolarRoundedMagniferLinear, SolarSettingsLinear, SolarUploadMinimalisticLinear, StereoCircularProgress, StereoLogoLinear } from "../misc/Icons";
|
import { SolarLibraryLinear, SolarQuestionCircleLinear, SolarRoundedMagniferLinear, SolarSettingsLinear, SolarUploadMinimalisticLinear, StereoCircularProgress, StereoLogoLinear } from "../misc/Icons";
|
||||||
|
|
||||||
export default component$(() => {
|
export default component$(() => {
|
||||||
|
@ -8,6 +11,30 @@ export default component$(() => {
|
||||||
const total = 15;
|
const total = 15;
|
||||||
|
|
||||||
const settingsOpen = useNanostore$<boolean>(isSettingsOpen);
|
const settingsOpen = useNanostore$<boolean>(isSettingsOpen);
|
||||||
|
const fileInputRef = useSignal<HTMLInputElement>();
|
||||||
|
const files = useNanostore$<StereoFile[]>(loadedFiles, []);
|
||||||
|
|
||||||
|
const handleFileChange = $(async (event: Event) => {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
if (input.files && input.files.length > 0) {
|
||||||
|
const fi: File[] = Array.from(input.files);
|
||||||
|
|
||||||
|
const metas: StereoFile[] = await Promise.all(
|
||||||
|
fi.map(async (file) => {
|
||||||
|
try {
|
||||||
|
const id = (await (await api.upload(file)).json()).id;
|
||||||
|
return await api.meta(id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("actionbar: file upload failed:", error);
|
||||||
|
throw new Error("File upload failed");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
input.value = "";
|
||||||
|
files.value = [...files.value, ...metas];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="absolute bottom-0 left-0 flex items-center justify-between py-7 px-16 w-full">
|
<div class="absolute bottom-0 left-0 flex items-center justify-between py-7 px-16 w-full">
|
||||||
|
@ -31,11 +58,25 @@ export default component$(() => {
|
||||||
}} class="flex items-center justify-center px-6 py-4 gap-5 text-white text-3xl absolute left-1/2 transform -translate-x-1/2">
|
}} class="flex items-center justify-center px-6 py-4 gap-5 text-white text-3xl absolute left-1/2 transform -translate-x-1/2">
|
||||||
<a onClick$={() => settingsOpen.value = true}><StereoLogoLinear /></a>
|
<a onClick$={() => settingsOpen.value = true}><StereoLogoLinear /></a>
|
||||||
<SolarLibraryLinear />
|
<SolarLibraryLinear />
|
||||||
<SolarUploadMinimalisticLinear />
|
<a
|
||||||
|
onClick$={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
fileInputRef.value?.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SolarUploadMinimalisticLinear />
|
||||||
|
</a>
|
||||||
<SolarRoundedMagniferLinear />
|
<SolarRoundedMagniferLinear />
|
||||||
<SolarSettingsLinear />
|
<SolarSettingsLinear />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
class="hidden"
|
||||||
|
onChange$={handleFileChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
borderRadius: "999px",
|
borderRadius: "999px",
|
||||||
border: "0.5px solid #FF264E",
|
border: "0.5px solid #FF264E",
|
||||||
|
|
|
@ -14,8 +14,8 @@ function writeable<T>(store: Atom<T> | WritableAtom<T>): store is WritableAtom<T
|
||||||
return typeof (store as WritableAtom<T>).set === 'function';
|
return typeof (store as WritableAtom<T>).set === 'function';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useNanostoreQrl<T>(qrl: QRL<WritableAtom<T> | Atom<T>>): Signal<T> {
|
export function useNanostoreQrl<T>(qrl: QRL<WritableAtom<T> | Atom<T>>, defaultValue?: T): Signal<T> {
|
||||||
const signal = useSignal<T | undefined>(undefined);
|
const signal = useSignal<T | undefined>(defaultValue);
|
||||||
const storeSignal = useSignal<NoSerialize<WritableAtom<T> | Atom<T>> | undefined>(undefined);
|
const storeSignal = useSignal<NoSerialize<WritableAtom<T> | Atom<T>> | undefined>(undefined);
|
||||||
|
|
||||||
useTask$(async ({ track }) => {
|
useTask$(async ({ track }) => {
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
import ky from 'ky';
|
import ky from "ky";
|
||||||
import { StereoFile } from './types';
|
import { StereoFile, StereoUser } from "./types";
|
||||||
|
|
||||||
export const client = ky.create({
|
export const client = ky.create({
|
||||||
prefixUrl: '/api',
|
prefixUrl: "/api",
|
||||||
credentials: 'include'
|
credentials: "include"
|
||||||
});
|
});
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
file: async (uid: string) => await client.get<Blob>(uid),
|
file: async (uid: string) => await client.get<Blob>(uid),
|
||||||
|
meta: async (uid: string) => await client.get<StereoFile>(uid + "/meta").json(),
|
||||||
list: async (page?: number, size?: number) => {
|
list: async (page?: number, size?: number) => {
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (page !== undefined) searchParams.append('page', String(page));
|
if (page !== undefined) searchParams.append("page", String(page));
|
||||||
if (size !== undefined) searchParams.append('size', String(size));
|
if (size !== undefined) searchParams.append("size", String(size));
|
||||||
|
|
||||||
return await client.get('list', { searchParams }).json<StereoFile[]>();
|
return await client.get("list", { searchParams }).json<StereoFile[]>();
|
||||||
},
|
},
|
||||||
upload: async (file: File) => {
|
upload: async (file: File) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append("file", file);
|
||||||
return await client.post('upload', { body: formData });
|
return await client.post<{id: string, message: string}>("upload", { body: formData });
|
||||||
},
|
},
|
||||||
delete: async (uid: string) => await client.delete(uid).json(),
|
delete: async (uid: string) => await client.delete(uid).json(),
|
||||||
me: async () => (await client.get('auth/me').json() as any).user,
|
me: async () => (await client.get<any>("auth/me").json()).user as StereoUser,
|
||||||
}
|
}
|
|
@ -10,4 +10,5 @@ export const userInfo = atom<StereoUser>({
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
created_at: Date.now().toString(),
|
created_at: Date.now().toString(),
|
||||||
});
|
});
|
||||||
export const isSettingsOpen = atom<boolean>(false);
|
export const isSettingsOpen = atom<boolean>(false);
|
||||||
|
export const loadedFiles = atom<StereoFile[]>([]);
|
|
@ -4,9 +4,11 @@ import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city";
|
||||||
import Actionbar from "~/components/dashboard/Actionbar";
|
import Actionbar from "~/components/dashboard/Actionbar";
|
||||||
import Settings from "~/components/dashboard/Settings";
|
import Settings from "~/components/dashboard/Settings";
|
||||||
import Titlebar from "~/components/dashboard/Titlebar";
|
import Titlebar from "~/components/dashboard/Titlebar";
|
||||||
|
import { useNanostore$ } from "~/hooks/nanostores";
|
||||||
// import Dropzone from "~/components/Dropzone";
|
// import Dropzone from "~/components/Dropzone";
|
||||||
import { api } from "~/lib/api";
|
import { api } from "~/lib/api";
|
||||||
import { debounce } from "~/lib/misc";
|
import { debounce } from "~/lib/misc";
|
||||||
|
import { loadedFiles } from "~/lib/stores";
|
||||||
import { StereoFile } from "~/lib/types";
|
import { StereoFile } from "~/lib/types";
|
||||||
|
|
||||||
export const useAuthenticated = routeLoader$(({ cookie, redirect: r }) => {
|
export const useAuthenticated = routeLoader$(({ cookie, redirect: r }) => {
|
||||||
|
@ -111,7 +113,7 @@ const Files = component$(() => {
|
||||||
const hasMore = useSignal(true);
|
const hasMore = useSignal(true);
|
||||||
const sentinel = useSignal<HTMLDivElement>();
|
const sentinel = useSignal<HTMLDivElement>();
|
||||||
|
|
||||||
const files = useSignal<StereoFile[]>([]);
|
const files = useNanostore$<StereoFile[]>(loadedFiles, []);
|
||||||
const page = useSignal(1);
|
const page = useSignal(1);
|
||||||
|
|
||||||
// TODO: make it load enough images to fill the viewport instead
|
// TODO: make it load enough images to fill the viewport instead
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue