mirror of https://github.com/VERT-sh/VERT.git
feat: start how settings is defined
god kill me i fought with paraglide for a while
This commit is contained in:
parent
d54094722f
commit
575f444abc
|
|
@ -83,7 +83,26 @@
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"title": "File conversion settings"
|
"title": "File conversion settings",
|
||||||
|
"image": {
|
||||||
|
"quality": "Quality",
|
||||||
|
"depth": "Color depth",
|
||||||
|
"color_space": "Color space",
|
||||||
|
"transparency": "Transparency",
|
||||||
|
"metadata": "Metadata"
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"quality": "Quality",
|
||||||
|
"rate": "Sample rate",
|
||||||
|
"metadata": "Metadata"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"quality": "Quality",
|
||||||
|
"metadata": "Metadata"
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"metadata": "Metadata"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"unknown_file": "Unknown file type",
|
"unknown_file": "Unknown file type",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
import Modal from "./Modal.svelte";
|
import Modal from "./Modal.svelte";
|
||||||
import Dropdown from "./Dropdown.svelte";
|
import Dropdown from "./Dropdown.svelte";
|
||||||
import FancyInput from "./FancyInput.svelte";
|
import FancyInput from "./FancyInput.svelte";
|
||||||
|
import SettingsModal from "./SettingsModal.svelte";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
categories: Categories;
|
categories: Categories;
|
||||||
|
|
@ -335,9 +336,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
let showSettingsModal = $state(false);
|
let showSettingsModal = $state(false);
|
||||||
|
|
||||||
const settings = () => {
|
const settings = () => {
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
// TODO: temporary - will have individual settings modals for each converter and show those instead
|
|
||||||
showSettingsModal = true;
|
showSettingsModal = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -365,66 +366,8 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showSettingsModal}
|
{#if showSettingsModal && file}
|
||||||
<Modal
|
<SettingsModal {file} onclose={() => (showSettingsModal = false)} />
|
||||||
icon={SearchIcon}
|
|
||||||
title="Conversion Settings"
|
|
||||||
color="purple"
|
|
||||||
buttons={[
|
|
||||||
{
|
|
||||||
text: "Cancel",
|
|
||||||
action: () => (showSettingsModal = false),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Apply",
|
|
||||||
action: () => (showSettingsModal = false),
|
|
||||||
primary: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
onclose={() => (showSettingsModal = false)}
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-8">
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<p class="text-base font-bold">Format Settings</p>
|
|
||||||
<p class="text-sm text-muted font-normal">
|
|
||||||
Configure conversion options for {file?.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<p class="text-sm font-bold">Example dropdown</p>
|
|
||||||
<Dropdown
|
|
||||||
options={categories[currentCategory!]?.formats || []}
|
|
||||||
settingsStyle
|
|
||||||
{selected}
|
|
||||||
onselect={(e) => {
|
|
||||||
console.log("selected format", e);
|
|
||||||
selected = e;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<p class="text-sm font-bold">Resolution</p>
|
|
||||||
<FancyInput
|
|
||||||
type="text"
|
|
||||||
placeholder="1920x1080 or smth"
|
|
||||||
class="rounded-lg bg-button text-foreground p-3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<p class="text-sm font-bold">Frame Rate (FPS)</p>
|
|
||||||
<FancyInput
|
|
||||||
type="number"
|
|
||||||
placeholder="30"
|
|
||||||
class="rounded-lg bg-button text-foreground p-3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -499,7 +442,7 @@
|
||||||
<!-- search box -->
|
<!-- search box -->
|
||||||
<div class="p-3 w-full">
|
<div class="p-3 w-full">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<FancyInput
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={m["convert.dropdown.placeholder"]()}
|
placeholder={m["convert.dropdown.placeholder"]()}
|
||||||
class="flex-grow w-full !pl-11 !pr-3 rounded-lg bg-panel text-foreground"
|
class="flex-grow w-full !pl-11 !pr-3 rounded-lg bg-panel text-foreground"
|
||||||
|
|
@ -577,8 +520,7 @@
|
||||||
{m["convert.archive_file.extract"]()}
|
{m["convert.archive_file.extract"]()}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{:else if file}
|
||||||
{#if file}
|
|
||||||
<div class="border-t border-separator text-base p-2">
|
<div class="border-t border-separator text-base p-2">
|
||||||
<button
|
<button
|
||||||
class="w-full p-2 text-center rounded-lg bg-accent text-black"
|
class="w-full p-2 text-center rounded-lg bg-accent text-black"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { SearchIcon } from "lucide-svelte";
|
||||||
|
import Dropdown from "./Dropdown.svelte";
|
||||||
|
import FancyInput from "./FancyInput.svelte";
|
||||||
|
import Modal from "./Modal.svelte";
|
||||||
|
import { m } from "$lib/paraglide/messages";
|
||||||
|
import type { VertFile } from "$lib/types";
|
||||||
|
import type { SettingDefinition } from "$lib/types/conversion-settings";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
file: VertFile | null;
|
||||||
|
onclose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { file, onclose }: Props = $props();
|
||||||
|
|
||||||
|
const handleSettingChange = (key: string, value: any) => {
|
||||||
|
if (!file) return;
|
||||||
|
file.conversionSettings[key] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const applySettings = () => {
|
||||||
|
onclose?.();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
icon={SearchIcon}
|
||||||
|
title="Conversion Settings"
|
||||||
|
color="purple"
|
||||||
|
buttons={[
|
||||||
|
{
|
||||||
|
text: "Cancel",
|
||||||
|
action: () => onclose?.(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Apply",
|
||||||
|
action: applySettings,
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onclose={() => onclose?.()}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
{#if !file}
|
||||||
|
<p class="text-sm text-muted">No file selected</p>
|
||||||
|
{:else}
|
||||||
|
{@const settings = file.getAvailableSettings()}
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-base font-bold">
|
||||||
|
{m["settings.conversion.title"]?.() ||
|
||||||
|
"Conversion Settings"}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-muted font-normal">
|
||||||
|
{m["settings.conversion.description"]?.() ||
|
||||||
|
`Configure conversion options for ${file.name}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if settings.length === 0}
|
||||||
|
<p class="text-sm text-muted">
|
||||||
|
{m["settings.conversion.no_settings"]?.() ||
|
||||||
|
"No settings available for this converter"}
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
{#each settings as setting (setting.key)}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-bold">
|
||||||
|
{setting.label}
|
||||||
|
</p>
|
||||||
|
<!-- prob unneeded -->
|
||||||
|
{#if setting.description}
|
||||||
|
<p class="text-xs text-muted">
|
||||||
|
{setting.description}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if setting.type === "select"}
|
||||||
|
<Dropdown
|
||||||
|
options={setting.options?.map(
|
||||||
|
(opt) => opt.value,
|
||||||
|
) || []}
|
||||||
|
selected={file.conversionSettings[
|
||||||
|
setting.key
|
||||||
|
] ?? setting.default}
|
||||||
|
settingsStyle
|
||||||
|
onselect={(value) =>
|
||||||
|
handleSettingChange(setting.key, value)}
|
||||||
|
/>
|
||||||
|
{:else if setting.type === "boolean"}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={file.conversionSettings[
|
||||||
|
setting.key
|
||||||
|
] ?? setting.default}
|
||||||
|
onchange={(e) =>
|
||||||
|
handleSettingChange(
|
||||||
|
setting.key,
|
||||||
|
e.currentTarget.checked,
|
||||||
|
)}
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<FancyInput
|
||||||
|
type={setting.type}
|
||||||
|
value={file.conversionSettings[
|
||||||
|
setting.key
|
||||||
|
] ?? setting.default}
|
||||||
|
oninput={(e) =>
|
||||||
|
handleSettingChange(
|
||||||
|
setting.key,
|
||||||
|
e.detail.value,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import type { VertFile } from "$lib/types";
|
import type { VertFile } from "$lib/types";
|
||||||
|
import type {
|
||||||
|
ConversionSettings,
|
||||||
|
SettingDefinition,
|
||||||
|
} from "$lib/types/conversion-settings";
|
||||||
|
|
||||||
export type WorkerStatus = "not-ready" | "downloading" | "ready" | "error";
|
export type WorkerStatus = "not-ready" | "downloading" | "ready" | "error";
|
||||||
|
|
||||||
|
|
@ -44,6 +50,27 @@ export class Converter {
|
||||||
this.startTimeout();
|
this.startTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available settings for this converter.
|
||||||
|
* Can be overridden per converter for format-specific settings.
|
||||||
|
* @param input The input file.
|
||||||
|
*/
|
||||||
|
public getAvailableSettings(input: VertFile): SettingDefinition[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default settings for a conversion.
|
||||||
|
* @param input The input file.
|
||||||
|
*/
|
||||||
|
public getDefaultSettings(input: VertFile): ConversionSettings {
|
||||||
|
const defaults: ConversionSettings = {};
|
||||||
|
this.getAvailableSettings(input).forEach((setting) => {
|
||||||
|
defaults[setting.key] = setting.default;
|
||||||
|
});
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
|
||||||
private startTimeout() {
|
private startTimeout() {
|
||||||
this.timeoutId = setTimeout(() => {
|
this.timeoutId = setTimeout(() => {
|
||||||
if (this.status !== "ready") this.status = "not-ready";
|
if (this.status !== "ready") this.status = "not-ready";
|
||||||
|
|
@ -63,11 +90,9 @@ export class Converter {
|
||||||
* @param to The format to convert to. Includes the dot.
|
* @param to The format to convert to. Includes the dot.
|
||||||
*/
|
*/
|
||||||
public async convert(
|
public async convert(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
input: VertFile,
|
input: VertFile,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
to: string,
|
to: string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
settings?: ConversionSettings,
|
||||||
...args: any[]
|
...args: any[]
|
||||||
): Promise<VertFile> {
|
): Promise<VertFile> {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
|
|
@ -77,7 +102,6 @@ export class Converter {
|
||||||
* Cancel the active conversion of a file.
|
* Cancel the active conversion of a file.
|
||||||
* @param input The input file.
|
* @param input The input file.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
public async cancel(input: VertFile): Promise<void> {
|
public async cancel(input: VertFile): Promise<void> {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ import { imageFormats } from "./magick-automated";
|
||||||
import { Settings } from "$lib/sections/settings/index.svelte";
|
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||||
import magickWasm from "@imagemagick/magick-wasm/magick.wasm?url";
|
import magickWasm from "@imagemagick/magick-wasm/magick.wasm?url";
|
||||||
import { ToastManager } from "$lib/util/toast.svelte";
|
import { ToastManager } from "$lib/util/toast.svelte";
|
||||||
|
import type {
|
||||||
|
SettingDefinition,
|
||||||
|
ConversionSettings,
|
||||||
|
} from "$lib/types/conversion-settings";
|
||||||
|
|
||||||
export class MagickConverter extends Converter {
|
export class MagickConverter extends Converter {
|
||||||
public name = "imagemagick";
|
public name = "imagemagick";
|
||||||
|
|
@ -112,6 +116,81 @@ export class MagickConverter extends Converter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
public getAvailableSettings(input: VertFile): SettingDefinition[] {
|
||||||
|
// images - quality/compression/quantize/interlace/depth-DPI, resize, crop, rotate, flip/flop, autoOrient?, color space/bit depth, transparency settings
|
||||||
|
|
||||||
|
const quality: SettingDefinition = {
|
||||||
|
key: "quality",
|
||||||
|
label: m["convert.settings.image.quality"](),
|
||||||
|
type: "number",
|
||||||
|
default: 100,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
const depth: SettingDefinition = {
|
||||||
|
key: "depth",
|
||||||
|
label: m["convert.settings.image.depth"](),
|
||||||
|
type: "select",
|
||||||
|
default: "auto",
|
||||||
|
// somehow implement custom option
|
||||||
|
options: [
|
||||||
|
{ value: "auto", label: "Auto" },
|
||||||
|
{ value: "8", label: "8-bit" },
|
||||||
|
{ value: "16", label: "16-bit" },
|
||||||
|
{ value: "32", label: "32-bit" },
|
||||||
|
{ value: "custom", label: "Custom" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorSpace: SettingDefinition = {
|
||||||
|
key: "colorSpace",
|
||||||
|
label: m["convert.settings.image.color_space"](),
|
||||||
|
type: "select",
|
||||||
|
default: "auto",
|
||||||
|
options: [
|
||||||
|
// what are these even lmao
|
||||||
|
{ value: "auto", label: "Auto" },
|
||||||
|
{ value: "srgb", label: "sRGB" },
|
||||||
|
{ value: "adobe98", label: "Adobe RGB" },
|
||||||
|
{ value: "prophoto", label: "ProPhoto RGB" },
|
||||||
|
{ value: "displayp3", label: "Display P3" },
|
||||||
|
{ value: "xyz", label: "CIEXYZ" },
|
||||||
|
{ value: "lab", label: "CIELAB" },
|
||||||
|
{ value: "gray", label: "Grayscale" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// allow transparency or not
|
||||||
|
// TODO: disable if jpg/jpeg input/output
|
||||||
|
const transparency: SettingDefinition = {
|
||||||
|
key: "transparency",
|
||||||
|
label: m["convert.settings.image.transparency"](),
|
||||||
|
type: "boolean",
|
||||||
|
default: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const metadata: SettingDefinition = {
|
||||||
|
key: "metadata",
|
||||||
|
label: m["convert.settings.image.metadata"](),
|
||||||
|
type: "boolean",
|
||||||
|
default: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// resize, crop, rotate - prob want a ui
|
||||||
|
|
||||||
|
return [quality, depth, colorSpace, transparency, metadata];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDefaultSettings(input: VertFile): ConversionSettings {
|
||||||
|
const defaults: ConversionSettings = {};
|
||||||
|
this.getAvailableSettings(input).forEach((setting) => {
|
||||||
|
defaults[setting.key] = setting.default;
|
||||||
|
});
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
|
||||||
public async convert(
|
public async convert(
|
||||||
input: VertFile,
|
input: VertFile,
|
||||||
to: string,
|
to: string,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
export type SettingType = "number" | "select" | "boolean" | "string" | "range";
|
||||||
|
|
||||||
|
export interface SettingDefinition {
|
||||||
|
key: string;
|
||||||
|
label: () => string;
|
||||||
|
type: SettingType;
|
||||||
|
default: any;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
options?: Array<{ value: string; label: string }>; // for select types
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConversionSettings {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,10 @@ import { m } from "$lib/paraglide/messages";
|
||||||
import { ToastManager } from "$lib/util/toast.svelte";
|
import { ToastManager } from "$lib/util/toast.svelte";
|
||||||
import type { Component } from "svelte";
|
import type { Component } from "svelte";
|
||||||
import { MAX_ARRAY_BUFFER_SIZE } from "$lib/store/index.svelte";
|
import { MAX_ARRAY_BUFFER_SIZE } from "$lib/store/index.svelte";
|
||||||
|
import type {
|
||||||
|
ConversionSettings,
|
||||||
|
SettingDefinition,
|
||||||
|
} from "./conversion-settings";
|
||||||
|
|
||||||
const MAX_BLOB_SIZE_LIMIT = 2 * 1024 * 1024 * 1024; // 2GB
|
const MAX_BLOB_SIZE_LIMIT = 2 * 1024 * 1024 * 1024; // 2GB
|
||||||
|
|
||||||
|
|
@ -19,6 +23,7 @@ export class VertFile {
|
||||||
return this.file.name;
|
return this.file.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public conversionSettings = $state<ConversionSettings>({});
|
||||||
public progress = $state(0);
|
public progress = $state(0);
|
||||||
public result = $state<VertFile | null>(null);
|
public result = $state<VertFile | null>(null);
|
||||||
|
|
||||||
|
|
@ -34,6 +39,12 @@ export class VertFile {
|
||||||
|
|
||||||
public isZip = $state(() => this.from === ".zip");
|
public isZip = $state(() => this.from === ".zip");
|
||||||
|
|
||||||
|
public getAvailableSettings(): SettingDefinition[] {
|
||||||
|
const converter = this.findConverter();
|
||||||
|
if (!converter) return [];
|
||||||
|
return converter.getAvailableSettings(this);
|
||||||
|
}
|
||||||
|
|
||||||
public findConverters(supportedFormats: string[] = [this.from]) {
|
public findConverters(supportedFormats: string[] = [this.from]) {
|
||||||
const converter = this.converters
|
const converter = this.converters
|
||||||
.filter((converter) =>
|
.filter((converter) =>
|
||||||
|
|
@ -120,7 +131,12 @@ export class VertFile {
|
||||||
// else convert normally
|
// else convert normally
|
||||||
res = this.isZip()
|
res = this.isZip()
|
||||||
? await this.convertZip(converter)
|
? await this.convertZip(converter)
|
||||||
: await converter.convert(this, this.to, ...args);
|
: await converter.convert(
|
||||||
|
this,
|
||||||
|
this.to,
|
||||||
|
this.conversionSettings,
|
||||||
|
...args,
|
||||||
|
);
|
||||||
this.result = res;
|
this.result = res;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!this.cancelled) this.toastErr(err);
|
if (!this.cancelled) this.toastErr(err);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue