From 0ed6c141b0dcb1c0c44b1246907f3406f605b492 Mon Sep 17 00:00:00 2001 From: Shine1i Date: Thu, 12 Feb 2026 00:18:56 +0100 Subject: [PATCH] feat: add interactive viewport controls and refactor floating button styles - Introduced `ViewportControls` for zoom, fit view, and interactive toggle in canvas lab. - Extracted and reused `CANVAS_FLOATING_ICON_BUTTON_CLASS` for consistent button styling. - Updated API base paths and server proxy settings. - Enabled dynamic interaction states for nodes and connections in canvas lab. --- studio/frontend/.gitignore | 1 + .../src/features/canvas-lab/api/index.ts | 2 +- .../features/canvas-lab/canvas-lab-page.tsx | 85 ++++++++++++++++++- .../canvas-lab/components/block-sheet.tsx | 14 +-- .../components/floating-icon-button-class.ts | 2 + studio/frontend/vite.config.ts | 13 ++- 6 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 studio/frontend/src/features/canvas-lab/components/floating-icon-button-class.ts diff --git a/studio/frontend/.gitignore b/studio/frontend/.gitignore index ec9ec8d674..7358cc82ad 100644 --- a/studio/frontend/.gitignore +++ b/studio/frontend/.gitignore @@ -26,3 +26,4 @@ test/ *.njsproj *.sln *.sw? +/src/features/canvas-lab/AGENTS.md diff --git a/studio/frontend/src/features/canvas-lab/api/index.ts b/studio/frontend/src/features/canvas-lab/api/index.ts index c04efb0be6..ea4890ba57 100644 --- a/studio/frontend/src/features/canvas-lab/api/index.ts +++ b/studio/frontend/src/features/canvas-lab/api/index.ts @@ -1,4 +1,4 @@ -const DEFAULT_BASE = "http://127.0.0.1:8000"; +const DEFAULT_BASE = "/preview-api"; export const CANVAS_LAB_API_BASE = import.meta.env.VITE_DATA_DESIGNER_API ?? DEFAULT_BASE; diff --git a/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx b/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx index 2449f4831a..3a4a74a6ce 100644 --- a/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx +++ b/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx @@ -1,7 +1,6 @@ import { Background, BackgroundVariant, - Controls, type Edge, type EdgeChange, type EdgeTypes, @@ -26,9 +25,11 @@ import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { EyeIcon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; +import { Lock, LockOpen, Maximize2, Minus, Plus } from "lucide-react"; import { previewCanvas } from "./api"; import { CanvasAuxNode, type CanvasAuxNodeData } from "./components/canvas-aux-node"; import { BlockSheet } from "./components/block-sheet"; +import { CANVAS_FLOATING_ICON_BUTTON_CLASS } from "./components/floating-icon-button-class"; import { CanvasNode } from "./components/canvas-node"; import { CanvasSemanticEdge } from "./components/canvas-semantic-edge"; import { DataEdge } from "./components/rf-ui/data-edge"; @@ -56,6 +57,11 @@ type LayoutControlsProps = { onToggleDirection: () => void; }; +type ViewportControlsProps = { + interactive: boolean; + onToggleInteractive: () => void; +}; + type InternalsSyncProps = { nodeIds: string[]; }; @@ -125,6 +131,74 @@ function LayoutControls({ ); } +function ViewportControls({ + interactive, + onToggleInteractive, +}: ViewportControlsProps): ReactElement { + const { zoomIn, zoomOut, fitView } = useReactFlow(); + + const handleZoomIn = useCallback(() => { + zoomIn({ duration: 150 }); + }, [zoomIn]); + + const handleZoomOut = useCallback(() => { + zoomOut({ duration: 150 }); + }, [zoomOut]); + + const handleFitView = useCallback(() => { + fitView({ duration: 250 }); + }, [fitView]); + + return ( + + + + + + + ); +} + export function CanvasLabPage(): ReactElement { const { nodes, @@ -200,6 +274,7 @@ export function CanvasLabPage(): ReactElement { const [copied, setCopied] = useState(false); const [importOpen, setImportOpen] = useState(false); const [processorsOpen, setProcessorsOpen] = useState(false); + const [interactive, setInteractive] = useState(true); const [statusMessage, setStatusMessage] = useState<{ tone: "success" | "error"; text: string; @@ -481,6 +556,9 @@ export function CanvasLabPage(): ReactElement { onConnect={onConnect} onNodeClick={handleNodeClick} isValidConnection={isValidConnection} + nodesDraggable={interactive} + nodesConnectable={interactive} + elementsSelectable={interactive} fitView={true} className="h-full w-full" > @@ -512,7 +590,10 @@ export function CanvasLabPage(): ReactElement { onImport={() => setImportOpen(true)} /> - + setInteractive((value) => !value)} + /> diff --git a/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx b/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx index 65ddb19662..bb27ba4d5d 100644 --- a/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx +++ b/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx @@ -18,6 +18,7 @@ import { } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import type { ReactElement } from "react"; +import { CANVAS_FLOATING_ICON_BUTTON_CLASS } from "./floating-icon-button-class"; import type { LlmType, SamplerType } from "../types"; import { BLOCK_GROUPS, getBlocksForKind } from "../blocks/registry"; @@ -118,9 +119,6 @@ function BlockSheetButton({ ); } -const FLOATING_ICON_BUTTON_CLASS = - "corner-squircle group h-11 w-11 rounded-xl border border-border/60 bg-transparent p-0 text-muted-foreground hover:bg-transparent hover:text-primary hover:border-primary/60"; - export function BlockSheet({ container, view, @@ -146,7 +144,11 @@ export function BlockSheet({ }} > -