mirror of https://github.com/VERT-sh/VERT.git
342 lines
8.9 KiB
Svelte
342 lines
8.9 KiB
Svelte
<script lang="ts">
|
|
import Uploader from "$lib/components/functional/Uploader.svelte";
|
|
import Tooltip from "$lib/components/visual/Tooltip.svelte";
|
|
import { converters } from "$lib/converters";
|
|
import { vertdLoaded } from "$lib/store/index.svelte";
|
|
import clsx from "clsx";
|
|
import { AudioLines, BookText, Check, Film, Image } from "lucide-svelte";
|
|
import { m } from "$lib/paraglide/messages";
|
|
import { OverlayScrollbarsComponent } from "overlayscrollbars-svelte";
|
|
import { browser } from "$app/environment";
|
|
import "overlayscrollbars/overlayscrollbars.css";
|
|
import { onMount } from "svelte";
|
|
import type { WorkerStatus } from "$lib/converters/converter.svelte";
|
|
import { sanitize } from "$lib/store/index.svelte";
|
|
import { DISABLE_ALL_EXTERNAL_REQUESTS } from "$lib/util/consts";
|
|
|
|
const getSupportedFormats = (name: string) =>
|
|
converters
|
|
.find((c) => c.name === name)
|
|
?.supportedFormats.map(
|
|
(f) =>
|
|
`${f.name}${f.fromSupported && f.toSupported ? "" : "*"}`,
|
|
)
|
|
.join(", ") || "none";
|
|
|
|
const worker: {
|
|
[key: string]: {
|
|
formats: string;
|
|
icon: typeof Image;
|
|
title: string;
|
|
status: WorkerStatus;
|
|
};
|
|
} = $derived.by(() => {
|
|
const output: {
|
|
[key: string]: {
|
|
formats: string;
|
|
icon: typeof Image;
|
|
title: string;
|
|
status: WorkerStatus;
|
|
};
|
|
} = {
|
|
Images: {
|
|
formats: getSupportedFormats("imagemagick"),
|
|
icon: Image,
|
|
title: m["upload.cards.images"](),
|
|
status:
|
|
converters.find((c) => c.name === "imagemagick")?.status ||
|
|
"not-ready",
|
|
},
|
|
Audio: {
|
|
formats: getSupportedFormats("ffmpeg"),
|
|
icon: AudioLines,
|
|
title: m["upload.cards.audio"](),
|
|
status:
|
|
converters.find((c) => c.name === "ffmpeg")?.status ||
|
|
"not-ready",
|
|
},
|
|
Documents: {
|
|
formats: getSupportedFormats("pandoc"),
|
|
icon: BookText,
|
|
title: m["upload.cards.documents"](),
|
|
status:
|
|
converters.find((c) => c.name === "pandoc")?.status ||
|
|
"not-ready",
|
|
},
|
|
};
|
|
|
|
if (!DISABLE_ALL_EXTERNAL_REQUESTS) {
|
|
const formats = Array.from(
|
|
new Set([
|
|
...getSupportedFormats("vertd").split(", "),
|
|
...getSupportedFormats("mediabunny").split(", "),
|
|
]),
|
|
)
|
|
.filter((f) => f !== "none")
|
|
.join(", ");
|
|
|
|
const mediabunnyStatus = converters.find(
|
|
(c) => c.name === "mediabunny",
|
|
)?.status;
|
|
const vertdReady = $vertdLoaded === true;
|
|
const mediabunnyReady = mediabunnyStatus === "ready";
|
|
const videoStatus =
|
|
vertdReady && mediabunnyReady
|
|
? "ready"
|
|
: vertdReady || mediabunnyReady
|
|
? "partially-ready"
|
|
: "not-ready";
|
|
|
|
output.Video = {
|
|
formats,
|
|
icon: Film,
|
|
title: m["upload.cards.video"](),
|
|
status: videoStatus as WorkerStatus,
|
|
};
|
|
}
|
|
|
|
return output;
|
|
});
|
|
|
|
const getTooltip = (format: string) => {
|
|
const converter = converters.find((c) =>
|
|
c.supportedFormats.some((sf) => sf.name === format),
|
|
);
|
|
|
|
const formatInfo = converter?.supportedFormats.find(
|
|
(sf) => sf.name === format,
|
|
);
|
|
|
|
if (formatInfo) {
|
|
const direction = formatInfo.fromSupported
|
|
? m["upload.tooltip.direction_input"]()
|
|
: m["upload.tooltip.direction_output"]();
|
|
return m["upload.tooltip.partial_support"]({ direction });
|
|
}
|
|
return "";
|
|
};
|
|
|
|
const getStatusText = (status: WorkerStatus) => {
|
|
switch (status) {
|
|
case "downloading":
|
|
return m["upload.cards.status.downloading"]();
|
|
case "partially-ready":
|
|
return m["upload.cards.status.partially_ready"]();
|
|
case "ready":
|
|
return m["upload.cards.status.ready"]();
|
|
default:
|
|
// "not-ready", "error" and other statuses (somehow)
|
|
return m["upload.cards.status.not_ready"]();
|
|
}
|
|
};
|
|
|
|
let scrollContainers: HTMLElement[] = $state([]);
|
|
// svelte-ignore state_referenced_locally
|
|
let showBlur = $state(Array(Object.keys(worker).length).fill(false));
|
|
|
|
onMount(() => {
|
|
const handleResize = () => {
|
|
for (let i = 0; i < scrollContainers.length; i++) {
|
|
// show bottom blur if scrollable
|
|
const container = scrollContainers[i];
|
|
if (!container) return;
|
|
showBlur[i] = container.scrollHeight > container.clientHeight;
|
|
}
|
|
};
|
|
|
|
handleResize();
|
|
window.addEventListener("resize", handleResize);
|
|
|
|
return () => {
|
|
window.removeEventListener("resize", handleResize);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div class="max-w-6xl w-full mx-auto px-6 md:px-8">
|
|
<div class="flex items-center justify-center pb-10 md:py-16">
|
|
<div
|
|
class="flex items-center h-auto gap-12 md:gap-24 md:flex-row flex-col"
|
|
>
|
|
<div class="flex-grow w-full text-center md:text-left">
|
|
<h1
|
|
class="text-4xl px-12 md:p-0 md:text-6xl flex-wrap tracking-tight leading-tight md:leading-[72px] mb-4 md:mb-6"
|
|
>
|
|
{m["upload.title"]()}
|
|
</h1>
|
|
<p
|
|
class="font-normal px-5 md:p-0 text-lg md:text-xl text-black text-muted dynadark:text-muted"
|
|
>
|
|
{m["upload.subtitle"]()}
|
|
</p>
|
|
</div>
|
|
<div class="flex-grow w-full h-72">
|
|
<Uploader class="w-full h-full" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
<div class="mt-10 md:mt-16">
|
|
<h2 class="text-center text-4xl">{m["upload.cards.title"]()}</h2>
|
|
|
|
<div class="flex gap-4 mt-8 md:flex-row flex-col">
|
|
{#if browser}
|
|
{#each Object.entries(worker) as [key, s], i}
|
|
{@const Icon = s.icon}
|
|
<div class="file-category-card w-full flex flex-col gap-4">
|
|
<div class="file-category-card-inner">
|
|
<div
|
|
class={clsx("icon-container", {
|
|
"bg-accent-blue": key === "Images",
|
|
"bg-accent-purple": key === "Audio",
|
|
"bg-accent-green": key === "Documents",
|
|
"bg-accent-red": key === "Video",
|
|
})}
|
|
>
|
|
<Icon size="20" />
|
|
</div>
|
|
<span>{s.title}</span>
|
|
</div>
|
|
|
|
<div
|
|
class="file-category-card-content flex-grow relative"
|
|
>
|
|
<OverlayScrollbarsComponent
|
|
options={{
|
|
scrollbars: {
|
|
autoHide: "move",
|
|
autoHideDelay: 1500,
|
|
},
|
|
}}
|
|
defer
|
|
>
|
|
<div
|
|
class="flex flex-col gap-4 h-[12.25rem] relative"
|
|
bind:this={scrollContainers[i]}
|
|
>
|
|
{#if key === "Video"}
|
|
<p
|
|
class="flex tems-center justify-center gap-2"
|
|
>
|
|
<Check size="20" />
|
|
<Tooltip
|
|
text={m[
|
|
"upload.tooltip.video_server_processing"
|
|
]()}
|
|
>
|
|
<span>
|
|
<a
|
|
href="https://github.com/VERT-sh/VERT/blob/main/docs/VIDEO_CONVERSION.md"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{m[
|
|
"upload.cards.video_server_processing"
|
|
]()}
|
|
</a>
|
|
<span
|
|
class="text-red-500 -ml-0.5"
|
|
>*</span
|
|
>
|
|
</span>
|
|
</Tooltip>
|
|
</p>
|
|
{:else}
|
|
<p
|
|
class="flex tems-center justify-center gap-2"
|
|
>
|
|
<Check size="20" />
|
|
{m[
|
|
"upload.cards.local_supported"
|
|
]()}
|
|
</p>
|
|
{/if}
|
|
<p>
|
|
{@html sanitize(
|
|
m["upload.cards.status.text"]({
|
|
status: getStatusText(s.status),
|
|
}),
|
|
)}
|
|
</p>
|
|
<div
|
|
class="flex flex-col items-center relative"
|
|
>
|
|
<b
|
|
>{m[
|
|
"upload.cards.supported_formats"
|
|
]()} </b
|
|
>
|
|
<p
|
|
class="flex flex-wrap justify-center leading-tight px-2"
|
|
>
|
|
{#each s.formats.split(", ") as format, index}
|
|
{@const isPartial =
|
|
format.endsWith("*")}
|
|
{@const formatName = isPartial
|
|
? format.slice(0, -1)
|
|
: format}
|
|
<span
|
|
class="text-sm font-normal flex items-center relative"
|
|
>
|
|
{#if isPartial}
|
|
<Tooltip
|
|
text={getTooltip(
|
|
formatName,
|
|
)}
|
|
>
|
|
{formatName}<span
|
|
class="text-red-500"
|
|
>*</span
|
|
>
|
|
</Tooltip>
|
|
{:else}
|
|
{formatName}
|
|
{/if}
|
|
{#if index < s.formats.split(", ").length - 1}
|
|
<span>, </span>
|
|
{/if}
|
|
</span>
|
|
{/each}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</OverlayScrollbarsComponent>
|
|
<!-- bottom blur if scrollable -->
|
|
{#if showBlur[i]}
|
|
<div
|
|
class="absolute left-0 bottom-0 w-full h-10 pointer-events-none"
|
|
style={`background: linear-gradient(to top, var(--bg-panel), transparent 65%);`}
|
|
></div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style lang="postcss">
|
|
.file-category-card {
|
|
@apply bg-panel rounded-2xl p-5 shadow-panel relative;
|
|
}
|
|
|
|
.file-category-card p {
|
|
@apply font-normal text-center text-sm;
|
|
}
|
|
|
|
.file-category-card-inner {
|
|
@apply flex items-center justify-center gap-3 text-xl;
|
|
}
|
|
|
|
.file-category-card-content {
|
|
@apply flex flex-col text-center justify-between;
|
|
}
|
|
|
|
.icon-container {
|
|
@apply p-2 rounded-full text-on-accent;
|
|
}
|
|
</style>
|