mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
feat: add show mask chip for tools
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
dims?: string;
|
||||
note?: string;
|
||||
noteTone?: "warning" | "info";
|
||||
actions?: Snippet;
|
||||
}
|
||||
let {
|
||||
label,
|
||||
@@ -26,6 +27,7 @@
|
||||
dims = undefined,
|
||||
note = undefined,
|
||||
noteTone = "warning",
|
||||
actions = undefined,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
@@ -44,6 +46,9 @@
|
||||
{/if}
|
||||
</span>
|
||||
<span class="tile-note">
|
||||
{#if actions}
|
||||
{@render actions()}
|
||||
{/if}
|
||||
{#if note}
|
||||
<Badge tone={noteTone} label={note} />
|
||||
{/if}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
||||
import PreviewTile from "./PreviewTile.svelte";
|
||||
import Toggle from "$lib/components/ui/Toggle.svelte";
|
||||
import ChipButton from "$lib/components/ui/ChipButton.svelte";
|
||||
import { toDataUrl } from "$lib/core/io";
|
||||
import type { PixelImage } from "$lib/core/types";
|
||||
import type { OutputMime } from "$lib/core/io";
|
||||
@@ -27,9 +28,16 @@
|
||||
textSource?: string;
|
||||
textResult?: string | null;
|
||||
textVars?: Record<string, string | number>;
|
||||
mask?: PixelImage | null;
|
||||
maskOn?: boolean;
|
||||
ontogglemask?: () => void;
|
||||
running?: boolean;
|
||||
error?: string;
|
||||
stepResults?: (PixelImage | null)[];
|
||||
stepMaskable?: boolean[];
|
||||
stepMaskOn?: boolean[];
|
||||
stepMasks?: (PixelImage | null)[];
|
||||
ontogglestepmask?: (i: number) => void;
|
||||
aligned?: boolean;
|
||||
ontogglealign?: () => void;
|
||||
format?: OutputMime;
|
||||
@@ -57,9 +65,16 @@
|
||||
textSource = "",
|
||||
textResult = null,
|
||||
textVars = undefined,
|
||||
mask = null,
|
||||
maskOn = false,
|
||||
ontogglemask,
|
||||
running = false,
|
||||
error = "",
|
||||
stepResults = [],
|
||||
stepMaskable = [],
|
||||
stepMaskOn = [],
|
||||
stepMasks = [],
|
||||
ontogglestepmask,
|
||||
aligned = false,
|
||||
ontogglealign,
|
||||
format = "image/png",
|
||||
@@ -132,7 +147,21 @@
|
||||
viewMode="image"
|
||||
dims={`${img.width} × ${img.height}`}
|
||||
>
|
||||
<img src={toDataUrl(img)} alt="" />
|
||||
{#snippet actions()}
|
||||
{#if stepMaskable[i]}
|
||||
<ChipButton
|
||||
label={t("resultCard.maskToggle")}
|
||||
active={stepMaskOn[i] ?? false}
|
||||
onclick={() => ontogglestepmask?.(i)}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
<img
|
||||
src={toDataUrl(
|
||||
stepMaskOn[i] && stepMasks[i] ? (stepMasks[i] as PixelImage) : img,
|
||||
)}
|
||||
alt=""
|
||||
/>
|
||||
</PreviewTile>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -145,6 +174,9 @@
|
||||
{textVars}
|
||||
{toolId}
|
||||
{running}
|
||||
{mask}
|
||||
{maskOn}
|
||||
{ontogglemask}
|
||||
oncopy={oncopytext}
|
||||
{ondownloadtxt}
|
||||
/>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { t } from "$lib/i18n/t";
|
||||
import type { FileResult, ResultKind, ResultNote } from "$lib/registry";
|
||||
import { Check } from "@lucide/svelte";
|
||||
import ChipButton from "$lib/components/ui/ChipButton.svelte";
|
||||
import SchemaTextResult from "./SchemaTextResult.svelte";
|
||||
import PreviewTile from "./PreviewTile.svelte";
|
||||
|
||||
@@ -16,6 +17,9 @@
|
||||
textVars?: Record<string, string | number>;
|
||||
toolId?: string;
|
||||
running?: boolean;
|
||||
mask?: PixelImage | null;
|
||||
maskOn?: boolean;
|
||||
ontogglemask?: () => void;
|
||||
oncopy?: () => void;
|
||||
ondownloadtxt?: () => void;
|
||||
}
|
||||
@@ -28,11 +32,15 @@
|
||||
textVars = undefined,
|
||||
toolId = "",
|
||||
running = false,
|
||||
mask = null,
|
||||
maskOn = false,
|
||||
ontogglemask,
|
||||
oncopy,
|
||||
ondownloadtxt,
|
||||
}: Props = $props();
|
||||
|
||||
const resultUrl = $derived(result ? toDataUrl(result) : null);
|
||||
const shown = $derived(maskOn && mask ? mask : result);
|
||||
const resultUrl = $derived(shown ? toDataUrl(shown) : null);
|
||||
const resultDims = $derived(
|
||||
resultKind === "image" && result
|
||||
? `${result.width} × ${result.height}`
|
||||
@@ -68,6 +76,15 @@
|
||||
note={resultNote ? t(resultNote.key) : undefined}
|
||||
noteTone={resultNote?.tone ?? "warning"}
|
||||
>
|
||||
{#snippet actions()}
|
||||
{#if ontogglemask}
|
||||
<ChipButton
|
||||
label={t("resultCard.maskToggle")}
|
||||
active={maskOn}
|
||||
onclick={ontogglemask}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
<div
|
||||
class="canvas"
|
||||
class:checker={resultKind === "image"}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { resolve } from "$app/paths";
|
||||
import StepCard from "$lib/components/display/StepCard.svelte";
|
||||
import Button from "$lib/components/ui/Button.svelte";
|
||||
import ChipButton from "$lib/components/ui/ChipButton.svelte";
|
||||
import { ButtonVariantDefine } from "$lib/components/ui/define";
|
||||
import Toggle from "$lib/components/ui/Toggle.svelte";
|
||||
import { isFav, toggleFav } from "$lib/favorites.svelte";
|
||||
@@ -114,6 +115,10 @@
|
||||
let formatQuality = $state<Record<string, number>>({});
|
||||
// Optional download size limit (KB); undefined = no limit.
|
||||
let limitKb = $state<number | undefined>(undefined);
|
||||
// Mask preview per step (keyed by step.key): the toggle lives on every
|
||||
// step result tile, not just the final one.
|
||||
const maskOnKeys = new SvelteSet<string>();
|
||||
let maskResults = $state<Record<string, PixelImage | null>>({});
|
||||
let dragFrom = $state<number | null>(null);
|
||||
|
||||
const lastTool = $derived(
|
||||
@@ -165,6 +170,10 @@
|
||||
: current.text;
|
||||
});
|
||||
|
||||
const stepMaskable = $derived(steps.map((s) => !!getTool(s.id)?.runMask));
|
||||
const stepMaskOn = $derived(steps.map((s) => maskOnKeys.has(s.key)));
|
||||
const stepMasks = $derived(steps.map((s) => maskResults[s.key] ?? null));
|
||||
|
||||
const debouncedRun = debounce(() => run(), 200);
|
||||
|
||||
function touchedFor(key: string): SvelteSet<string> {
|
||||
@@ -209,6 +218,8 @@
|
||||
textResult = null;
|
||||
verdictVars = undefined;
|
||||
displayError = null;
|
||||
maskOnKeys.clear();
|
||||
maskResults = {};
|
||||
}
|
||||
|
||||
function setStepValue(
|
||||
@@ -517,6 +528,41 @@
|
||||
return () => debouncedRun.cancel();
|
||||
});
|
||||
|
||||
// Mask preview: recomputed on the main thread per toggled step from that
|
||||
// step's input; a mask never enters the chain itself.
|
||||
$effect(() => {
|
||||
void steps;
|
||||
void stepResults;
|
||||
void source;
|
||||
const next: Record<string, PixelImage | null> = {};
|
||||
steps.forEach((step, i) => {
|
||||
if (!maskOnKeys.has(step.key)) return;
|
||||
const stepTool = getTool(step.id);
|
||||
if (!stepTool?.runMask) return;
|
||||
const input = i === 0 ? source : stepResults[i - 1];
|
||||
if (!input) return;
|
||||
try {
|
||||
next[step.key] = stepTool.runMask({
|
||||
params: step.params,
|
||||
source: input,
|
||||
});
|
||||
} catch {
|
||||
next[step.key] = null;
|
||||
}
|
||||
});
|
||||
maskResults = next;
|
||||
});
|
||||
|
||||
function toggleStepMask(key: string) {
|
||||
if (maskOnKeys.has(key)) maskOnKeys.delete(key);
|
||||
else maskOnKeys.add(key);
|
||||
}
|
||||
|
||||
function toggleStepMaskByIndex(i: number) {
|
||||
const step = steps[i];
|
||||
if (step) toggleStepMask(step.key);
|
||||
}
|
||||
|
||||
function toDisplayError(e: unknown): DisplayError {
|
||||
if (e instanceof ToolError) {
|
||||
return { kind: "i18n", key: e.key, vars: e.vars };
|
||||
@@ -614,6 +660,16 @@
|
||||
</StepCard>
|
||||
{/snippet}
|
||||
|
||||
{#snippet maskChip(i: number)}
|
||||
{#if stepMaskable[i]}
|
||||
<ChipButton
|
||||
label={t("resultCard.maskToggle")}
|
||||
active={stepMaskOn[i]}
|
||||
onclick={() => toggleStepMask(steps[i].key)}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#if !alignedMode}
|
||||
<div class="workspace">
|
||||
<section class="settings">
|
||||
@@ -648,6 +704,15 @@
|
||||
{alphaLoss}
|
||||
{stepResults}
|
||||
{aligned}
|
||||
{stepMaskable}
|
||||
{stepMaskOn}
|
||||
{stepMasks}
|
||||
ontogglestepmask={toggleStepMaskByIndex}
|
||||
mask={stepMasks.at(-1) ?? null}
|
||||
maskOn={stepMaskOn.at(-1) ?? false}
|
||||
ontogglemask={stepMaskable.at(-1)
|
||||
? () => toggleStepMask(steps[steps.length - 1].key)
|
||||
: undefined}
|
||||
ontogglealign={() => (aligned = !aligned)}
|
||||
onformat={(v) => (format = v)}
|
||||
onquality={setFormatQuality}
|
||||
@@ -746,6 +811,11 @@
|
||||
textVars={verdictVars}
|
||||
toolId={lastTool.id}
|
||||
{running}
|
||||
mask={stepMasks[i] ?? null}
|
||||
maskOn={stepMaskOn[i] ?? false}
|
||||
ontogglemask={stepMaskable[i]
|
||||
? () => toggleStepMask(steps[i].key)
|
||||
: undefined}
|
||||
oncopy={copyText}
|
||||
ondownloadtxt={downloadText}
|
||||
/>
|
||||
@@ -755,8 +825,16 @@
|
||||
viewMode="image"
|
||||
dims={out ? `${out.width} × ${out.height}` : undefined}
|
||||
>
|
||||
{#snippet actions()}
|
||||
{@render maskChip(i)}
|
||||
{/snippet}
|
||||
{#if out}
|
||||
<img src={toDataUrl(out)} alt="" />
|
||||
<img
|
||||
src={toDataUrl(
|
||||
stepMaskOn[i] && stepMasks[i] ? stepMasks[i]! : out,
|
||||
)}
|
||||
alt=""
|
||||
/>
|
||||
{:else}
|
||||
<span class="empty">{t("resultCard.noResult")}</span>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user