mirror of https://github.com/VERT-sh/VERT.git
feat: keep videos for debugging (#130)
* feat: keep videos for debugging * fix: remove unnecessary $inspect
This commit is contained in:
parent
704e693511
commit
92fa929d2a
|
@ -79,6 +79,11 @@
|
|||
"errors": {
|
||||
"cant_convert": "We can't convert this file.",
|
||||
"vertd_server": "what are you doing..? you're supposed to run the vertd server!",
|
||||
"vertd_generic_body": "An error occurred whilst whilst trying convert your video. Would you like to submit this video to the developers to help fix this bug? Only your video file will be sent. No identifiers will be uploaded.",
|
||||
"vertd_generic_title": "Video conversion error",
|
||||
"vertd_generic_yes": "Submit video",
|
||||
"vertd_generic_no": "Don't submit",
|
||||
"vertd_failed_to_keep": "Failed to keep the video on the server: {error}",
|
||||
"unsupported_format": "Only image, video, audio, and document files are supported",
|
||||
"vertd_not_found": "Could not find the vertd instance to start video conversion. Are you sure the instance URL is set correctly?",
|
||||
"worker_downloading": "The {type} converter is currently being initialized, please wait a few moments.",
|
||||
|
@ -167,7 +172,8 @@
|
|||
"loading_cache": "Loading...",
|
||||
"total_size": "Total Size",
|
||||
"files_cached_label": "Files Cached",
|
||||
"cache_cleared": "Cache cleared successfully!"
|
||||
"cache_cleared": "Cache cleared successfully!",
|
||||
"cache_clear_error": "Failed to clear cache."
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<script lang="ts" module>
|
||||
export interface VertdErrorProps {
|
||||
jobId: string;
|
||||
auth: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { vertdFetch } from "$lib/converters/vertd.svelte";
|
||||
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { ToastManager, type ToastProps } from "$lib/toast/index.svelte";
|
||||
|
||||
const toast: ToastProps<VertdErrorProps> = $props();
|
||||
|
||||
let submitting = $state(false);
|
||||
|
||||
export const title = "An error occurred";
|
||||
|
||||
const remove = () => {
|
||||
ToastManager.remove(toast.id);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
submitting = true;
|
||||
try {
|
||||
await submitInner();
|
||||
} catch (e) {}
|
||||
submitting = false;
|
||||
};
|
||||
|
||||
const submitInner = async () => {
|
||||
try {
|
||||
await vertdFetch(
|
||||
"/api/keep",
|
||||
{
|
||||
method: "POST",
|
||||
},
|
||||
{
|
||||
token: toast.additional.auth,
|
||||
id: toast.additional.jobId,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["convert.errors.vertd_failed_to_keep"]({
|
||||
error: (e as Error).message || e || "Unknown error",
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
ToastManager.remove(toast.id);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<p class="text-black">{m["convert.errors.vertd_generic_body"]()}</p>
|
||||
<div class="flex gap-4">
|
||||
<button
|
||||
onclick={submit}
|
||||
class="btn rounded-lg h-fit py-2 w-full bg-accent-red-alt text-white"
|
||||
disabled={submitting}
|
||||
>{m["convert.errors.vertd_generic_yes"]()}</button
|
||||
>
|
||||
<button
|
||||
onclick={remove}
|
||||
class="btn rounded-lg h-fit py-2 w-full"
|
||||
disabled={submitting}
|
||||
>{m["convert.errors.vertd_generic_no"]()}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
|
@ -1,19 +1,14 @@
|
|||
<script lang="ts">
|
||||
import Toast from "$lib/components/visual/Toast.svelte";
|
||||
import { type Toast as ToastType, toasts } from "$lib/store/ToastProvider";
|
||||
|
||||
let toastList = $state<ToastType[]>([]);
|
||||
toasts.subscribe((value) => {
|
||||
toastList = value as ToastType[];
|
||||
});
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="fixed bottom-28 md:bottom-0 right-0 p-4 flex flex-col-reverse gap-4 z-50"
|
||||
>
|
||||
{#each toastList as { id, type, message, durations }}
|
||||
{#each ToastManager.toasts as toast (toast.id)}
|
||||
<div class="flex justify-end">
|
||||
<Toast {id} {type} {message} {durations} />
|
||||
<Toast {toast} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -8,20 +8,20 @@
|
|||
XIcon,
|
||||
} from "lucide-svelte";
|
||||
import { quintOut } from "svelte/easing";
|
||||
import { removeToast } from "$lib/store/ToastProvider";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
import type { ToastProps } from "$lib/toast/index.svelte";
|
||||
import type { SvelteComponent } from "svelte";
|
||||
import clsx from "clsx";
|
||||
import type { Toast as ToastType } from "$lib/toast/index.svelte";
|
||||
|
||||
type Props = {
|
||||
id: number;
|
||||
type: "success" | "error" | "info" | "warning";
|
||||
message: string;
|
||||
durations: {
|
||||
enter: number;
|
||||
stay: number;
|
||||
exit: number;
|
||||
};
|
||||
};
|
||||
const props: {
|
||||
toast: ToastType<unknown>;
|
||||
} = $props();
|
||||
|
||||
let { id, type, message, durations }: Props = $props();
|
||||
const { id, type, message, durations } = props.toast;
|
||||
|
||||
const additional =
|
||||
"additional" in props.toast ? props.toast.additional : {};
|
||||
|
||||
const colors = {
|
||||
success: "purple",
|
||||
|
@ -40,6 +40,9 @@
|
|||
let color = $derived(colors[type]);
|
||||
let Icon = $derived(Icons[type]);
|
||||
|
||||
let msg = $state<SvelteComponent<ToastProps>>();
|
||||
const title = $derived(((msg as any)?.title as string) ?? "");
|
||||
|
||||
// intentionally unused. this is so tailwind can generate the css for these colours as it doesn't detect if it's dynamically loaded
|
||||
// this would lead to the colours not being generated in the final css file by tailwind
|
||||
const colourVariants = [
|
||||
|
@ -51,7 +54,7 @@
|
|||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between max-w-[100%] md:max-w-md p-4 gap-4 bg-accent-{color} border-accent-{color}-alt border-l-4 rounded-lg shadow-md"
|
||||
class="flex flex-col max-w-[100%] md:max-w-md p-4 gap-2 bg-accent-{color} border-accent-{color}-alt border-l-4 rounded-lg shadow-md"
|
||||
in:fly={{
|
||||
duration: durations.enter,
|
||||
easing: quintOut,
|
||||
|
@ -63,21 +66,40 @@
|
|||
easing: quintOut,
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<Icon
|
||||
class="w-6 h-6 text-black flex-shrink-0"
|
||||
size="32"
|
||||
stroke="2"
|
||||
fill="none"
|
||||
/>
|
||||
<p class="text-black font-normal whitespace-pre-wrap break-all">
|
||||
{message}
|
||||
</p>
|
||||
<div class="flex flex-row items-center justify-between w-full gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon
|
||||
class="w-6 h-6 text-black flex-shrink-0"
|
||||
size="24"
|
||||
stroke="2"
|
||||
fill="none"
|
||||
/>
|
||||
<p
|
||||
class={clsx("text-black whitespace-pre-wrap", {
|
||||
"font-normal": !title,
|
||||
})}
|
||||
>
|
||||
{title || message}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="text-gray-600 hover:text-black flex-shrink-0"
|
||||
onclick={() => ToastManager.remove(id)}
|
||||
>
|
||||
<XIcon size="16" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="text-gray-600 hover:text-black flex-shrink-0"
|
||||
onclick={() => removeToast(id)}
|
||||
>
|
||||
<XIcon size="16" />
|
||||
</button>
|
||||
{#if typeof message !== "string"}
|
||||
{@const MessageComponent = message}
|
||||
<div class="font-normal">
|
||||
<MessageComponent
|
||||
bind:this={msg}
|
||||
{durations}
|
||||
{id}
|
||||
{message}
|
||||
{type}
|
||||
{additional}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -3,9 +3,9 @@ import { Converter, FormatInfo } from "./converter.svelte";
|
|||
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
||||
import { browser } from "$app/environment";
|
||||
import { error, log } from "$lib/logger";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
|
||||
// TODO: differentiate in UI? (not native formats)
|
||||
const videoFormats = [
|
||||
|
@ -98,7 +98,10 @@ export class FFmpegConverter extends Converter {
|
|||
} catch (err) {
|
||||
error(["converters", this.name], `error loading ffmpeg: ${err}`);
|
||||
this.status = "error";
|
||||
addToast("error", m["workers.errors.ffmpeg"]());
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["workers.errors.ffmpeg"](),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { browser } from "$app/environment";
|
||||
import { error, log } from "$lib/logger";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { VertFile, type WorkerMessage } from "$lib/types";
|
||||
import MagickWorker from "$lib/workers/magick?worker&url";
|
||||
import { Converter, FormatInfo } from "./converter.svelte";
|
||||
import { imageFormats } from "./magick-automated";
|
||||
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||
import magickWasm from "@imagemagick/magick-wasm/magick.wasm?url";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
|
||||
export class MagickConverter extends Converter {
|
||||
public name = "imagemagick";
|
||||
|
@ -104,7 +104,11 @@ export class MagickConverter extends Converter {
|
|||
["converters", this.name],
|
||||
`Failed to load ImageMagick WASM: ${err}`,
|
||||
);
|
||||
addToast("error", m["workers.errors.magick"]());
|
||||
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["workers.errors.magick"](),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,9 @@ import { VertFile, type WorkerMessage } from "$lib/types";
|
|||
import { Converter, FormatInfo } from "./converter.svelte";
|
||||
import { browser } from "$app/environment";
|
||||
import PandocWorker from "$lib/workers/pandoc?worker&url";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { error, log } from "$lib/logger";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
|
||||
export class PandocConverter extends Converter {
|
||||
public name = "pandoc";
|
||||
|
@ -25,7 +26,10 @@ export class PandocConverter extends Converter {
|
|||
this.status = "ready";
|
||||
} catch (err) {
|
||||
this.status = "error";
|
||||
addToast("error", `Failed to load Pandoc worker: ${err}`);
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: `Failed to load Pandoc worker: ${err}`, // TODO: i18n
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import VertdErrorComponent from "$lib/components/functional/VertdError.svelte";
|
||||
import { error, log } from "$lib/logger";
|
||||
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||
import { VertdInstance } from "$lib/sections/settings/vertdSettings.svelte";
|
||||
|
@ -25,19 +26,45 @@ interface UploadResponse {
|
|||
totalFrames: number;
|
||||
}
|
||||
|
||||
interface RouteMap {
|
||||
"/api/upload": UploadResponse;
|
||||
"/api/version": string;
|
||||
interface RouteRequestMap {
|
||||
"/api/keep": {
|
||||
id: string;
|
||||
token: string;
|
||||
};
|
||||
}
|
||||
|
||||
const vertdFetch = async <U extends keyof RouteMap>(
|
||||
url: U,
|
||||
options: RequestInit,
|
||||
): Promise<RouteMap[U]> => {
|
||||
interface RouteResponseMap {
|
||||
"/api/upload": UploadResponse;
|
||||
"/api/version": string;
|
||||
"/api/keep": void;
|
||||
}
|
||||
|
||||
export const vertdFetch: {
|
||||
<U extends keyof RouteRequestMap>(
|
||||
url: U,
|
||||
options: RequestInit,
|
||||
body: RouteRequestMap[U],
|
||||
): Promise<RouteResponseMap[U]>;
|
||||
<U extends Exclude<keyof RouteResponseMap, keyof RouteRequestMap>>(
|
||||
url: U,
|
||||
options: RequestInit,
|
||||
): Promise<RouteResponseMap[U]>;
|
||||
} = async (url: any, options: RequestInit, body?: any) => {
|
||||
const domain = await VertdInstance.instance.url();
|
||||
const res = await fetch(`${domain}${url}`, options);
|
||||
|
||||
// if there is a body, insert a Content-Type: application/json header
|
||||
if (body) {
|
||||
options.headers = {
|
||||
"Content-Type": "application/json",
|
||||
...(options.headers || {}),
|
||||
};
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const res = await fetch(domain + url, options);
|
||||
|
||||
const text = await res.text();
|
||||
let json: VertdResponse<RouteMap[U]> = null!;
|
||||
let json = null;
|
||||
try {
|
||||
json = JSON.parse(text);
|
||||
} catch {
|
||||
|
@ -48,7 +75,7 @@ const vertdFetch = async <U extends keyof RouteMap>(
|
|||
throw new Error(json.data);
|
||||
}
|
||||
|
||||
return json.data as RouteMap[U];
|
||||
return json.data;
|
||||
};
|
||||
|
||||
// ws types
|
||||
|
@ -345,7 +372,13 @@ export class VertdConverter extends Converter {
|
|||
case "error": {
|
||||
this.log(`error: ${msg.data.message}`);
|
||||
this.activeConversions.delete(input.id);
|
||||
reject(msg.data.message);
|
||||
reject({
|
||||
component: VertdErrorComponent,
|
||||
additional: {
|
||||
jobId: uploadRes.id,
|
||||
auth: uploadRes.auth,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
import FancyInput from "$lib/components/functional/FancyInput.svelte";
|
||||
import Panel from "$lib/components/visual/Panel.svelte";
|
||||
import { effects } from "$lib/store/index.svelte";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import {
|
||||
loadStripe,
|
||||
type Stripe,
|
||||
|
@ -39,6 +38,7 @@
|
|||
import { Elements, PaymentElement } from "svelte-stripe";
|
||||
import { quintOut } from "svelte/easing";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
|
||||
let amount = $state(1);
|
||||
let customAmount = $state("");
|
||||
|
@ -67,7 +67,10 @@
|
|||
|
||||
if (!res.ok) {
|
||||
paymentState = "prepay";
|
||||
addToast("error", m["about.donate.payment_error"]());
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["about.donate.payment_error"](),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -97,13 +100,13 @@
|
|||
const submitResult = await elements.submit();
|
||||
if (submitResult.error) {
|
||||
const period = submitResult.error.message?.endsWith(".") ? "" : ".";
|
||||
addToast(
|
||||
"error",
|
||||
m["about.donate.payment_failed"]({
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["about.donate.payment_failed"]({
|
||||
message: submitResult.error.message || "",
|
||||
period,
|
||||
}),
|
||||
);
|
||||
});
|
||||
enablePay = true;
|
||||
return;
|
||||
}
|
||||
|
@ -119,15 +122,18 @@
|
|||
|
||||
if (res.error) {
|
||||
const period = res.error.message?.endsWith(".") ? "" : ".";
|
||||
addToast(
|
||||
"error",
|
||||
m["about.donate.payment_failed"]({
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["about.donate.payment_failed"]({
|
||||
message: res.error.message || "",
|
||||
period,
|
||||
}),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
addToast("success", m["about.donate.thank_you"]());
|
||||
ToastManager.add({
|
||||
type: "info",
|
||||
message: m["about.donate.thank_you"](),
|
||||
});
|
||||
}
|
||||
|
||||
paymentState = "prepay";
|
||||
|
@ -146,10 +152,16 @@
|
|||
if (status) {
|
||||
switch (status) {
|
||||
case "succeeded":
|
||||
addToast("success", m["about.donate.thank_you"]());
|
||||
ToastManager.add({
|
||||
type: "success",
|
||||
message: m["about.donate.thank_you"](),
|
||||
});
|
||||
break;
|
||||
default:
|
||||
addToast("error", m["about.donate.donation_error"]());
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["about.donate.donation_error"](),
|
||||
});
|
||||
}
|
||||
|
||||
goto("/about");
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
import HotMilk from "$lib/assets/hotmilk.svg?component";
|
||||
import { DISCORD_URL } from "$lib/consts";
|
||||
import { error } from "$lib/logger";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { link } from "$lib/store/index.svelte";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
|
||||
let copied = false;
|
||||
let timeoutId: number | undefined;
|
||||
|
@ -15,7 +15,10 @@
|
|||
try {
|
||||
navigator.clipboard.writeText("hello@vert.sh");
|
||||
copied = true;
|
||||
addToast("success", m["about.sponsors.email_copied"]());
|
||||
ToastManager.add({
|
||||
type: "success",
|
||||
message: m["about.sponsors.email_copied"](),
|
||||
});
|
||||
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => (copied = false), 2000);
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
import { m } from "$lib/paraglide/messages";
|
||||
import { link } from "$lib/store/index.svelte";
|
||||
import { swManager, type CacheInfo } from "$lib/sw/register";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { onMount } from "svelte";
|
||||
import { error } from "$lib/logger";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
|
||||
const { settings = $bindable() }: { settings: ISettings } = $props();
|
||||
|
||||
|
@ -50,10 +50,16 @@
|
|||
await swManager.clearCache();
|
||||
cacheInfo = null;
|
||||
await loadCacheInfo();
|
||||
addToast("success", m["settings.privacy.cache_cleared"]());
|
||||
ToastManager.add({
|
||||
type: "success",
|
||||
message: m["settings.privacy.cache_cleared"](),
|
||||
});
|
||||
} catch (err) {
|
||||
error(["privacy", "cache"], "Failed to clear cache:", err);
|
||||
addToast("error", "Failed to clear cache");
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["settings.privacy.cache_clear_error"](),
|
||||
});
|
||||
} finally {
|
||||
isLoadingCache = false;
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
export type ToastType = "success" | "error" | "info" | "warning";
|
||||
|
||||
export interface Toast {
|
||||
id: number;
|
||||
type: ToastType;
|
||||
message: string;
|
||||
disappearing: boolean;
|
||||
durations: {
|
||||
enter: number;
|
||||
stay: number;
|
||||
exit: number;
|
||||
};
|
||||
}
|
||||
|
||||
const toasts = writable<Toast[]>([]);
|
||||
|
||||
let toastId = 0;
|
||||
|
||||
function addToast(
|
||||
type: ToastType,
|
||||
message: string,
|
||||
disappearing?: boolean,
|
||||
durations?: { enter: number; stay: number; exit: number },
|
||||
) {
|
||||
const id = toastId++;
|
||||
|
||||
durations = durations ?? {
|
||||
enter: 300,
|
||||
stay: disappearing || disappearing === undefined ? 5000 : 86400000, // 24h cause why not
|
||||
exit: 500,
|
||||
};
|
||||
|
||||
const newToast: Toast = {
|
||||
id,
|
||||
type,
|
||||
message,
|
||||
disappearing: disappearing ?? true,
|
||||
durations,
|
||||
};
|
||||
toasts.update((currentToasts) => [...currentToasts, newToast]);
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
removeToast(id);
|
||||
},
|
||||
durations.enter + durations.stay + durations.exit,
|
||||
);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function removeToast(id: number) {
|
||||
toasts.update((currentToasts) =>
|
||||
currentToasts.filter((toast) => toast.id !== id),
|
||||
);
|
||||
}
|
||||
|
||||
export { toasts, addToast, removeToast };
|
|
@ -0,0 +1,226 @@
|
|||
import type { Component } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export type ToastType = "success" | "error" | "info" | "warning";
|
||||
|
||||
// export interface Toast<
|
||||
// T = unknown,
|
||||
// U extends string | ToastComponent<T> = string | ToastComponent<T>,
|
||||
// > {
|
||||
// id: number;
|
||||
// type: ToastType;
|
||||
// message: U;
|
||||
// disappearing: boolean;
|
||||
// durations: {
|
||||
// enter: number;
|
||||
// stay: number;
|
||||
// exit: number;
|
||||
// };
|
||||
// additional: U extends string ? undefined : T;
|
||||
// }
|
||||
|
||||
type BaseToast = {
|
||||
id: number;
|
||||
type: ToastType;
|
||||
disappearing: boolean;
|
||||
durations: {
|
||||
enter: number;
|
||||
stay: number;
|
||||
exit: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type StringToast = BaseToast & {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ComponentToast<T> = BaseToast & {
|
||||
message: ToastComponent<T>;
|
||||
additional: T;
|
||||
};
|
||||
|
||||
export type Toast<T = unknown> = StringToast | ComponentToast<T>;
|
||||
|
||||
export type ToastProps<T = unknown> = Omit<ComponentToast<T>, "disappearing">;
|
||||
|
||||
export type ToastExports = {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export type ToastComponent<T> = Component<ToastProps<T>, ToastExports>;
|
||||
|
||||
// export interface ToastOptions<T = unknown> {
|
||||
// type?: ToastType;
|
||||
// message: string | ToastComponent<T>;
|
||||
// disappearing?: boolean;
|
||||
// durations?: {
|
||||
// enter?: number;
|
||||
// stay?: number;
|
||||
// exit?: number;
|
||||
// };
|
||||
// additional?: T;
|
||||
// }
|
||||
|
||||
type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] extends (infer U)[]
|
||||
? RecursivePartial<U>[]
|
||||
: T[P] extends object | undefined
|
||||
? RecursivePartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
|
||||
type BaseToastOptions = Omit<RecursivePartial<BaseToast>, "id"> & {
|
||||
disappearing?: boolean;
|
||||
};
|
||||
|
||||
export type StringToastOptions = BaseToastOptions & {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ComponentToastOptions<T> = BaseToastOptions & {
|
||||
message: ToastComponent<T>;
|
||||
additional: T;
|
||||
};
|
||||
|
||||
export type ToastOptions<T = unknown> =
|
||||
| StringToastOptions
|
||||
| ComponentToastOptions<T>;
|
||||
|
||||
// const toasts = writable<Toast[]>([]);
|
||||
|
||||
// let toastId = 0;
|
||||
|
||||
// function addToast(
|
||||
// type: ToastType,
|
||||
// message: string | Component,
|
||||
// disappearing?: boolean,
|
||||
// durations?: { enter: number; stay: number; exit: number },
|
||||
// ) {
|
||||
// const id = toastId++;
|
||||
|
||||
// durations = durations ?? {
|
||||
// enter: 300,
|
||||
// stay: disappearing || disappearing === undefined ? 5000 : 86400000, // 24h cause why not
|
||||
// exit: 500,
|
||||
// };
|
||||
|
||||
// const newToast: Toast = {
|
||||
// id,
|
||||
// type,
|
||||
// message,
|
||||
// disappearing: disappearing ?? true,
|
||||
// durations,
|
||||
// };
|
||||
// toasts.update((currentToasts) => [...currentToasts, newToast]);
|
||||
|
||||
// setTimeout(
|
||||
// () => {
|
||||
// removeToast(id);
|
||||
// },
|
||||
// durations.enter + durations.stay + durations.exit,
|
||||
// );
|
||||
|
||||
// return id;
|
||||
// }
|
||||
|
||||
// function removeToast(id: number) {
|
||||
// toasts.update((currentToasts) =>
|
||||
// currentToasts.filter((toast) => toast.id !== id),
|
||||
// );
|
||||
// }
|
||||
|
||||
// export { toasts, addToast, removeToast };
|
||||
|
||||
// const DURATION_DEFAULTS = {
|
||||
// enter: 300,
|
||||
// stay: 5000,
|
||||
// exit: 500,
|
||||
// };
|
||||
|
||||
const durationDefault = (disappearing: boolean) => ({
|
||||
enter: 300,
|
||||
stay: disappearing ? 5000 : 86400000, // 24h cause why not
|
||||
exit: 500,
|
||||
});
|
||||
|
||||
// const toastState = {
|
||||
// toasts: $state<Toast[]>([]),
|
||||
// };
|
||||
|
||||
class ToastState {
|
||||
private pId = $state(0);
|
||||
private pToasts = $state<Toast<unknown>[]>([]);
|
||||
|
||||
public add<T>(toast: Toast<T>) {
|
||||
this.pToasts.push(toast as Toast<unknown>);
|
||||
}
|
||||
|
||||
public remove(id: number) {
|
||||
this.pToasts = this.pToasts.filter((toast) => toast.id !== id);
|
||||
}
|
||||
|
||||
public id(): number {
|
||||
return this.pId++;
|
||||
}
|
||||
|
||||
public get toasts() {
|
||||
return this.pToasts;
|
||||
}
|
||||
}
|
||||
|
||||
export class ToastManager {
|
||||
static pToasts = new ToastState();
|
||||
|
||||
public static add<T = unknown>(toastOptions: ToastOptions<T>): number {
|
||||
const id = this.pToasts.id();
|
||||
const {
|
||||
type = "info",
|
||||
disappearing = true,
|
||||
durations: d = durationDefault(toastOptions.disappearing ?? true),
|
||||
} = toastOptions;
|
||||
const durations = {
|
||||
...durationDefault(disappearing),
|
||||
...d,
|
||||
};
|
||||
|
||||
if (typeof toastOptions.message === "string") {
|
||||
const newToast: StringToast = {
|
||||
id,
|
||||
type,
|
||||
message: toastOptions.message,
|
||||
disappearing,
|
||||
durations,
|
||||
};
|
||||
|
||||
this.pToasts.add(newToast);
|
||||
} else {
|
||||
const newToast: ComponentToast<T> = {
|
||||
id,
|
||||
type,
|
||||
message: toastOptions.message,
|
||||
disappearing,
|
||||
durations,
|
||||
additional: (toastOptions as ComponentToastOptions<T>)
|
||||
.additional,
|
||||
};
|
||||
|
||||
this.pToasts.add(newToast);
|
||||
}
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
this.remove(id);
|
||||
},
|
||||
durations.enter + durations.stay + durations.exit,
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
public static remove(id: number) {
|
||||
this.pToasts.remove(id);
|
||||
}
|
||||
|
||||
public static get toasts() {
|
||||
return this.pToasts.toasts;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,8 @@ import { byNative, converters } from "$lib/converters";
|
|||
import type { Converter } from "$lib/converters/converter.svelte";
|
||||
import { error } from "$lib/logger";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
import type { Component } from "svelte";
|
||||
|
||||
export class VertFile {
|
||||
public id: string = Math.random().toString(36).slice(2, 8);
|
||||
|
@ -93,15 +94,7 @@ export class VertFile {
|
|||
this.result = res;
|
||||
} catch (err) {
|
||||
if (!this.cancelled) {
|
||||
const castedErr = err as Error;
|
||||
error(["files"], castedErr.message);
|
||||
addToast(
|
||||
"error",
|
||||
m["workers.errors.general"]({
|
||||
file: this.file.name,
|
||||
message: castedErr.message || castedErr,
|
||||
}),
|
||||
);
|
||||
this.toastErr(err);
|
||||
}
|
||||
this.result = null;
|
||||
}
|
||||
|
@ -119,15 +112,51 @@ export class VertFile {
|
|||
this.processing = false;
|
||||
this.result = null;
|
||||
} catch (err) {
|
||||
const castedErr = err as Error;
|
||||
error(["files"], castedErr.message);
|
||||
addToast(
|
||||
"error",
|
||||
m["workers.errors.cancel"]({
|
||||
this.toastErr(err);
|
||||
}
|
||||
}
|
||||
|
||||
private toastErr(err: unknown) {
|
||||
type ToastMsg = {
|
||||
component: Component;
|
||||
additional: unknown;
|
||||
};
|
||||
|
||||
const castedErr = err as Error | string | ToastMsg;
|
||||
let toastMsg: string | ToastMsg = "";
|
||||
if (typeof castedErr === "string") {
|
||||
toastMsg = castedErr;
|
||||
} else if (castedErr instanceof Error) {
|
||||
toastMsg = castedErr.message;
|
||||
} else {
|
||||
toastMsg = castedErr;
|
||||
}
|
||||
|
||||
// ToastManager.add({
|
||||
// type: "error",
|
||||
// message:
|
||||
// typeof toastMsg === "string"
|
||||
// ? m["workers.errors.general"]({
|
||||
// file: this.file.name,
|
||||
// message: toastMsg,
|
||||
// })
|
||||
// : toastMsg,
|
||||
// });
|
||||
|
||||
if (typeof toastMsg === "string") {
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["workers.errors.general"]({
|
||||
file: this.file.name,
|
||||
message: castedErr.message || castedErr,
|
||||
message: toastMsg,
|
||||
}),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: toastMsg.component,
|
||||
additional: toastMsg.additional,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
import { initStores as initAnimStores } from "$lib/animation/index.js";
|
||||
import { locales, localizeHref } from "$lib/paraglide/runtime";
|
||||
import { VertdInstance } from "$lib/sections/settings/vertdSettings.svelte.js";
|
||||
import { ToastManager } from "$lib/toast/index.svelte.js";
|
||||
import VertdError from "$lib/components/functional/VertdError.svelte";
|
||||
|
||||
let { children, data } = $props();
|
||||
let enablePlausible = $state(false);
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
import avatarRealmy from "$lib/assets/avatars/realmy.jpg";
|
||||
import avatarAzurejelly from "$lib/assets/avatars/azurejelly.jpg";
|
||||
import { GITHUB_API_URL } from "$lib/consts";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { dev } from "$app/environment";
|
||||
import { page } from "$app/state";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
// import { dev } from "$app/environment";
|
||||
// import { page } from "$app/state";
|
||||
|
||||
|
@ -81,7 +81,10 @@
|
|||
try {
|
||||
const response = await fetch(`${GITHUB_API_URL}/contributors`);
|
||||
if (!response.ok) {
|
||||
addToast("error", m["about.errors.github_contributors"]());
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["about.errors.github_contributors"](),
|
||||
});
|
||||
throw new Error(`HTTP error, status: ${response.status}`);
|
||||
}
|
||||
const allContribs = await response.json();
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
import { browser } from "$app/environment";
|
||||
import { log } from "$lib/logger";
|
||||
import * as Settings from "$lib/sections/settings/index.svelte";
|
||||
import { addToast } from "$lib/store/ToastProvider";
|
||||
import { PUB_PLAUSIBLE_URL } from "$env/static/public";
|
||||
import { SettingsIcon } from "lucide-svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { ToastManager } from "$lib/toast/index.svelte";
|
||||
|
||||
let settings = $state(Settings.Settings.instance.settings);
|
||||
|
||||
|
@ -32,7 +32,10 @@
|
|||
log(["settings"], "saving settings");
|
||||
} catch (error) {
|
||||
log(["settings", "error"], `failed to save settings: ${error}`);
|
||||
addToast("error", m["settings.errors.save_failed"]());
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: m["settings.errors.save_failed"](),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue