adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
175 lines
3.9 KiB
Svelte
175 lines
3.9 KiB
Svelte
<script lang="ts">
|
|
import { workflow } from "./workflow-store";
|
|
import { PORT_COLOR } from "./workflow-types";
|
|
import type { PortType } from "./workflow-types";
|
|
|
|
interface Props {
|
|
pending: {
|
|
from_node_id: string;
|
|
from_port_id: string;
|
|
type: PortType;
|
|
x: number;
|
|
y: number;
|
|
reversed?: boolean;
|
|
} | null;
|
|
onremove: (id: string) => void;
|
|
zoom: number;
|
|
panX: number;
|
|
panY: number;
|
|
}
|
|
|
|
let { pending, onremove, zoom, panX, panY }: Props = $props();
|
|
|
|
function bez(x1: number, y1: number, x2: number, y2: number): string {
|
|
const cx = Math.max(Math.abs(x2 - x1) * 0.5, 60);
|
|
return `M ${x1} ${y1} C ${x1 + cx} ${y1}, ${x2 - cx} ${y2}, ${x2} ${y2}`;
|
|
}
|
|
|
|
function getPortPos(
|
|
nodeId: string,
|
|
portId: string,
|
|
side: "output" | "input"
|
|
): { x: number; y: number } {
|
|
const dotEl = document.querySelector(
|
|
`[data-port-id="${nodeId}:${portId}:${side}"]`
|
|
);
|
|
const transformEl = document.querySelector(".canvas-transform");
|
|
if (!dotEl || !transformEl) return { x: 0, y: 0 };
|
|
const dotRect = dotEl.getBoundingClientRect();
|
|
const transformRect = transformEl.getBoundingClientRect();
|
|
const edgeX =
|
|
side === "output"
|
|
? dotRect.right - transformRect.left
|
|
: dotRect.left - transformRect.left;
|
|
return {
|
|
x: edgeX / zoom,
|
|
y: (dotRect.top + dotRect.height / 2 - transformRect.top) / zoom
|
|
};
|
|
}
|
|
</script>
|
|
|
|
<svg class="edge-layer">
|
|
<defs>
|
|
{#each $workflow.edges as edge (edge.id)}
|
|
<linearGradient id="grad-{edge.id}" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop
|
|
offset="0%"
|
|
stop-color={PORT_COLOR[edge.type]}
|
|
stop-opacity="0.9"
|
|
/>
|
|
<stop
|
|
offset="100%"
|
|
stop-color={PORT_COLOR[edge.type]}
|
|
stop-opacity="0.4"
|
|
/>
|
|
</linearGradient>
|
|
{/each}
|
|
</defs>
|
|
|
|
{#each $workflow.edges as edge (edge.id)}
|
|
{@const from = getPortPos(edge.from_node_id, edge.from_port_id, "output")}
|
|
{@const to = getPortPos(edge.to_node_id, edge.to_port_id, "input")}
|
|
<!-- glow -->
|
|
<path
|
|
d={bez(from.x, from.y, to.x, to.y)}
|
|
fill="none"
|
|
stroke={PORT_COLOR[edge.type]}
|
|
stroke-width={6 / zoom}
|
|
stroke-opacity="0.08"
|
|
style="pointer-events: none"
|
|
/>
|
|
<!-- hitbox -->
|
|
<path
|
|
class="wire-hitbox"
|
|
d={bez(from.x, from.y, to.x, to.y)}
|
|
fill="none"
|
|
stroke="transparent"
|
|
stroke-width={16 / zoom}
|
|
onclick={() => onremove(edge.id)}
|
|
role="button"
|
|
tabindex="-1"
|
|
/>
|
|
<!-- wire -->
|
|
<path
|
|
class="wire"
|
|
d={bez(from.x, from.y, to.x, to.y)}
|
|
fill="none"
|
|
stroke="url(#grad-{edge.id})"
|
|
stroke-width={2 / zoom}
|
|
style="pointer-events: none"
|
|
/>
|
|
<!-- flow particles -->
|
|
<path
|
|
class="wire-flow"
|
|
d={bez(from.x, from.y, to.x, to.y)}
|
|
fill="none"
|
|
stroke={PORT_COLOR[edge.type]}
|
|
stroke-width={2 / zoom}
|
|
stroke-dasharray="{4 / zoom} {12 / zoom}"
|
|
stroke-opacity="0.5"
|
|
style="pointer-events: none"
|
|
/>
|
|
{/each}
|
|
|
|
{#if pending}
|
|
{@const anchor = getPortPos(
|
|
pending.from_node_id,
|
|
pending.from_port_id,
|
|
pending.reversed ? "input" : "output"
|
|
)}
|
|
<path
|
|
d={pending.reversed
|
|
? bez(pending.x, pending.y, anchor.x, anchor.y)
|
|
: bez(anchor.x, anchor.y, pending.x, pending.y)}
|
|
fill="none"
|
|
stroke={PORT_COLOR[pending.type] ?? "#5c5e6a"}
|
|
stroke-width={2 / zoom}
|
|
stroke-dasharray="{6 / zoom} {4 / zoom}"
|
|
stroke-opacity="0.6"
|
|
style="pointer-events: none"
|
|
/>
|
|
<circle
|
|
cx={pending.x}
|
|
cy={pending.y}
|
|
r={4 / zoom}
|
|
fill="none"
|
|
stroke={PORT_COLOR[pending.type] ?? "#5c5e6a"}
|
|
stroke-width={1.5 / zoom}
|
|
stroke-opacity="0.5"
|
|
style="pointer-events: none"
|
|
/>
|
|
{/if}
|
|
</svg>
|
|
|
|
<style>
|
|
.edge-layer {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: visible;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.wire-hitbox {
|
|
pointer-events: stroke;
|
|
cursor: pointer;
|
|
transition: stroke 0.15s;
|
|
}
|
|
|
|
.wire-hitbox:hover {
|
|
stroke: rgba(239, 68, 68, 0.25);
|
|
}
|
|
|
|
.wire-flow {
|
|
animation: flow 1.2s linear infinite;
|
|
}
|
|
|
|
@keyframes flow {
|
|
to {
|
|
stroke-dashoffset: -16;
|
|
}
|
|
}
|
|
</style>
|