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",
|
||||
"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": {
|
||||
"unknown_file": "Unknown file type",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
import Modal from "./Modal.svelte";
|
||||
import Dropdown from "./Dropdown.svelte";
|
||||
import FancyInput from "./FancyInput.svelte";
|
||||
import SettingsModal from "./SettingsModal.svelte";
|
||||
|
||||
type Props = {
|
||||
categories: Categories;
|
||||
|
|
@ -335,9 +336,9 @@
|
|||
};
|
||||
|
||||
let showSettingsModal = $state(false);
|
||||
|
||||
const settings = () => {
|
||||
if (!file) return;
|
||||
// TODO: temporary - will have individual settings modals for each converter and show those instead
|
||||
showSettingsModal = true;
|
||||
};
|
||||
|
||||
|
|
@ -365,66 +366,8 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{#if showSettingsModal}
|
||||
<Modal
|
||||
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 showSettingsModal && file}
|
||||
<SettingsModal {file} onclose={() => (showSettingsModal = false)} />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
|
|
@ -499,7 +442,7 @@
|
|||
<!-- search box -->
|
||||
<div class="p-3 w-full">
|
||||
<div class="relative">
|
||||
<FancyInput
|
||||
<input
|
||||
type="text"
|
||||
placeholder={m["convert.dropdown.placeholder"]()}
|
||||
class="flex-grow w-full !pl-11 !pr-3 rounded-lg bg-panel text-foreground"
|
||||
|
|
@ -577,8 +520,7 @@
|
|||
{m["convert.archive_file.extract"]()}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if file}
|
||||
{:else if file}
|
||||
<div class="border-t border-separator text-base p-2">
|
||||
<button
|
||||
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 {
|
||||
ConversionSettings,
|
||||
SettingDefinition,
|
||||
} from "$lib/types/conversion-settings";
|
||||
|
||||
export type WorkerStatus = "not-ready" | "downloading" | "ready" | "error";
|
||||
|
||||
|
|
@ -44,6 +50,27 @@ export class Converter {
|
|||
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() {
|
||||
this.timeoutId = setTimeout(() => {
|
||||
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.
|
||||
*/
|
||||
public async convert(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
input: VertFile,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
to: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
||||
settings?: ConversionSettings,
|
||||
...args: any[]
|
||||
): Promise<VertFile> {
|
||||
throw new Error("Not implemented");
|
||||
|
|
@ -77,7 +102,6 @@ export class Converter {
|
|||
* Cancel the active conversion of a file.
|
||||
* @param input The input file.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public async cancel(input: VertFile): Promise<void> {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import { imageFormats } from "./magick-automated";
|
|||
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||
import magickWasm from "@imagemagick/magick-wasm/magick.wasm?url";
|
||||
import { ToastManager } from "$lib/util/toast.svelte";
|
||||
import type {
|
||||
SettingDefinition,
|
||||
ConversionSettings,
|
||||
} from "$lib/types/conversion-settings";
|
||||
|
||||
export class MagickConverter extends Converter {
|
||||
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(
|
||||
input: VertFile,
|
||||
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 type { Component } from "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
|
||||
|
||||
|
|
@ -19,6 +23,7 @@ export class VertFile {
|
|||
return this.file.name;
|
||||
}
|
||||
|
||||
public conversionSettings = $state<ConversionSettings>({});
|
||||
public progress = $state(0);
|
||||
public result = $state<VertFile | null>(null);
|
||||
|
||||
|
|
@ -34,6 +39,12 @@ export class VertFile {
|
|||
|
||||
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]) {
|
||||
const converter = this.converters
|
||||
.filter((converter) =>
|
||||
|
|
@ -120,7 +131,12 @@ export class VertFile {
|
|||
// else convert normally
|
||||
res = this.isZip()
|
||||
? await this.convertZip(converter)
|
||||
: await converter.convert(this, this.to, ...args);
|
||||
: await converter.convert(
|
||||
this,
|
||||
this.to,
|
||||
this.conversionSettings,
|
||||
...args,
|
||||
);
|
||||
this.result = res;
|
||||
} catch (err) {
|
||||
if (!this.cancelled) this.toastErr(err);
|
||||
|
|
|
|||
Loading…
Reference in New Issue