Co-authored-by: alleschen <alleschen@tencent.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>tags/1.9.0
| @@ -62,9 +62,9 @@ const CandidateNode = () => { | |||
| }) | |||
| setNodes(newNodes) | |||
| if (candidateNode.type === CUSTOM_NOTE_NODE) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteAdd) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteAdd, { nodeId: candidateNode.id }) | |||
| else | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeAdd) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: candidateNode.id }) | |||
| workflowStore.setState({ candidateNode: undefined }) | |||
| @@ -89,10 +89,19 @@ const ViewWorkflowHistory = () => { | |||
| const calculateChangeList: ChangeHistoryList = useMemo(() => { | |||
| 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 { | |||
| label: state.workflowHistoryEvent && getHistoryLabel(state.workflowHistoryEvent), | |||
| 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) | |||
| @@ -110,6 +119,12 @@ const ViewWorkflowHistory = () => { | |||
| } | |||
| }, [futureStates, getHistoryLabel, pastStates, store]) | |||
| const composeHistoryItemLabel = useCallback((nodeTitle: string | undefined, baseLabel: string) => { | |||
| if (!nodeTitle) | |||
| return baseLabel | |||
| return `${nodeTitle} ${baseLabel}` | |||
| }, []) | |||
| return ( | |||
| ( | |||
| <PortalToFollowElem | |||
| @@ -197,7 +212,10 @@ const ViewWorkflowHistory = () => { | |||
| '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> | |||
| @@ -222,7 +240,10 @@ const ViewWorkflowHistory = () => { | |||
| '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> | |||
| @@ -174,7 +174,7 @@ export const useNodesInteractions = () => { | |||
| if (x !== 0 && y !== 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]) | |||
| @@ -423,7 +423,7 @@ export const useNodesInteractions = () => { | |||
| setEdges(newEdges) | |||
| handleSyncWorkflowDraft() | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeConnect) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeConnect, { nodeId: targetNode?.id }) | |||
| } | |||
| else { | |||
| const { | |||
| @@ -659,10 +659,10 @@ export const useNodesInteractions = () => { | |||
| handleSyncWorkflowDraft() | |||
| if (currentNode.type === CUSTOM_NOTE_NODE) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteDelete) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteDelete, { nodeId: currentNode.id }) | |||
| else | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeDelete) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeDelete, { nodeId: currentNode.id }) | |||
| }, [getNodesReadOnly, store, deleteNodeInspectorVars, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t]) | |||
| const handleNodeAdd = useCallback<OnNodeAdd>(( | |||
| @@ -1100,7 +1100,7 @@ export const useNodesInteractions = () => { | |||
| setEdges(newEdges) | |||
| } | |||
| handleSyncWorkflowDraft() | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeAdd) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: newNode.id }) | |||
| }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit]) | |||
| const handleNodeChange = useCallback(( | |||
| @@ -1182,7 +1182,7 @@ export const useNodesInteractions = () => { | |||
| setEdges(newEdges) | |||
| handleSyncWorkflowDraft() | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeChange) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeChange, { nodeId: currentNodeId }) | |||
| }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory]) | |||
| const handleNodesCancelSelected = useCallback(() => { | |||
| @@ -1404,7 +1404,7 @@ export const useNodesInteractions = () => { | |||
| setNodes([...nodes, ...nodesToPaste]) | |||
| setEdges([...edges, ...edgesToPaste]) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodePaste) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodePaste, { nodeId: nodesToPaste?.[0]?.id }) | |||
| handleSyncWorkflowDraft() | |||
| } | |||
| }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy]) | |||
| @@ -1501,7 +1501,7 @@ export const useNodesInteractions = () => { | |||
| }) | |||
| setNodes(newNodes) | |||
| handleSyncWorkflowDraft() | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeResize) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeResize, { nodeId }) | |||
| }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory]) | |||
| const handleNodeDisconnect = useCallback((nodeId: string) => { | |||
| @@ -8,6 +8,7 @@ import { | |||
| } from 'reactflow' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { useWorkflowHistoryStore } from '../workflow-history-store' | |||
| import type { WorkflowHistoryEventMeta } from '../workflow-history-store' | |||
| /** | |||
| * 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. | |||
| // We debounce the history state update to avoid creating multiple history states | |||
| // with minimal changes. | |||
| const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent) => { | |||
| const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => { | |||
| workflowHistoryStore.setState({ | |||
| workflowHistoryEvent: event, | |||
| workflowHistoryEventMeta: meta, | |||
| nodes: store.getState().getNodes(), | |||
| edges: store.getState().edges, | |||
| }) | |||
| }, 500)) | |||
| const saveStateToHistory = useCallback((event: WorkflowHistoryEvent) => { | |||
| const saveStateToHistory = useCallback((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => { | |||
| switch (event) { | |||
| case WorkflowHistoryEvent.NoteChange: | |||
| // Hint: Note change does not trigger when note text changes, | |||
| // because the note editors have their own history states. | |||
| saveStateToHistoryRef.current(event) | |||
| saveStateToHistoryRef.current(event, meta) | |||
| break | |||
| case WorkflowHistoryEvent.NodeTitleChange: | |||
| case WorkflowHistoryEvent.NodeDescriptionChange: | |||
| @@ -93,7 +95,7 @@ export const useWorkflowHistory = () => { | |||
| case WorkflowHistoryEvent.NoteAdd: | |||
| case WorkflowHistoryEvent.LayoutOrganize: | |||
| case WorkflowHistoryEvent.NoteDelete: | |||
| saveStateToHistoryRef.current(event) | |||
| saveStateToHistoryRef.current(event, meta) | |||
| break | |||
| default: | |||
| // We do not create a history state for every event. | |||
| @@ -154,11 +154,11 @@ const BasePanel: FC<BasePanelProps> = ({ | |||
| const handleTitleBlur = useCallback((title: string) => { | |||
| handleNodeDataUpdateWithSyncDraft({ id, data: { title } }) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange, { nodeId: id }) | |||
| }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) | |||
| const handleDescriptionChange = useCallback((desc: string) => { | |||
| handleNodeDataUpdateWithSyncDraft({ id, data: { desc } }) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange) | |||
| saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange, { nodeId: id }) | |||
| }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) | |||
| const isChildNode = !!(data.isInIteration || data.isInLoop) | |||
| @@ -9,7 +9,7 @@ export const useNote = (id: string) => { | |||
| const handleThemeChange = useCallback((theme: NoteTheme) => { | |||
| handleNodeDataUpdateWithSyncDraft({ id, data: { theme } }) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteChange) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteChange, { nodeId: id }) | |||
| }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) | |||
| const handleEditorChange = useCallback((editorState: EditorState) => { | |||
| @@ -21,7 +21,7 @@ export const useNote = (id: string) => { | |||
| const handleShowAuthorChange = useCallback((showAuthor: boolean) => { | |||
| handleNodeDataUpdateWithSyncDraft({ id, data: { showAuthor } }) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteChange) | |||
| saveStateToHistory(WorkflowHistoryEvent.NoteChange, { nodeId: id }) | |||
| }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) | |||
| return { | |||
| @@ -51,6 +51,7 @@ export function useWorkflowHistoryStore() { | |||
| setState: (state: WorkflowHistoryState) => { | |||
| store.setState({ | |||
| workflowHistoryEvent: state.workflowHistoryEvent, | |||
| workflowHistoryEventMeta: state.workflowHistoryEventMeta, | |||
| nodes: state.nodes.map((node: Node) => ({ ...node, data: { ...node.data, selected: false } })), | |||
| edges: state.edges.map((edge: Edge) => ({ ...edge, selected: false }) as Edge), | |||
| }) | |||
| @@ -76,6 +77,7 @@ function createStore({ | |||
| (set, get) => { | |||
| return { | |||
| workflowHistoryEvent: undefined, | |||
| workflowHistoryEventMeta: undefined, | |||
| nodes: storeNodes, | |||
| edges: storeEdges, | |||
| getNodes: () => get().nodes, | |||
| @@ -97,6 +99,7 @@ export type WorkflowHistoryStore = { | |||
| nodes: Node[] | |||
| edges: Edge[] | |||
| workflowHistoryEvent: WorkflowHistoryEvent | undefined | |||
| workflowHistoryEventMeta?: WorkflowHistoryEventMeta | |||
| } | |||
| export type WorkflowHistoryActions = { | |||
| @@ -119,3 +122,8 @@ export type WorkflowWithHistoryProviderProps = { | |||
| edges: Edge[] | |||
| children: ReactNode | |||
| } | |||
| export type WorkflowHistoryEventMeta = { | |||
| nodeId?: string | |||
| nodeTitle?: string | |||
| } | |||