mirror of https://github.com/VERT-sh/VERT.git
feat: default conversion format
also improves formatdropdown and thumbnail generation
This commit is contained in:
parent
ab1dd2b507
commit
93faaa4b34
|
|
@ -104,6 +104,12 @@
|
||||||
"filename_format": "File name format",
|
"filename_format": "File name format",
|
||||||
"filename_description": "This will determine the name of the file on download, <b>not including the file extension.</b> You can put these following templates in the format, which will be replaced with the relevant information: <b>%name%</b> for the original file name, <b>%extension%</b> for the original file extension, and <b>%date%</b> for a date string of when the file was converted.",
|
"filename_description": "This will determine the name of the file on download, <b>not including the file extension.</b> You can put these following templates in the format, which will be replaced with the relevant information: <b>%name%</b> for the original file name, <b>%extension%</b> for the original file extension, and <b>%date%</b> for a date string of when the file was converted.",
|
||||||
"placeholder": "VERT_%name%",
|
"placeholder": "VERT_%name%",
|
||||||
|
"default_format": "Default conversion format",
|
||||||
|
"default_format_description": "This will change the default format selected when you upload a file of this file type.",
|
||||||
|
"default_format_image": "Images",
|
||||||
|
"default_format_video": "Videos",
|
||||||
|
"default_format_audio": "Audio",
|
||||||
|
"default_format_document": "Documents",
|
||||||
"metadata": "File metadata",
|
"metadata": "File metadata",
|
||||||
"metadata_description": "This changes whether any metadata (EXIF, song info, etc.) on the original file is preserved in converted files.",
|
"metadata_description": "This changes whether any metadata (EXIF, song info, etc.) on the original file is preserved in converted files.",
|
||||||
"keep": "Keep",
|
"keep": "Keep",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
let searchQuery = $state("");
|
let searchQuery = $state("");
|
||||||
let dropdownMenu: HTMLElement | undefined = $state();
|
let dropdownMenu: HTMLElement | undefined = $state();
|
||||||
let rootCategory: string | null = null;
|
let rootCategory: string | null = null;
|
||||||
|
let dropdownPosition = $state<"left" | "center" | "right">("center");
|
||||||
|
|
||||||
// initialize current category
|
// initialize current category
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|
@ -86,13 +87,15 @@
|
||||||
|
|
||||||
// if no query, return formats for current category
|
// if no query, return formats for current category
|
||||||
if (!searchQuery) {
|
if (!searchQuery) {
|
||||||
|
let formats = currentCategory
|
||||||
|
? categories[currentCategory].formats.filter((format) =>
|
||||||
|
shouldInclude(format, currentCategory!),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
categories: availableCategories,
|
categories: availableCategories,
|
||||||
formats: currentCategory
|
formats,
|
||||||
? categories[currentCategory].formats.filter((format) =>
|
|
||||||
shouldInclude(format, currentCategory!),
|
|
||||||
)
|
|
||||||
: [],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const searchLower = normalize(searchQuery);
|
const searchLower = normalize(searchQuery);
|
||||||
|
|
@ -213,6 +216,34 @@
|
||||||
const clickDropdown = () => {
|
const clickDropdown = () => {
|
||||||
open = !open;
|
open = !open;
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
|
|
||||||
|
// keep within viewport
|
||||||
|
if (dropdown) {
|
||||||
|
const rect = dropdown.getBoundingClientRect();
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
|
||||||
|
let dropdownWidth: number;
|
||||||
|
if (dropdownSize === "large") {
|
||||||
|
dropdownWidth = rect.width * 3.2;
|
||||||
|
} else if (dropdownSize === "default") {
|
||||||
|
dropdownWidth = rect.width * 2.5;
|
||||||
|
} else {
|
||||||
|
dropdownWidth = rect.width * 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
const centerX = rect.left + rect.width / 2;
|
||||||
|
const leftEdge = centerX - dropdownWidth / 2;
|
||||||
|
const rightEdge = centerX + dropdownWidth / 2;
|
||||||
|
|
||||||
|
if (leftEdge < 0) {
|
||||||
|
dropdownPosition = "left";
|
||||||
|
} else if (rightEdge > viewportWidth) {
|
||||||
|
dropdownPosition = "right";
|
||||||
|
} else {
|
||||||
|
dropdownPosition = "center";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!dropdownMenu) return;
|
if (!dropdownMenu) return;
|
||||||
const searchInput = dropdownMenu.querySelector(
|
const searchInput = dropdownMenu.querySelector(
|
||||||
|
|
@ -232,23 +263,20 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("click", handleClickOutside);
|
const handleResize = () => {
|
||||||
return () => window.removeEventListener("click", handleClickOutside);
|
if (open) {
|
||||||
});
|
// recalculate dropdown position on resize
|
||||||
|
clickDropdown();
|
||||||
|
open = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// initialize selected format if none chosen
|
window.addEventListener("click", handleClickOutside);
|
||||||
$effect(() => {
|
window.addEventListener("resize", handleResize);
|
||||||
if (
|
return () => {
|
||||||
!selected &&
|
window.removeEventListener("click", handleClickOutside);
|
||||||
currentCategory &&
|
window.removeEventListener("resize", handleResize);
|
||||||
categories[currentCategory]?.formats?.length > 0
|
};
|
||||||
) {
|
|
||||||
const from = files.files[0]?.from;
|
|
||||||
const firstDiff = categories[currentCategory].formats.find(
|
|
||||||
(f) => f !== from,
|
|
||||||
);
|
|
||||||
selected = firstDiff || categories[currentCategory].formats[0];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -258,7 +286,7 @@
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="relative flex items-center justify-center w-full font-display px-3 py-3.5 bg-button rounded-full overflow-hidden cursor-pointer focus:!outline-none
|
class="relative flex items-center justify-center w-full font-display px-3 py-3.5 bg-button rounded-full overflow-hidden cursor-pointer focus:!outline-none
|
||||||
{disabled ? 'opacity-50 cursor-auto' : 'cursor-pointer'}"
|
{disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
|
||||||
onclick={() => clickDropdown()}
|
onclick={() => clickDropdown()}
|
||||||
{disabled}
|
{disabled}
|
||||||
>
|
>
|
||||||
|
|
@ -278,7 +306,7 @@
|
||||||
}}
|
}}
|
||||||
class="col-start-1 row-start-1 text-center font-body font-medium truncate max-w-[4rem]"
|
class="col-start-1 row-start-1 text-center font-body font-medium truncate max-w-[4rem]"
|
||||||
>
|
>
|
||||||
{selected}
|
{selected || "N/A"}
|
||||||
</p>
|
</p>
|
||||||
{/key}
|
{/key}
|
||||||
{#if currentCategory}
|
{#if currentCategory}
|
||||||
|
|
@ -308,12 +336,17 @@
|
||||||
class={clsx(
|
class={clsx(
|
||||||
$isMobile
|
$isMobile
|
||||||
? "fixed inset-x-0 bottom-0 w-full z-[200] shadow-xl bg-panel-alt shadow-black/25 rounded-t-2xl overflow-hidden"
|
? "fixed inset-x-0 bottom-0 w-full z-[200] shadow-xl bg-panel-alt shadow-black/25 rounded-t-2xl overflow-hidden"
|
||||||
: "min-w-full shadow-xl bg-panel-alt shadow-black/25 absolute -translate-x-1/2 top-full mt-2 z-50 rounded-2xl overflow-hidden",
|
: "min-w-full shadow-xl bg-panel-alt shadow-black/25 absolute top-full mt-2 z-50 rounded-2xl overflow-hidden",
|
||||||
!$isMobile && {
|
!$isMobile && {
|
||||||
"w-[320%]": dropdownSize === "large",
|
"w-[320%]": dropdownSize === "large",
|
||||||
"w-[250%]": dropdownSize === "default",
|
"w-[250%]": dropdownSize === "default",
|
||||||
"w-[150%]": dropdownSize === "small",
|
"w-[150%]": dropdownSize === "small",
|
||||||
},
|
},
|
||||||
|
!$isMobile && {
|
||||||
|
"-translate-x-1/2 left-1/2": dropdownPosition === "center",
|
||||||
|
"left-0": dropdownPosition === "left",
|
||||||
|
"right-0": dropdownPosition === "right",
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<!-- search box -->
|
<!-- search box -->
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@
|
||||||
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
||||||
import FancyInput from "$lib/components/functional/FancyInput.svelte";
|
import FancyInput from "$lib/components/functional/FancyInput.svelte";
|
||||||
import { effects } from "$lib/store/index.svelte";
|
import { effects } from "$lib/store/index.svelte";
|
||||||
|
import FormatDropdown from "$lib/components/functional/FormatDropdown.svelte";
|
||||||
|
import { categories } from "$lib/converters";
|
||||||
|
|
||||||
const { settings }: { settings: ISettings } = $props();
|
const { settings = $bindable() }: { settings: ISettings } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel class="flex flex-col gap-8 p-6">
|
<Panel class="flex flex-col gap-8 p-6">
|
||||||
|
|
@ -44,6 +46,89 @@
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-base font-bold">
|
||||||
|
{m["settings.conversion.default_format"]()}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-muted font-normal">
|
||||||
|
{m["settings.conversion.default_format_description"]()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3 w-full">
|
||||||
|
<div class="flex gap-3 w-full">
|
||||||
|
<button
|
||||||
|
onclick={() => (settings.useDefaultFormat = true)}
|
||||||
|
class="btn {$effects
|
||||||
|
? ''
|
||||||
|
: '!scale-100'} {settings.useDefaultFormat
|
||||||
|
? 'selected'
|
||||||
|
: ''} flex-1 p-4 rounded-lg text-black dynadark:text-white flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<PlayIcon size="24" class="inline-block mr-2" />
|
||||||
|
Enable
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick={() => (settings.useDefaultFormat = false)}
|
||||||
|
class="btn {$effects
|
||||||
|
? ''
|
||||||
|
: '!scale-100'} {settings.useDefaultFormat
|
||||||
|
? ''
|
||||||
|
: 'selected'} flex-1 p-4 rounded-lg text-black dynadark:text-white flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<PauseIcon size="24" class="inline-block mr-2" />
|
||||||
|
Disable
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-3 grid-cols-2 md:grid-cols-4" class:opacity-50={!settings.useDefaultFormat}>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-bold">
|
||||||
|
{m["settings.conversion.default_format_image"]()}
|
||||||
|
</p>
|
||||||
|
<FormatDropdown
|
||||||
|
categories={{image: categories.image}}
|
||||||
|
from={".png"}
|
||||||
|
bind:selected={settings.defaultFormat.image}
|
||||||
|
disabled={!settings.useDefaultFormat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-bold">
|
||||||
|
{m["settings.conversion.default_format_audio"]()}
|
||||||
|
</p>
|
||||||
|
<FormatDropdown
|
||||||
|
categories={{audio: categories.audio}}
|
||||||
|
from={".mp3"}
|
||||||
|
bind:selected={settings.defaultFormat.audio}
|
||||||
|
disabled={!settings.useDefaultFormat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-bold">
|
||||||
|
{m["settings.conversion.default_format_video"]()}
|
||||||
|
</p>
|
||||||
|
<FormatDropdown
|
||||||
|
categories={{video: categories.video}}
|
||||||
|
from={".mp4"}
|
||||||
|
bind:selected={settings.defaultFormat.video}
|
||||||
|
disabled={!settings.useDefaultFormat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-bold">
|
||||||
|
{m["settings.conversion.default_format_document"]()}
|
||||||
|
</p>
|
||||||
|
<FormatDropdown
|
||||||
|
categories={{doc: categories.doc}}
|
||||||
|
from={".docx"}
|
||||||
|
bind:selected={settings.defaultFormat.document}
|
||||||
|
disabled={!settings.useDefaultFormat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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">
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
import { link } from "$lib/store/index.svelte";
|
import { link } from "$lib/store/index.svelte";
|
||||||
|
|
||||||
const { settings }: { settings: ISettings } = $props();
|
const { settings = $bindable() }: { settings: ISettings } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel class="flex flex-col gap-8 p-6">
|
<Panel class="flex flex-col gap-8 p-6">
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
let vertdCommit = $state<string | null>(null);
|
let vertdCommit = $state<string | null>(null);
|
||||||
let abortController: AbortController | null = null;
|
let abortController: AbortController | null = null;
|
||||||
|
|
||||||
const { settings }: { settings: ISettings } = $props();
|
const { settings = $bindable() }: { settings: ISettings } = $props();
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (settings.vertdURL) {
|
if (settings.vertdURL) {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,17 @@ export { default as Vertd } from "./Vertd.svelte";
|
||||||
export { default as Privacy } from "./Privacy.svelte";
|
export { default as Privacy } from "./Privacy.svelte";
|
||||||
|
|
||||||
// TODO: clean up settings & button code (componetize)
|
// TODO: clean up settings & button code (componetize)
|
||||||
|
|
||||||
|
export interface DefaultFormats {
|
||||||
|
image: string;
|
||||||
|
video: string;
|
||||||
|
audio: string;
|
||||||
|
document: string;
|
||||||
|
}
|
||||||
export interface ISettings {
|
export interface ISettings {
|
||||||
filenameFormat: string;
|
filenameFormat: string;
|
||||||
|
defaultFormat: DefaultFormats;
|
||||||
|
useDefaultFormat: boolean;
|
||||||
metadata: boolean;
|
metadata: boolean;
|
||||||
plausible: boolean;
|
plausible: boolean;
|
||||||
vertdURL: string;
|
vertdURL: string;
|
||||||
|
|
@ -25,6 +34,13 @@ export class Settings {
|
||||||
|
|
||||||
public settings: ISettings = $state({
|
public settings: ISettings = $state({
|
||||||
filenameFormat: "VERT_%name%",
|
filenameFormat: "VERT_%name%",
|
||||||
|
defaultFormat: {
|
||||||
|
image: ".png",
|
||||||
|
video: ".mp4",
|
||||||
|
audio: ".mp3",
|
||||||
|
document: ".docx",
|
||||||
|
},
|
||||||
|
useDefaultFormat: false,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
plausible: true,
|
plausible: true,
|
||||||
vertdURL: PUB_VERTD_URL,
|
vertdURL: PUB_VERTD_URL,
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,10 @@ class Files {
|
||||||
});
|
});
|
||||||
const cover = selectCover(common.picture);
|
const cover = selectCover(common.picture);
|
||||||
if (cover) {
|
if (cover) {
|
||||||
const arrayBuffer = cover.data.buffer instanceof ArrayBuffer
|
const arrayBuffer =
|
||||||
? cover.data.buffer
|
cover.data.buffer instanceof ArrayBuffer
|
||||||
: new Uint8Array(cover.data).buffer;
|
? cover.data.buffer
|
||||||
|
: new Uint8Array(cover.data).buffer;
|
||||||
const blob = new Blob([new Uint8Array(arrayBuffer)], {
|
const blob = new Blob([new Uint8Array(arrayBuffer)], {
|
||||||
type: cover.format,
|
type: cover.format,
|
||||||
});
|
});
|
||||||
|
|
@ -114,15 +115,23 @@ class Files {
|
||||||
? (mediaElement as HTMLVideoElement).videoHeight
|
? (mediaElement as HTMLVideoElement).videoHeight
|
||||||
: (mediaElement as HTMLImageElement).height;
|
: (mediaElement as HTMLImageElement).height;
|
||||||
|
|
||||||
if (width === 0 || height === 0) {
|
|
||||||
URL.revokeObjectURL(mediaElement.src);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scale = Math.max(maxSize / width, maxSize / height);
|
const scale = Math.max(maxSize / width, maxSize / height);
|
||||||
canvas.width = width * scale;
|
canvas.width = width * scale;
|
||||||
canvas.height = height * scale;
|
canvas.height = height * scale;
|
||||||
ctx.drawImage(mediaElement, 0, 0, canvas.width, canvas.height);
|
ctx.drawImage(mediaElement, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// check if completely transparent
|
||||||
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
const isTransparent = Array.from(imageData.data).every(
|
||||||
|
(value, index) => {
|
||||||
|
return (index + 1) % 4 !== 0 || value === 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (isTransparent) {
|
||||||
|
canvas.remove();
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const url = canvas.toDataURL();
|
const url = canvas.toDataURL();
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
return url;
|
return url;
|
||||||
|
|
@ -313,9 +322,9 @@ export const effects = writable(true);
|
||||||
export const theme = writable<"light" | "dark">("light");
|
export const theme = writable<"light" | "dark">("light");
|
||||||
export const locale = writable(getLocale());
|
export const locale = writable(getLocale());
|
||||||
export const availableLocales = {
|
export const availableLocales = {
|
||||||
"en": "English",
|
en: "English",
|
||||||
"es": "Español",
|
es: "Español",
|
||||||
}
|
};
|
||||||
|
|
||||||
export function updateLocale(newLocale: string) {
|
export function updateLocale(newLocale: string) {
|
||||||
log(["locale"], `set to ${newLocale}`);
|
log(["locale"], `set to ${newLocale}`);
|
||||||
|
|
@ -331,7 +340,7 @@ export function link(
|
||||||
text: string,
|
text: string,
|
||||||
links: string | string[],
|
links: string | string[],
|
||||||
newTab?: boolean | boolean[],
|
newTab?: boolean | boolean[],
|
||||||
className?: string | string[]
|
className?: string | string[],
|
||||||
) {
|
) {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
|
|
||||||
|
|
@ -344,12 +353,15 @@ export function link(
|
||||||
|
|
||||||
tags.forEach((t, i) => {
|
tags.forEach((t, i) => {
|
||||||
const link = linksArr[i] ?? "#";
|
const link = linksArr[i] ?? "#";
|
||||||
const target = newTabArr[i] ? 'target="_blank" rel="noopener noreferrer"' : "";
|
const target = newTabArr[i]
|
||||||
|
? 'target="_blank" rel="noopener noreferrer"'
|
||||||
|
: "";
|
||||||
const cls = classArr[i] ? `class="${classArr[i]}"` : "";
|
const cls = classArr[i] ? `class="${classArr[i]}"` : "";
|
||||||
|
|
||||||
const regex = new RegExp(`\\[${t}\\](.*?)\\[\\/${t}\\]`, "g");
|
const regex = new RegExp(`\\[${t}\\](.*?)\\[\\/${t}\\]`, "g");
|
||||||
result = result.replace(regex, (_, inner) =>
|
result = result.replace(
|
||||||
`<a href="${link}" ${target} ${cls} >${inner}</a>`
|
regex,
|
||||||
|
(_, inner) => `<a href="${link}" ${target} ${cls} >${inner}</a>`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,21 +27,79 @@
|
||||||
RotateCwIcon,
|
RotateCwIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "lucide-svelte";
|
} from "lucide-svelte";
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
|
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||||
|
|
||||||
|
let processedFileIds = $state(new Set<string>());
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!Settings.instance.settings || files.files.length === 0) return;
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// depending on format, select right category and format
|
|
||||||
files.files.forEach((file) => {
|
files.files.forEach((file) => {
|
||||||
|
const settings = Settings.instance.settings;
|
||||||
|
if (processedFileIds.has(file.id)) return;
|
||||||
|
|
||||||
const converter = file.findConverter();
|
const converter = file.findConverter();
|
||||||
if (converter) {
|
if (!converter) return;
|
||||||
const category = Object.keys(categories).find((cat) =>
|
|
||||||
categories[cat].formats.includes(file.to),
|
let category: string | undefined;
|
||||||
);
|
const isImage = converters
|
||||||
if (category) {
|
.find((c) => c.name === "imagemagick")
|
||||||
file.to = file.to || categories[category].formats[0];
|
?.formatStrings((f) => f.fromSupported)
|
||||||
|
.includes(file.from);
|
||||||
|
const isAudio = converters
|
||||||
|
.find((c) => c.name === "ffmpeg")
|
||||||
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
|
.includes(file.from);
|
||||||
|
const isVideo = converters
|
||||||
|
.find((c) => c.name === "vertd")
|
||||||
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
|
.includes(file.from);
|
||||||
|
const isDocument = converters
|
||||||
|
.find((c) => c.name === "pandoc")
|
||||||
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
|
.includes(file.from);
|
||||||
|
|
||||||
|
if (isImage) category = "image";
|
||||||
|
else if (isAudio) category = "audio";
|
||||||
|
else if (isVideo) category = "video";
|
||||||
|
else if (isDocument) category = "doc";
|
||||||
|
if (!category) return;
|
||||||
|
|
||||||
|
let targetFormat: string | undefined;
|
||||||
|
|
||||||
|
// use default format if enabled
|
||||||
|
if (settings.useDefaultFormat) {
|
||||||
|
let defaultFormat: string | undefined;
|
||||||
|
const df = settings.defaultFormat;
|
||||||
|
if (category === "image") defaultFormat = df.image;
|
||||||
|
else if (category === "audio") defaultFormat = df.audio;
|
||||||
|
else if (category === "video") defaultFormat = df.video;
|
||||||
|
else if (category === "doc") defaultFormat = df.document;
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultFormat &&
|
||||||
|
defaultFormat !== file.from &&
|
||||||
|
categories[category]?.formats.includes(defaultFormat)
|
||||||
|
) {
|
||||||
|
targetFormat = defaultFormat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// else use first available format (or if default format is same as input)
|
||||||
|
if (!targetFormat) {
|
||||||
|
const firstDiff = categories[category]?.formats.find(
|
||||||
|
(f) => f !== file.from,
|
||||||
|
);
|
||||||
|
targetFormat =
|
||||||
|
firstDiff || categories[category]?.formats[0] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
file.to = targetFormat;
|
||||||
|
processedFileIds.add(file.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -132,23 +190,38 @@
|
||||||
<Panel class="p-5 flex flex-col min-w-0 gap-4 relative">
|
<Panel class="p-5 flex flex-col min-w-0 gap-4 relative">
|
||||||
<div class="flex-shrink-0 h-8 w-full flex items-center gap-2">
|
<div class="flex-shrink-0 h-8 w-full flex items-center gap-2">
|
||||||
{#if !converters.length}
|
{#if !converters.length}
|
||||||
<Tooltip text={m["convert.tooltips.unknown_file"]()} position="bottom">
|
<Tooltip
|
||||||
|
text={m["convert.tooltips.unknown_file"]()}
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
<FileQuestionIcon size="24" class="flex-shrink-0" />
|
<FileQuestionIcon size="24" class="flex-shrink-0" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else if isAudio}
|
{:else if isAudio}
|
||||||
<Tooltip text={m["convert.tooltips.audio_file"]()} position="bottom">
|
<Tooltip
|
||||||
|
text={m["convert.tooltips.audio_file"]()}
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
<AudioLines size="24" class="flex-shrink-0" />
|
<AudioLines size="24" class="flex-shrink-0" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else if isVideo}
|
{:else if isVideo}
|
||||||
<Tooltip text={m["convert.tooltips.video_file"]()} position="bottom">
|
<Tooltip
|
||||||
|
text={m["convert.tooltips.video_file"]()}
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
<FilmIcon size="24" class="flex-shrink-0" />
|
<FilmIcon size="24" class="flex-shrink-0" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else if isDocument}
|
{:else if isDocument}
|
||||||
<Tooltip text={m["convert.tooltips.document_file"]()} position="bottom">
|
<Tooltip
|
||||||
|
text={m["convert.tooltips.document_file"]()}
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
<BookText size="24" class="flex-shrink-0" />
|
<BookText size="24" class="flex-shrink-0" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else}
|
{:else}
|
||||||
<Tooltip text={m["convert.tooltips.image_file"]()} position="bottom">
|
<Tooltip
|
||||||
|
text={m["convert.tooltips.image_file"]()}
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
<ImageIcon size="24" class="flex-shrink-0" />
|
<ImageIcon size="24" class="flex-shrink-0" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -206,7 +279,9 @@
|
||||||
<div
|
<div
|
||||||
class="h-full flex flex-col text-center justify-center text-failure"
|
class="h-full flex flex-col text-center justify-center text-failure"
|
||||||
>
|
>
|
||||||
<p class="font-body font-bold">{m["convert.errors.cant_convert"]()}</p>
|
<p class="font-body font-bold">
|
||||||
|
{m["convert.errors.cant_convert"]()}
|
||||||
|
</p>
|
||||||
<p class="font-normal">
|
<p class="font-normal">
|
||||||
{m["convert.errors.worker_downloading"]({
|
{m["convert.errors.worker_downloading"]({
|
||||||
type: isAudio
|
type: isAudio
|
||||||
|
|
@ -215,7 +290,7 @@
|
||||||
? "Video"
|
? "Video"
|
||||||
: isDocument
|
: isDocument
|
||||||
? m["convert.errors.doc"]()
|
? m["convert.errors.doc"]()
|
||||||
: m["convert.errors.image"]()
|
: m["convert.errors.image"](),
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -223,7 +298,9 @@
|
||||||
<div
|
<div
|
||||||
class="h-full flex flex-col text-center justify-center text-failure"
|
class="h-full flex flex-col text-center justify-center text-failure"
|
||||||
>
|
>
|
||||||
<p class="font-body font-bold">{m["convert.errors.cant_convert"]()}</p>
|
<p class="font-body font-bold">
|
||||||
|
{m["convert.errors.cant_convert"]()}
|
||||||
|
</p>
|
||||||
<p class="font-normal">
|
<p class="font-normal">
|
||||||
{m["convert.errors.worker_error"]({
|
{m["convert.errors.worker_error"]({
|
||||||
type: isAudio
|
type: isAudio
|
||||||
|
|
@ -232,7 +309,7 @@
|
||||||
? "Video"
|
? "Video"
|
||||||
: isDocument
|
: isDocument
|
||||||
? m["convert.errors.doc"]()
|
? m["convert.errors.doc"]()
|
||||||
: m["convert.errors.image"]()
|
: m["convert.errors.image"](),
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -240,7 +317,9 @@
|
||||||
<div
|
<div
|
||||||
class="h-full flex flex-col text-center justify-center text-failure"
|
class="h-full flex flex-col text-center justify-center text-failure"
|
||||||
>
|
>
|
||||||
<p class="font-body font-bold">{m["convert.errors.cant_convert"]()}</p>
|
<p class="font-body font-bold">
|
||||||
|
{m["convert.errors.cant_convert"]()}
|
||||||
|
</p>
|
||||||
<p class="font-normal">
|
<p class="font-normal">
|
||||||
{m["convert.errors.worker_timeout"]({
|
{m["convert.errors.worker_timeout"]({
|
||||||
type: isAudio
|
type: isAudio
|
||||||
|
|
@ -249,7 +328,7 @@
|
||||||
? "Video"
|
? "Video"
|
||||||
: isDocument
|
: isDocument
|
||||||
? m["convert.errors.doc"]()
|
? m["convert.errors.doc"]()
|
||||||
: m["convert.errors.image"]()
|
: m["convert.errors.image"](),
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -257,7 +336,9 @@
|
||||||
<div
|
<div
|
||||||
class="h-full flex flex-col text-center justify-center text-failure"
|
class="h-full flex flex-col text-center justify-center text-failure"
|
||||||
>
|
>
|
||||||
<p class="font-body font-bold">{m["convert.errors.cant_convert"]()}</p>
|
<p class="font-body font-bold">
|
||||||
|
{m["convert.errors.cant_convert"]()}
|
||||||
|
</p>
|
||||||
<p class="font-normal">
|
<p class="font-normal">
|
||||||
{m["convert.errors.vertd_not_found"]()}
|
{m["convert.errors.vertd_not_found"]()}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -311,7 +392,10 @@
|
||||||
onselect={(option) => handleSelect(option, file)}
|
onselect={(option) => handleSelect(option, file)}
|
||||||
/>
|
/>
|
||||||
<div class="w-full flex items-center justify-between">
|
<div class="w-full flex items-center justify-between">
|
||||||
<Tooltip text={m["convert.tooltips.convert_file"]()} position="bottom">
|
<Tooltip
|
||||||
|
text={m["convert.tooltips.convert_file"]()}
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="btn {$effects
|
class="btn {$effects
|
||||||
? ''
|
? ''
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,14 @@
|
||||||
class="w-full max-w-[1280px] flex flex-col md:flex-row gap-4 p-4 md:px-4 md:py-0"
|
class="w-full max-w-[1280px] flex flex-col md:flex-row gap-4 p-4 md:px-4 md:py-0"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4 flex-1">
|
<div class="flex flex-col gap-4 flex-1">
|
||||||
<Settings.Conversion {settings} />
|
<Settings.Conversion bind:settings />
|
||||||
<Settings.Vertd {settings} />
|
<Settings.Vertd bind:settings />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 flex-1">
|
<div class="flex flex-col gap-4 flex-1">
|
||||||
<Settings.Appearance />
|
<Settings.Appearance />
|
||||||
{#if PUB_PLAUSIBLE_URL}
|
{#if PUB_PLAUSIBLE_URL}
|
||||||
<Settings.Privacy {settings} />
|
<Settings.Privacy bind:settings />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue