feat: add limit size to download flow

This commit is contained in:
2026-10-08 17:42:21 +05:00
parent 7361a82c18
commit 8dcc263461
11 changed files with 247 additions and 15 deletions
@@ -14,11 +14,13 @@
running: boolean;
format?: OutputMime;
quality?: number;
limitKb?: number;
alphaLoss?: boolean;
onupload: (file: File) => void;
ondownload: () => void;
onformat?: (mime: OutputMime) => void;
onquality?: (value: number) => void;
onlimit?: (kb: number | undefined) => void;
}
let {
inputMode,
@@ -27,11 +29,13 @@
running,
format = "image/png",
quality = undefined,
limitKb = undefined,
alphaLoss = false,
onupload,
ondownload,
onformat,
onquality,
onlimit,
}: Props = $props();
</script>
@@ -43,10 +47,12 @@
<SchemaDownload
{format}
{quality}
{limitKb}
{running}
{ondownload}
onformat={(v) => onformat?.(v)}
onquality={(v) => onquality?.(v)}
onlimit={(v) => onlimit?.(v)}
/>
{:else if canDownload && resultKind === "files"}
<DownloadButton
@@ -13,13 +13,23 @@
interface Props {
format: OutputMime;
quality?: number;
limitKb?: number;
running: boolean;
ondownload: () => void;
onformat: (mime: OutputMime) => void;
onquality: (value: number) => void;
onlimit: (kb: number | undefined) => void;
}
let { format, quality, running, ondownload, onformat, onquality }: Props =
$props();
let {
format,
quality,
limitKb,
running,
ondownload,
onformat,
onquality,
onlimit,
}: Props = $props();
const current = $derived(outputFormatByMime(format));
const qualitySetting = $derived(current.settings?.quality);
@@ -44,6 +54,11 @@
onformat(mime);
}
function handleLimit(e: Event) {
const raw = (e.target as HTMLInputElement).valueAsNumber;
onlimit(Number.isFinite(raw) && raw >= 1 ? Math.round(raw) : undefined);
}
$effect(() => {
if (!open) return;
function onPointerDown(e: PointerEvent) {
@@ -110,6 +125,24 @@
/>
</div>
{/if}
<div class="limit">
<label class="limit-field">
<span>{t("actions.sizeLimitKb")}</span>
<input
type="number"
min="1"
step="1"
placeholder="∞"
value={limitKb ?? ""}
oninput={handleLimit}
/>
</label>
{#if limitKb !== undefined && format === "image/bmp"}
<p class="limit-warn" role="status">
{t("actions.sizeLimitBmp")}
</p>
{/if}
</div>
</div>
{/if}
</div>
@@ -183,4 +216,37 @@
padding-top: var(--space-m);
border-top: var(--size-border) solid var(--color-border);
}
.limit {
padding-top: var(--space-m);
border-top: var(--size-border) solid var(--color-border);
}
.limit-field {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-m);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
}
.limit-field input {
width: calc(var(--space-xxxl) * 3);
padding: var(--space-m) var(--space-l);
background: var(--color-background);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s);
color: var(--color-text);
font: var(--font-size-m) var(--font-mono);
text-align: center;
appearance: textfield;
}
.limit-field input::-webkit-outer-spin-button,
.limit-field input::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
.limit-warn {
margin: var(--space-m) 0 0;
color: var(--color-warning);
font: var(--font-size-s) var(--font-mono);
}
</style>
@@ -34,6 +34,7 @@
ontogglealign?: () => void;
format?: OutputMime;
quality?: number;
limitKb?: number;
alphaLoss?: boolean;
onupload: (file: File) => void;
ontextsource?: (text: string) => void;
@@ -43,6 +44,7 @@
ondownload: () => void;
onformat?: (mime: OutputMime) => void;
onquality?: (value: number) => void;
onlimit?: (kb: number | undefined) => void;
}
let {
inputMode,
@@ -62,6 +64,7 @@
ontogglealign,
format = "image/png",
quality = undefined,
limitKb = undefined,
alphaLoss = false,
onupload,
ontextsource,
@@ -71,6 +74,7 @@
ondownload,
onformat,
onquality,
onlimit,
}: Props = $props();
const hasResult = $derived(
@@ -97,11 +101,13 @@
{running}
{format}
{quality}
{limitKb}
{alphaLoss}
{onupload}
{ondownload}
{onformat}
{onquality}
{onlimit}
/>
</div>
@@ -10,6 +10,7 @@
decodeFile,
downloadBlob,
encode,
fitWithinBytes,
outputFormatByMime,
toDataUrl,
type OutputMime,
@@ -98,6 +99,8 @@
let format = $state<OutputMime>("image/png");
// Lossy-format quality for tools without a quality param in the schema.
let formatQuality = $state<Record<string, number>>({});
// Optional download size limit (KB); undefined = no limit.
let limitKb = $state<number | undefined>(undefined);
let dragFrom = $state<number | null>(null);
const lastTool = $derived(
@@ -174,6 +177,7 @@
initedFor = page.slug;
steps = loadChain(page.slug) ?? createChain(page);
format = lastTool.output?.mime ?? "image/png";
limitKb = undefined;
source = null;
result = null;
fileResult = null;
@@ -397,7 +401,9 @@
const out = outputFormatByMime(format);
const quality =
currentQuality !== undefined ? currentQuality / 100 : undefined;
const blob = await encode(result, out.mime, quality);
const blob = limitKb
? await fitWithinBytes(result, out.mime, limitKb * 1024, quality)
: await encode(result, out.mime, quality);
downloadBlob(blob, `${page.slug}.${out.ext}`);
} catch (e) {
displayError = toDisplayError(e);
@@ -569,12 +575,14 @@
error={errorText}
{format}
quality={currentQuality}
{limitKb}
{alphaLoss}
{stepResults}
{aligned}
ontogglealign={() => (aligned = !aligned)}
onformat={(v) => (format = v)}
onquality={setFormatQuality}
onlimit={(v) => (limitKb = v)}
onupload={handleFile}
ontextsource={(textValue) => {
textSource = textValue;
@@ -603,11 +611,13 @@
{running}
{format}
quality={currentQuality}
{limitKb}
{alphaLoss}
onupload={handleFile}
ondownload={download}
onformat={(v) => (format = v)}
onquality={setFormatQuality}
onlimit={(v) => (limitKb = v)}
/>
</div>
</div>