mirror of https://github.com/VERT-sh/VERT.git
feat: start vertd settings
removed the settings auto detection (since it made UX a bit worse), added conversion speed slider - need to implement in vertd
This commit is contained in:
parent
f129682474
commit
892db30480
|
|
@ -84,7 +84,7 @@
|
|||
"settings": {
|
||||
"settings": "Settings",
|
||||
"title": "File conversion settings",
|
||||
"description": "Change the conversion settings for <b>{filename}</b>, which is using <b>{converter}</b>. These settings may not be available for all formats.",
|
||||
"description": "Change the conversion settings for <b>{filename}</b>, which is using <b>{converter}</b>. These settings may not be available for all formats. This is an early beta and may have some issues.",
|
||||
"none": "No settings available for this format.",
|
||||
"image": {
|
||||
"quality": "Quality",
|
||||
|
|
@ -94,30 +94,42 @@
|
|||
},
|
||||
"audio": {
|
||||
"bitrate": "Bitrate (kbps)",
|
||||
"bitrate_placeholder": "Custom bitrate",
|
||||
"sample_rate": "Sample rate (Hz)",
|
||||
"sample_rate_placeholder": "Custom sample rate",
|
||||
"channels": "Audio channels",
|
||||
"tracks": "Audio tracks"
|
||||
"channels_placeholder": "Custom audio channels",
|
||||
"tracks": "Audio tracks",
|
||||
"tracks_placeholder": "Custom audio tracks"
|
||||
},
|
||||
"video": {
|
||||
"quality": "Quality",
|
||||
"metadata": "Metadata",
|
||||
"speed": "Conversion speed",
|
||||
"speed_very_slow": "Very Slow",
|
||||
"speed_slower": "Slower",
|
||||
"speed_slow": "Slow",
|
||||
"speed_medium": "Medium",
|
||||
"speed_fast": "Fast",
|
||||
"speed_ultra_fast": "Ultra Fast",
|
||||
"speed_description": "This will be overridden if you manually set the bitrate or resolution below - selecting options other than \"auto\".",
|
||||
"speed_very_slow": "Highest quality (slowest)",
|
||||
"speed_slower": "Higher quality (slower)",
|
||||
"speed_slow": "High quality (slow)",
|
||||
"speed_medium": "Medium quality (average)",
|
||||
"speed_fast": "Lower quality (faster)",
|
||||
"speed_ultra_fast": "Lowest quality (fastest)",
|
||||
"fps": "Frame rate (FPS)",
|
||||
"fps_placeholder": "Auto",
|
||||
"fps_placeholder": "Custom frame rate",
|
||||
"resolution": "Resolution",
|
||||
"resolution_placeholder": "Auto (e.g., 1920x1080)"
|
||||
"resolution_placeholder": "Custom resolution",
|
||||
"video_bitrate": "Video bitrate (kbps)",
|
||||
"audio_bitrate": "Audio bitrate (kbps)",
|
||||
"bitrate_placeholder": "Custom bitrate",
|
||||
"sample_rate": "Audio sample rate (Hz)",
|
||||
"sample_rate_placeholder": "Custom sample rate"
|
||||
},
|
||||
"document": {
|
||||
"something": "Something"
|
||||
},
|
||||
"common": {
|
||||
"metadata": "Metadata"
|
||||
"metadata": "Metadata",
|
||||
"auto": "auto",
|
||||
"custom": "custom"
|
||||
}
|
||||
},
|
||||
"tooltips": {
|
||||
|
|
|
|||
|
|
@ -86,13 +86,17 @@
|
|||
{:else}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{#each availableSettings as setting (setting.key)}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
class={setting.forceFullWidth
|
||||
? "col-span-2"
|
||||
: "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">
|
||||
<p class="text-xs text-muted mt-1">
|
||||
{setting.description}
|
||||
</p>
|
||||
{/if}
|
||||
|
|
@ -102,9 +106,11 @@
|
|||
options={setting.options?.map(
|
||||
(opt) => opt.value,
|
||||
) || []}
|
||||
selected={file.conversionSettings[
|
||||
setting.key
|
||||
] ?? setting.default}
|
||||
selected={settings[setting.key] ??
|
||||
file.conversionSettings[
|
||||
setting.key
|
||||
] ??
|
||||
setting.default}
|
||||
settingsStyle
|
||||
onselect={(value) =>
|
||||
handleSettingChange(
|
||||
|
|
@ -139,9 +145,11 @@
|
|||
{:else if setting.type === "boolean"}
|
||||
<FancyInput
|
||||
type="checkbox"
|
||||
checked={file.conversionSettings[
|
||||
setting.key
|
||||
] ?? setting.default}
|
||||
checked={settings[setting.key] ??
|
||||
file.conversionSettings[
|
||||
setting.key
|
||||
] ??
|
||||
setting.default}
|
||||
placeholder={setting.placeholder}
|
||||
onchange={(e) =>
|
||||
handleSettingChange(
|
||||
|
|
@ -149,12 +157,51 @@
|
|||
e.currentTarget.checked,
|
||||
)}
|
||||
/>
|
||||
{:else if setting.type === "range"}
|
||||
{@const rangeValue = (settings[
|
||||
setting.key
|
||||
] ??
|
||||
file.conversionSettings[
|
||||
setting.key
|
||||
] ??
|
||||
setting.default ??
|
||||
setting.min ??
|
||||
0) as number}
|
||||
{@const rangeLabel =
|
||||
setting.options?.[rangeValue]
|
||||
?.label ?? rangeValue}
|
||||
<div class="flex items-center mt-2 gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min={setting.min}
|
||||
max={setting.max}
|
||||
step={setting.step}
|
||||
value={rangeValue}
|
||||
class="range-slider w-full"
|
||||
oninput={(e) => {
|
||||
const nextValue =
|
||||
e.currentTarget
|
||||
.valueAsNumber;
|
||||
handleSettingChange(
|
||||
setting.key,
|
||||
nextValue,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
class="text-sm max-w-28 w-full text-right"
|
||||
>
|
||||
{rangeLabel}
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<FancyInput
|
||||
type={setting.type}
|
||||
value={file.conversionSettings[
|
||||
setting.key
|
||||
] ?? setting.default}
|
||||
value={settings[setting.key] ??
|
||||
file.conversionSettings[
|
||||
setting.key
|
||||
] ??
|
||||
setting.default}
|
||||
placeholder={setting.placeholder}
|
||||
oninput={(e) =>
|
||||
handleSettingChange(
|
||||
|
|
|
|||
|
|
@ -109,21 +109,11 @@ export class FFmpegConverter extends Converter {
|
|||
}
|
||||
}
|
||||
|
||||
public async getAvailableSettings(
|
||||
input: VertFile,
|
||||
): Promise<SettingDefinition[]> {
|
||||
public async getAvailableSettings(): Promise<SettingDefinition[]> {
|
||||
// audio - bitrate, sample rate, channels, normalize, trim silence
|
||||
|
||||
const global = Settings.instance.settings;
|
||||
|
||||
const ffmpeg = await this.setupFFmpeg(input, true);
|
||||
const buf = new Uint8Array(await input.file.arrayBuffer());
|
||||
await ffmpeg.writeFile("input", buf);
|
||||
|
||||
// TODO: should we really be doing all this detection here? it adds a lot of time before the settings even show up.
|
||||
// which isn't very nice for the UX guh
|
||||
|
||||
const detectedBitrate = await this.detectAudioBitrate(ffmpeg);
|
||||
const bitrate: SettingDefinition = {
|
||||
key: "bitrate",
|
||||
label: m["convert.settings.audio.bitrate"](),
|
||||
|
|
@ -135,10 +125,9 @@ export class FFmpegConverter extends Converter {
|
|||
})),
|
||||
hasCustomInput: true,
|
||||
customInputKey: "customBitrate",
|
||||
placeholder: detectedBitrate ?? "128"
|
||||
placeholder: m["convert.settings.audio.bitrate_placeholder"](),
|
||||
};
|
||||
|
||||
const detectedSampleRate = await this.detectAudioSampleRate(ffmpeg);
|
||||
const sampleRate: SettingDefinition = {
|
||||
key: "sampleRate",
|
||||
label: m["convert.settings.audio.sample_rate"](),
|
||||
|
|
@ -153,31 +142,31 @@ export class FFmpegConverter extends Converter {
|
|||
})),
|
||||
hasCustomInput: true,
|
||||
customInputKey: "customSampleRate",
|
||||
placeholder: detectedSampleRate ?? "44100"
|
||||
placeholder: m["convert.settings.audio.sample_rate_placeholder"](),
|
||||
};
|
||||
|
||||
const audioTracks = await this.detectAudioTracks(ffmpeg);
|
||||
const tracks: SettingDefinition = {
|
||||
key: "tracks",
|
||||
label: m["convert.settings.audio.tracks"](),
|
||||
type: "number",
|
||||
default: audioTracks ?? 1,
|
||||
default: 1,
|
||||
min: 1,
|
||||
max: audioTracks ? audioTracks : 1,
|
||||
placeholder: audioTracks ?? 1
|
||||
placeholder: m["convert.settings.audio.tracks_placeholder"](),
|
||||
};
|
||||
|
||||
const audioChannels = await this.detectAudioChannels(ffmpeg);
|
||||
const channels: SettingDefinition = {
|
||||
key: "channels",
|
||||
label: m["convert.settings.audio.channels"](),
|
||||
type: "number",
|
||||
default: audioChannels ?? 2,
|
||||
default: 2,
|
||||
min: 1,
|
||||
max: audioChannels ? audioChannels * 2 : 5,
|
||||
placeholder: audioChannels ?? 2
|
||||
max: 8,
|
||||
placeholder: m["convert.settings.audio.channels_placeholder"](),
|
||||
};
|
||||
|
||||
/*
|
||||
* common
|
||||
*/
|
||||
const metadata: SettingDefinition = {
|
||||
key: "metadata",
|
||||
label: m["convert.settings.common.metadata"](),
|
||||
|
|
@ -426,100 +415,6 @@ export class FFmpegConverter extends Converter {
|
|||
}
|
||||
}
|
||||
|
||||
private async detectAudioTracks(ffmpeg: FFmpeg): Promise<number | null> {
|
||||
const args = [
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"a",
|
||||
"-show_entries",
|
||||
"stream=index",
|
||||
"-of",
|
||||
"json",
|
||||
"input",
|
||||
];
|
||||
|
||||
try {
|
||||
let output = "";
|
||||
|
||||
const tracksListener = (event: { message: string }) => {
|
||||
output += `${event.message}\n`;
|
||||
};
|
||||
|
||||
ffmpeg.on("log", tracksListener);
|
||||
|
||||
try {
|
||||
log(
|
||||
["converters", this.name],
|
||||
`Running ffprobe to detect audio tracks with args: ${args.join(" ")}`,
|
||||
);
|
||||
await ffmpeg.ffprobe.call(ffmpeg, args);
|
||||
} finally {
|
||||
ffmpeg.off("log", tracksListener);
|
||||
}
|
||||
|
||||
if (!output.trim()) return null;
|
||||
|
||||
const parsed = JSON.parse(output);
|
||||
const tracks = Array.isArray(parsed?.streams)
|
||||
? parsed.streams.length
|
||||
: null;
|
||||
|
||||
log(
|
||||
["converters", this.name],
|
||||
`Detected stream audio tracks: ${tracks}`,
|
||||
);
|
||||
|
||||
return tracks;
|
||||
} catch (err) {
|
||||
error(
|
||||
["converters", this.name],
|
||||
`Error detecting audio tracks: ${err}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async detectAudioChannels(ffmpeg: FFmpeg): Promise<number | null> {
|
||||
const args = [
|
||||
"-v",
|
||||
"0",
|
||||
"-select_streams",
|
||||
"a",
|
||||
"-show_entries",
|
||||
"stream=channels",
|
||||
"-of",
|
||||
"compact=p=0:nk=1",
|
||||
"input",
|
||||
];
|
||||
|
||||
try {
|
||||
let channels: number | null = null;
|
||||
|
||||
const channelsListener = (event: { message: string }) => {
|
||||
if (channels !== null) return;
|
||||
const n = parseInt(event.message.trim(), 10);
|
||||
if (!n) return;
|
||||
channels = n;
|
||||
log(
|
||||
["converters", this.name],
|
||||
`Detected stream audio channels: ${channels}`,
|
||||
);
|
||||
};
|
||||
|
||||
ffmpeg.on("log", channelsListener);
|
||||
|
||||
try {
|
||||
await ffmpeg.ffprobe.call(ffmpeg, args);
|
||||
return channels;
|
||||
} finally {
|
||||
ffmpeg.off("log", channelsListener);
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async buildConversionCommand(
|
||||
ffmpeg: FFmpeg,
|
||||
input: VertFile,
|
||||
|
|
@ -962,12 +857,12 @@ export type ConversionBitrate = (typeof CONVERSION_BITRATES)[number];
|
|||
export const SAMPLE_RATES = [
|
||||
"auto",
|
||||
"custom",
|
||||
"48000",
|
||||
"44100",
|
||||
"32000",
|
||||
"22050",
|
||||
"16000",
|
||||
"11025",
|
||||
"8000",
|
||||
48000,
|
||||
44100,
|
||||
32000,
|
||||
22050,
|
||||
16000,
|
||||
11025,
|
||||
8000,
|
||||
] as const;
|
||||
export type SampleRate = (typeof SAMPLE_RATES)[number];
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type {
|
|||
SettingDefinition,
|
||||
ConversionSettings,
|
||||
} from "$lib/types/conversion-settings";
|
||||
import { CONVERSION_BITRATES, SAMPLE_RATES } from "./ffmpeg.svelte";
|
||||
|
||||
interface UploadResponse {
|
||||
id: string;
|
||||
|
|
@ -85,6 +86,15 @@ export type ConversionSpeed =
|
|||
| "fast"
|
||||
| "ultraFast";
|
||||
|
||||
const vertdSpeedValues: ConversionSpeed[] = [
|
||||
"verySlow",
|
||||
"slower",
|
||||
"slow",
|
||||
"medium",
|
||||
"fast",
|
||||
"ultraFast",
|
||||
];
|
||||
|
||||
interface StartJobMessage {
|
||||
type: "startJob";
|
||||
data: {
|
||||
|
|
@ -375,30 +385,128 @@ export class VertdConverter extends Converter {
|
|||
},
|
||||
];
|
||||
|
||||
const quality: SettingDefinition = {
|
||||
key: "vertdSpeed",
|
||||
const qualitySpeedRange: SettingDefinition = {
|
||||
key: "vertdSpeedSlider",
|
||||
label: m["convert.settings.video.speed"](),
|
||||
type: "select",
|
||||
default: "medium",
|
||||
options: qualityOptions,
|
||||
description: m["convert.settings.video.speed_description"](),
|
||||
type: "range",
|
||||
min: 0,
|
||||
max: qualityOptions.length - 1,
|
||||
step: 1,
|
||||
default: 3,
|
||||
options: qualityOptions.map((option, index) => ({
|
||||
value: index,
|
||||
label: option.label,
|
||||
speedValue: option.value,
|
||||
})),
|
||||
forceFullWidth: true,
|
||||
};
|
||||
|
||||
// TODO: for fps and resolution, set placeholder to detected values
|
||||
const fps: SettingDefinition = {
|
||||
key: "fps",
|
||||
label: m["convert.settings.video.fps"](),
|
||||
type: "select",
|
||||
default: "auto",
|
||||
options: [
|
||||
{ value: "auto", label: m["convert.settings.common.auto"]() },
|
||||
{
|
||||
value: "custom",
|
||||
label: m["convert.settings.common.custom"](),
|
||||
},
|
||||
{ value: 24, label: "24" },
|
||||
{ value: 30, label: "30" },
|
||||
{ value: 60, label: "60" },
|
||||
{ value: 120, label: "120" },
|
||||
{ value: 144, label: "144" },
|
||||
{ value: 240, label: "240" },
|
||||
],
|
||||
hasCustomInput: true,
|
||||
customInputKey: "customFps",
|
||||
placeholder: m["convert.settings.video.fps_placeholder"](),
|
||||
type: "number",
|
||||
min: 1,
|
||||
};
|
||||
|
||||
const resolution: SettingDefinition = {
|
||||
key: "resolution",
|
||||
label: m["convert.settings.video.resolution"](),
|
||||
type: "select",
|
||||
default: "auto",
|
||||
options: [
|
||||
{ value: "auto", label: m["convert.settings.common.auto"]() },
|
||||
{
|
||||
value: "custom",
|
||||
label: m["convert.settings.common.custom"](),
|
||||
},
|
||||
{ value: "426x240", label: "426x240" },
|
||||
{ value: "640x360", label: "640x360" },
|
||||
{ value: "854x480", label: "854x480" },
|
||||
{ value: "1280x720", label: "1280x720" },
|
||||
{ value: "1920x1080", label: "1920x1080" },
|
||||
{ value: "2560x1440", label: "2560x1440" },
|
||||
{ value: "3840x2160", label: "3840x2160" },
|
||||
],
|
||||
hasCustomInput: true,
|
||||
customInputKey: "customResolution",
|
||||
placeholder: m["convert.settings.video.resolution_placeholder"](),
|
||||
type: "string",
|
||||
};
|
||||
|
||||
// TODO: allow CRF for consistent quality?
|
||||
const videoBitrate: SettingDefinition = {
|
||||
key: "videoBitrate",
|
||||
label: m["convert.settings.video.video_bitrate"](),
|
||||
type: "select",
|
||||
default: "auto",
|
||||
options: [
|
||||
{ value: "auto", label: m["convert.settings.common.auto"]() },
|
||||
{
|
||||
value: "custom",
|
||||
label: m["convert.settings.common.custom"](),
|
||||
},
|
||||
{ value: 1000, label: "1000 kbps" },
|
||||
{ value: 2500, label: "2500 kbps" },
|
||||
{ value: 5000, label: "5000 kbps" },
|
||||
{ value: 8000, label: "8000 kbps" },
|
||||
{ value: 12000, label: "12000 kbps" },
|
||||
{ value: 18000, label: "18000 kbps" },
|
||||
],
|
||||
hasCustomInput: true,
|
||||
customInputKey: "customBitrate",
|
||||
placeholder: m["convert.settings.video.bitrate_placeholder"](),
|
||||
};
|
||||
|
||||
/*
|
||||
* audio settings
|
||||
*/
|
||||
const audioBitrate: SettingDefinition = {
|
||||
key: "audioBitrate",
|
||||
label: m["convert.settings.video.audio_bitrate"](),
|
||||
type: "select",
|
||||
default: "auto",
|
||||
options: CONVERSION_BITRATES.map((b) => ({
|
||||
value: b,
|
||||
label: b,
|
||||
})),
|
||||
hasCustomInput: true,
|
||||
customInputKey: "customBitrate",
|
||||
placeholder: m["convert.settings.audio.bitrate_placeholder"](),
|
||||
};
|
||||
|
||||
const sampleRate: SettingDefinition = {
|
||||
key: "sampleRate",
|
||||
label: m["convert.settings.audio.sample_rate"](),
|
||||
type: "select",
|
||||
default: "auto",
|
||||
options: SAMPLE_RATES.map((r) => ({
|
||||
value: r,
|
||||
label: r,
|
||||
})),
|
||||
hasCustomInput: true,
|
||||
customInputKey: "customSampleRate",
|
||||
placeholder: m["convert.settings.audio.sample_rate_placeholder"](),
|
||||
};
|
||||
|
||||
/*
|
||||
* common
|
||||
*/
|
||||
const metadata: SettingDefinition = {
|
||||
key: "metadata",
|
||||
label: m["convert.settings.common.metadata"](),
|
||||
|
|
@ -408,9 +516,15 @@ export class VertdConverter extends Converter {
|
|||
|
||||
// trim/crop/rotate - also have another ui for this prob
|
||||
|
||||
// import all audio settings?
|
||||
|
||||
return [quality, fps, resolution, metadata];
|
||||
return [
|
||||
qualitySpeedRange,
|
||||
videoBitrate,
|
||||
resolution,
|
||||
fps,
|
||||
metadata,
|
||||
audioBitrate,
|
||||
sampleRate,
|
||||
];
|
||||
}
|
||||
|
||||
public async getDefaultSettings(): Promise<ConversionSettings> {
|
||||
|
|
@ -419,10 +533,19 @@ export class VertdConverter extends Converter {
|
|||
settings.forEach((setting) => {
|
||||
defaults[setting.key] = setting.default;
|
||||
});
|
||||
|
||||
if (defaults.vertdSpeedSlider !== undefined) {
|
||||
const sliderIndex = defaults.vertdSpeedSlider as number;
|
||||
defaults.vertdSpeed = vertdSpeedValues[sliderIndex];
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
public async convert(input: VertFile, to: string, settings: ConversionSettings): Promise<VertFile> {
|
||||
public async convert(
|
||||
input: VertFile,
|
||||
to: string,
|
||||
settings: ConversionSettings,
|
||||
): Promise<VertFile> {
|
||||
if (to.startsWith(".")) to = to.slice(1);
|
||||
|
||||
let fileUpload = input;
|
||||
|
|
@ -481,7 +604,14 @@ export class VertdConverter extends Converter {
|
|||
});
|
||||
|
||||
ws.onopen = () => {
|
||||
const speed = Settings.instance.settings.vertdSpeed;
|
||||
let speed = settings.vertdSpeed as ConversionSpeed | undefined;
|
||||
const sliderIndex = settings.vertdSpeedSlider as
|
||||
| number
|
||||
| undefined;
|
||||
if (sliderIndex !== undefined) {
|
||||
speed = vertdSpeedValues[sliderIndex] || speed;
|
||||
}
|
||||
if (!speed) speed = Settings.instance.settings.vertdSpeed;
|
||||
const keepMetadata = Settings.instance.settings.metadata;
|
||||
this.log(
|
||||
`opened ws connection to vertd for file ${input.name}`,
|
||||
|
|
|
|||
|
|
@ -413,6 +413,36 @@ body {
|
|||
@apply outline outline-accent outline-2;
|
||||
}
|
||||
|
||||
input[type="range"].range-slider {
|
||||
@apply w-full h-[10px] appearance-none bg-transparent;
|
||||
}
|
||||
|
||||
input[type="range"].range-slider:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
// for some reason, thumb and tracks behave differently in webkit (chromium) and firefox
|
||||
// so i had to do some manual adjustments to get them similar :sob: -maya
|
||||
input[type="range"].range-slider::-webkit-slider-runnable-track {
|
||||
@apply h-[10px] rounded-full bg-button;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
input[type="range"].range-slider::-webkit-slider-thumb {
|
||||
@apply bg-panel w-[18px] h-[18px] -mt-1 rounded-full cursor-pointer shadow-md;
|
||||
appearance: none;
|
||||
border: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
input[type="range"].range-slider::-moz-range-track {
|
||||
@apply h-[10px] rounded-full bg-button;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
input[type="range"].range-slider::-moz-range-thumb {
|
||||
@apply bg-panel border-2 border-accent w-4 h-4 rounded-full cursor-pointer shadow-md;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-separator;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@
|
|||
export type SettingType = "number" | "select" | "boolean" | "string" | "range";
|
||||
|
||||
export interface SettingDefinition {
|
||||
key: string;
|
||||
label: string;
|
||||
type: SettingType;
|
||||
default?: any;
|
||||
placeholder?: any;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
options?: Array<{ value: any; label: any }>; // for select types
|
||||
description?: string;
|
||||
hasCustomInput?: boolean; // for select types with a "custom" option
|
||||
customInputKey?: string; // key to use for custom input value in settings object
|
||||
key: string;
|
||||
label: string;
|
||||
type: SettingType;
|
||||
default?: any;
|
||||
placeholder?: any;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
options?: Array<{ value: any; label: any; speedValue?: any }>; // for select/range types
|
||||
description?: string;
|
||||
hasCustomInput?: boolean; // for select types with a "custom" option
|
||||
customInputKey?: string; // key to use for custom input value in settings object
|
||||
forceFullWidth?: boolean; // force setting to take up full width (usually grid 2)
|
||||
}
|
||||
|
||||
export interface ConversionSettings {
|
||||
[key: string]: any;
|
||||
}
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue