diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 93e8d054..f4d038a8 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -623,5 +623,17 @@ "scenario_script_node": "Script Execution", "scenario_condition_node": "Condition", "scenario_delay_node": "Delay", - "scenario_loop_node": "Loop" + "scenario_loop_node": "Loop", + "scenario_fit_view": "Fit View", + "scenario_grid_view": "Grid View", + "scenario_zoom_in": "Zoom In", + "scenario_zoom_out": "Zoom Out", + "scenario_fit_canvas": "Fit Canvas", + "scenario_lock": "Lock", + "scenario_unlock": "Unlock", + "scenario_comments": "Comments", + "scenario_undo": "Undo", + "scenario_redo": "Redo", + "scenario_alerts": "Alerts", + "scenario_test_run": "Test Run" } \ No newline at end of file diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index fe5f3647..6dd476bd 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -623,5 +623,17 @@ "scenario_script_node": "脚本执行", "scenario_condition_node": "条件判断", "scenario_delay_node": "延迟", - "scenario_loop_node": "循环" + "scenario_loop_node": "循环", + "scenario_fit_view": "适应视图", + "scenario_grid_view": "网格视图", + "scenario_zoom_in": "放大", + "scenario_zoom_out": "缩小", + "scenario_fit_canvas": "适应画布", + "scenario_lock": "锁定", + "scenario_unlock": "解锁", + "scenario_comments": "注释", + "scenario_undo": "撤销", + "scenario_redo": "重做", + "scenario_alerts": "提醒", + "scenario_test_run": "测试运行" } \ No newline at end of file diff --git a/src/pages/scenario/flowgram/FlowgramEditor.tsx b/src/pages/scenario/flowgram/FlowgramEditor.tsx index 307bcf38..b67da033 100644 --- a/src/pages/scenario/flowgram/FlowgramEditor.tsx +++ b/src/pages/scenario/flowgram/FlowgramEditor.tsx @@ -4,7 +4,6 @@ import { DockedPanelLayer } from '@flowgram.ai/panel-manager-plugin'; import '@flowgram.ai/free-layout-editor/index.css'; import { nodeRegistries } from './nodes'; import { useEditorProps } from './hooks/use-editor-props'; -import { FlowgramTools } from './components/tools'; import './editor.less'; export interface FlowgramEditorProps { @@ -47,7 +46,6 @@ const FlowgramEditor = forwardRef(
-
diff --git a/src/pages/scenario/flowgram/hooks/use-editor-props.tsx b/src/pages/scenario/flowgram/hooks/use-editor-props.tsx index 74293cbb..02d42852 100644 --- a/src/pages/scenario/flowgram/hooks/use-editor-props.tsx +++ b/src/pages/scenario/flowgram/hooks/use-editor-props.tsx @@ -7,6 +7,7 @@ import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'; import { createPanelManagerPlugin } from '@flowgram.ai/panel-manager-plugin'; import { createHistoryNodePlugin } from '@flowgram.ai/history-node-plugin'; import { FlowNodeRegistry } from '../nodes/http'; +import { createToolsPlugin } from '../plugins/tools-plugin'; export function useEditorProps( initialData: any, @@ -39,6 +40,7 @@ export function useEditorProps( factories: [], layerProps: {}, }), + createToolsPlugin(), ], onChange: (data) => { console.log('Workflow changed:', data); diff --git a/src/pages/scenario/flowgram/plugins/tools-plugin/DemoTools.tsx b/src/pages/scenario/flowgram/plugins/tools-plugin/DemoTools.tsx new file mode 100644 index 00000000..df4f69a4 --- /dev/null +++ b/src/pages/scenario/flowgram/plugins/tools-plugin/DemoTools.tsx @@ -0,0 +1,266 @@ +import React, { useState, useEffect } from 'react'; +import { + usePlaygroundTools, + useHistoryService, + useNodeService, +} from '@flowgram.ai/free-layout-editor'; +import { + DesktopOutlined, + AppstoreOutlined, + ZoomInOutlined, + ZoomOutOutlined, + FullscreenOutlined, + LockOutlined, + UnlockOutlined, + CommentOutlined, + UndoOutlined, + RedoOutlined, + ExclamationCircleOutlined, + PlusOutlined, + PlayCircleOutlined, +} from '@ant-design/icons'; +import { Dropdown, Menu, Button, Tooltip } from 'antd'; +import intl from 'react-intl-universal'; +import { NODE_TYPES } from '../../nodes/constants'; +import './styles.less'; + +export const DemoTools: React.FC = () => { + const playgroundTools = usePlaygroundTools(); + const historyService = useHistoryService(); + const nodeService = useNodeService(); + + const [zoom, setZoom] = useState(100); + const [canUndo, setCanUndo] = useState(false); + const [canRedo, setCanRedo] = useState(false); + const [isLocked, setIsLocked] = useState(false); + + useEffect(() => { + if (playgroundTools) { + const updateZoom = () => { + const currentZoom = playgroundTools.getZoom(); + setZoom(Math.round(currentZoom * 100)); + }; + updateZoom(); + + // Listen for zoom changes + const unsubscribe = playgroundTools.onZoomChange?.(updateZoom); + return () => unsubscribe?.(); + } + }, [playgroundTools]); + + useEffect(() => { + if (historyService) { + const updateHistory = () => { + setCanUndo(historyService.canUndo()); + setCanRedo(historyService.canRedo()); + }; + updateHistory(); + + const unsubscribe = historyService.onChange?.(updateHistory); + return () => unsubscribe?.(); + } + }, [historyService]); + + const handleZoom = (value: number) => { + if (playgroundTools) { + playgroundTools.setZoom(value / 100); + } + }; + + const handleZoomIn = () => { + if (playgroundTools) { + const current = playgroundTools.getZoom(); + playgroundTools.setZoom(Math.min(current + 0.1, 2)); + } + }; + + const handleZoomOut = () => { + if (playgroundTools) { + const current = playgroundTools.getZoom(); + playgroundTools.setZoom(Math.max(current - 0.1, 0.5)); + } + }; + + const handleFitView = () => { + if (playgroundTools?.viewport) { + playgroundTools.viewport.fitView(); + } + }; + + const handleUndo = () => { + if (historyService && canUndo) { + historyService.undo(); + } + }; + + const handleRedo = () => { + if (historyService && canRedo) { + historyService.redo(); + } + }; + + const handleAddNode = (type: string) => { + if (nodeService && playgroundTools?.viewport) { + const center = playgroundTools.viewport.getCenter(); + nodeService.createNode({ + type, + position: center, + }); + } + }; + + const zoomMenu = ( + handleZoom(Number(key))} + items={[ + { key: '50', label: '50%' }, + { key: '75', label: '75%' }, + { key: '100', label: '100%' }, + { key: '125', label: '125%' }, + { key: '150', label: '150%' }, + { key: '200', label: '200%' }, + ]} + /> + ); + + const addNodeMenu = ( + handleAddNode(key)} + items={[ + { key: NODE_TYPES.HTTP, label: intl.get('scenario_http_node'), icon: }, + { key: NODE_TYPES.SCRIPT, label: intl.get('scenario_script_node'), icon: }, + { key: NODE_TYPES.CONDITION, label: intl.get('scenario_condition_node'), icon: }, + { key: NODE_TYPES.DELAY, label: intl.get('scenario_delay_node'), icon: }, + { key: NODE_TYPES.LOOP, label: intl.get('scenario_loop_node'), icon: }, + ]} + /> + ); + + return ( +
+
+ + + + + + + + + +
+
+ ); +}; diff --git a/src/pages/scenario/flowgram/plugins/tools-plugin/index.tsx b/src/pages/scenario/flowgram/plugins/tools-plugin/index.tsx new file mode 100644 index 00000000..784e35e4 --- /dev/null +++ b/src/pages/scenario/flowgram/plugins/tools-plugin/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { createPlugin } from '@flowgram.ai/free-layout-editor'; +import { DemoTools } from './DemoTools'; + +export const createToolsPlugin = () => { + return createPlugin({ + name: 'tools-plugin', + layer: () => , + }); +}; diff --git a/src/pages/scenario/flowgram/plugins/tools-plugin/styles.less b/src/pages/scenario/flowgram/plugins/tools-plugin/styles.less new file mode 100644 index 00000000..4c12fd3a --- /dev/null +++ b/src/pages/scenario/flowgram/plugins/tools-plugin/styles.less @@ -0,0 +1,101 @@ +.demo-tools { + position: absolute; + top: 16px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + padding: 4px 8px; + display: flex; + align-items: center; + gap: 4px; + + .demo-tools-section { + display: flex; + align-items: center; + gap: 4px; + } + + .demo-tools-button { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border: none; + background: transparent; + color: rgba(0, 0, 0, 0.65); + cursor: pointer; + border-radius: 4px; + transition: all 0.2s; + + &:hover:not(:disabled) { + background: rgba(0, 0, 0, 0.06); + color: rgba(0, 0, 0, 0.85); + } + + &:disabled { + color: rgba(0, 0, 0, 0.25); + cursor: not-allowed; + } + } + + .demo-tools-zoom { + min-width: 56px; + font-size: 14px; + font-weight: 500; + } + + .demo-tools-divider { + width: 1px; + height: 24px; + background: rgba(0, 0, 0, 0.1); + margin: 0 4px; + } + + .demo-tools-add-button { + height: 36px; + padding: 0 16px; + border-radius: 4px; + font-weight: 500; + display: flex; + align-items: center; + gap: 6px; + } + + .demo-tools-run-button { + height: 36px; + padding: 0 16px; + border-radius: 4px; + font-weight: 500; + display: flex; + align-items: center; + gap: 6px; + margin-left: 4px; + } +} + +// Dark mode support +html[data-theme='dark'] .demo-tools { + background: #1f1f1f; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45); + + .demo-tools-button { + color: rgba(255, 255, 255, 0.65); + + &:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.08); + color: rgba(255, 255, 255, 0.85); + } + + &:disabled { + color: rgba(255, 255, 255, 0.25); + } + } + + .demo-tools-divider { + background: rgba(255, 255, 255, 0.15); + } +}