refactor: run prettier

This commit is contained in:
Maya 2026-05-25 21:29:28 +03:00
parent 35949a6d04
commit c139eae565
No known key found for this signature in database
24 changed files with 583 additions and 524 deletions

View File

@ -1,53 +1,53 @@
<!doctype html> <!doctype html>
<html lang="%lang%"> <html lang="%lang%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="apple-touch-icon" href="%sveltekit.assets%/favicon.png" /> <link rel="apple-touch-icon" href="%sveltekit.assets%/favicon.png" />
<link <link
rel="apple-touch-startup-image" rel="apple-touch-startup-image"
href="%sveltekit.assets%/lettermark.jpg" href="%sveltekit.assets%/lettermark.jpg"
/> />
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta <meta
name="apple-mobile-web-app-status-bar-style" name="apple-mobile-web-app-status-bar-style"
content="black-translucent" content="black-translucent"
/> />
%sveltekit.head% %sveltekit.head%
<script> <script>
(function () { (function () {
// Apply theme before DOM is loaded // Apply theme before DOM is loaded
let theme = localStorage.getItem("theme"); let theme = localStorage.getItem("theme");
const prefersDark = window.matchMedia( const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)", "(prefers-color-scheme: dark)",
).matches; ).matches;
console.log( console.log(
`Theme: ${theme || "N/A"}, prefers dark: ${prefersDark}`, `Theme: ${theme || "N/A"}, prefers dark: ${prefersDark}`,
); );
if (theme !== "light" && theme !== "dark") { if (theme !== "light" && theme !== "dark") {
console.log("Invalid theme, setting to default"); console.log("Invalid theme, setting to default");
theme = prefersDark ? "dark" : "light"; theme = prefersDark ? "dark" : "light";
localStorage.setItem("theme", theme); localStorage.setItem("theme", theme);
} }
console.log(`Applying theme: ${theme}`); console.log(`Applying theme: ${theme}`);
document.documentElement.classList.add(theme); document.documentElement.classList.add(theme);
// Lock dark reader if it's set to dark mode // Lock dark reader if it's set to dark mode
if (theme === "dark") { if (theme === "dark") {
const lock = document.createElement("meta"); const lock = document.createElement("meta");
lock.name = "darkreader-lock"; lock.name = "darkreader-lock";
document.head.appendChild(lock); document.head.appendChild(lock);
} }
})(); })();
</script> </script>
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@ -58,7 +58,8 @@
if (!file) return; if (!file) return;
file.conversionSettings.imageSequence = imageSequence; file.conversionSettings.imageSequence = imageSequence;
file.conversionSettings.imageSequenceFPS = imageSequenceFPS; file.conversionSettings.imageSequenceFPS = imageSequenceFPS;
file.conversionSettings.imageSequenceTransparency = imageSequenceTransparency; file.conversionSettings.imageSequenceTransparency =
imageSequenceTransparency;
}); });
const normalize = (str: string) => str.replace(/^\./, "").toLowerCase(); const normalize = (str: string) => str.replace(/^\./, "").toLowerCase();

View File

@ -75,7 +75,7 @@
</div> </div>
<h2 class="text-center text-2xl font-semibold mt-4"> <h2 class="text-center text-2xl font-semibold mt-4">
{m["upload.uploader.text"]({ {m["upload.uploader.text"]({
action: m["upload.uploader.convert"]() action: m["upload.uploader.convert"](),
})} })}
</h2> </h2>
</Panel> </Panel>

View File

@ -19,8 +19,8 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<p class="text-black"> <p class="text-black">
{m["convert.external_warning.text"]({ {m["convert.external_warning.text"]({
filename: toast.additional.filename, filename: toast.additional.filename,
})} })}
</p> </p>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<button <button

View File

@ -38,40 +38,50 @@
<p>{@html sanitize(m["convert.errors.vertd.details.body"]())}</p> <p>{@html sanitize(m["convert.errors.vertd.details.body"]())}</p>
<p> <p>
<span class="text-black dynadark:text-white"> <span class="text-black dynadark:text-white">
{@html sanitize(m["convert.errors.vertd.details.job_id"]({ {@html sanitize(
jobId: additional.jobId, m["convert.errors.vertd.details.job_id"]({
}))} jobId: additional.jobId,
}),
)}
</span> </span>
</p> </p>
<p> <p>
<span class="text-black dynadark:text-white"> <span class="text-black dynadark:text-white">
{@html sanitize(m["convert.errors.vertd.details.from"]({ {@html sanitize(
from: additional.from, m["convert.errors.vertd.details.from"]({
}))} from: additional.from,
}),
)}
</span> </span>
</p> </p>
<p> <p>
<span class="text-black dynadark:text-white"> <span class="text-black dynadark:text-white">
{@html sanitize(m["convert.errors.vertd.details.to"]({ to: additional.to }))} {@html sanitize(
m["convert.errors.vertd.details.to"]({ to: additional.to }),
)}
</span> </span>
</p> </p>
<p> <p>
<span class="text-black dynadark:text-white"> <span class="text-black dynadark:text-white">
{@html sanitize(link( {@html sanitize(
["view_link"], link(
m["convert.errors.vertd.details.error_message"](), ["view_link"],
[errorBlobUrl || "#"], m["convert.errors.vertd.details.error_message"](),
[errorBlobUrl || "#"],
[true],
["text-blue-500 font-normal"],
),
)}
</span>
</p>
<p>
{@html sanitize(
link(
["privacy_link"],
m["convert.errors.vertd.details.footer"](),
"/privacy",
[true], [true],
["text-blue-500 font-normal"], ),
))} )}
</span>
</p>
<p>
{@html sanitize(link(
["privacy_link"],
m["convert.errors.vertd.details.footer"](),
"/privacy",
[true],
))}
</p> </p>
</div> </div>

View File

@ -37,10 +37,7 @@
{m["footer.discord_server"]()} {m["footer.discord_server"]()}
</a> </a>
<p></p> <p></p>
<a <a class="hover:underline font-normal" href="/privacy/">
class="hover:underline font-normal"
href="/privacy/"
>
{m["footer.privacy_policy"]()} {m["footer.privacy_policy"]()}
</a> </a>
{#if commitHash} {#if commitHash}

View File

@ -1,212 +1,212 @@
<script lang="ts"> <script lang="ts">
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { page } from "$app/state"; import { page } from "$app/state";
import { duration, fade } from "$lib/util/animation"; import { duration, fade } from "$lib/util/animation";
import { import {
effects, effects,
files, files,
goingLeft, goingLeft,
setTheme, setTheme,
} from "$lib/store/index.svelte"; } from "$lib/store/index.svelte";
import clsx from "clsx"; import clsx from "clsx";
import { import {
InfoIcon, InfoIcon,
MoonIcon, MoonIcon,
RefreshCw, RefreshCw,
SettingsIcon, SettingsIcon,
SunIcon, SunIcon,
UploadIcon, UploadIcon,
type Icon as IconType, type Icon as IconType,
} from "lucide-svelte"; } from "lucide-svelte";
import { quintOut } from "svelte/easing"; import { quintOut } from "svelte/easing";
import Panel from "../../visual/Panel.svelte"; import Panel from "../../visual/Panel.svelte";
import Logo from "../../visual/svg/Logo.svelte"; import Logo from "../../visual/svg/Logo.svelte";
import { beforeNavigate } from "$app/navigation"; import { beforeNavigate } from "$app/navigation";
import Tooltip from "$lib/components/visual/Tooltip.svelte"; import Tooltip from "$lib/components/visual/Tooltip.svelte";
import { m } from "$lib/paraglide/messages"; import { m } from "$lib/paraglide/messages";
const items = $derived< const items = $derived<
{ {
name: string; name: string;
url: string; url: string;
activeMatch: (pathname: string) => boolean; activeMatch: (pathname: string) => boolean;
icon: typeof IconType; icon: typeof IconType;
badge?: number; badge?: number;
}[] }[]
>([ >([
{ {
name: m["navbar.upload"](), name: m["navbar.upload"](),
url: "/", url: "/",
activeMatch: (pathname) => pathname === "/", activeMatch: (pathname) => pathname === "/",
icon: UploadIcon, icon: UploadIcon,
}, },
{ {
name: m["navbar.convert"](), name: m["navbar.convert"](),
url: "/convert/", url: "/convert/",
activeMatch: (pathname) => activeMatch: (pathname) =>
pathname === "/convert/" || pathname === "/convert", pathname === "/convert/" || pathname === "/convert",
icon: RefreshCw, icon: RefreshCw,
badge: files.files.length, badge: files.files.length,
}, },
{ {
name: m["navbar.settings"](), name: m["navbar.settings"](),
url: "/settings/", url: "/settings/",
activeMatch: (pathname) => pathname.startsWith("/settings"), activeMatch: (pathname) => pathname.startsWith("/settings"),
icon: SettingsIcon, icon: SettingsIcon,
}, },
{ {
name: m["navbar.about"](), name: m["navbar.about"](),
url: "/about/", url: "/about/",
activeMatch: (pathname) => pathname.startsWith("/about"), activeMatch: (pathname) => pathname.startsWith("/about"),
icon: InfoIcon, icon: InfoIcon,
}, },
]); ]);
let links = $state<HTMLAnchorElement[]>([]); let links = $state<HTMLAnchorElement[]>([]);
let container = $state<HTMLDivElement>(); let container = $state<HTMLDivElement>();
let containerRect = $derived(container?.getBoundingClientRect()); let containerRect = $derived(container?.getBoundingClientRect());
let isInitialized = $state(false); let isInitialized = $state(false);
const linkRects = $derived(links.map((l) => l.getBoundingClientRect())); const linkRects = $derived(links.map((l) => l.getBoundingClientRect()));
const selectedIndex = $derived( const selectedIndex = $derived(
items.findIndex((i) => i.activeMatch(page.url.pathname)), items.findIndex((i) => i.activeMatch(page.url.pathname)),
); );
const isSecretPage = $derived(selectedIndex === -1); const isSecretPage = $derived(selectedIndex === -1);
$effect(() => { $effect(() => {
if (containerRect && linkRects.length > 0 && links.length > 0) { if (containerRect && linkRects.length > 0 && links.length > 0) {
setTimeout(() => { setTimeout(() => {
isInitialized = true; isInitialized = true;
}, 10); }, 10);
} else { } else {
isInitialized = false; isInitialized = false;
} }
}); });
beforeNavigate((e) => { beforeNavigate((e) => {
const oldIndex = items.findIndex((i) => const oldIndex = items.findIndex((i) =>
i.activeMatch(e.from?.url.pathname || ""), i.activeMatch(e.from?.url.pathname || ""),
); );
const newIndex = items.findIndex((i) => const newIndex = items.findIndex((i) =>
i.activeMatch(e.to?.url.pathname || ""), i.activeMatch(e.to?.url.pathname || ""),
); );
if (newIndex < oldIndex) { if (newIndex < oldIndex) {
goingLeft.set(true); goingLeft.set(true);
} else { } else {
goingLeft.set(false); goingLeft.set(false);
} }
}); });
</script> </script>
{#snippet link(item: (typeof items)[0], index: number)} {#snippet link(item: (typeof items)[0], index: number)}
{@const Icon = item.icon} {@const Icon = item.icon}
<a <a
bind:this={links[index]} bind:this={links[index]}
href={item.url} href={item.url}
aria-label={item.name} aria-label={item.name}
class={clsx( class={clsx(
"min-w-16 md:min-w-32 h-full relative z-10 rounded-xl flex flex-1 items-center justify-center gap-3 overflow-hidden", "min-w-16 md:min-w-32 h-full relative z-10 rounded-xl flex flex-1 items-center justify-center gap-3 overflow-hidden",
{ {
"bg-panel-highlight": "bg-panel-highlight":
item.activeMatch(page.url.pathname) && !browser, item.activeMatch(page.url.pathname) && !browser,
}, },
)} )}
draggable={false} draggable={false}
> >
<div class="grid grid-rows-1 grid-cols-1"> <div class="grid grid-rows-1 grid-cols-1">
{#key item.name} {#key item.name}
<div <div
class="w-full row-start-1 col-start-1 h-full flex items-center justify-center gap-3" class="w-full row-start-1 col-start-1 h-full flex items-center justify-center gap-3"
in:fade={{ in:fade={{
duration, duration,
easing: quintOut, easing: quintOut,
}} }}
out:fade={{ out:fade={{
duration, duration,
easing: quintOut, easing: quintOut,
}} }}
> >
<div class="relative"> <div class="relative">
<Icon /> <Icon />
{#if item.badge} {#if item.badge}
<div <div
class="absolute overflow-hidden grid grid-rows-1 grid-cols-1 -top-1 font-display -right-1 w-fit px-1.5 h-4 rounded-full bg-badge text-on-badge font-medium" class="absolute overflow-hidden grid grid-rows-1 grid-cols-1 -top-1 font-display -right-1 w-fit px-1.5 h-4 rounded-full bg-badge text-on-badge font-medium"
style="font-size: 0.7rem;" style="font-size: 0.7rem;"
transition:fade={{ transition:fade={{
duration, duration,
easing: quintOut, easing: quintOut,
}} }}
> >
{#key item.badge} {#key item.badge}
<div <div
class="flex items-center justify-center w-full h-full col-start-1 row-start-1" class="flex items-center justify-center w-full h-full col-start-1 row-start-1"
in:fade={{ in:fade={{
duration, duration,
easing: quintOut, easing: quintOut,
}} }}
out:fade={{ out:fade={{
duration, duration,
easing: quintOut, easing: quintOut,
}} }}
> >
{item.badge} {item.badge}
</div> </div>
{/key} {/key}
</div> </div>
{/if} {/if}
</div> </div>
<p <p
class="font-medium hidden hyphens-auto break-all md:flex min-w-0" class="font-medium hidden hyphens-auto break-all md:flex min-w-0"
> >
{item.name} {item.name}
</p> </p>
</div> </div>
{/key} {/key}
</div> </div>
</a> </a>
{/snippet} {/snippet}
<div bind:this={container}> <div bind:this={container}>
<Panel class="max-w-[778px] w-screen h-20 flex items-center gap-3 relative"> <Panel class="max-w-[778px] w-screen h-20 flex items-center gap-3 relative">
{@const linkRect = linkRects.at(selectedIndex) || linkRects[0]} {@const linkRect = linkRects.at(selectedIndex) || linkRects[0]}
{#if linkRect && isInitialized} {#if linkRect && isInitialized}
<div <div
class="absolute bg-panel-highlight rounded-xl" class="absolute bg-panel-highlight rounded-xl"
style="width: {linkRect.width}px; height: {linkRect.height}px; top: {linkRect.top - style="width: {linkRect.width}px; height: {linkRect.height}px; top: {linkRect.top -
(containerRect?.top || 0)}px; left: {linkRect.left - (containerRect?.top || 0)}px; left: {linkRect.left -
(containerRect?.left || 0)}px; opacity: {isSecretPage (containerRect?.left || 0)}px; opacity: {isSecretPage
? 0 ? 0
: 1}; {$effects : 1}; {$effects
? `transition: left var(--transition) ${duration}ms, top var(--transition) ${duration}ms, opacity var(--transition) ${duration}ms;` ? `transition: left var(--transition) ${duration}ms, top var(--transition) ${duration}ms, opacity var(--transition) ${duration}ms;`
: ''}" : ''}"
></div> ></div>
{/if} {/if}
<a <a
class="w-28 h-full bg-accent rounded-xl items-center justify-center hidden md:flex" class="w-28 h-full bg-accent rounded-xl items-center justify-center hidden md:flex"
href="/" href="/"
> >
<div class="h-5 w-full"> <div class="h-5 w-full">
<Logo /> <Logo />
</div> </div>
</a> </a>
{#each items as item, i (item.url)} {#each items as item, i (item.url)}
{@render link(item, i)} {@render link(item, i)}
{/each} {/each}
<div class="w-0.5 bg-separator h-full hidden md:flex"></div> <div class="w-0.5 bg-separator h-full hidden md:flex"></div>
<Tooltip text={m["navbar.toggle_theme"]()} position="right"> <Tooltip text={m["navbar.toggle_theme"]()} position="right">
<button <button
onclick={() => { onclick={() => {
const isDark = const isDark =
document.documentElement.classList.contains("dark"); document.documentElement.classList.contains("dark");
setTheme(isDark ? "light" : "dark"); setTheme(isDark ? "light" : "dark");
}} }}
class="w-14 h-full items-center justify-center hidden md:flex" class="w-14 h-full items-center justify-center hidden md:flex"
> >
<SunIcon class="dynadark:hidden block" /> <SunIcon class="dynadark:hidden block" />
<MoonIcon class="dynadark:block hidden" /> <MoonIcon class="dynadark:block hidden" />
</button> </button>
</Tooltip> </Tooltip>
</Panel> </Panel>
</div> </div>

View File

@ -6,7 +6,12 @@ import type {
SettingDefinition, SettingDefinition,
} from "$lib/types/conversion-settings"; } from "$lib/types/conversion-settings";
export type WorkerStatus = "not-ready" | "downloading" | "ready" | "partially-ready" | "error"; export type WorkerStatus =
| "not-ready"
| "downloading"
| "ready"
| "partially-ready"
| "error";
export class FormatInfo { export class FormatInfo {
public name: string; public name: string;
@ -56,7 +61,9 @@ export class Converter {
* Can be overridden per converter for format-specific settings. * Can be overridden per converter for format-specific settings.
* @param input The input file. * @param input The input file.
*/ */
public async getAvailableSettings(input?: VertFile): Promise<SettingDefinition[]> { public async getAvailableSettings(
input?: VertFile,
): Promise<SettingDefinition[]> {
return []; return [];
} }
@ -64,7 +71,9 @@ export class Converter {
* Get default settings for a conversion. * Get default settings for a conversion.
* @param input The input file. * @param input The input file.
*/ */
public async getDefaultSettings(input?: VertFile): Promise<ConversionSettings> { public async getDefaultSettings(
input?: VertFile,
): Promise<ConversionSettings> {
const defaults: ConversionSettings = {}; const defaults: ConversionSettings = {};
const settings = await this.getAvailableSettings(input); const settings = await this.getAvailableSettings(input);
settings.forEach((setting) => { settings.forEach((setting) => {

View File

@ -1,4 +1,7 @@
import { toArgs, animatedImageFormats } from "$lib/converters/ffmpeg/ffmpeg.codecs"; import {
toArgs,
animatedImageFormats,
} from "$lib/converters/ffmpeg/ffmpeg.codecs";
import type { ConversionSettings } from "$lib/types/conversion-settings"; import type { ConversionSettings } from "$lib/types/conversion-settings";
import { videoFormats } from "../vertd/vertd.svelte"; import { videoFormats } from "../vertd/vertd.svelte";

View File

@ -27,7 +27,7 @@ export const converterCategories = {
video: ["mediabunny", "vertd"], video: ["mediabunny", "vertd"],
audio: ["ffmpeg"], audio: ["ffmpeg"],
doc: ["pandoc"], doc: ["pandoc"],
} };
export function getConverterByFormat(format: string) { export function getConverterByFormat(format: string) {
for (const converter of converters) { for (const converter of converters) {

View File

@ -1,6 +1,10 @@
<script lang="ts"> <script lang="ts">
import Panel from "$lib/components/visual/Panel.svelte"; import Panel from "$lib/components/visual/Panel.svelte";
import { CONTACT_EMAIL, DISCORD_URL, GITHUB_URL_VERT } from "$lib/util/consts"; import {
CONTACT_EMAIL,
DISCORD_URL,
GITHUB_URL_VERT,
} from "$lib/util/consts";
import { effects } from "$lib/store/index.svelte"; import { effects } from "$lib/store/index.svelte";
import { import {
GithubIcon, GithubIcon,

View File

@ -48,12 +48,14 @@
</a> </a>
</div> </div>
<p class="text-muted"> <p class="text-muted">
{@html sanitize(link( {@html sanitize(
"discord_link", link(
m["about.sponsors.description"](), "discord_link",
DISCORD_URL, m["about.sponsors.description"](),
true DISCORD_URL,
))} true,
),
)}
<span class="inline-block mx-[2px] relative top-[2px]"> <span class="inline-block mx-[2px] relative top-[2px]">
<button <button
id="email" id="email"

View File

@ -179,7 +179,8 @@
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p class="text-base font-bold"> <p class="text-base font-bold">
{m["settings.appearance.language.title"]()} {m["settings.appearance.language.title"]()}
{#if currentLocale !== "en"} (Language){/if} {#if currentLocale !== "en"}
(Language){/if}
</p> </p>
<p class="text-sm text-muted font-normal italic"> <p class="text-sm text-muted font-normal italic">
{m["settings.appearance.language.description"]()} {m["settings.appearance.language.description"]()}

View File

@ -40,11 +40,11 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p class="text-base font-bold"> <p class="text-base font-bold">
{m["settings.conversion.filename.format"]()} {m["settings.conversion.filename.format"]()}
</p> </p>
<p class="text-sm text-muted font-normal"> <p class="text-sm text-muted font-normal">
{@html sanitize( {@html sanitize(
m["settings.conversion.filename.description"](), m["settings.conversion.filename.description"](),
)} )}
</p> </p>
</div> </div>
@ -87,7 +87,9 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<p class="text-base font-bold"> <p class="text-base font-bold">
{m["settings.conversion.default_format.label"]()} {m[
"settings.conversion.default_format.label"
]()}
</p> </p>
<p class="text-sm text-muted font-normal"> <p class="text-sm text-muted font-normal">
{m[ {m[
@ -226,7 +228,9 @@
size="24" size="24"
class="inline-block mr-2" class="inline-block mr-2"
/> />
{m["settings.conversion.metadata.keep"]()} {m[
"settings.conversion.metadata.keep"
]()}
</button> </button>
<button <button
@ -242,7 +246,9 @@
size="24" size="24"
class="inline-block mr-2" class="inline-block mr-2"
/> />
{m["settings.conversion.metadata.remove"]()} {m[
"settings.conversion.metadata.remove"
]()}
</button> </button>
</div> </div>
</div> </div>

View File

@ -76,7 +76,9 @@
m["settings.privacy.site_data.clear_all_data_confirm"](), m["settings.privacy.site_data.clear_all_data_confirm"](),
[ [
{ {
text: m["settings.privacy.site_data.clear_all_data_cancel"](), text: m[
"settings.privacy.site_data.clear_all_data_cancel"
](),
action: () => {}, action: () => {},
}, },
{ {
@ -95,7 +97,9 @@
ToastManager.add({ ToastManager.add({
type: "success", type: "success",
message: message:
m["settings.privacy.site_data.all_data_cleared"](), m[
"settings.privacy.site_data.all_data_cleared"
](),
}); });
} catch (err) { } catch (err) {
error( error(

View File

@ -11,11 +11,11 @@ export interface SettingDefinition {
min?: number; min?: number;
max?: number; max?: number;
step?: number; step?: number;
options?: Array<{ value: any; label: any; }>; // for select/range types options?: Array<{ value: any; label: any }>; // for select/range types
description?: string; description?: string;
hasCustomInput?: boolean; // for select types with a "custom" option hasCustomInput?: boolean; // for select types with a "custom" option
customInputKey?: string; // key to use for custom input value in settings object customInputKey?: string; // key to use for custom input value in settings object
forceFullWidth?: boolean; // force setting to take up full width (usually grid 2) forceFullWidth?: boolean; // force setting to take up full width (usually grid 2)
} }
export interface ConversionSettings { export interface ConversionSettings {

View File

@ -1,48 +1,50 @@
import { VertFile } from "./file.svelte"; import { VertFile } from "./file.svelte";
interface ConvertMessage { interface ConvertMessage {
type: "convert"; type: "convert";
input: { input:
file: File; | {
name: string; file: File;
from: string; name: string;
to: string; from: string;
} | VertFile; to: string;
to: string; }
conversionSettings: string; // JSON stringified ConversionSettings | VertFile;
} to: string;
conversionSettings: string; // JSON stringified ConversionSettings
interface FinishedMessage { }
type: "finished";
output: ArrayBufferLike | Uint8Array; interface FinishedMessage {
zip?: boolean; type: "finished";
} output: ArrayBufferLike | Uint8Array;
zip?: boolean;
interface LoadMessage { }
type: "load";
wasm: ArrayBuffer; interface LoadMessage {
} type: "load";
wasm: ArrayBuffer;
interface LoadedMessage { }
type: "loaded";
} interface LoadedMessage {
type: "loaded";
interface ReadyMessage { }
type: "ready";
} interface ReadyMessage {
type: "ready";
interface ErrorMessage { }
type: "error";
error: string; interface ErrorMessage {
} type: "error";
error: string;
export type WorkerMessage = ( }
| ConvertMessage
| FinishedMessage export type WorkerMessage = (
| LoadMessage | ConvertMessage
| LoadedMessage | FinishedMessage
| ReadyMessage | LoadMessage
| ErrorMessage | LoadedMessage
) & { | ReadyMessage
id: string; // unused? rn just using file id, probably meant to be incrementing w/ every message posted? | ErrorMessage
}; ) & {
id: string; // unused? rn just using file id, probably meant to be incrementing w/ every message posted?
};

View File

@ -89,4 +89,4 @@ export const formatBytes = (bytes: number): string => {
} }
return `${value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${units[unitIndex]}`; return `${value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${units[unitIndex]}`;
}; };

View File

@ -1,150 +1,150 @@
// THIS CODE IS FROM https://github.com/captbaritone/webamp/blob/15b0312cb794973a0e615d894df942452e920c36/packages/ani-cursor/src/parser.ts // THIS CODE IS FROM https://github.com/captbaritone/webamp/blob/15b0312cb794973a0e615d894df942452e920c36/packages/ani-cursor/src/parser.ts
// LICENSED UNDER MIT. (c) Jordan Eldredge and Webamp contributors // LICENSED UNDER MIT. (c) Jordan Eldredge and Webamp contributors
// this code is ripped from their project because i didn't want to // this code is ripped from their project because i didn't want to
// re-invent the wheel, BUT the library they provide (ani-cursor) // re-invent the wheel, BUT the library they provide (ani-cursor)
// doesn't expose the internals. // doesn't expose the internals.
import { RIFFFile } from "riff-file"; import { RIFFFile } from "riff-file";
import { unpackArray, unpackString } from "byte-data"; import { unpackArray, unpackString } from "byte-data";
type Chunk = { type Chunk = {
format: string; format: string;
chunkId: string; chunkId: string;
chunkData: { chunkData: {
start: number; start: number;
end: number; end: number;
}; };
subChunks: Chunk[]; subChunks: Chunk[];
}; };
// https://www.informit.com/articles/article.aspx?p=1189080&seqNum=3 // https://www.informit.com/articles/article.aspx?p=1189080&seqNum=3
type AniMetadata = { type AniMetadata = {
cbSize: number; // Data structure size (in bytes) cbSize: number; // Data structure size (in bytes)
nFrames: number; // Number of images (also known as frames) stored in the file nFrames: number; // Number of images (also known as frames) stored in the file
nSteps: number; // Number of frames to be displayed before the animation repeats nSteps: number; // Number of frames to be displayed before the animation repeats
iWidth: number; // Width of frame (in pixels) iWidth: number; // Width of frame (in pixels)
iHeight: number; // Height of frame (in pixels) iHeight: number; // Height of frame (in pixels)
iBitCount: number; // Number of bits per pixel iBitCount: number; // Number of bits per pixel
nPlanes: number; // Number of color planes nPlanes: number; // Number of color planes
iDispRate: number; // Default frame display rate (measured in 1/60th-of-a-second units) iDispRate: number; // Default frame display rate (measured in 1/60th-of-a-second units)
bfAttributes: number; // ANI attribute bit flags bfAttributes: number; // ANI attribute bit flags
}; };
type ParsedAni = { type ParsedAni = {
rate: number[] | null; rate: number[] | null;
seq: number[] | null; seq: number[] | null;
images: Uint8Array[]; images: Uint8Array[];
metadata: AniMetadata; metadata: AniMetadata;
artist: string | null; artist: string | null;
title: string | null; title: string | null;
}; };
const DWORD = { bits: 32, be: false, signed: false, fp: false }; const DWORD = { bits: 32, be: false, signed: false, fp: false };
export function parseAni(arr: Uint8Array): ParsedAni { export function parseAni(arr: Uint8Array): ParsedAni {
const riff = new RIFFFile(); const riff = new RIFFFile();
riff.setSignature(arr); riff.setSignature(arr);
const signature = riff.signature as Chunk; const signature = riff.signature as Chunk;
if (signature.format !== "ACON") { if (signature.format !== "ACON") {
throw new Error( throw new Error(
`Expected format. Expected "ACON", got "${signature.format}"`, `Expected format. Expected "ACON", got "${signature.format}"`,
); );
} }
// Helper function to get a chunk by chunkId and transform it if it's non-null. // Helper function to get a chunk by chunkId and transform it if it's non-null.
function mapChunk<T>( function mapChunk<T>(
chunkId: string, chunkId: string,
mapper: (chunk: Chunk) => T, mapper: (chunk: Chunk) => T,
): T | null { ): T | null {
const chunk = riff.findChunk(chunkId) as Chunk | null; const chunk = riff.findChunk(chunkId) as Chunk | null;
return chunk == null ? null : mapper(chunk); return chunk == null ? null : mapper(chunk);
} }
function readImages(chunk: Chunk, frameCount: number): Uint8Array[] { function readImages(chunk: Chunk, frameCount: number): Uint8Array[] {
return chunk.subChunks.slice(0, frameCount).map((c) => { return chunk.subChunks.slice(0, frameCount).map((c) => {
if (c.chunkId !== "icon") { if (c.chunkId !== "icon") {
throw new Error(`Unexpected chunk type in fram: ${c.chunkId}`); throw new Error(`Unexpected chunk type in fram: ${c.chunkId}`);
} }
return arr.slice(c.chunkData.start, c.chunkData.end); return arr.slice(c.chunkData.start, c.chunkData.end);
}); });
} }
const metadata = mapChunk("anih", (c) => { const metadata = mapChunk("anih", (c) => {
const words = unpackArray( const words = unpackArray(
arr, arr,
DWORD, DWORD,
c.chunkData.start, c.chunkData.start,
c.chunkData.end, c.chunkData.end,
); );
return { return {
cbSize: words[0], cbSize: words[0],
nFrames: words[1], nFrames: words[1],
nSteps: words[2], nSteps: words[2],
iWidth: words[3], iWidth: words[3],
iHeight: words[4], iHeight: words[4],
iBitCount: words[5], iBitCount: words[5],
nPlanes: words[6], nPlanes: words[6],
iDispRate: words[7], iDispRate: words[7],
bfAttributes: words[8], bfAttributes: words[8],
}; };
}); });
if (metadata == null) { if (metadata == null) {
throw new Error("Did not find anih"); throw new Error("Did not find anih");
} }
const rate = mapChunk("rate", (c) => { const rate = mapChunk("rate", (c) => {
return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end); return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end);
}); });
// chunkIds are always four chars, hence the trailing space. // chunkIds are always four chars, hence the trailing space.
const seq = mapChunk("seq ", (c) => { const seq = mapChunk("seq ", (c) => {
return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end); return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end);
}); });
const lists = riff.findChunk("LIST", true) as Chunk[] | null; const lists = riff.findChunk("LIST", true) as Chunk[] | null;
const imageChunk = lists?.find((c) => c.format === "fram"); const imageChunk = lists?.find((c) => c.format === "fram");
if (imageChunk == null) { if (imageChunk == null) {
throw new Error("Did not find fram LIST"); throw new Error("Did not find fram LIST");
} }
let images = readImages(imageChunk, metadata.nFrames); let images = readImages(imageChunk, metadata.nFrames);
let title = null; let title = null;
let artist = null; let artist = null;
const infoChunk = lists?.find((c) => c.format === "INFO"); const infoChunk = lists?.find((c) => c.format === "INFO");
if (infoChunk != null) { if (infoChunk != null) {
infoChunk.subChunks.forEach((c) => { infoChunk.subChunks.forEach((c) => {
switch (c.chunkId) { switch (c.chunkId) {
case "INAM": case "INAM":
title = unpackString( title = unpackString(
arr, arr,
c.chunkData.start, c.chunkData.start,
c.chunkData.end, c.chunkData.end,
); );
break; break;
case "IART": case "IART":
artist = unpackString( artist = unpackString(
arr, arr,
c.chunkData.start, c.chunkData.start,
c.chunkData.end, c.chunkData.end,
); );
break; break;
case "LIST": case "LIST":
// Some cursors with an artist of "Created with Take ONE 3.5 (unregisterred version)" seem to have their frames here for some reason? // Some cursors with an artist of "Created with Take ONE 3.5 (unregisterred version)" seem to have their frames here for some reason?
if (c.format === "fram") { if (c.format === "fram") {
images = readImages(c, metadata.nFrames); images = readImages(c, metadata.nFrames);
} }
break; break;
default: default:
// Unexpected subchunk // Unexpected subchunk
} }
}); });
} }
return { images, rate, seq, metadata, artist, title }; return { images, rate, seq, metadata, artist, title };
} }

View File

@ -1,7 +1,9 @@
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { error } from "$lib/util/logger"; import { error } from "$lib/util/logger";
export function readSettings<T extends object = Record<string, unknown>>(): Partial<T> { export function readSettings<
T extends object = Record<string, unknown>,
>(): Partial<T> {
if (!browser) return {}; if (!browser) return {};
const raw = localStorage.getItem("settings"); const raw = localStorage.getItem("settings");
@ -20,4 +22,4 @@ export function readSettings<T extends object = Record<string, unknown>>(): Part
localStorage.removeItem("settings"); localStorage.removeItem("settings");
return {}; return {};
} }
} }

View File

@ -279,7 +279,10 @@ const pandocToFiles = (entries: PandocEntries, parent = ""): File[] => {
const nestedFiles = pandocToFiles(entry.entries, fullPath); const nestedFiles = pandocToFiles(entry.entries, fullPath);
flattened.push(...nestedFiles); flattened.push(...nestedFiles);
} else { } else {
const file = new File([new Uint8Array(Array.from(entry.data))], fullPath); const file = new File(
[new Uint8Array(Array.from(entry.data))],
fullPath,
);
flattened.push(file); flattened.push(file);
} }
} }

View File

@ -3,7 +3,10 @@
import { goto, beforeNavigate, afterNavigate } from "$app/navigation"; import { goto, beforeNavigate, afterNavigate } from "$app/navigation";
import { PUB_PLAUSIBLE_URL, PUB_HOSTNAME } from "$env/static/public"; import { PUB_PLAUSIBLE_URL, PUB_HOSTNAME } from "$env/static/public";
import { DISABLE_ALL_EXTERNAL_REQUESTS, VERT_NAME } from "$lib/util/consts.js"; import {
DISABLE_ALL_EXTERNAL_REQUESTS,
VERT_NAME,
} from "$lib/util/consts.js";
import * as Layout from "$lib/components/layout"; import * as Layout from "$lib/components/layout";
import * as Navbar from "$lib/components/layout/Navbar"; import * as Navbar from "$lib/components/layout/Navbar";
import { Settings } from "$lib/sections/settings/index.svelte"; import { Settings } from "$lib/sections/settings/index.svelte";
@ -96,7 +99,10 @@
// detect if insecure context // detect if insecure context
if (!window.isSecureContext) { if (!window.isSecureContext) {
log(["layout"], "Insecure context (HTTP) detected, some features may not work as expected -- you may want to enable \"PUB_DISABLE_FAILURE_BLOCKS\" on local deployments."); log(
["layout"],
'Insecure context (HTTP) detected, some features may not work as expected -- you may want to enable "PUB_DISABLE_FAILURE_BLOCKS" on local deployments.',
);
ToastManager.add({ ToastManager.add({
type: "warning", type: "warning",
message: m["toast.insecure_context"](), message: m["toast.insecure_context"](),
@ -155,7 +161,10 @@
property="twitter:description" property="twitter:description"
content="With VERT, you can quickly convert any image, video, audio, and document file. No ads, no tracking, open source, and all processing is done on your device." content="With VERT, you can quickly convert any image, video, audio, and document file. No ads, no tracking, open source, and all processing is done on your device."
/> />
<meta property="twitter:image" content="https://vert.sh/VERT_Feature.webp" /> <meta
property="twitter:image"
content="https://vert.sh/VERT_Feature.webp"
/>
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<link rel="canonical" href="https://vert.sh/" /> <link rel="canonical" href="https://vert.sh/" />
{#if enablePlausible} {#if enablePlausible}

View File

@ -37,7 +37,10 @@
link( link(
["about_link", "stripe_link"], ["about_link", "stripe_link"],
m["privacy.donations.description"](), m["privacy.donations.description"](),
["/about", "https://stripe.com/docs/disputes/prevention/advanced-fraud-detection"], [
"/about",
"https://stripe.com/docs/disputes/prevention/advanced-fraud-detection",
],
[false, true], [false, true],
), ),
)} )}

View File

@ -26,7 +26,10 @@
} }
const parsedSettings = readSavedSettings(); const parsedSettings = readSavedSettings();
if (parsedSettings && JSON.stringify(parsedSettings) === JSON.stringify(settings)) if (
parsedSettings &&
JSON.stringify(parsedSettings) === JSON.stringify(settings)
)
return; return;
try { try {