use-workflow-history.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import {
  2. useCallback,
  3. useRef, useState,
  4. } from 'react'
  5. import { debounce } from 'lodash-es'
  6. import {
  7. useStoreApi,
  8. } from 'reactflow'
  9. import { useTranslation } from 'react-i18next'
  10. import { useWorkflowHistoryStore } from '../workflow-history-store'
  11. /**
  12. * All supported Events that create a new history state.
  13. * Current limitations:
  14. * - InputChange events in Node Panels do not trigger state changes.
  15. * - Resizing UI elements does not trigger state changes.
  16. */
  17. export enum WorkflowHistoryEvent {
  18. NodeTitleChange = 'NodeTitleChange',
  19. NodeDescriptionChange = 'NodeDescriptionChange',
  20. NodeDragStop = 'NodeDragStop',
  21. NodeChange = 'NodeChange',
  22. NodeConnect = 'NodeConnect',
  23. NodePaste = 'NodePaste',
  24. NodeDelete = 'NodeDelete',
  25. EdgeDelete = 'EdgeDelete',
  26. EdgeDeleteByDeleteBranch = 'EdgeDeleteByDeleteBranch',
  27. NodeAdd = 'NodeAdd',
  28. NodeResize = 'NodeResize',
  29. NoteAdd = 'NoteAdd',
  30. NoteChange = 'NoteChange',
  31. NoteDelete = 'NoteDelete',
  32. LayoutOrganize = 'LayoutOrganize',
  33. }
  34. export const useWorkflowHistory = () => {
  35. const store = useStoreApi()
  36. const { store: workflowHistoryStore } = useWorkflowHistoryStore()
  37. const { t } = useTranslation()
  38. const [undoCallbacks, setUndoCallbacks] = useState<any[]>([])
  39. const [redoCallbacks, setRedoCallbacks] = useState<any[]>([])
  40. const onUndo = useCallback((callback: unknown) => {
  41. setUndoCallbacks((prev: any) => [...prev, callback])
  42. return () => setUndoCallbacks(prev => prev.filter(cb => cb !== callback))
  43. }, [])
  44. const onRedo = useCallback((callback: unknown) => {
  45. setRedoCallbacks((prev: any) => [...prev, callback])
  46. return () => setRedoCallbacks(prev => prev.filter(cb => cb !== callback))
  47. }, [])
  48. const undo = useCallback(() => {
  49. workflowHistoryStore.temporal.getState().undo()
  50. undoCallbacks.forEach(callback => callback())
  51. }, [undoCallbacks, workflowHistoryStore.temporal])
  52. const redo = useCallback(() => {
  53. workflowHistoryStore.temporal.getState().redo()
  54. redoCallbacks.forEach(callback => callback())
  55. }, [redoCallbacks, workflowHistoryStore.temporal])
  56. // Some events may be triggered multiple times in a short period of time.
  57. // We debounce the history state update to avoid creating multiple history states
  58. // with minimal changes.
  59. const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent) => {
  60. workflowHistoryStore.setState({
  61. workflowHistoryEvent: event,
  62. nodes: store.getState().getNodes(),
  63. edges: store.getState().edges,
  64. })
  65. }, 500))
  66. const saveStateToHistory = useCallback((event: WorkflowHistoryEvent) => {
  67. switch (event) {
  68. case WorkflowHistoryEvent.NoteChange:
  69. // Hint: Note change does not trigger when note text changes,
  70. // because the note editors have their own history states.
  71. saveStateToHistoryRef.current(event)
  72. break
  73. case WorkflowHistoryEvent.NodeTitleChange:
  74. case WorkflowHistoryEvent.NodeDescriptionChange:
  75. case WorkflowHistoryEvent.NodeDragStop:
  76. case WorkflowHistoryEvent.NodeChange:
  77. case WorkflowHistoryEvent.NodeConnect:
  78. case WorkflowHistoryEvent.NodePaste:
  79. case WorkflowHistoryEvent.NodeDelete:
  80. case WorkflowHistoryEvent.EdgeDelete:
  81. case WorkflowHistoryEvent.EdgeDeleteByDeleteBranch:
  82. case WorkflowHistoryEvent.NodeAdd:
  83. case WorkflowHistoryEvent.NodeResize:
  84. case WorkflowHistoryEvent.NoteAdd:
  85. case WorkflowHistoryEvent.LayoutOrganize:
  86. case WorkflowHistoryEvent.NoteDelete:
  87. saveStateToHistoryRef.current(event)
  88. break
  89. default:
  90. // We do not create a history state for every event.
  91. // Some events of reactflow may change things the user would not want to undo/redo.
  92. // For example: UI state changes like selecting a node.
  93. break
  94. }
  95. }, [])
  96. const getHistoryLabel = useCallback((event: WorkflowHistoryEvent) => {
  97. switch (event) {
  98. case WorkflowHistoryEvent.NodeTitleChange:
  99. return t('workflow.changeHistory.nodeTitleChange')
  100. case WorkflowHistoryEvent.NodeDescriptionChange:
  101. return t('workflow.changeHistory.nodeDescriptionChange')
  102. case WorkflowHistoryEvent.LayoutOrganize:
  103. case WorkflowHistoryEvent.NodeDragStop:
  104. return t('workflow.changeHistory.nodeDragStop')
  105. case WorkflowHistoryEvent.NodeChange:
  106. return t('workflow.changeHistory.nodeChange')
  107. case WorkflowHistoryEvent.NodeConnect:
  108. return t('workflow.changeHistory.nodeConnect')
  109. case WorkflowHistoryEvent.NodePaste:
  110. return t('workflow.changeHistory.nodePaste')
  111. case WorkflowHistoryEvent.NodeDelete:
  112. return t('workflow.changeHistory.nodeDelete')
  113. case WorkflowHistoryEvent.NodeAdd:
  114. return t('workflow.changeHistory.nodeAdd')
  115. case WorkflowHistoryEvent.EdgeDelete:
  116. case WorkflowHistoryEvent.EdgeDeleteByDeleteBranch:
  117. return t('workflow.changeHistory.edgeDelete')
  118. case WorkflowHistoryEvent.NodeResize:
  119. return t('workflow.changeHistory.nodeResize')
  120. case WorkflowHistoryEvent.NoteAdd:
  121. return t('workflow.changeHistory.noteAdd')
  122. case WorkflowHistoryEvent.NoteChange:
  123. return t('workflow.changeHistory.noteChange')
  124. case WorkflowHistoryEvent.NoteDelete:
  125. return t('workflow.changeHistory.noteDelete')
  126. default:
  127. return 'Unknown Event'
  128. }
  129. }, [t])
  130. return {
  131. store: workflowHistoryStore,
  132. saveStateToHistory,
  133. getHistoryLabel,
  134. undo,
  135. redo,
  136. onUndo,
  137. onRedo,
  138. }
  139. }