Spaces:
Running
Running
some fixes
Browse files
src/lib/components/flow/FitViewOnResize.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import {
|
| 3 |
import type { Node, Edge } from '@xyflow/svelte';
|
| 4 |
import { useNodes, useEdges } from '@xyflow/svelte';
|
| 5 |
import { useSvelteFlow } from '@xyflow/svelte';
|
|
@@ -24,6 +24,7 @@
|
|
| 24 |
let lastLayoutKey = $state<string | null>(null);
|
| 25 |
let isFirstLayout = $state(true);
|
| 26 |
let lastDraggable = $state(viewState.draggable);
|
|
|
|
| 27 |
|
| 28 |
// βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 29 |
|
|
@@ -64,10 +65,10 @@
|
|
| 64 |
})()
|
| 65 |
);
|
| 66 |
|
| 67 |
-
// βββ Fit view helper
|
| 68 |
|
| 69 |
-
function doFitView(opts?: { animate?: boolean; forceAnimate?: boolean }) {
|
| 70 |
-
if (!viewState.draggable) return;
|
| 71 |
const nodes = nodesStore.current;
|
| 72 |
if (nodes.length === 0) return;
|
| 73 |
|
|
@@ -105,15 +106,15 @@
|
|
| 105 |
});
|
| 106 |
});
|
| 107 |
|
| 108 |
-
//
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
if (
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
|
| 118 |
// βββ Layout algorithm ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 119 |
//
|
|
@@ -291,7 +292,7 @@
|
|
| 291 |
belowTotalW += extents.get(belowIds[i])!.width;
|
| 292 |
}
|
| 293 |
|
| 294 |
-
|
| 295 |
let childX = allocX + (blockAWidth - belowTotalW) / 2;
|
| 296 |
|
| 297 |
for (const cid of belowIds) {
|
|
@@ -374,14 +375,18 @@
|
|
| 374 |
);
|
| 375 |
}
|
| 376 |
|
| 377 |
-
|
| 378 |
-
|
|
|
|
|
|
|
| 379 |
tick().then(() => {
|
| 380 |
requestAnimationFrame(() => {
|
| 381 |
-
doFitView();
|
| 382 |
isFirstLayout = false;
|
| 383 |
});
|
| 384 |
});
|
|
|
|
|
|
|
| 385 |
}
|
| 386 |
}
|
| 387 |
</script>
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import { tick } from 'svelte';
|
| 3 |
import type { Node, Edge } from '@xyflow/svelte';
|
| 4 |
import { useNodes, useEdges } from '@xyflow/svelte';
|
| 5 |
import { useSvelteFlow } from '@xyflow/svelte';
|
|
|
|
| 24 |
let lastLayoutKey = $state<string | null>(null);
|
| 25 |
let isFirstLayout = $state(true);
|
| 26 |
let lastDraggable = $state(viewState.draggable);
|
| 27 |
+
let lastFitViewRequest = $state(0);
|
| 28 |
|
| 29 |
// βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 30 |
|
|
|
|
| 65 |
})()
|
| 66 |
);
|
| 67 |
|
| 68 |
+
// βββ Fit view helper ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 69 |
|
| 70 |
+
function doFitView(opts?: { animate?: boolean; forceAnimate?: boolean; force?: boolean }) {
|
| 71 |
+
if (!viewState.draggable && !opts?.force) return;
|
| 72 |
const nodes = nodesStore.current;
|
| 73 |
if (nodes.length === 0) return;
|
| 74 |
|
|
|
|
| 106 |
});
|
| 107 |
});
|
| 108 |
|
| 109 |
+
// Respond to explicit fitView requests (e.g. button click in PanelCanvasActions)
|
| 110 |
+
$effect(() => {
|
| 111 |
+
const req = viewState.fitViewRequest;
|
| 112 |
+
if (req === 0 || req === lastFitViewRequest) return;
|
| 113 |
+
lastFitViewRequest = req;
|
| 114 |
+
tick().then(() => {
|
| 115 |
+
requestAnimationFrame(() => doFitView({ forceAnimate: true, force: true }));
|
| 116 |
+
});
|
| 117 |
+
});
|
| 118 |
|
| 119 |
// βββ Layout algorithm ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 120 |
//
|
|
|
|
| 292 |
belowTotalW += extents.get(belowIds[i])!.width;
|
| 293 |
}
|
| 294 |
|
| 295 |
+
const childY = y + nodeH + V_SPACING;
|
| 296 |
let childX = allocX + (blockAWidth - belowTotalW) / 2;
|
| 297 |
|
| 298 |
for (const cid of belowIds) {
|
|
|
|
| 375 |
);
|
| 376 |
}
|
| 377 |
|
| 378 |
+
// Only fit the view on the very first layout (component mount).
|
| 379 |
+
// Subsequent layout updates (new nodes, resizes) reposition nodes
|
| 380 |
+
// without moving the camera β the user stays in control of the viewport.
|
| 381 |
+
if (isFirstLayout) {
|
| 382 |
tick().then(() => {
|
| 383 |
requestAnimationFrame(() => {
|
| 384 |
+
doFitView({ animate: false });
|
| 385 |
isFirstLayout = false;
|
| 386 |
});
|
| 387 |
});
|
| 388 |
+
} else {
|
| 389 |
+
isFirstLayout = false;
|
| 390 |
}
|
| 391 |
}
|
| 392 |
</script>
|
src/lib/components/flow/actions/PanelCanvasActions.svelte
CHANGED
|
@@ -1,20 +1,16 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import { Maximize, Minus, Plus
|
| 3 |
import { Panel } from '@xyflow/svelte';
|
| 4 |
|
| 5 |
import { Button } from '$lib/components/ui/button';
|
| 6 |
import { viewState } from '$lib/state/view.svelte';
|
| 7 |
import { useSvelteFlow } from '@xyflow/svelte';
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
$effect(() => {
|
| 12 |
-
localStorage.setItem('hf-playground-draggable-option', viewState.draggable.toString());
|
| 13 |
-
});
|
| 14 |
-
|
| 15 |
-
function handleDraggable() {
|
| 16 |
-
viewState.draggable = !viewState.draggable;
|
| 17 |
}
|
|
|
|
|
|
|
| 18 |
</script>
|
| 19 |
|
| 20 |
<Panel
|
|
@@ -40,53 +36,8 @@
|
|
| 40 |
>
|
| 41 |
<Minus />
|
| 42 |
</Button>
|
| 43 |
-
<Button
|
| 44 |
-
variant="ghost"
|
| 45 |
-
size="icon-sm"
|
| 46 |
-
onclick={() =>
|
| 47 |
-
fitView({
|
| 48 |
-
interpolate: 'smooth',
|
| 49 |
-
duration: 250,
|
| 50 |
-
padding: 0.15
|
| 51 |
-
})}
|
| 52 |
-
>
|
| 53 |
<Maximize />
|
| 54 |
</Button>
|
| 55 |
</div>
|
| 56 |
-
<!-- <div
|
| 57 |
-
class="inline-flex w-fit flex-row gap-0.5 rounded-lg border border-border bg-background/30 p-1 backdrop-blur-sm dark:bg-gray-900/30"
|
| 58 |
-
>
|
| 59 |
-
<Tooltip.Root delayDuration={0}>
|
| 60 |
-
<Tooltip.Trigger>
|
| 61 |
-
<Button
|
| 62 |
-
variant={!viewState.draggable ? 'default' : 'ghost'}
|
| 63 |
-
size="icon-sm"
|
| 64 |
-
onclick={handleDraggable}
|
| 65 |
-
>
|
| 66 |
-
<MousePointer />
|
| 67 |
-
</Button>
|
| 68 |
-
</Tooltip.Trigger>
|
| 69 |
-
<Tooltip.Content>
|
| 70 |
-
<p class="flex items-center gap-1">
|
| 71 |
-
<MousePointer class="size-3.5" /> Free: Move the canvas freely
|
| 72 |
-
</p>
|
| 73 |
-
</Tooltip.Content>
|
| 74 |
-
</Tooltip.Root>
|
| 75 |
-
<Tooltip.Root>
|
| 76 |
-
<Tooltip.Trigger>
|
| 77 |
-
<Button
|
| 78 |
-
variant={viewState.draggable ? 'default' : 'ghost'}
|
| 79 |
-
size="icon-sm"
|
| 80 |
-
onclick={handleDraggable}
|
| 81 |
-
>
|
| 82 |
-
<Spotlight />
|
| 83 |
-
</Button>
|
| 84 |
-
</Tooltip.Trigger>
|
| 85 |
-
<Tooltip.Content>
|
| 86 |
-
<p class="flex items-center gap-1">
|
| 87 |
-
<Spotlight class="size-3.5" /> Spotlight: Focus on the selected node
|
| 88 |
-
</p>
|
| 89 |
-
</Tooltip.Content>
|
| 90 |
-
</Tooltip.Root>
|
| 91 |
-
</div> -->
|
| 92 |
</Panel>
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import { Maximize, Minus, Plus } from '@lucide/svelte';
|
| 3 |
import { Panel } from '@xyflow/svelte';
|
| 4 |
|
| 5 |
import { Button } from '$lib/components/ui/button';
|
| 6 |
import { viewState } from '$lib/state/view.svelte';
|
| 7 |
import { useSvelteFlow } from '@xyflow/svelte';
|
| 8 |
|
| 9 |
+
function requestFitView() {
|
| 10 |
+
viewState.fitViewRequest++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
+
|
| 13 |
+
const { zoomIn, zoomOut, getZoom } = useSvelteFlow();
|
| 14 |
</script>
|
| 15 |
|
| 16 |
<Panel
|
|
|
|
| 36 |
>
|
| 37 |
<Minus />
|
| 38 |
</Button>
|
| 39 |
+
<Button variant="ghost" size="icon-sm" onclick={requestFitView}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
<Maximize />
|
| 41 |
</Button>
|
| 42 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
</Panel>
|
src/lib/state/view.svelte.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
export const viewState = $state({
|
| 2 |
-
draggable: true
|
|
|
|
| 3 |
});
|
|
|
|
| 1 |
export const viewState = $state({
|
| 2 |
+
draggable: true,
|
| 3 |
+
fitViewRequest: 0
|
| 4 |
});
|