mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
feat: add limit size to download flow
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user