diff --git a/src/components/File.tsx b/src/components/File.tsx index 2819db6..9532f92 100644 --- a/src/components/File.tsx +++ b/src/components/File.tsx @@ -9,12 +9,6 @@ type FileProps = { file: StereoFile; } -const getBase64Size = (b: string) => { - if (!b) return 0; - const padding = (b.match(/=+$/) || [""])[0].length; - return Math.floor((b.length * 3) / 4) - padding; -}; - const formatSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; @@ -31,105 +25,95 @@ export default component$(({ file }: FileProps) => { files.value = await api.list(); }); - const addFileToClipboard = $(async (base64: string) => { - if (!base64) return; - try { - let mime = "image/png"; - if (file.ID.endsWith(".png")) mime = "image/png"; - else if (file.ID.endsWith(".jpg") || file.ID.endsWith(".jpeg")) mime = "image/jpeg"; - else if (file.ID.endsWith(".gif")) mime = "image/gif"; - if (!mime.startsWith("image/")) { - alert("Clipboard copy is only supported for images in your browser."); - return; - } + const addFileToClipboard = $(async () => { + const response = await api.file(file.ID); + const data = await response.blob(); + let mime = data.type || "application/octet-stream"; + let clip; - let pngBlob: Blob; - if (mime !== "image/png") { - const img = new window.Image(); - img.src = `data:${mime};base64,${base64}`; - await new Promise((res, rej) => { - img.onload = res; - img.onerror = rej; - }); + if (navigator.clipboard && window.ClipboardItem) { + if (mime === "image/jpeg" || mime === "image/jpg") { + const img = document.createElement("img"); + img.src = URL.createObjectURL(data); + await new Promise((res) => (img.onload = res)); const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext("2d"); ctx?.drawImage(img, 0, 0); - pngBlob = await new Promise((resolve, reject) => { - canvas.toBlob(blob => { - if (blob) resolve(blob); - else reject(new Error("Failed to convert image to PNG")); - }, "image/png"); - }); + const png = await new Promise((resolve) => + canvas.toBlob((b) => resolve(b!), "image/png") + ); + mime = "image/png"; + clip = new ClipboardItem({ [mime]: png }); } else { - const binary = atob(base64); - const len = binary.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binary.charCodeAt(i); - } - pngBlob = new Blob([bytes], { type: "image/png" }); + clip = new ClipboardItem({ [mime]: data }); } - const item = new ClipboardItem({ "image/png": pngBlob }); - await navigator.clipboard.write([item]); - alert("Image copied to clipboard as PNG!"); - } catch (error) { - console.error("Failed to copy file to clipboard:", error); - alert("Failed to copy file to clipboard."); + try { + await navigator.clipboard.write([clip]); + alert("File added to clipboard successfully!"); + } catch (error) { + console.error("Failed to add file to clipboard:", error); + alert("Failed to add file to clipboard. Please try again."); + } + } else { + alert("Clipboard API not supported in this browser."); } }); return ( -
-
- - { file.Base64 && (file.ID.endsWith(".png") || file.ID.endsWith(".jpg") || file.ID.endsWith(".jpeg")) && ( - {file.ID} - )} - +
+
+ + { (file.ID.endsWith(".png") || file.ID.endsWith(".jpg") || file.ID.endsWith(".jpeg")) && ( + {file.ID} + )} + - + -
+
+ +
+

{ file.ID.split("_").slice(1).join("_") || "Untitled" }

-
- { formatSize(getBase64Size(file.Base64)) } +
+ { formatSize(file.Size) } -

Uploaded on { new Date(file.CreatedAt).toLocaleDateString() }

+ Uploaded on { new Date(file.CreatedAt).toLocaleDateString() }
+
) }) \ No newline at end of file diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 1c35d25..5500ae1 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -31,4 +31,9 @@ export function SolarDownloadMinimalisticBold(props: QwikIntrinsicElements['svg' return ( ) +} +export function SvgSpinnersBarsRotateFade(props: QwikIntrinsicElements['svg'], key: string) { + return ( + + ) } \ No newline at end of file diff --git a/src/lib/api.ts b/src/lib/api.ts index d75807b..ac2cb28 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -16,6 +16,7 @@ export const apiClient = ky.create({ }); // TODO: make wrapper for apiclient fr export const api = { + file: async (file_id: string) => await apiClient.get(file_id), list: async () => await apiClient.get('list').json(), upload: async (file: File) => { const formData = new FormData(); diff --git a/src/lib/types.ts b/src/lib/types.ts index 37050e2..ee21a0b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -3,5 +3,5 @@ export type StereoFile = { Path: string; Owner: string; CreatedAt: string; - Base64: string; + Size: number; } \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 9cf4a51..9705b84 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -2,6 +2,7 @@ import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik"; import type { DocumentHead } from "@builder.io/qwik-city"; import Controlbar from "~/components/Controlbar"; import File from "~/components/File"; +import { SolarUploadLinear, SvgSpinnersBarsRotateFade } from "~/components/Icons"; import { useNanostore$ } from "~/hooks/nanostores"; import { api } from "~/lib/api"; import { OAUTH_LINK } from "~/lib/constants"; @@ -25,13 +26,19 @@ export default component$(() => { <> oauth - - {/* TODO: make ts better :broken_heart: */} {!loaded.value ? ( -

loading

+
+

+

loading your files...

+ please wait +
) : ( files.value.length === 0 ? ( -

no files found fr

+
+

{"┻━┻︵ \\(°□°)/ ︵ ┻━┻"}

+

you haven't uploaded any files yet!

+ click the button to get started +
) : (