mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
feat: change tool inplace
This commit is contained in:
@@ -1,123 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
|
||||||
import { pageTitle } from "$lib/i18n/schema-tool-strings";
|
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import { chainablePages } from "$lib/pipeline.svelte";
|
import ToolPickerButton from "./ToolPickerButton.svelte";
|
||||||
import type { Page } from "$lib/registry";
|
|
||||||
import { Plus } from "@lucide/svelte";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onadd: (toolId: string) => void;
|
onadd: (toolId: string) => void;
|
||||||
}
|
}
|
||||||
let { onadd }: Props = $props();
|
let { onadd }: Props = $props();
|
||||||
|
|
||||||
const pages = chainablePages();
|
|
||||||
|
|
||||||
let open = $state(false);
|
|
||||||
let root = $state<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
function choose(page: Page) {
|
|
||||||
onadd(page.steps[0].id);
|
|
||||||
open = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (!open) return;
|
|
||||||
function onPointerDown(e: PointerEvent) {
|
|
||||||
if (!root?.contains(e.target as Node)) open = false;
|
|
||||||
}
|
|
||||||
function onKey(e: KeyboardEvent) {
|
|
||||||
if (e.key === "Escape") open = false;
|
|
||||||
}
|
|
||||||
window.addEventListener("pointerdown", onPointerDown);
|
|
||||||
window.addEventListener("keydown", onKey);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("pointerdown", onPointerDown);
|
|
||||||
window.removeEventListener("keydown", onKey);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="add-step" bind:this={root}>
|
<div class="add-step">
|
||||||
<button
|
<ToolPickerButton label={t("chain.addStep")} {onadd} />
|
||||||
type="button"
|
|
||||||
class="add-btn"
|
|
||||||
aria-haspopup="menu"
|
|
||||||
aria-expanded={open}
|
|
||||||
onclick={() => (open = !open)}
|
|
||||||
>
|
|
||||||
<Icon icon={Plus} size={14} />
|
|
||||||
<span>{t("chain.addStep")}</span>
|
|
||||||
</button>
|
|
||||||
{#if open}
|
|
||||||
<div class="menu" role="menu">
|
|
||||||
{#each pages as page (page.slug)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="item"
|
|
||||||
role="menuitem"
|
|
||||||
onclick={() => choose(page)}
|
|
||||||
>
|
|
||||||
{pageTitle(page)}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.add-step {
|
.add-step {
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin: var(--space-s) 0;
|
margin: var(--space-s) 0;
|
||||||
}
|
}
|
||||||
.add-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--space-m);
|
|
||||||
padding: var(--space-m) var(--space-l);
|
|
||||||
border: var(--size-border) solid var(--color-main);
|
|
||||||
border-radius: var(--radius-m);
|
|
||||||
background: var(--color-main);
|
|
||||||
color: var(--color-background);
|
|
||||||
font: 600 var(--font-size-s) var(--font-mono);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.add-btn:hover {
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
.menu {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + var(--space-s));
|
|
||||||
right: 0;
|
|
||||||
z-index: var(--z-dropdown);
|
|
||||||
min-width: calc(var(--space-xxxl) * 8);
|
|
||||||
max-height: calc(var(--space-xxxl) * 8);
|
|
||||||
overflow-y: auto;
|
|
||||||
display: grid;
|
|
||||||
gap: var(--space-s);
|
|
||||||
padding: var(--space-m);
|
|
||||||
border: var(--size-border) solid var(--color-border);
|
|
||||||
border-radius: var(--radius-m);
|
|
||||||
background: var(--color-panel);
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
padding: var(--space-m) var(--space-l);
|
|
||||||
border: var(--size-border) solid transparent;
|
|
||||||
border-radius: var(--radius-s);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font: var(--font-size-s) var(--font-mono);
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.item:hover {
|
|
||||||
color: var(--color-text);
|
|
||||||
border-color: var(--color-border);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import AddStepButton from "./AddStepButton.svelte";
|
import AddStepButton from "./AddStepButton.svelte";
|
||||||
import SchemaFields from "./SchemaFields.svelte";
|
import SchemaFields from "./SchemaFields.svelte";
|
||||||
import SchemaPreview from "./SchemaPreview.svelte";
|
import SchemaPreview from "./SchemaPreview.svelte";
|
||||||
|
import ToolPickerButton from "./ToolPickerButton.svelte";
|
||||||
import StepCard from "$lib/components/display/StepCard.svelte";
|
import StepCard from "$lib/components/display/StepCard.svelte";
|
||||||
import { hasTransparency } from "$lib/core/analyze";
|
import { hasTransparency } from "$lib/core/analyze";
|
||||||
import { debounce } from "$lib/core/debounce";
|
import { debounce } from "$lib/core/debounce";
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import {
|
import {
|
||||||
createChain,
|
createChain,
|
||||||
|
createStep,
|
||||||
insertStep,
|
insertStep,
|
||||||
loadChain,
|
loadChain,
|
||||||
moveStep,
|
moveStep,
|
||||||
@@ -246,6 +248,20 @@
|
|||||||
steps = steps.with(index, { ...step, collapsed: !step.collapsed });
|
steps = steps.with(index, { ...step, collapsed: !step.collapsed });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Замена инструмента на месте: ключ и свёрнутость шага сохраняются,
|
||||||
|
// параметры — дефолты нового инструмента, вход пересчитывается при прогоне.
|
||||||
|
function replaceStepTool(index: number, toolId: string) {
|
||||||
|
const step = steps[index];
|
||||||
|
const next = createStep(toolId);
|
||||||
|
if (!step || !next) return;
|
||||||
|
touchedFor(step.key).clear();
|
||||||
|
steps = steps.with(index, {
|
||||||
|
...next,
|
||||||
|
key: step.key,
|
||||||
|
collapsed: step.collapsed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function onStepDrop(e: DragEvent, to: number) {
|
function onStepDrop(e: DragEvent, to: number) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (dragFrom !== null) steps = moveStep(steps, dragFrom, to);
|
if (dragFrom !== null) steps = moveStep(steps, dragFrom, to);
|
||||||
@@ -483,6 +499,13 @@
|
|||||||
ondrop={(e) => onStepDrop(e, i)}
|
ondrop={(e) => onStepDrop(e, i)}
|
||||||
ondragend={() => (dragFrom = null)}
|
ondragend={() => (dragFrom = null)}
|
||||||
>
|
>
|
||||||
|
{#snippet tools()}
|
||||||
|
<ToolPickerButton
|
||||||
|
label={t("chain.changeTool")}
|
||||||
|
iconOnly
|
||||||
|
onadd={(id) => replaceStepTool(i, id)}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
<SchemaFields
|
<SchemaFields
|
||||||
schema={toolSchemaOf(step)}
|
schema={toolSchemaOf(step)}
|
||||||
values={step.params}
|
values={step.params}
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
|
import { pageTitle } from "$lib/i18n/schema-tool-strings";
|
||||||
|
import { chainablePages } from "$lib/pipeline.svelte";
|
||||||
|
import type { Page } from "$lib/registry";
|
||||||
|
import { Plus, Repeat } from "@lucide/svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
iconOnly?: boolean;
|
||||||
|
onadd: (toolId: string) => void;
|
||||||
|
}
|
||||||
|
let { label, iconOnly = false, onadd }: Props = $props();
|
||||||
|
|
||||||
|
const pages = chainablePages();
|
||||||
|
|
||||||
|
let open = $state(false);
|
||||||
|
let root = $state<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
function choose(page: Page) {
|
||||||
|
onadd(page.steps[0].id);
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
function onPointerDown(e: PointerEvent) {
|
||||||
|
if (!root?.contains(e.target as Node)) open = false;
|
||||||
|
}
|
||||||
|
function onKey(e: KeyboardEvent) {
|
||||||
|
if (e.key === "Escape") open = false;
|
||||||
|
}
|
||||||
|
window.addEventListener("pointerdown", onPointerDown);
|
||||||
|
window.addEventListener("keydown", onKey);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("pointerdown", onPointerDown);
|
||||||
|
window.removeEventListener("keydown", onKey);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="tool-picker" bind:this={root}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={iconOnly ? "icon-btn" : "add-btn"}
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-expanded={open}
|
||||||
|
aria-label={iconOnly ? label : undefined}
|
||||||
|
onclick={() => (open = !open)}
|
||||||
|
>
|
||||||
|
{#if iconOnly}
|
||||||
|
<Icon icon={Repeat} size={14} />
|
||||||
|
{:else}
|
||||||
|
<Icon icon={Plus} size={14} />
|
||||||
|
<span>{label}</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{#if open}
|
||||||
|
<div class="menu" role="menu">
|
||||||
|
{#each pages as page (page.slug)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="item"
|
||||||
|
role="menuitem"
|
||||||
|
onclick={() => choose(page)}
|
||||||
|
>
|
||||||
|
{pageTitle(page)}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tool-picker {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.add-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
padding: var(--space-m) var(--space-l);
|
||||||
|
border: var(--size-border) solid var(--color-main);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
background: var(--color-main);
|
||||||
|
color: var(--color-background);
|
||||||
|
font: 600 var(--font-size-s) var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.add-btn:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
}
|
||||||
|
.icon-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--space-s);
|
||||||
|
border: var(--size-border) solid transparent;
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
background: none;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.icon-btn:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + var(--space-s));
|
||||||
|
right: 0;
|
||||||
|
z-index: var(--z-dropdown);
|
||||||
|
min-width: calc(var(--space-xxxl) * 8);
|
||||||
|
max-height: calc(var(--space-xxxl) * 8);
|
||||||
|
overflow-y: auto;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-s);
|
||||||
|
padding: var(--space-m);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
background: var(--color-panel);
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
padding: var(--space-m) var(--space-l);
|
||||||
|
border: var(--size-border) solid transparent;
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.item:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -49,6 +49,7 @@ export const en: Dict = {
|
|||||||
},
|
},
|
||||||
chain: {
|
chain: {
|
||||||
addStep: "Add step",
|
addStep: "Add step",
|
||||||
|
changeTool: "Change tool",
|
||||||
collapseStep: "Collapse step",
|
collapseStep: "Collapse step",
|
||||||
expandStep: "Expand step",
|
expandStep: "Expand step",
|
||||||
inputLegend: "Input",
|
inputLegend: "Input",
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const ru: Dict = {
|
|||||||
},
|
},
|
||||||
chain: {
|
chain: {
|
||||||
addStep: "Добавить шаг",
|
addStep: "Добавить шаг",
|
||||||
|
changeTool: "Поменять инструмент",
|
||||||
collapseStep: "Свернуть шаг",
|
collapseStep: "Свернуть шаг",
|
||||||
expandStep: "Развернуть шаг",
|
expandStep: "Развернуть шаг",
|
||||||
inputLegend: "Вход",
|
inputLegend: "Вход",
|
||||||
|
|||||||
Reference in New Issue
Block a user