feat: change history by supplementary node information (#25294)
Co-authored-by: alleschen <alleschen@tencent.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
This commit is contained in:
parent
d2e50a508c
commit
ac2aa967c4
7 changed files with 52 additions and 21 deletions
|
|
@ -62,9 +62,9 @@ const CandidateNode = () => {
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
if (candidateNode.type === CUSTOM_NOTE_NODE)
|
if (candidateNode.type === CUSTOM_NOTE_NODE)
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NoteAdd)
|
saveStateToHistory(WorkflowHistoryEvent.NoteAdd, { nodeId: candidateNode.id })
|
||||||
else
|
else
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
|
saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: candidateNode.id })
|
||||||
|
|
||||||
workflowStore.setState({ candidateNode: undefined })
|
workflowStore.setState({ candidateNode: undefined })
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,19 @@ const ViewWorkflowHistory = () => {
|
||||||
|
|
||||||
const calculateChangeList: ChangeHistoryList = useMemo(() => {
|
const calculateChangeList: ChangeHistoryList = useMemo(() => {
|
||||||
const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => {
|
const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => {
|
||||||
|
const nodes = (state.nodes || store.getState().nodes) || []
|
||||||
|
const nodeId = state?.workflowHistoryEventMeta?.nodeId
|
||||||
|
const targetTitle = nodes.find(n => n.id === nodeId)?.data?.title ?? ''
|
||||||
return {
|
return {
|
||||||
label: state.workflowHistoryEvent && getHistoryLabel(state.workflowHistoryEvent),
|
label: state.workflowHistoryEvent && getHistoryLabel(state.workflowHistoryEvent),
|
||||||
index: reverse ? list.length - 1 - index - startIndex : index - startIndex,
|
index: reverse ? list.length - 1 - index - startIndex : index - startIndex,
|
||||||
state,
|
state: {
|
||||||
|
...state,
|
||||||
|
workflowHistoryEventMeta: state.workflowHistoryEventMeta ? {
|
||||||
|
...state.workflowHistoryEventMeta,
|
||||||
|
nodeTitle: state.workflowHistoryEventMeta.nodeTitle || targetTitle,
|
||||||
|
} : undefined,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}).filter(Boolean)
|
}).filter(Boolean)
|
||||||
|
|
||||||
|
|
@ -110,6 +119,12 @@ const ViewWorkflowHistory = () => {
|
||||||
}
|
}
|
||||||
}, [futureStates, getHistoryLabel, pastStates, store])
|
}, [futureStates, getHistoryLabel, pastStates, store])
|
||||||
|
|
||||||
|
const composeHistoryItemLabel = useCallback((nodeTitle: string | undefined, baseLabel: string) => {
|
||||||
|
if (!nodeTitle)
|
||||||
|
return baseLabel
|
||||||
|
return `${nodeTitle} ${baseLabel}`
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
|
|
@ -197,7 +212,10 @@ const ViewWorkflowHistory = () => {
|
||||||
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
|
{composeHistoryItemLabel(
|
||||||
|
item?.state?.workflowHistoryEventMeta?.nodeTitle,
|
||||||
|
item?.label || t('workflow.changeHistory.sessionStart'),
|
||||||
|
)} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -222,7 +240,10 @@ const ViewWorkflowHistory = () => {
|
||||||
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)})
|
{composeHistoryItemLabel(
|
||||||
|
item?.state?.workflowHistoryEventMeta?.nodeTitle,
|
||||||
|
item?.label || t('workflow.changeHistory.sessionStart'),
|
||||||
|
)} ({calculateStepLabel(item?.index)})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ export const useNodesInteractions = () => {
|
||||||
|
|
||||||
if (x !== 0 && y !== 0) {
|
if (x !== 0 && y !== 0) {
|
||||||
// selecting a note will trigger a drag stop event with x and y as 0
|
// selecting a note will trigger a drag stop event with x and y as 0
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeDragStop)
|
saveStateToHistory(WorkflowHistoryEvent.NodeDragStop, { nodeId: node.id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [workflowStore, getNodesReadOnly, saveStateToHistory, handleSyncWorkflowDraft])
|
}, [workflowStore, getNodesReadOnly, saveStateToHistory, handleSyncWorkflowDraft])
|
||||||
|
|
@ -423,7 +423,7 @@ export const useNodesInteractions = () => {
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
|
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeConnect)
|
saveStateToHistory(WorkflowHistoryEvent.NodeConnect, { nodeId: targetNode?.id })
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const {
|
const {
|
||||||
|
|
@ -659,10 +659,10 @@ export const useNodesInteractions = () => {
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
|
|
||||||
if (currentNode.type === CUSTOM_NOTE_NODE)
|
if (currentNode.type === CUSTOM_NOTE_NODE)
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NoteDelete)
|
saveStateToHistory(WorkflowHistoryEvent.NoteDelete, { nodeId: currentNode.id })
|
||||||
|
|
||||||
else
|
else
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeDelete)
|
saveStateToHistory(WorkflowHistoryEvent.NodeDelete, { nodeId: currentNode.id })
|
||||||
}, [getNodesReadOnly, store, deleteNodeInspectorVars, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t])
|
}, [getNodesReadOnly, store, deleteNodeInspectorVars, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t])
|
||||||
|
|
||||||
const handleNodeAdd = useCallback<OnNodeAdd>((
|
const handleNodeAdd = useCallback<OnNodeAdd>((
|
||||||
|
|
@ -1100,7 +1100,7 @@ export const useNodesInteractions = () => {
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
}
|
}
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
|
saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: newNode.id })
|
||||||
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit])
|
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit])
|
||||||
|
|
||||||
const handleNodeChange = useCallback((
|
const handleNodeChange = useCallback((
|
||||||
|
|
@ -1182,7 +1182,7 @@ export const useNodesInteractions = () => {
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
|
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeChange)
|
saveStateToHistory(WorkflowHistoryEvent.NodeChange, { nodeId: currentNodeId })
|
||||||
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory])
|
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory])
|
||||||
|
|
||||||
const handleNodesCancelSelected = useCallback(() => {
|
const handleNodesCancelSelected = useCallback(() => {
|
||||||
|
|
@ -1404,7 +1404,7 @@ export const useNodesInteractions = () => {
|
||||||
|
|
||||||
setNodes([...nodes, ...nodesToPaste])
|
setNodes([...nodes, ...nodesToPaste])
|
||||||
setEdges([...edges, ...edgesToPaste])
|
setEdges([...edges, ...edgesToPaste])
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodePaste)
|
saveStateToHistory(WorkflowHistoryEvent.NodePaste, { nodeId: nodesToPaste?.[0]?.id })
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
}
|
}
|
||||||
}, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy])
|
}, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy])
|
||||||
|
|
@ -1501,7 +1501,7 @@ export const useNodesInteractions = () => {
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeResize)
|
saveStateToHistory(WorkflowHistoryEvent.NodeResize, { nodeId })
|
||||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
|
||||||
|
|
||||||
const handleNodeDisconnect = useCallback((nodeId: string) => {
|
const handleNodeDisconnect = useCallback((nodeId: string) => {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useWorkflowHistoryStore } from '../workflow-history-store'
|
import { useWorkflowHistoryStore } from '../workflow-history-store'
|
||||||
|
import type { WorkflowHistoryEventMeta } from '../workflow-history-store'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All supported Events that create a new history state.
|
* All supported Events that create a new history state.
|
||||||
|
|
@ -64,20 +65,21 @@ export const useWorkflowHistory = () => {
|
||||||
// Some events may be triggered multiple times in a short period of time.
|
// Some events may be triggered multiple times in a short period of time.
|
||||||
// We debounce the history state update to avoid creating multiple history states
|
// We debounce the history state update to avoid creating multiple history states
|
||||||
// with minimal changes.
|
// with minimal changes.
|
||||||
const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent) => {
|
const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => {
|
||||||
workflowHistoryStore.setState({
|
workflowHistoryStore.setState({
|
||||||
workflowHistoryEvent: event,
|
workflowHistoryEvent: event,
|
||||||
|
workflowHistoryEventMeta: meta,
|
||||||
nodes: store.getState().getNodes(),
|
nodes: store.getState().getNodes(),
|
||||||
edges: store.getState().edges,
|
edges: store.getState().edges,
|
||||||
})
|
})
|
||||||
}, 500))
|
}, 500))
|
||||||
|
|
||||||
const saveStateToHistory = useCallback((event: WorkflowHistoryEvent) => {
|
const saveStateToHistory = useCallback((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case WorkflowHistoryEvent.NoteChange:
|
case WorkflowHistoryEvent.NoteChange:
|
||||||
// Hint: Note change does not trigger when note text changes,
|
// Hint: Note change does not trigger when note text changes,
|
||||||
// because the note editors have their own history states.
|
// because the note editors have their own history states.
|
||||||
saveStateToHistoryRef.current(event)
|
saveStateToHistoryRef.current(event, meta)
|
||||||
break
|
break
|
||||||
case WorkflowHistoryEvent.NodeTitleChange:
|
case WorkflowHistoryEvent.NodeTitleChange:
|
||||||
case WorkflowHistoryEvent.NodeDescriptionChange:
|
case WorkflowHistoryEvent.NodeDescriptionChange:
|
||||||
|
|
@ -93,7 +95,7 @@ export const useWorkflowHistory = () => {
|
||||||
case WorkflowHistoryEvent.NoteAdd:
|
case WorkflowHistoryEvent.NoteAdd:
|
||||||
case WorkflowHistoryEvent.LayoutOrganize:
|
case WorkflowHistoryEvent.LayoutOrganize:
|
||||||
case WorkflowHistoryEvent.NoteDelete:
|
case WorkflowHistoryEvent.NoteDelete:
|
||||||
saveStateToHistoryRef.current(event)
|
saveStateToHistoryRef.current(event, meta)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
// We do not create a history state for every event.
|
// We do not create a history state for every event.
|
||||||
|
|
|
||||||
|
|
@ -154,11 +154,11 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||||
|
|
||||||
const handleTitleBlur = useCallback((title: string) => {
|
const handleTitleBlur = useCallback((title: string) => {
|
||||||
handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
|
handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange)
|
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange, { nodeId: id })
|
||||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||||
const handleDescriptionChange = useCallback((desc: string) => {
|
const handleDescriptionChange = useCallback((desc: string) => {
|
||||||
handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
|
handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange)
|
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange, { nodeId: id })
|
||||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||||
|
|
||||||
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export const useNote = (id: string) => {
|
||||||
|
|
||||||
const handleThemeChange = useCallback((theme: NoteTheme) => {
|
const handleThemeChange = useCallback((theme: NoteTheme) => {
|
||||||
handleNodeDataUpdateWithSyncDraft({ id, data: { theme } })
|
handleNodeDataUpdateWithSyncDraft({ id, data: { theme } })
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NoteChange)
|
saveStateToHistory(WorkflowHistoryEvent.NoteChange, { nodeId: id })
|
||||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||||
|
|
||||||
const handleEditorChange = useCallback((editorState: EditorState) => {
|
const handleEditorChange = useCallback((editorState: EditorState) => {
|
||||||
|
|
@ -21,7 +21,7 @@ export const useNote = (id: string) => {
|
||||||
|
|
||||||
const handleShowAuthorChange = useCallback((showAuthor: boolean) => {
|
const handleShowAuthorChange = useCallback((showAuthor: boolean) => {
|
||||||
handleNodeDataUpdateWithSyncDraft({ id, data: { showAuthor } })
|
handleNodeDataUpdateWithSyncDraft({ id, data: { showAuthor } })
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NoteChange)
|
saveStateToHistory(WorkflowHistoryEvent.NoteChange, { nodeId: id })
|
||||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export function useWorkflowHistoryStore() {
|
||||||
setState: (state: WorkflowHistoryState) => {
|
setState: (state: WorkflowHistoryState) => {
|
||||||
store.setState({
|
store.setState({
|
||||||
workflowHistoryEvent: state.workflowHistoryEvent,
|
workflowHistoryEvent: state.workflowHistoryEvent,
|
||||||
|
workflowHistoryEventMeta: state.workflowHistoryEventMeta,
|
||||||
nodes: state.nodes.map((node: Node) => ({ ...node, data: { ...node.data, selected: false } })),
|
nodes: state.nodes.map((node: Node) => ({ ...node, data: { ...node.data, selected: false } })),
|
||||||
edges: state.edges.map((edge: Edge) => ({ ...edge, selected: false }) as Edge),
|
edges: state.edges.map((edge: Edge) => ({ ...edge, selected: false }) as Edge),
|
||||||
})
|
})
|
||||||
|
|
@ -76,6 +77,7 @@ function createStore({
|
||||||
(set, get) => {
|
(set, get) => {
|
||||||
return {
|
return {
|
||||||
workflowHistoryEvent: undefined,
|
workflowHistoryEvent: undefined,
|
||||||
|
workflowHistoryEventMeta: undefined,
|
||||||
nodes: storeNodes,
|
nodes: storeNodes,
|
||||||
edges: storeEdges,
|
edges: storeEdges,
|
||||||
getNodes: () => get().nodes,
|
getNodes: () => get().nodes,
|
||||||
|
|
@ -97,6 +99,7 @@ export type WorkflowHistoryStore = {
|
||||||
nodes: Node[]
|
nodes: Node[]
|
||||||
edges: Edge[]
|
edges: Edge[]
|
||||||
workflowHistoryEvent: WorkflowHistoryEvent | undefined
|
workflowHistoryEvent: WorkflowHistoryEvent | undefined
|
||||||
|
workflowHistoryEventMeta?: WorkflowHistoryEventMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkflowHistoryActions = {
|
export type WorkflowHistoryActions = {
|
||||||
|
|
@ -119,3 +122,8 @@ export type WorkflowWithHistoryProviderProps = {
|
||||||
edges: Edge[]
|
edges: Edge[]
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorkflowHistoryEventMeta = {
|
||||||
|
nodeId?: string
|
||||||
|
nodeTitle?: string
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue