diff --git a/web/app/components/workflow/header/run-and-history.tsx b/web/app/components/workflow/header/run-and-history.tsx index 047104912c..d3c5b99964 100644 --- a/web/app/components/workflow/header/run-and-history.tsx +++ b/web/app/components/workflow/header/run-and-history.tsx @@ -35,7 +35,9 @@ const RunMode = memo(() => { 'hover:bg-state-accent-hover cursor-pointer', isRunning && 'bg-state-accent-hover !cursor-not-allowed', )} - onClick={() => handleWorkflowStartRunInWorkflow()} + onClick={() => { + handleWorkflowStartRunInWorkflow() + }} > { isRunning diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx index 6711ff2589..a6318dbfeb 100644 --- a/web/app/components/workflow/header/view-history.tsx +++ b/web/app/components/workflow/header/view-history.tsx @@ -17,7 +17,7 @@ import { useWorkflowInteractions, useWorkflowRun, } from '../hooks' -import { WorkflowRunningStatus } from '../types' +import { ControlMode, WorkflowRunningStatus } from '../types' import cn from '@/utils/classnames' import { PortalToFollowElem, @@ -58,6 +58,7 @@ const ViewHistory = ({ handleCancelDebugAndPreviewPanel, } = useWorkflowInteractions() const workflowStore = useWorkflowStore() + const setControlMode = useStore(s => s.setControlMode) const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({ appDetail: state.appDetail, setCurrentLogItem: state.setCurrentLogItem, @@ -173,6 +174,7 @@ const ViewHistory = ({ setOpen(false) handleNodesCancelSelected() handleCancelDebugAndPreviewPanel() + setControlMode(ControlMode.Hand) }} > { diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts index b2aa958025..463e9b3271 100644 --- a/web/app/components/workflow/hooks/index.ts +++ b/web/app/components/workflow/hooks/index.ts @@ -7,11 +7,12 @@ export * from './use-workflow' export * from './use-workflow-run' export * from './use-workflow-template' export * from './use-checklist' -export * from './use-workflow-mode' -export * from './use-workflow-interactions' export * from './use-selection-interactions' export * from './use-panel-interactions' export * from './use-workflow-start-run' export * from './use-nodes-layout' export * from './use-workflow-history' export * from './use-workflow-variables' +export * from './use-shortcuts' +export * from './use-workflow-interactions' +export * from './use-workflow-mode' diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 914901ebb1..87d1b4de8c 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -48,6 +48,7 @@ import { useHelpline } from './use-helpline' import { useNodesReadOnly, useWorkflow, + useWorkflowReadOnly, } from './use-workflow' import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' @@ -62,6 +63,7 @@ export const useNodesInteractions = () => { getAfterNodesInSameBranch, } = useWorkflow() const { getNodesReadOnly } = useNodesReadOnly() + const { getWorkflowReadOnly } = useWorkflowReadOnly() const { handleSetHelpline } = useHelpline() const { handleNodeIterationChildDrag, @@ -1029,14 +1031,7 @@ export const useNodesInteractions = () => { if (getNodesReadOnly()) return - const { - setClipboardElements, - shortcutsDisabled, - showFeaturesPanel, - } = workflowStore.getState() - - if (shortcutsDisabled || showFeaturesPanel) - return + const { setClipboardElements } = workflowStore.getState() const { getNodes, @@ -1062,14 +1057,9 @@ export const useNodesInteractions = () => { const { clipboardElements, - shortcutsDisabled, - showFeaturesPanel, mousePosition, } = workflowStore.getState() - if (shortcutsDisabled || showFeaturesPanel) - return - const { getNodes, setNodes, @@ -1107,6 +1097,11 @@ export const useNodesInteractions = () => { }) newNode.id = newNode.id + index + // If only the iteration start node is copied, remove the isIterationStart flag + // This new node is movable and can be placed anywhere + if (clipboardElements.length === 1 && newNode.data.isIterationStart) + newNode.data.isIterationStart = false + let newChildren: Node[] = [] if (nodeToPaste.data.type === BlockEnum.Iteration) { newNode.data._children = []; @@ -1145,14 +1140,6 @@ export const useNodesInteractions = () => { if (getNodesReadOnly()) return - const { - shortcutsDisabled, - showFeaturesPanel, - } = workflowStore.getState() - - if (shortcutsDisabled || showFeaturesPanel) - return - const { getNodes, edges, @@ -1175,7 +1162,7 @@ export const useNodesInteractions = () => { if (selectedNode) handleNodeDelete(selectedNode.id) - }, [store, workflowStore, getNodesReadOnly, handleNodeDelete]) + }, [store, getNodesReadOnly, handleNodeDelete]) const handleNodeResize = useCallback((nodeId: string, params: ResizeParamsWithDirection) => { if (getNodesReadOnly()) @@ -1234,14 +1221,7 @@ export const useNodesInteractions = () => { }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory]) const handleHistoryBack = useCallback(() => { - if (getNodesReadOnly()) - return - - const { - shortcutsDisabled, - } = workflowStore.getState() - - if (shortcutsDisabled) + if (getNodesReadOnly() || getWorkflowReadOnly()) return const { setEdges, setNodes } = store.getState() @@ -1253,17 +1233,10 @@ export const useNodesInteractions = () => { setEdges(edges) setNodes(nodes) - }, [store, undo, workflowHistoryStore, workflowStore, getNodesReadOnly]) + }, [store, undo, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly]) const handleHistoryForward = useCallback(() => { - if (getNodesReadOnly()) - return - - const { - shortcutsDisabled, - } = workflowStore.getState() - - if (shortcutsDisabled) + if (getNodesReadOnly() || getWorkflowReadOnly()) return const { setEdges, setNodes } = store.getState() @@ -1275,7 +1248,7 @@ export const useNodesInteractions = () => { setEdges(edges) setNodes(nodes) - }, [redo, store, workflowHistoryStore, workflowStore, getNodesReadOnly]) + }, [redo, store, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly]) return { handleNodeDragStart, diff --git a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts index 06d0113df6..203386f5a7 100644 --- a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts @@ -8,7 +8,9 @@ import { } from '../store' import { BlockEnum } from '../types' import { useWorkflowUpdate } from '../hooks' -import { useNodesReadOnly } from './use-workflow' +import { + useNodesReadOnly, +} from './use-workflow' import { syncWorkflowDraft } from '@/service/workflow' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { API_PREFIX } from '@/config' diff --git a/web/app/components/workflow/hooks/use-shortcuts.ts b/web/app/components/workflow/hooks/use-shortcuts.ts new file mode 100644 index 0000000000..9484f9c16e --- /dev/null +++ b/web/app/components/workflow/hooks/use-shortcuts.ts @@ -0,0 +1,186 @@ +import { useReactFlow } from 'reactflow' +import { useKeyPress } from 'ahooks' +import { useCallback } from 'react' +import { + getKeyboardKeyCodeBySystem, + isEventTargetInputArea, +} from '../utils' +import { useWorkflowHistoryStore } from '../workflow-history-store' +import { useWorkflowStore } from '../store' +import { + useEdgesInteractions, + useNodesInteractions, + useNodesSyncDraft, + useWorkflowMoveMode, + useWorkflowOrganize, + useWorkflowStartRun, +} from '.' + +export const useShortcuts = (): void => { + const { + handleNodesCopy, + handleNodesPaste, + handleNodesDuplicate, + handleNodesDelete, + handleHistoryBack, + handleHistoryForward, + } = useNodesInteractions() + const { handleStartWorkflowRun } = useWorkflowStartRun() + const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore() + const { handleSyncWorkflowDraft } = useNodesSyncDraft() + const { handleEdgeDelete } = useEdgesInteractions() + const workflowStore = useWorkflowStore() + const { + handleModeHand, + handleModePointer, + } = useWorkflowMoveMode() + const { handleLayout } = useWorkflowOrganize() + + const { + zoomIn, + zoomOut, + zoomTo, + fitView, + } = useReactFlow() + + const shouldHandleShortcut = useCallback((e: KeyboardEvent) => { + const { showFeaturesPanel } = workflowStore.getState() + return !showFeaturesPanel && !isEventTargetInputArea(e.target as HTMLElement) + }, [workflowStore]) + + useKeyPress(['delete', 'backspace'], (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleNodesDelete() + handleEdgeDelete() + } + }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleNodesCopy() + } + }, { exactMatch: true, useCapture: true }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleNodesPaste() + } + }, { exactMatch: true, useCapture: true }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.d`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleNodesDuplicate() + } + }, { exactMatch: true, useCapture: true }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleStartWorkflowRun() + } + }, { exactMatch: true, useCapture: true }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.z`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + workflowHistoryShortcutsEnabled && handleHistoryBack() + } + }, { exactMatch: true, useCapture: true }) + + useKeyPress( + [`${getKeyboardKeyCodeBySystem('ctrl')}.y`, `${getKeyboardKeyCodeBySystem('ctrl')}.shift.z`], + (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + workflowHistoryShortcutsEnabled && handleHistoryForward() + } + }, + { exactMatch: true, useCapture: true }, + ) + + useKeyPress('h', (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleModeHand() + } + }, { + exactMatch: true, + useCapture: true, + }) + + useKeyPress('v', (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleModePointer() + } + }, { + exactMatch: true, + useCapture: true, + }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.o`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + handleLayout() + } + }, { exactMatch: true, useCapture: true }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.1`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + fitView() + handleSyncWorkflowDraft() + } + }, { + exactMatch: true, + useCapture: true, + }) + + useKeyPress('shift.1', (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + zoomTo(1) + handleSyncWorkflowDraft() + } + }, { + exactMatch: true, + useCapture: true, + }) + + useKeyPress('shift.5', (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + zoomTo(0.5) + handleSyncWorkflowDraft() + } + }, { + exactMatch: true, + useCapture: true, + }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.dash`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + zoomOut() + handleSyncWorkflowDraft() + } + }, { + exactMatch: true, + useCapture: true, + }) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.equalsign`, (e) => { + if (shouldHandleShortcut(e)) { + e.preventDefault() + zoomIn() + handleSyncWorkflowDraft() + } + }, { + exactMatch: true, + useCapture: true, + }) +} diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts index 820d5fe2fa..47b8a30a5a 100644 --- a/web/app/components/workflow/hooks/use-workflow-interactions.ts +++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts @@ -3,17 +3,29 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { useReactFlow } from 'reactflow' -import { useWorkflowStore } from '../store' -import { DSL_EXPORT_CHECK, WORKFLOW_DATA_UPDATE } from '../constants' -import type { WorkflowDataUpdator } from '../types' +import { useReactFlow, useStoreApi } from 'reactflow' +import produce from 'immer' +import { useStore, useWorkflowStore } from '../store' import { + CUSTOM_NODE, DSL_EXPORT_CHECK, + WORKFLOW_DATA_UPDATE, +} from '../constants' +import type { Node, WorkflowDataUpdator } from '../types' +import { ControlMode } from '../types' +import { + getLayoutByDagre, initialEdges, initialNodes, } from '../utils' +import { + useNodesReadOnly, + useSelectionInteractions, + useWorkflowReadOnly, +} from '../hooks' import { useEdgesInteractions } from './use-edges-interactions' import { useNodesInteractions } from './use-nodes-interactions' import { useNodesSyncDraft } from './use-nodes-sync-draft' +import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' import { useEventEmitterContextContext } from '@/context/event-emitter' import { fetchWorkflowDraft } from '@/service/workflow' import { exportAppConfig } from '@/service/apps' @@ -39,6 +51,158 @@ export const useWorkflowInteractions = () => { } } +export const useWorkflowMoveMode = () => { + const setControlMode = useStore(s => s.setControlMode) + const { + getNodesReadOnly, + } = useNodesReadOnly() + const { handleSelectionCancel } = useSelectionInteractions() + + const handleModePointer = useCallback(() => { + if (getNodesReadOnly()) + return + + setControlMode(ControlMode.Pointer) + }, [getNodesReadOnly, setControlMode]) + + const handleModeHand = useCallback(() => { + if (getNodesReadOnly()) + return + + setControlMode(ControlMode.Hand) + handleSelectionCancel() + }, [getNodesReadOnly, setControlMode, handleSelectionCancel]) + + return { + handleModePointer, + handleModeHand, + } +} + +export const useWorkflowOrganize = () => { + const workflowStore = useWorkflowStore() + const store = useStoreApi() + const reactflow = useReactFlow() + const { getNodesReadOnly } = useNodesReadOnly() + const { saveStateToHistory } = useWorkflowHistory() + const { handleSyncWorkflowDraft } = useNodesSyncDraft() + + const handleLayout = useCallback(async () => { + if (getNodesReadOnly()) + return + workflowStore.setState({ nodeAnimation: true }) + const { + getNodes, + edges, + setNodes, + } = store.getState() + const { setViewport } = reactflow + const nodes = getNodes() + const layout = getLayoutByDagre(nodes, edges) + const rankMap = {} as Record + + nodes.forEach((node) => { + if (!node.parentId && node.type === CUSTOM_NODE) { + const rank = layout.node(node.id).rank! + + if (!rankMap[rank]) { + rankMap[rank] = node + } + else { + if (rankMap[rank].position.y > node.position.y) + rankMap[rank] = node + } + } + }) + + const newNodes = produce(nodes, (draft) => { + draft.forEach((node) => { + if (!node.parentId && node.type === CUSTOM_NODE) { + const nodeWithPosition = layout.node(node.id) + + node.position = { + x: nodeWithPosition.x - node.width! / 2, + y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2, + } + } + }) + }) + setNodes(newNodes) + const zoom = 0.7 + setViewport({ + x: 0, + y: 0, + zoom, + }) + saveStateToHistory(WorkflowHistoryEvent.LayoutOrganize) + setTimeout(() => { + handleSyncWorkflowDraft() + }) + }, [getNodesReadOnly, store, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory]) + return { + handleLayout, + } +} + +export const useWorkflowZoom = () => { + const { handleSyncWorkflowDraft } = useNodesSyncDraft() + const { getWorkflowReadOnly } = useWorkflowReadOnly() + const { + zoomIn, + zoomOut, + zoomTo, + fitView, + } = useReactFlow() + + const handleFitView = useCallback(() => { + if (getWorkflowReadOnly()) + return + + fitView() + handleSyncWorkflowDraft() + }, [getWorkflowReadOnly, fitView, handleSyncWorkflowDraft]) + + const handleBackToOriginalSize = useCallback(() => { + if (getWorkflowReadOnly()) + return + + zoomTo(1) + handleSyncWorkflowDraft() + }, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft]) + + const handleSizeToHalf = useCallback(() => { + if (getWorkflowReadOnly()) + return + + zoomTo(0.5) + handleSyncWorkflowDraft() + }, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft]) + + const handleZoomOut = useCallback(() => { + if (getWorkflowReadOnly()) + return + + zoomOut() + handleSyncWorkflowDraft() + }, [getWorkflowReadOnly, zoomOut, handleSyncWorkflowDraft]) + + const handleZoomIn = useCallback(() => { + if (getWorkflowReadOnly()) + return + + zoomIn() + handleSyncWorkflowDraft() + }, [getWorkflowReadOnly, zoomIn, handleSyncWorkflowDraft]) + + return { + handleFitView, + handleBackToOriginalSize, + handleSizeToHalf, + handleZoomOut, + handleZoomIn, + } +} + export const useWorkflowUpdate = () => { const reactflow = useReactFlow() const workflowStore = useWorkflowStore() diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 71f0600d39..cfff4220fa 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -7,19 +7,14 @@ import { import dayjs from 'dayjs' import { uniqBy } from 'lodash-es' import { useContext } from 'use-context-selector' -import produce from 'immer' import { getIncomers, getOutgoers, - useReactFlow, useStoreApi, } from 'reactflow' import type { Connection, } from 'reactflow' -import { - getLayoutByDagre, -} from '../utils' import type { Edge, Node, @@ -34,15 +29,12 @@ import { useWorkflowStore, } from '../store' import { - CUSTOM_NODE, SUPPORT_OUTPUT_VARS_NODE, } from '../constants' import { CUSTOM_NOTE_NODE } from '../note-node/constants' import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' import { useNodesExtraData } from './use-nodes-data' import { useWorkflowTemplate } from './use-workflow-template' -import { useNodesSyncDraft } from './use-nodes-sync-draft' -import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' import { useStore as useAppStore } from '@/app/components/app/store' import { fetchNodesDefaultConfigs, @@ -68,68 +60,13 @@ export const useIsChatMode = () => { export const useWorkflow = () => { const { locale } = useContext(I18n) const store = useStoreApi() - const reactflow = useReactFlow() const workflowStore = useWorkflowStore() const nodesExtraData = useNodesExtraData() - const { handleSyncWorkflowDraft } = useNodesSyncDraft() - const { saveStateToHistory } = useWorkflowHistory() - const setPanelWidth = useCallback((width: number) => { localStorage.setItem('workflow-node-panel-width', `${width}`) workflowStore.setState({ panelWidth: width }) }, [workflowStore]) - const handleLayout = useCallback(async () => { - workflowStore.setState({ nodeAnimation: true }) - const { - getNodes, - edges, - setNodes, - } = store.getState() - const { setViewport } = reactflow - const nodes = getNodes() - const layout = getLayoutByDagre(nodes, edges) - const rankMap = {} as Record - - nodes.forEach((node) => { - if (!node.parentId && node.type === CUSTOM_NODE) { - const rank = layout.node(node.id).rank! - - if (!rankMap[rank]) { - rankMap[rank] = node - } - else { - if (rankMap[rank].position.y > node.position.y) - rankMap[rank] = node - } - } - }) - - const newNodes = produce(nodes, (draft) => { - draft.forEach((node) => { - if (!node.parentId && node.type === CUSTOM_NODE) { - const nodeWithPosition = layout.node(node.id) - - node.position = { - x: nodeWithPosition.x - node.width! / 2, - y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2, - } - } - }) - }) - setNodes(newNodes) - const zoom = 0.7 - setViewport({ - x: 0, - y: 0, - zoom, - }) - saveStateToHistory(WorkflowHistoryEvent.LayoutOrganize) - setTimeout(() => { - handleSyncWorkflowDraft() - }) - }, [workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft]) - const getTreeLeafNodes = useCallback((nodeId: string) => { const { getNodes, @@ -392,19 +329,8 @@ export const useWorkflow = () => { return nodes.find(node => node.id === nodeId) || nodes.find(node => node.data.type === BlockEnum.Start) }, [store]) - const enableShortcuts = useCallback(() => { - const { setShortcutsDisabled } = workflowStore.getState() - setShortcutsDisabled(false) - }, [workflowStore]) - - const disableShortcuts = useCallback(() => { - const { setShortcutsDisabled } = workflowStore.getState() - setShortcutsDisabled(true) - }, [workflowStore]) - return { setPanelWidth, - handleLayout, getTreeLeafNodes, getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent, @@ -418,8 +344,6 @@ export const useWorkflow = () => { getNode, getBeforeNodeById, getIterationNodeChildren, - enableShortcuts, - disableShortcuts, } } diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index c06e1b9524..d96faa8677 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -12,7 +12,6 @@ import { import { setAutoFreeze } from 'immer' import { useEventListener, - useKeyPress, } from 'ahooks' import ReactFlow, { Background, @@ -34,6 +33,9 @@ import type { EnvironmentVariable, Node, } from './types' +import { + ControlMode, +} from './types' import { WorkflowContextProvider } from './context' import { useDSL, @@ -43,10 +45,10 @@ import { useNodesSyncDraft, usePanelInteractions, useSelectionInteractions, + useShortcuts, useWorkflow, useWorkflowInit, useWorkflowReadOnly, - useWorkflowStartRun, useWorkflowUpdate, } from './hooks' import Header from './header' @@ -70,10 +72,8 @@ import { useWorkflowStore, } from './store' import { - getKeyboardKeyCodeBySystem, initialEdges, initialNodes, - isEventTargetInputArea, } from './utils' import { CUSTOM_NODE, @@ -81,7 +81,7 @@ import { ITERATION_CHILDREN_Z_INDEX, WORKFLOW_DATA_UPDATE, } from './constants' -import { WorkflowHistoryProvider, useWorkflowHistoryStore } from './workflow-history-store' +import { WorkflowHistoryProvider } from './workflow-history-store' import Loading from '@/app/components/base/loading' import { FeaturesProvider } from '@/app/components/base/features' import type { Features as FeaturesData } from '@/app/components/base/features/types' @@ -225,17 +225,12 @@ const Workflow: FC = memo(({ handleNodeConnectStart, handleNodeConnectEnd, handleNodeContextMenu, - handleNodesCopy, - handleNodesPaste, - handleNodesDuplicate, - handleNodesDelete, handleHistoryBack, handleHistoryForward, } = useNodesInteractions() const { handleEdgeEnter, handleEdgeLeave, - handleEdgeDelete, handleEdgesChange, } = useEdgesInteractions() const { @@ -250,7 +245,6 @@ const Workflow: FC = memo(({ const { isValidConnection, } = useWorkflow() - const { handleStartWorkflowRun } = useWorkflowStartRun() const { exportCheck, handleExportDSL, @@ -262,41 +256,7 @@ const Workflow: FC = memo(({ }, }) - const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore() - - useKeyPress(['delete', 'backspace'], (e) => { - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - handleNodesDelete() - }) - useKeyPress(['delete', 'backspace'], handleEdgeDelete) - useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, (e) => { - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - handleNodesCopy() - }, { exactMatch: true, useCapture: true }) - useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, (e) => { - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - handleNodesPaste() - }, { exactMatch: true, useCapture: true }) - useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.d`, handleNodesDuplicate, { exactMatch: true, useCapture: true }) - useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, handleStartWorkflowRun, { exactMatch: true, useCapture: true }) - useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, handleStartWorkflowRun, { exactMatch: true, useCapture: true }) - useKeyPress( - `${getKeyboardKeyCodeBySystem('ctrl')}.z`, - () => workflowHistoryShortcutsEnabled && handleHistoryBack(), - { exactMatch: true, useCapture: true }, - ) - - useKeyPress( - [`${getKeyboardKeyCodeBySystem('ctrl')}.y`, `${getKeyboardKeyCodeBySystem('ctrl')}.shift.z`], - () => workflowHistoryShortcutsEnabled && handleHistoryForward(), - { exactMatch: true, useCapture: true }, - ) + useShortcuts() const store = useStoreApi() if (process.env.NODE_ENV === 'development') { @@ -388,14 +348,14 @@ const Workflow: FC = memo(({ nodesConnectable={!nodesReadOnly} nodesFocusable={!nodesReadOnly} edgesFocusable={!nodesReadOnly} - panOnDrag={controlMode === 'hand' && !workflowReadOnly} + panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly} zoomOnPinch={!workflowReadOnly} zoomOnScroll={!workflowReadOnly} zoomOnDoubleClick={!workflowReadOnly} isValidConnection={isValidConnection} selectionKeyCode={null} selectionMode={SelectionMode.Partial} - selectionOnDrag={controlMode === 'pointer' && !workflowReadOnly} + selectionOnDrag={controlMode === ControlMode.Pointer && !workflowReadOnly} minZoom={0.25} > { const { t } = useTranslation() const controlMode = useStore(s => s.controlMode) - const setControlMode = useStore(s => s.setControlMode) - const { handleLayout } = useWorkflow() + const { handleModePointer, handleModeHand } = useWorkflowMoveMode() + const { handleLayout } = useWorkflowOrganize() const { handleAddNote } = useOperator() const { nodesReadOnly, getNodesReadOnly, } = useNodesReadOnly() - const { handleSelectionCancel } = useSelectionInteractions() - - const handleModePointer = useCallback(() => { - if (getNodesReadOnly()) - return - setControlMode('pointer') - }, [getNodesReadOnly, setControlMode]) - const handleModeHand = useCallback(() => { - if (getNodesReadOnly()) - return - setControlMode('hand') - handleSelectionCancel() - }, [getNodesReadOnly, setControlMode, handleSelectionCancel]) - - useKeyPress('h', (e) => { - if (getNodesReadOnly()) - return - - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - e.preventDefault() - handleModeHand() - }, { - exactMatch: true, - useCapture: true, - }) - - useKeyPress('v', (e) => { - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - e.preventDefault() - handleModePointer() - }, { - exactMatch: true, - useCapture: true, - }) - - const goLayout = () => { - if (getNodesReadOnly()) - return - handleLayout() - } - - useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.o`, (e) => { - e.preventDefault() - goLayout() - }, { exactMatch: true, useCapture: true }) const addNote = (e: MouseEvent) => { if (getNodesReadOnly()) @@ -110,7 +61,7 @@ const Control = () => {
{
{ 'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer', `${nodesReadOnly && '!cursor-not-allowed opacity-50'}`, )} - onClick={goLayout} + onClick={handleLayout} >
diff --git a/web/app/components/workflow/operator/zoom-in-out.tsx b/web/app/components/workflow/operator/zoom-in-out.tsx index 13047cdafc..654097b430 100644 --- a/web/app/components/workflow/operator/zoom-in-out.tsx +++ b/web/app/components/workflow/operator/zoom-in-out.tsx @@ -9,7 +9,6 @@ import { RiZoomInLine, RiZoomOutLine, } from '@remixicon/react' -import { useKeyPress } from 'ahooks' import { useTranslation } from 'react-i18next' import { useReactFlow, @@ -20,9 +19,7 @@ import { useWorkflowReadOnly, } from '../hooks' import { - getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem, - isEventTargetInputArea, } from '../utils' import ShortcutsName from '../shortcuts-name' import TipPopup from './tip-popup' @@ -116,87 +113,6 @@ const ZoomInOut: FC = () => { handleSyncWorkflowDraft() } - useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.1`, (e) => { - e.preventDefault() - if (workflowReadOnly) - return - - fitView() - handleSyncWorkflowDraft() - }, { - exactMatch: true, - useCapture: true, - }) - - useKeyPress('shift.1', (e) => { - if (workflowReadOnly) - return - - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - e.preventDefault() - zoomTo(1) - handleSyncWorkflowDraft() - }, { - exactMatch: true, - useCapture: true, - }) - - useKeyPress('shift.2', (e) => { - if (workflowReadOnly) - return - - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - e.preventDefault() - zoomTo(2) - handleSyncWorkflowDraft() - }, { - exactMatch: true, - useCapture: true, - }) - - useKeyPress('shift.5', (e) => { - if (workflowReadOnly) - return - - if (isEventTargetInputArea(e.target as HTMLElement)) - return - - e.preventDefault() - zoomTo(0.5) - handleSyncWorkflowDraft() - }, { - exactMatch: true, - useCapture: true, - }) - - useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.dash`, (e) => { - e.preventDefault() - if (workflowReadOnly) - return - - zoomOut() - handleSyncWorkflowDraft() - }, { - exactMatch: true, - useCapture: true, - }) - - useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.equalsign`, (e) => { - e.preventDefault() - if (workflowReadOnly) - return - - zoomIn() - handleSyncWorkflowDraft() - }, { - exactMatch: true, - useCapture: true, - }) - const handleTrigger = useCallback(() => { if (getWorkflowReadOnly()) return @@ -289,11 +205,6 @@ const ZoomInOut: FC = () => { ) } - { - option.key === ZoomType.zoomTo200 && ( - - ) - }
)) } diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx index 864e24aa80..a35bedc929 100644 --- a/web/app/components/workflow/panel/index.tsx +++ b/web/app/components/workflow/panel/index.tsx @@ -7,7 +7,6 @@ import { Panel as NodePanel } from '../nodes' import { useStore } from '../store' import { useIsChatMode, - useWorkflow, } from '../hooks' import DebugAndPreview from './debug-and-preview' import Record from './record' @@ -28,10 +27,6 @@ const Panel: FC = () => { const showEnvPanel = useStore(s => s.showEnvPanel) const showChatVariablePanel = useStore(s => s.showChatVariablePanel) const isRestoring = useStore(s => s.isRestoring) - const { - enableShortcuts, - disableShortcuts, - } = useWorkflow() const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({ currentLogItem: state.currentLogItem, setCurrentLogItem: state.setCurrentLogItem, @@ -44,8 +39,6 @@ const Panel: FC = () => {
{ diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index 854684e5c3..2e5e774191 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -99,8 +99,6 @@ type Shape = { setWorkflowTools: (tools: ToolWithProvider[]) => void clipboardElements: Node[] setClipboardElements: (clipboardElements: Node[]) => void - shortcutsDisabled: boolean - setShortcutsDisabled: (shortcutsDisabled: boolean) => void showDebugAndPreviewPanel: boolean setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void showEnvPanel: boolean @@ -217,8 +215,6 @@ export const createWorkflowStore = () => { setWorkflowTools: workflowTools => set(() => ({ workflowTools })), clipboardElements: [], setClipboardElements: clipboardElements => set(() => ({ clipboardElements })), - shortcutsDisabled: false, - setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })), showDebugAndPreviewPanel: false, setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })), showEnvPanel: false, diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 03f78ea21b..034376fed5 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -29,6 +29,11 @@ export enum BlockEnum { Assigner = 'assigner', // is now named as VariableAssigner } +export enum ControlMode { + Pointer = 'pointer', + Hand = 'hand', +} + export type Branch = { id: string name: string