=== NEW === - studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan) - docs/FACTORY-V2.md — Factory v2 architecture doc - docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report === UPDATED SERVERS === - fieldedge: Added jobs-tools, UI build script, main entry update - lightspeed: Updated main + server entry points - squarespace: Added collection-browser + page-manager apps - toast: Added main + server entry points === INFRA === - infra/command-center/state.json — Updated pipeline state - infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import React, { useCallback, useState } from 'react';
|
|
import { useReactFlow } from '@xyflow/react';
|
|
import {
|
|
ZoomIn,
|
|
ZoomOut,
|
|
Maximize2,
|
|
Map,
|
|
LayoutGrid,
|
|
} from 'lucide-react';
|
|
|
|
interface ToolbarButtonProps {
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
onClick: () => void;
|
|
active?: boolean;
|
|
}
|
|
|
|
function ToolbarButton({ icon, label, onClick, active }: ToolbarButtonProps) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
title={label}
|
|
className={`
|
|
flex items-center justify-center w-8 h-8 rounded-full
|
|
transition-colors duration-150
|
|
${active
|
|
? 'bg-indigo-500/20 text-indigo-400'
|
|
: 'text-gray-400 hover:text-gray-200 hover:bg-gray-700/60'
|
|
}
|
|
`}
|
|
>
|
|
{icon}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function CanvasToolbar() {
|
|
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
|
const [minimapVisible, setMinimapVisible] = useState(true);
|
|
|
|
const handleZoomIn = useCallback(() => zoomIn({ duration: 200 }), [zoomIn]);
|
|
const handleZoomOut = useCallback(() => zoomOut({ duration: 200 }), [zoomOut]);
|
|
const handleFitView = useCallback(() => fitView({ duration: 300, padding: 0.2 }), [fitView]);
|
|
|
|
const handleToggleMinimap = useCallback(() => {
|
|
setMinimapVisible((prev) => !prev);
|
|
// Toggle minimap visibility via CSS since ReactFlow MiniMap doesn't have a built-in toggle
|
|
const minimap = document.querySelector('.react-flow__minimap') as HTMLElement;
|
|
if (minimap) {
|
|
minimap.style.display = minimapVisible ? 'none' : 'block';
|
|
}
|
|
}, [minimapVisible]);
|
|
|
|
const handleAutoLayout = useCallback(() => {
|
|
// Auto-layout: redistribute nodes in a grid pattern
|
|
// This dispatches to the store which handles repositioning
|
|
const event = new CustomEvent('canvas:auto-layout');
|
|
window.dispatchEvent(event);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-50">
|
|
<div className="flex items-center gap-1 bg-gray-800 border border-gray-700 rounded-full px-2 py-1 shadow-lg">
|
|
<ToolbarButton
|
|
icon={<ZoomIn className="w-4 h-4" />}
|
|
label="Zoom In"
|
|
onClick={handleZoomIn}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<ZoomOut className="w-4 h-4" />}
|
|
label="Zoom Out"
|
|
onClick={handleZoomOut}
|
|
/>
|
|
<div className="w-px h-5 bg-gray-700 mx-0.5" />
|
|
<ToolbarButton
|
|
icon={<Maximize2 className="w-4 h-4" />}
|
|
label="Fit View"
|
|
onClick={handleFitView}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<Map className="w-4 h-4" />}
|
|
label="Toggle MiniMap"
|
|
onClick={handleToggleMinimap}
|
|
active={minimapVisible}
|
|
/>
|
|
<div className="w-px h-5 bg-gray-700 mx-0.5" />
|
|
<ToolbarButton
|
|
icon={<LayoutGrid className="w-4 h-4" />}
|
|
label="Auto Layout"
|
|
onClick={handleAutoLayout}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|