Compare commits
7 Commits
main
...
feat/workf
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4fcb048607 | ||
![]() |
18b61591b8 | ||
![]() |
5408e923e3 | ||
![]() |
1bcfa747db | ||
![]() |
071b7d607b | ||
![]() |
1a5e5cd8f8 | ||
![]() |
3bd5f9542e |
@ -5,7 +5,7 @@ import { useWorkflowStore } from '../store'
|
|||||||
export const usePanelInteractions = () => {
|
export const usePanelInteractions = () => {
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
|
|
||||||
const handlePaneContextMenu = useCallback((e: MouseEvent) => {
|
const handlePanelContextMenu = useCallback((e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const container = document.querySelector('#workflow-container')
|
const container = document.querySelector('#workflow-container')
|
||||||
const { x, y } = container!.getBoundingClientRect()
|
const { x, y } = container!.getBoundingClientRect()
|
||||||
@ -17,7 +17,7 @@ export const usePanelInteractions = () => {
|
|||||||
})
|
})
|
||||||
}, [workflowStore])
|
}, [workflowStore])
|
||||||
|
|
||||||
const handlePaneContextmenuCancel = useCallback(() => {
|
const handlePanelContextmenuCancel = useCallback(() => {
|
||||||
workflowStore.setState({
|
workflowStore.setState({
|
||||||
panelMenu: undefined,
|
panelMenu: undefined,
|
||||||
})
|
})
|
||||||
@ -30,8 +30,8 @@ export const usePanelInteractions = () => {
|
|||||||
}, [workflowStore])
|
}, [workflowStore])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handlePaneContextMenu,
|
handlePanelContextMenu,
|
||||||
handlePaneContextmenuCancel,
|
handlePanelContextmenuCancel,
|
||||||
handleNodeContextmenuCancel,
|
handleNodeContextmenuCancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,13 @@ export const useShortcuts = (): void => {
|
|||||||
}
|
}
|
||||||
}, { exactMatch: true, useCapture: true })
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress(`${getKeyboardKeyCodeBySystem('shift')}.a`, (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
workflowStore.setState({ showAddBlock: true })
|
||||||
|
}
|
||||||
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, (e) => {
|
useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, (e) => {
|
||||||
if (shouldHandleShortcut(e)) {
|
if (shouldHandleShortcut(e)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -239,8 +239,8 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
handleSelectionDrag,
|
handleSelectionDrag,
|
||||||
} = useSelectionInteractions()
|
} = useSelectionInteractions()
|
||||||
const {
|
const {
|
||||||
handlePaneContextMenu,
|
handlePanelContextMenu,
|
||||||
handlePaneContextmenuCancel,
|
handlePanelContextmenuCancel,
|
||||||
} = usePanelInteractions()
|
} = usePanelInteractions()
|
||||||
const {
|
const {
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
@ -304,7 +304,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
<UpdateDSLModal
|
<UpdateDSLModal
|
||||||
onCancel={() => setShowImportDSLModal(false)}
|
onCancel={() => setShowImportDSLModal(false)}
|
||||||
onBackup={exportCheck}
|
onBackup={exportCheck}
|
||||||
onImport={handlePaneContextmenuCancel}
|
onImport={handlePanelContextmenuCancel}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -338,7 +338,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
onSelectionStart={handleSelectionStart}
|
onSelectionStart={handleSelectionStart}
|
||||||
onSelectionChange={handleSelectionChange}
|
onSelectionChange={handleSelectionChange}
|
||||||
onSelectionDrag={handleSelectionDrag}
|
onSelectionDrag={handleSelectionDrag}
|
||||||
onPaneContextMenu={handlePaneContextMenu}
|
onPaneContextMenu={handlePanelContextMenu}
|
||||||
connectionLineComponent={CustomConnectionLine}
|
connectionLineComponent={CustomConnectionLine}
|
||||||
connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
|
connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
|
||||||
defaultViewport={viewport}
|
defaultViewport={viewport}
|
||||||
|
@ -13,14 +13,14 @@ import { usePanelInteractions } from './hooks'
|
|||||||
const NodeContextmenu = () => {
|
const NodeContextmenu = () => {
|
||||||
const ref = useRef(null)
|
const ref = useRef(null)
|
||||||
const nodes = useNodes()
|
const nodes = useNodes()
|
||||||
const { handleNodeContextmenuCancel, handlePaneContextmenuCancel } = usePanelInteractions()
|
const { handleNodeContextmenuCancel, handlePanelContextmenuCancel } = usePanelInteractions()
|
||||||
const nodeMenu = useStore(s => s.nodeMenu)
|
const nodeMenu = useStore(s => s.nodeMenu)
|
||||||
const currentNode = nodes.find(node => node.id === nodeMenu?.nodeId) as Node
|
const currentNode = nodes.find(node => node.id === nodeMenu?.nodeId) as Node
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeMenu)
|
if (nodeMenu)
|
||||||
handlePaneContextmenuCancel()
|
handlePanelContextmenuCancel()
|
||||||
}, [nodeMenu, handlePaneContextmenuCancel])
|
}, [nodeMenu, handlePanelContextmenuCancel])
|
||||||
|
|
||||||
useClickAway(() => {
|
useClickAway(() => {
|
||||||
handleNodeContextmenuCancel()
|
handleNodeContextmenuCancel()
|
||||||
|
@ -1,58 +1,61 @@
|
|||||||
import {
|
import React, { memo, useCallback, useEffect, useState } from 'react'
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { RiAddCircleFill } from '@remixicon/react'
|
import { RiAddCircleFill } from '@remixicon/react'
|
||||||
import { useStoreApi } from 'reactflow'
|
import { useStoreApi } from 'reactflow'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { OffsetOptions } from '@floating-ui/react'
|
import type { OffsetOptions } from '@floating-ui/react'
|
||||||
import {
|
import { generateNewNode } from '../utils'
|
||||||
generateNewNode,
|
|
||||||
} from '../utils'
|
|
||||||
import {
|
import {
|
||||||
useAvailableBlocks,
|
useAvailableBlocks,
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
usePanelInteractions,
|
usePanelInteractions,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
import { NODES_INITIAL_DATA } from '../constants'
|
import { NODES_INITIAL_DATA } from '../constants'
|
||||||
import { useWorkflowStore } from '../store'
|
import { useStore, useWorkflowStore } from '../store'
|
||||||
import TipPopup from './tip-popup'
|
import TipPopup from './tip-popup'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import BlockSelector from '@/app/components/workflow/block-selector'
|
import BlockSelector from '@/app/components/workflow/block-selector'
|
||||||
import type {
|
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
||||||
OnSelectBlock,
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
} from '@/app/components/workflow/types'
|
|
||||||
import {
|
|
||||||
BlockEnum,
|
|
||||||
} from '@/app/components/workflow/types'
|
|
||||||
|
|
||||||
type AddBlockProps = {
|
type AddBlockProps = {
|
||||||
renderTrigger?: (open: boolean) => React.ReactNode
|
renderTrigger?: (open: boolean) => React.ReactNode
|
||||||
offset?: OffsetOptions
|
offset?: OffsetOptions
|
||||||
|
useShortcut?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddBlock = ({
|
const AddBlock = ({
|
||||||
renderTrigger,
|
renderTrigger,
|
||||||
offset,
|
offset,
|
||||||
|
useShortcut = false,
|
||||||
}: AddBlockProps) => {
|
}: AddBlockProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const { nodesReadOnly } = useNodesReadOnly()
|
const { nodesReadOnly } = useNodesReadOnly()
|
||||||
const { handlePaneContextmenuCancel } = usePanelInteractions()
|
const { handlePanelContextmenuCancel } = usePanelInteractions()
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
|
const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
|
||||||
|
|
||||||
|
const showAddBlock = useStore(state => state.showAddBlock)
|
||||||
|
const setShowAddBlock = useStore(state => state.setShowAddBlock)
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (useShortcut && showAddBlock)
|
||||||
|
setIsOpen(true)
|
||||||
|
}, [useShortcut, showAddBlock])
|
||||||
|
|
||||||
const handleOpenChange = useCallback((open: boolean) => {
|
const handleOpenChange = useCallback((open: boolean) => {
|
||||||
setOpen(open)
|
setIsOpen(open)
|
||||||
|
if (useShortcut)
|
||||||
|
setShowAddBlock(open)
|
||||||
|
|
||||||
if (!open)
|
if (!open)
|
||||||
handlePaneContextmenuCancel()
|
handlePanelContextmenuCancel()
|
||||||
}, [handlePaneContextmenuCancel])
|
}, [useShortcut, setShowAddBlock, handlePanelContextmenuCancel])
|
||||||
|
|
||||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||||
const {
|
const { getNodes } = store.getState()
|
||||||
getNodes,
|
|
||||||
} = store.getState()
|
|
||||||
const nodes = getNodes()
|
const nodes = getNodes()
|
||||||
const nodesWithSameType = nodes.filter(node => node.data.type === type)
|
const nodesWithSameType = nodes.filter(node => node.data.type === type)
|
||||||
const newNode = generateNewNode({
|
const newNode = generateNewNode({
|
||||||
@ -67,16 +70,12 @@ const AddBlock = ({
|
|||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
workflowStore.setState({
|
workflowStore.setState({ candidateNode: newNode })
|
||||||
candidateNode: newNode,
|
|
||||||
})
|
|
||||||
}, [store, workflowStore, t])
|
}, [store, workflowStore, t])
|
||||||
|
|
||||||
const renderTriggerElement = useCallback((open: boolean) => {
|
const renderTriggerElement = useCallback((open: boolean) => {
|
||||||
return (
|
return (
|
||||||
<TipPopup
|
<TipPopup title={t('workflow.common.addBlock')} shortcuts={['shift', 'a']}>
|
||||||
title={t('workflow.common.addBlock')}
|
|
||||||
>
|
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
'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'}`,
|
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||||
@ -90,7 +89,7 @@ const AddBlock = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockSelector
|
<BlockSelector
|
||||||
open={open}
|
open={isOpen}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
disabled={nodesReadOnly}
|
disabled={nodesReadOnly}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
|
@ -44,7 +44,9 @@ const Control = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
|
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
|
||||||
<AddBlock />
|
<AddBlock
|
||||||
|
useShortcut={true}
|
||||||
|
/>
|
||||||
<TipPopup title={t('workflow.nodes.note.addNote')}>
|
<TipPopup title={t('workflow.nodes.note.addNote')}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
@ -24,7 +24,7 @@ const PanelContextmenu = () => {
|
|||||||
const clipboardElements = useStore(s => s.clipboardElements)
|
const clipboardElements = useStore(s => s.clipboardElements)
|
||||||
const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal)
|
const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal)
|
||||||
const { handleNodesPaste } = useNodesInteractions()
|
const { handleNodesPaste } = useNodesInteractions()
|
||||||
const { handlePaneContextmenuCancel, handleNodeContextmenuCancel } = usePanelInteractions()
|
const { handlePanelContextmenuCancel, handleNodeContextmenuCancel } = usePanelInteractions()
|
||||||
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
||||||
const { handleAddNote } = useOperator()
|
const { handleAddNote } = useOperator()
|
||||||
const { exportCheck } = useDSL()
|
const { exportCheck } = useDSL()
|
||||||
@ -35,7 +35,7 @@ const PanelContextmenu = () => {
|
|||||||
}, [panelMenu, handleNodeContextmenuCancel])
|
}, [panelMenu, handleNodeContextmenuCancel])
|
||||||
|
|
||||||
useClickAway(() => {
|
useClickAway(() => {
|
||||||
handlePaneContextmenuCancel()
|
handlePanelContextmenuCancel()
|
||||||
}, ref)
|
}, ref)
|
||||||
|
|
||||||
const renderTrigger = () => {
|
const renderTrigger = () => {
|
||||||
@ -44,6 +44,7 @@ const PanelContextmenu = () => {
|
|||||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||||
>
|
>
|
||||||
{t('workflow.common.addBlock')}
|
{t('workflow.common.addBlock')}
|
||||||
|
<ShortcutsName keys={['shift', 'a']} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -67,13 +68,14 @@ const PanelContextmenu = () => {
|
|||||||
mainAxis: -36,
|
mainAxis: -36,
|
||||||
crossAxis: -4,
|
crossAxis: -4,
|
||||||
}}
|
}}
|
||||||
|
useShortcut={false}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
handleAddNote()
|
handleAddNote()
|
||||||
handlePaneContextmenuCancel()
|
handlePanelContextmenuCancel()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('workflow.nodes.note.addNote')}
|
{t('workflow.nodes.note.addNote')}
|
||||||
@ -82,7 +84,7 @@ const PanelContextmenu = () => {
|
|||||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleStartWorkflowRun()
|
handleStartWorkflowRun()
|
||||||
handlePaneContextmenuCancel()
|
handlePanelContextmenuCancel()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('workflow.common.run')}
|
{t('workflow.common.run')}
|
||||||
@ -99,7 +101,7 @@ const PanelContextmenu = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (clipboardElements.length) {
|
if (clipboardElements.length) {
|
||||||
handleNodesPaste()
|
handleNodesPaste()
|
||||||
handlePaneContextmenuCancel()
|
handlePanelContextmenuCancel()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -162,6 +162,8 @@ type Shape = {
|
|||||||
setControlPromptEditorRerenderKey: (controlPromptEditorRerenderKey: number) => void
|
setControlPromptEditorRerenderKey: (controlPromptEditorRerenderKey: number) => void
|
||||||
showImportDSLModal: boolean
|
showImportDSLModal: boolean
|
||||||
setShowImportDSLModal: (showImportDSLModal: boolean) => void
|
setShowImportDSLModal: (showImportDSLModal: boolean) => void
|
||||||
|
showAddBlock: boolean
|
||||||
|
setShowAddBlock: (showAddBlock: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createWorkflowStore = () => {
|
export const createWorkflowStore = () => {
|
||||||
@ -262,6 +264,8 @@ export const createWorkflowStore = () => {
|
|||||||
setControlPromptEditorRerenderKey: controlPromptEditorRerenderKey => set(() => ({ controlPromptEditorRerenderKey })),
|
setControlPromptEditorRerenderKey: controlPromptEditorRerenderKey => set(() => ({ controlPromptEditorRerenderKey })),
|
||||||
showImportDSLModal: false,
|
showImportDSLModal: false,
|
||||||
setShowImportDSLModal: showImportDSLModal => set(() => ({ showImportDSLModal })),
|
setShowImportDSLModal: showImportDSLModal => set(() => ({ showImportDSLModal })),
|
||||||
|
showAddBlock: false,
|
||||||
|
setShowAddBlock: showAddBlock => set(() => ({ showAddBlock })),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,6 +427,7 @@ export const isMac = () => {
|
|||||||
const specialKeysNameMap: Record<string, string | undefined> = {
|
const specialKeysNameMap: Record<string, string | undefined> = {
|
||||||
ctrl: '⌘',
|
ctrl: '⌘',
|
||||||
alt: '⌥',
|
alt: '⌥',
|
||||||
|
shift: '⇧',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getKeyboardKeyNameBySystem = (key: string) => {
|
export const getKeyboardKeyNameBySystem = (key: string) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user