Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:17:32 +08:00

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>