mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
feat: update dnd tools in pipeline
This commit is contained in:
@@ -52,6 +52,8 @@
|
|||||||
"vitest": "^4.1.11"
|
"vitest": "^4.1.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/dom": "^0.3.2",
|
||||||
|
"@dnd-kit/svelte": "^0.3.2",
|
||||||
"@fontsource/ibm-plex-mono": "^5.3.0",
|
"@fontsource/ibm-plex-mono": "^5.3.0",
|
||||||
"@fontsource/ibm-plex-sans": "^5.3.0",
|
"@fontsource/ibm-plex-sans": "^5.3.0",
|
||||||
"@lucide/svelte": "^1.33.0",
|
"@lucide/svelte": "^1.33.0",
|
||||||
|
|||||||
Generated
+74
@@ -8,6 +8,12 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@dnd-kit/dom':
|
||||||
|
specifier: ^0.3.2
|
||||||
|
version: 0.3.2
|
||||||
|
'@dnd-kit/svelte':
|
||||||
|
specifier: ^0.3.2
|
||||||
|
version: 0.3.2(svelte@5.56.9(@typescript-eslint/types@8.67.0))
|
||||||
'@fontsource/ibm-plex-mono':
|
'@fontsource/ibm-plex-mono':
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
@@ -196,6 +202,26 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss-selector-parser: ^7.1.1
|
postcss-selector-parser: ^7.1.1
|
||||||
|
|
||||||
|
'@dnd-kit/abstract@0.3.2':
|
||||||
|
resolution: {integrity: sha512-uvPVK+SZYD6Viddn9M0K0JQdXknuVSxA/EbMlFRanve3P/XTc18oLa5zGftKSGjfQGmuzkZ34E26DSbly1zi3Q==}
|
||||||
|
|
||||||
|
'@dnd-kit/collision@0.3.2':
|
||||||
|
resolution: {integrity: sha512-pNmNSLCI8S9fNQ7QJ3fBCDjiT0sqBhUFcKgmyYaGvGCAU+kq0AP8OWlh0JSisc9k5mFyxmRpmFQcnJpILz/RPA==}
|
||||||
|
|
||||||
|
'@dnd-kit/dom@0.3.2':
|
||||||
|
resolution: {integrity: sha512-cIUAVgt2szQyz6JRy7I+0r+xeyOAGH21Y15hb5bIyHoDEaZBvIDH+OOlD9eoLjCbsxDLN9WloU2CBi3OE6LYDg==}
|
||||||
|
|
||||||
|
'@dnd-kit/geometry@0.3.2':
|
||||||
|
resolution: {integrity: sha512-3UBPuIS7E3oGiHxOE8h810QA+0pnrnCtGxl4Os1z3yy5YkC/BEYGY+TxWPTQaY1/OMV7GCX7ZNMlama2QN3n3w==}
|
||||||
|
|
||||||
|
'@dnd-kit/state@0.3.2':
|
||||||
|
resolution: {integrity: sha512-dLUIkoYrIJhGXfF2wGLTfb46vUokEsO/OoE21TSfmahYrx7ysTmnwbePsznFaHlwgZhQEh6AlLvthLCeY21b1A==}
|
||||||
|
|
||||||
|
'@dnd-kit/svelte@0.3.2':
|
||||||
|
resolution: {integrity: sha512-HkTnx/3GnxfVNqHSojxhxp5r+KymL9Q7UI2gonXN2+bmdObw03FSQOjicOoRzlV2u9o5DtK1sU4YUQAfkLCGGQ==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^5.29.0
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.10.1':
|
'@eslint-community/eslint-utils@4.10.1':
|
||||||
resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
|
resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@@ -313,6 +339,9 @@ packages:
|
|||||||
'@polka/url@1.0.0-next.29':
|
'@polka/url@1.0.0-next.29':
|
||||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||||
|
|
||||||
|
'@preact/signals-core@1.14.4':
|
||||||
|
resolution: {integrity: sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==}
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.2.4':
|
'@rolldown/binding-android-arm64@1.2.4':
|
||||||
resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==}
|
resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -1518,6 +1547,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4'
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
|
tslib@2.8.1:
|
||||||
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -1744,6 +1776,44 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
postcss-selector-parser: 7.1.5
|
postcss-selector-parser: 7.1.5
|
||||||
|
|
||||||
|
'@dnd-kit/abstract@0.3.2':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/geometry': 0.3.2
|
||||||
|
'@dnd-kit/state': 0.3.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/collision@0.3.2':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/abstract': 0.3.2
|
||||||
|
'@dnd-kit/geometry': 0.3.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/dom@0.3.2':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/abstract': 0.3.2
|
||||||
|
'@dnd-kit/collision': 0.3.2
|
||||||
|
'@dnd-kit/geometry': 0.3.2
|
||||||
|
'@dnd-kit/state': 0.3.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/geometry@0.3.2':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/state': 0.3.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/state@0.3.2':
|
||||||
|
dependencies:
|
||||||
|
'@preact/signals-core': 1.14.4
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/svelte@0.3.2(svelte@5.56.9(@typescript-eslint/types@8.67.0))':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/abstract': 0.3.2
|
||||||
|
'@dnd-kit/dom': 0.3.2
|
||||||
|
'@dnd-kit/state': 0.3.2
|
||||||
|
svelte: 5.56.9(@typescript-eslint/types@8.67.0)
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.10.1(eslint@10.9.0(supports-color@10.2.2))':
|
'@eslint-community/eslint-utils@4.10.1(eslint@10.9.0(supports-color@10.2.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 10.9.0(supports-color@10.2.2)
|
eslint: 10.9.0(supports-color@10.2.2)
|
||||||
@@ -1847,6 +1917,8 @@ snapshots:
|
|||||||
|
|
||||||
'@polka/url@1.0.0-next.29': {}
|
'@polka/url@1.0.0-next.29': {}
|
||||||
|
|
||||||
|
'@preact/signals-core@1.14.4': {}
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.2.4':
|
'@rolldown/binding-android-arm64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -2991,6 +3063,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|||||||
@@ -177,6 +177,7 @@
|
|||||||
--duration-l: 0.8s;
|
--duration-l: 0.8s;
|
||||||
|
|
||||||
--z-dropdown: 10;
|
--z-dropdown: 10;
|
||||||
|
--z-drag: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
|
|||||||
@@ -3,55 +3,44 @@
|
|||||||
import IconButton from "$lib/components/ui/IconButton.svelte";
|
import IconButton from "$lib/components/ui/IconButton.svelte";
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
|
import type { Attachment } from "svelte/attachments";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: number;
|
index: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
draggable?: boolean;
|
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
tools?: Snippet;
|
tools?: Snippet;
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
onremove?: () => void;
|
onremove?: () => void;
|
||||||
ontoggle?: () => void;
|
ontoggle?: () => void;
|
||||||
ondragstart?: (e: DragEvent) => void;
|
// dnd-kit attachments wired by SortableStepCard; noop on static usage.
|
||||||
ondragover?: (e: DragEvent) => void;
|
elementAttachment?: Attachment<HTMLElement>;
|
||||||
ondrop?: (e: DragEvent) => void;
|
handleAttachment?: Attachment<HTMLElement>;
|
||||||
ondragend?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const noopAttach: Attachment<HTMLElement> = () => {};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
index,
|
index,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
draggable = false,
|
|
||||||
collapsed = false,
|
collapsed = false,
|
||||||
tools,
|
tools,
|
||||||
children,
|
children,
|
||||||
onremove,
|
onremove,
|
||||||
ontoggle,
|
ontoggle,
|
||||||
ondragstart,
|
elementAttachment = noopAttach,
|
||||||
ondragover,
|
handleAttachment = noopAttach,
|
||||||
ondrop,
|
|
||||||
ondragend,
|
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<article
|
<article class="step-card" {@attach elementAttachment}>
|
||||||
class="step-card"
|
<header class="step-head" role="button" tabindex="0">
|
||||||
ondragover={(e) => ondragover?.(e)}
|
|
||||||
ondrop={(e) => ondrop?.(e)}
|
|
||||||
>
|
|
||||||
<header
|
|
||||||
class="step-head"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
{draggable}
|
|
||||||
ondragstart={(e) => ondragstart?.(e)}
|
|
||||||
ondragend={() => ondragend?.()}
|
|
||||||
>
|
|
||||||
<span class="step-index">{index.toString().padStart(2, "0")}</span>
|
<span class="step-index">{index.toString().padStart(2, "0")}</span>
|
||||||
|
<span class="drag-handle" {@attach handleAttachment}>
|
||||||
<GripVertical size={16} class="drag" aria-hidden="true" />
|
<GripVertical size={16} class="drag" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
<div class="step-heading-text">
|
<div class="step-heading-text">
|
||||||
{#if type}<span class="step-type">{type}</span>{/if}
|
{#if type}<span class="step-type">{type}</span>{/if}
|
||||||
{#if title}<h2 class="step-title">{title}</h2>{/if}
|
{#if title}<h2 class="step-title">{title}</h2>{/if}
|
||||||
@@ -101,6 +90,12 @@
|
|||||||
font: var(--font-size-s) var(--font-mono);
|
font: var(--font-size-s) var(--font-mono);
|
||||||
color: var(--color-main);
|
color: var(--color-main);
|
||||||
}
|
}
|
||||||
|
.drag-handle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: grab;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
.step-heading-text {
|
.step-heading-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -127,6 +122,5 @@
|
|||||||
}
|
}
|
||||||
.step-card :global(.drag) {
|
.step-card :global(.drag) {
|
||||||
color: var(--color-border);
|
color: var(--color-border);
|
||||||
cursor: grab;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,11 +19,14 @@
|
|||||||
import SchemaActions from "./SchemaActions.svelte";
|
import SchemaActions from "./SchemaActions.svelte";
|
||||||
import SchemaResultTile from "./SchemaResultTile.svelte";
|
import SchemaResultTile from "./SchemaResultTile.svelte";
|
||||||
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
||||||
|
import StepDropIndicator from "./StepDropIndicator.svelte";
|
||||||
|
import type { StepDnd } from "./step-dnd.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
steps: ChainStep[];
|
steps: ChainStep[];
|
||||||
stepCard: Snippet<[ChainStep, number]>;
|
stepCard: Snippet<[ChainStep, number]>;
|
||||||
canExtend: boolean;
|
canExtend: boolean;
|
||||||
|
stepDnd?: StepDnd;
|
||||||
aligned: boolean;
|
aligned: boolean;
|
||||||
inputMode: InputMode;
|
inputMode: InputMode;
|
||||||
source: PixelImage | null;
|
source: PixelImage | null;
|
||||||
@@ -65,6 +68,7 @@
|
|||||||
steps,
|
steps,
|
||||||
stepCard,
|
stepCard,
|
||||||
canExtend,
|
canExtend,
|
||||||
|
stepDnd,
|
||||||
aligned = $bindable(false),
|
aligned = $bindable(false),
|
||||||
inputMode,
|
inputMode,
|
||||||
source,
|
source,
|
||||||
@@ -146,13 +150,19 @@
|
|||||||
{#each warnings as w (w)}
|
{#each warnings as w (w)}
|
||||||
<p class="warn" role="status">{w}</p>
|
<p class="warn" role="status">{w}</p>
|
||||||
{/each}
|
{/each}
|
||||||
{#if canExtend}
|
{#if canExtend && !stepDnd?.active}
|
||||||
<div class="aligned-row">
|
<div class="aligned-row">
|
||||||
<AddStepButton onadd={(id) => onaddstep(0, id)} />
|
<AddStepButton onadd={(id) => onaddstep(0, id)} />
|
||||||
<div class="panel-segment connect-segment"></div>
|
<div class="panel-segment connect-segment"></div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each steps as step, i (step.key)}
|
{#each steps as step, i (step.key)}
|
||||||
|
{#if stepDnd?.isIndicatorAt(i)}
|
||||||
|
<div class="aligned-row">
|
||||||
|
<StepDropIndicator height={stepDnd.dragHeight} />
|
||||||
|
<div class="panel-segment connect-segment"></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{@const input = i === 0 ? null : stepResults[i - 1]}
|
{@const input = i === 0 ? null : stepResults[i - 1]}
|
||||||
{@const out = stepResults[i]}
|
{@const out = stepResults[i]}
|
||||||
{#if i > 0}
|
{#if i > 0}
|
||||||
@@ -241,7 +251,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if canExtend}
|
{#if canExtend && !stepDnd?.active}
|
||||||
<div class="aligned-row">
|
<div class="aligned-row">
|
||||||
<AddStepButton onadd={(id) => onaddstep(i + 1, id)} />
|
<AddStepButton onadd={(id) => onaddstep(i + 1, id)} />
|
||||||
<div
|
<div
|
||||||
@@ -251,6 +261,15 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if stepDnd?.isIndicatorAt(steps.length)}
|
||||||
|
<div class="aligned-row">
|
||||||
|
<StepDropIndicator height={stepDnd.dragHeight} />
|
||||||
|
<div
|
||||||
|
class="panel-segment connect-segment"
|
||||||
|
class:add-last={!canExtend || stepDnd.active}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { resolve } from "$app/paths";
|
import { resolve } from "$app/paths";
|
||||||
import StepCard from "$lib/components/display/StepCard.svelte";
|
|
||||||
import Button from "$lib/components/ui/Button.svelte";
|
import Button from "$lib/components/ui/Button.svelte";
|
||||||
import { ButtonVariantDefine } from "$lib/components/ui/define";
|
import { ButtonVariantDefine } from "$lib/components/ui/define";
|
||||||
import { isFav, toggleFav } from "$lib/favorites.svelte";
|
import { isFav, toggleFav } from "$lib/favorites.svelte";
|
||||||
import { Star } from "@lucide/svelte";
|
import { GripVertical, Star } from "@lucide/svelte";
|
||||||
|
import { DragDropProvider } from "@dnd-kit/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";
|
||||||
import { ToolError } from "$lib/core/errors";
|
import { ToolError } from "$lib/core/errors";
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
import {
|
import {
|
||||||
createChain,
|
createChain,
|
||||||
isStructuralDefault,
|
isStructuralDefault,
|
||||||
|
moveStep,
|
||||||
type ChainStep,
|
type ChainStep,
|
||||||
} from "$lib/pipeline.svelte";
|
} from "$lib/pipeline.svelte";
|
||||||
import {
|
import {
|
||||||
@@ -49,8 +50,11 @@
|
|||||||
import SchemaAlignedLayout from "./SchemaAlignedLayout.svelte";
|
import SchemaAlignedLayout from "./SchemaAlignedLayout.svelte";
|
||||||
import SchemaFields from "./SchemaFields.svelte";
|
import SchemaFields from "./SchemaFields.svelte";
|
||||||
import SchemaPreview from "./SchemaPreview.svelte";
|
import SchemaPreview from "./SchemaPreview.svelte";
|
||||||
|
import SortableStepCard from "./SortableStepCard.svelte";
|
||||||
|
import StepDropIndicator from "./StepDropIndicator.svelte";
|
||||||
import ToolPickerButton from "./ToolPickerButton.svelte";
|
import ToolPickerButton from "./ToolPickerButton.svelte";
|
||||||
import { createSchemaToolRunner } from "./schema-tool-runner.svelte";
|
import { createSchemaToolRunner } from "./schema-tool-runner.svelte";
|
||||||
|
import { createStepDnd } from "./step-dnd.svelte";
|
||||||
import {
|
import {
|
||||||
createSchemaToolState,
|
createSchemaToolState,
|
||||||
toolSchemaOf,
|
toolSchemaOf,
|
||||||
@@ -121,9 +125,13 @@
|
|||||||
removeStepAt,
|
removeStepAt,
|
||||||
toggleStep,
|
toggleStep,
|
||||||
replaceStepTool,
|
replaceStepTool,
|
||||||
onStepDrop,
|
|
||||||
} = stepState;
|
} = stepState;
|
||||||
const steps = $derived(stepState.steps);
|
const steps = $derived(stepState.steps);
|
||||||
|
const stepDnd = createStepDnd({
|
||||||
|
onMove: (from, to) => {
|
||||||
|
stepState.steps = moveStep(stepState.steps, from, to);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const runner = createSchemaToolRunner({
|
const runner = createSchemaToolRunner({
|
||||||
pageSlug: () => page.slug,
|
pageSlug: () => page.slug,
|
||||||
@@ -440,26 +448,13 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#snippet stepCard(step: ChainStep, i: number)}
|
{#snippet stepCard(step: ChainStep, i: number)}
|
||||||
<StepCard
|
<SortableStepCard
|
||||||
index={i + 1}
|
{step}
|
||||||
|
index={i}
|
||||||
title={stepTitle(step.id)}
|
title={stepTitle(step.id)}
|
||||||
draggable
|
|
||||||
collapsed={step.collapsed ?? false}
|
collapsed={step.collapsed ?? false}
|
||||||
ontoggle={() => toggleStep(i)}
|
ontoggle={() => toggleStep(i)}
|
||||||
onremove={steps.length > 1 ? () => removeStepAt(step.key) : undefined}
|
onremove={steps.length > 1 ? () => removeStepAt(step.key) : undefined}
|
||||||
ondragstart={(e) => {
|
|
||||||
stepState.dragFrom = i;
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.dataTransfer.effectAllowed = "move";
|
|
||||||
e.dataTransfer.setData("text/plain", String(i));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
ondragover={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
||||||
}}
|
|
||||||
ondrop={(e) => onStepDrop(e, i)}
|
|
||||||
ondragend={() => (stepState.dragFrom = null)}
|
|
||||||
>
|
>
|
||||||
{#snippet tools()}
|
{#snippet tools()}
|
||||||
<ToolPickerButton
|
<ToolPickerButton
|
||||||
@@ -479,21 +474,33 @@
|
|||||||
onreset={() => resetStep(i)}
|
onreset={() => resetStep(i)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</StepCard>
|
</SortableStepCard>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
<DragDropProvider
|
||||||
|
onDragStart={stepDnd.onDragStart}
|
||||||
|
onDragMove={stepDnd.onDragOver}
|
||||||
|
onDragOver={stepDnd.onDragOver}
|
||||||
|
onDragEnd={stepDnd.onDragEnd}
|
||||||
|
>
|
||||||
{#if !alignedMode}
|
{#if !alignedMode}
|
||||||
<div class="workspace">
|
<div class="workspace">
|
||||||
<section class="settings">
|
<section class="settings">
|
||||||
{#if canExtend}
|
{#if canExtend && !stepDnd.active}
|
||||||
<AddStepButton onadd={(id) => addStepAt(0, id)} />
|
<AddStepButton onadd={(id) => addStepAt(0, id)} />
|
||||||
{/if}
|
{/if}
|
||||||
{#each steps as step, i (step.key)}
|
{#each steps as step, i (step.key)}
|
||||||
|
{#if stepDnd.isIndicatorAt(i)}
|
||||||
|
<StepDropIndicator height={stepDnd.dragHeight} />
|
||||||
|
{/if}
|
||||||
{@render stepCard(step, i)}
|
{@render stepCard(step, i)}
|
||||||
{#if canExtend}
|
{#if canExtend && !stepDnd.active}
|
||||||
<AddStepButton onadd={(id) => addStepAt(i + 1, id)} />
|
<AddStepButton onadd={(id) => addStepAt(i + 1, id)} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if stepDnd.isIndicatorAt(steps.length)}
|
||||||
|
<StepDropIndicator height={stepDnd.dragHeight} />
|
||||||
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
@@ -557,6 +564,7 @@
|
|||||||
{steps}
|
{steps}
|
||||||
{stepCard}
|
{stepCard}
|
||||||
{canExtend}
|
{canExtend}
|
||||||
|
{stepDnd}
|
||||||
bind:aligned
|
bind:aligned
|
||||||
{inputMode}
|
{inputMode}
|
||||||
{source}
|
{source}
|
||||||
@@ -595,6 +603,18 @@
|
|||||||
onlimit={(v) => (limitKb = v)}
|
onlimit={(v) => (limitKb = v)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if stepDnd.activeData && stepDnd.pointer}
|
||||||
|
<div
|
||||||
|
class="step-ghost"
|
||||||
|
style:width="{stepDnd.ghostWidth}px"
|
||||||
|
style:left="{stepDnd.pointer.x - stepDnd.grabOffset.x}px"
|
||||||
|
style:top="{stepDnd.pointer.y - stepDnd.grabOffset.y}px"
|
||||||
|
>
|
||||||
|
<GripVertical size={16} aria-hidden="true" />
|
||||||
|
<span class="step-ghost-title">{stepDnd.activeData.title}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</DragDropProvider>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="no-schema">{t("paramsCard.noSchema")}</p>
|
<p class="no-schema">{t("paramsCard.noSchema")}</p>
|
||||||
@@ -606,6 +626,23 @@
|
|||||||
clamp(var(--space-m), 4vw, var(--space-xxxl));
|
clamp(var(--space-m), 4vw, var(--space-xxxl));
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
.step-ghost {
|
||||||
|
position: fixed;
|
||||||
|
z-index: var(--z-drag);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
padding: var(--space-m) var(--space-xl);
|
||||||
|
border: var(--size-border-thick) dashed var(--color-main);
|
||||||
|
background: var(--color-panel);
|
||||||
|
color: var(--color-border);
|
||||||
|
opacity: 0.85;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.step-ghost-title {
|
||||||
|
font: 600 var(--font-size-m) var(--font-mono);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import StepCard from "$lib/components/display/StepCard.svelte";
|
||||||
|
import type { ChainStep } from "$lib/pipeline.svelte";
|
||||||
|
import { createSortable } from "@dnd-kit/svelte/sortable";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
step: ChainStep;
|
||||||
|
index: number;
|
||||||
|
title: string;
|
||||||
|
collapsed?: boolean;
|
||||||
|
tools?: Snippet;
|
||||||
|
children?: Snippet;
|
||||||
|
onremove?: () => void;
|
||||||
|
ontoggle?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
step,
|
||||||
|
index,
|
||||||
|
title,
|
||||||
|
collapsed = false,
|
||||||
|
tools,
|
||||||
|
children,
|
||||||
|
onremove,
|
||||||
|
ontoggle,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
// feedback: "none" -- no transform shifting; step-dnd.svelte.ts hides the
|
||||||
|
// source and renders a dashed drop indicator instead.
|
||||||
|
const sortable = createSortable({
|
||||||
|
get id() {
|
||||||
|
return step.key;
|
||||||
|
},
|
||||||
|
get index() {
|
||||||
|
return index;
|
||||||
|
},
|
||||||
|
group: "steps",
|
||||||
|
type: "step",
|
||||||
|
accept: "step",
|
||||||
|
feedback: "none",
|
||||||
|
get data() {
|
||||||
|
return { title };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<StepCard
|
||||||
|
index={index + 1}
|
||||||
|
{title}
|
||||||
|
{collapsed}
|
||||||
|
{tools}
|
||||||
|
{children}
|
||||||
|
{onremove}
|
||||||
|
{ontoggle}
|
||||||
|
elementAttachment={sortable.attach}
|
||||||
|
handleAttachment={sortable.attachHandle}
|
||||||
|
/>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { height = 0 }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="step-drop-indicator"
|
||||||
|
style:height={height > 0 ? `${height}px` : null}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.step-drop-indicator {
|
||||||
|
border: var(--size-border-thick) dashed var(--color-main);
|
||||||
|
background: var(--color-panel);
|
||||||
|
opacity: 0.6;
|
||||||
|
min-height: var(--space-xxl);
|
||||||
|
pointer-events: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,7 +2,6 @@ import type { PixelImage } from "$lib/core/types";
|
|||||||
import {
|
import {
|
||||||
createStep,
|
createStep,
|
||||||
insertStep,
|
insertStep,
|
||||||
moveStep,
|
|
||||||
removeStep,
|
removeStep,
|
||||||
type ChainStep,
|
type ChainStep,
|
||||||
} from "$lib/pipeline.svelte";
|
} from "$lib/pipeline.svelte";
|
||||||
@@ -23,7 +22,6 @@ export interface SchemaToolStateCtx {
|
|||||||
|
|
||||||
interface StepsState {
|
interface StepsState {
|
||||||
steps: ChainStep[];
|
steps: ChainStep[];
|
||||||
dragFrom: number | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toolSchemaOf(step: ChainStep) {
|
export function toolSchemaOf(step: ChainStep) {
|
||||||
@@ -122,7 +120,7 @@ function touchedFor(marks: Marks, key: string): SvelteSet<string> {
|
|||||||
// Chain-step state of SchemaToolView: the steps array, per-step edit marks
|
// Chain-step state of SchemaToolView: the steps array, per-step edit marks
|
||||||
// and all mutations. Results/execution live in schema-tool-runner.svelte.ts.
|
// and all mutations. Results/execution live in schema-tool-runner.svelte.ts.
|
||||||
export function createSchemaToolState(ctx: SchemaToolStateCtx) {
|
export function createSchemaToolState(ctx: SchemaToolStateCtx) {
|
||||||
const s = $state<StepsState>({ steps: [], dragFrom: null });
|
const s = $state<StepsState>({ steps: [] });
|
||||||
const marks: Marks = {
|
const marks: Marks = {
|
||||||
touchedByStep: new SvelteMap(),
|
touchedByStep: new SvelteMap(),
|
||||||
lastAxis: {},
|
lastAxis: {},
|
||||||
@@ -134,12 +132,6 @@ export function createSchemaToolState(ctx: SchemaToolStateCtx) {
|
|||||||
set steps(v: ChainStep[]) {
|
set steps(v: ChainStep[]) {
|
||||||
s.steps = v;
|
s.steps = v;
|
||||||
},
|
},
|
||||||
get dragFrom() {
|
|
||||||
return s.dragFrom;
|
|
||||||
},
|
|
||||||
set dragFrom(v: number | null) {
|
|
||||||
s.dragFrom = v;
|
|
||||||
},
|
|
||||||
touchedFor: (key: string) => touchedFor(marks, key),
|
touchedFor: (key: string) => touchedFor(marks, key),
|
||||||
// Marks are keyed by step.key from the previous host; keep them and
|
// Marks are keyed by step.key from the previous host; keep them and
|
||||||
// stale keys leak into the new chain's fields.
|
// stale keys leak into the new chain's fields.
|
||||||
@@ -181,10 +173,5 @@ export function createSchemaToolState(ctx: SchemaToolStateCtx) {
|
|||||||
collapsed: step.collapsed,
|
collapsed: step.collapsed,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onStepDrop: (e: DragEvent, to: number) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (s.dragFrom !== null) s.steps = moveStep(s.steps, s.dragFrom, to);
|
|
||||||
s.dragFrom = null;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import type { Draggable, Droppable } from "@dnd-kit/dom";
|
||||||
|
import { isSortable, type SortableDraggable } from "@dnd-kit/dom/sortable";
|
||||||
|
|
||||||
|
export interface StepDragData {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StepDndOptions {
|
||||||
|
// Compressed-index semantics: `to` counts items with the source excluded,
|
||||||
|
// matching moveStep's remove-then-insert.
|
||||||
|
onMove: (from: number, to: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DndEvent {
|
||||||
|
operation?: {
|
||||||
|
source?: Draggable | null;
|
||||||
|
position?: { current?: { x: number; y: number } } | null;
|
||||||
|
};
|
||||||
|
canceled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the slot the pointer is over: how many non-source cards have their
|
||||||
|
// vertical midpoint above the pointer.
|
||||||
|
function indexAtPosition(
|
||||||
|
source: SortableDraggable<StepDragData>,
|
||||||
|
pointerY: number,
|
||||||
|
): number {
|
||||||
|
const manager = source.sortable.manager;
|
||||||
|
let index = 0;
|
||||||
|
if (!manager) return index;
|
||||||
|
for (const droppable of manager.registry.droppables) {
|
||||||
|
if (!isSortable(droppable)) continue;
|
||||||
|
if (droppable.sortable.id === source.id) continue;
|
||||||
|
const el = (droppable as Droppable).element;
|
||||||
|
if (!el) continue;
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
if (rect.height === 0) continue;
|
||||||
|
if (pointerY > rect.top + rect.height / 2) index++;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step-reorder state for SchemaToolView, single list. The dragged card is
|
||||||
|
// hidden (DragOverlay renders the ghost) and a dashed indicator of the
|
||||||
|
// dragged height marks the insertion slot between the remaining cards.
|
||||||
|
export function createStepDnd({ onMove }: StepDndOptions) {
|
||||||
|
let activeData = $state<StepDragData | null>(null);
|
||||||
|
let indicatorIndex = $state<number | null>(null);
|
||||||
|
let dragHeight = $state(0);
|
||||||
|
let sourceIndex = $state<number | null>(null);
|
||||||
|
let sourceElement: HTMLElement | null = null;
|
||||||
|
// Ghost positioning: current pointer, grab offset inside the card and the
|
||||||
|
// card width. DragOverlay is not used -- the Feedback plugin skips
|
||||||
|
// draggables with feedback: "none", so the ghost follows the pointer here.
|
||||||
|
let pointer = $state<{ x: number; y: number } | null>(null);
|
||||||
|
let grabOffset = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
|
let ghostWidth = $state(0);
|
||||||
|
// X-range of the source card: dragging sideways over the preview panel
|
||||||
|
// must not light up the indicator.
|
||||||
|
let sourceX: { left: number; right: number } | null = null;
|
||||||
|
|
||||||
|
function computeIndicator(event: DndEvent): number | null {
|
||||||
|
const source = event.operation?.source;
|
||||||
|
if (!source || !isSortable(source)) return null;
|
||||||
|
const position = event.operation?.position?.current;
|
||||||
|
if (!position || !sourceX) return null;
|
||||||
|
if (position.x < sourceX.left || position.x > sourceX.right) return null;
|
||||||
|
return indexAtPosition(
|
||||||
|
source as SortableDraggable<StepDragData>,
|
||||||
|
position.y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragStart(event: DndEvent) {
|
||||||
|
const source = event.operation?.source;
|
||||||
|
if (!source) return;
|
||||||
|
activeData = (source.data as StepDragData | undefined) ?? null;
|
||||||
|
sourceElement = (source.element as HTMLElement | undefined) ?? null;
|
||||||
|
const rect = sourceElement?.getBoundingClientRect();
|
||||||
|
dragHeight = rect?.height ?? 0;
|
||||||
|
ghostWidth = rect?.width ?? 0;
|
||||||
|
sourceX = rect ? { left: rect.left, right: rect.right } : null;
|
||||||
|
const start = event.operation?.position?.current;
|
||||||
|
grabOffset =
|
||||||
|
start && rect
|
||||||
|
? { x: start.x - rect.left, y: start.y - rect.top }
|
||||||
|
: { x: 0, y: 0 };
|
||||||
|
pointer = start ?? null;
|
||||||
|
if (isSortable(source)) sourceIndex = source.sortable.initialIndex;
|
||||||
|
if (sourceElement) sourceElement.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragOver(event: DndEvent) {
|
||||||
|
pointer = event.operation?.position?.current ?? pointer;
|
||||||
|
indicatorIndex = computeIndicator(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd(event: DndEvent) {
|
||||||
|
const drop = event.canceled ? null : computeIndicator(event);
|
||||||
|
if (sourceElement) {
|
||||||
|
sourceElement.style.display = "";
|
||||||
|
sourceElement = null;
|
||||||
|
}
|
||||||
|
const from = sourceIndex;
|
||||||
|
activeData = null;
|
||||||
|
indicatorIndex = null;
|
||||||
|
dragHeight = 0;
|
||||||
|
sourceIndex = null;
|
||||||
|
sourceX = null;
|
||||||
|
pointer = null;
|
||||||
|
if (from === null || drop === null || drop === from) return;
|
||||||
|
onMove(from, drop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slots render in the full list where the hidden source still occupies
|
||||||
|
// its DOM position: a compressed index at/past the source shifts by 1.
|
||||||
|
function isIndicatorAt(slot: number): boolean {
|
||||||
|
if (indicatorIndex === null) return false;
|
||||||
|
let renderIndex = indicatorIndex;
|
||||||
|
if (sourceIndex !== null && indicatorIndex >= sourceIndex) {
|
||||||
|
renderIndex += 1;
|
||||||
|
}
|
||||||
|
return renderIndex === slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get activeData() {
|
||||||
|
return activeData;
|
||||||
|
},
|
||||||
|
get dragHeight() {
|
||||||
|
return dragHeight;
|
||||||
|
},
|
||||||
|
get pointer() {
|
||||||
|
return pointer;
|
||||||
|
},
|
||||||
|
get grabOffset() {
|
||||||
|
return grabOffset;
|
||||||
|
},
|
||||||
|
get ghostWidth() {
|
||||||
|
return ghostWidth;
|
||||||
|
},
|
||||||
|
get active() {
|
||||||
|
return activeData !== null;
|
||||||
|
},
|
||||||
|
onDragStart,
|
||||||
|
onDragOver,
|
||||||
|
onDragEnd,
|
||||||
|
isIndicatorAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StepDnd = ReturnType<typeof createStepDnd>;
|
||||||
Reference in New Issue
Block a user