Compare commits

...

7 Commits

Author SHA1 Message Date
Yi
4fcb048607 add "add block" shortcut ui 2024-08-23 16:18:19 +08:00
Yi
18b61591b8 "add block" shortcut update 2024-08-23 16:17:14 +08:00
Yi
5408e923e3 Merge branch 'main' into feat/workflow-add-block-shortcut 2024-08-23 15:56:37 +08:00
Yi
1bcfa747db add block shortcut 2024-08-23 15:54:29 +08:00
Yi
071b7d607b Merge branch 'main' into feat/workflow-add-block-shortcut 2024-08-23 13:58:35 +08:00
Yi
1a5e5cd8f8 Merge branch 'main' into feat/workflow-add-block-shortcut 2024-08-23 11:46:15 +08:00
Yi
3bd5f9542e add "add block" shortcut ui 2024-08-22 17:51:12 +08:00
9 changed files with 62 additions and 47 deletions

View File

@ -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,
} }
} }

View File

@ -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()

View File

@ -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}

View File

@ -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()

View File

@ -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}

View File

@ -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(

View File

@ -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()
} }
}} }}
> >

View File

@ -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 })),
})) }))
} }

View File

@ -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) => {