mirror of
https://github.com/langgenius/dify.git
synced 2026-05-01 07:58:02 +08:00
fix undo/redo
This commit is contained in:
@ -658,10 +658,37 @@ export class CollaborationManager {
|
||||
})
|
||||
}
|
||||
|
||||
emitGraphViewActive(isActive: boolean): void {
|
||||
this.graphViewActive = isActive
|
||||
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId))
|
||||
return
|
||||
|
||||
this.sendCollaborationEvent({
|
||||
type: 'graph_view_active',
|
||||
data: { active: isActive },
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
emitHistoryAction(action: 'undo' | 'redo' | 'jump'): void {
|
||||
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId))
|
||||
return
|
||||
|
||||
this.sendCollaborationEvent({
|
||||
type: 'workflow_history_action',
|
||||
data: { action },
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
onUndoRedoStateChange(callback: (state: { canUndo: boolean, canRedo: boolean }) => void): () => void {
|
||||
return this.eventEmitter.on('undoRedoStateChange', callback)
|
||||
}
|
||||
|
||||
onHistoryAction(callback: (payload: { action: 'undo' | 'redo' | 'jump', userId?: string }) => void): () => void {
|
||||
return this.eventEmitter.on('historyAction', callback)
|
||||
}
|
||||
|
||||
emitRestoreRequest(data: RestoreRequestData): void {
|
||||
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId))
|
||||
return
|
||||
@ -1055,6 +1082,11 @@ export class CollaborationManager {
|
||||
else if (update.type === 'workflow_restore_complete') {
|
||||
this.eventEmitter.emit('restoreComplete', update.data as RestoreCompleteData)
|
||||
}
|
||||
else if (update.type === 'workflow_history_action') {
|
||||
const data = update.data as { action?: 'undo' | 'redo' | 'jump' } | undefined
|
||||
if (data?.action)
|
||||
this.eventEmitter.emit('historyAction', { action: data.action, userId: update.userId })
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('online_users', (data: { users: OnlineUser[], leader?: string }) => {
|
||||
|
||||
@ -66,6 +66,7 @@ export type CollaborationEventType
|
||||
| 'workflow_restore_request'
|
||||
| 'workflow_restore_intent'
|
||||
| 'workflow_restore_complete'
|
||||
| 'workflow_history_action'
|
||||
|
||||
export type CollaborationUpdate = {
|
||||
type: CollaborationEventType
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
|
||||
import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history'
|
||||
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
|
||||
import { useWorkflowHistoryStore } from '@/app/components/workflow/workflow-history-store'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Divider from '../../base/divider'
|
||||
import TipPopup from '../operator/tip-popup'
|
||||
@ -11,32 +11,18 @@ import TipPopup from '../operator/tip-popup'
|
||||
type UndoRedoProps = { handleUndo: () => void, handleRedo: () => void }
|
||||
const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => {
|
||||
const { t } = useTranslation()
|
||||
const { store } = useWorkflowHistoryStore()
|
||||
const [buttonsDisabled, setButtonsDisabled] = useState({ undo: true, redo: true })
|
||||
|
||||
useEffect(() => {
|
||||
// Update button states based on Loro's UndoManager
|
||||
const updateButtonStates = () => {
|
||||
const unsubscribe = store.temporal.subscribe((state) => {
|
||||
setButtonsDisabled({
|
||||
undo: !collaborationManager.canUndo(),
|
||||
redo: !collaborationManager.canRedo(),
|
||||
})
|
||||
}
|
||||
|
||||
// Initial state
|
||||
Promise.resolve().then(() => {
|
||||
updateButtonStates()
|
||||
})
|
||||
|
||||
// Listen for undo/redo state changes
|
||||
const unsubscribe = collaborationManager.onUndoRedoStateChange((state) => {
|
||||
setButtonsDisabled({
|
||||
undo: !state.canUndo,
|
||||
redo: !state.canRedo,
|
||||
undo: state.pastStates.length === 0,
|
||||
redo: state.futureStates.length === 0,
|
||||
})
|
||||
})
|
||||
|
||||
return () => unsubscribe()
|
||||
}, [])
|
||||
}, [store])
|
||||
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Divider from '../../base/divider'
|
||||
import { collaborationManager } from '../collaboration'
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
useWorkflowHistory,
|
||||
@ -76,6 +77,8 @@ const ViewWorkflowHistory = () => {
|
||||
|
||||
setEdges(edges)
|
||||
setNodes(nodes)
|
||||
if (collaborationManager.isConnected())
|
||||
collaborationManager.emitHistoryAction('jump')
|
||||
}, [currentHistoryStateIndex, reactFlowStore, redo, store, undo])
|
||||
|
||||
const calculateStepLabel = useCallback((index: number) => {
|
||||
|
||||
@ -96,7 +96,11 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
|
||||
|
||||
const { saveStateToHistory, undo } = useWorkflowHistory()
|
||||
const {
|
||||
saveStateToHistory,
|
||||
undo,
|
||||
redo,
|
||||
} = useWorkflowHistory()
|
||||
const autoGenerateWebhookUrl = useAutoGenerateWebhookUrl()
|
||||
|
||||
const handleNodeDragStart = useCallback<NodeDragHandler>(
|
||||
@ -2104,15 +2108,17 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly() || getWorkflowReadOnly())
|
||||
return
|
||||
|
||||
// Use collaborative undo from Loro
|
||||
collaborationManager.undo()
|
||||
undo()
|
||||
const { edges, nodes } = workflowHistoryStore.getState()
|
||||
if (edges.length === 0 && nodes.length === 0)
|
||||
return
|
||||
const { setNodes, setEdges } = collaborativeWorkflow.getState()
|
||||
|
||||
setEdges(edges)
|
||||
setNodes(nodes)
|
||||
const shouldBroadcast = collaborationManager.isConnected()
|
||||
setEdges(edges, shouldBroadcast)
|
||||
setNodes(nodes, shouldBroadcast)
|
||||
if (shouldBroadcast)
|
||||
collaborationManager.emitHistoryAction('undo')
|
||||
workflowStore.setState({ edgeMenu: undefined })
|
||||
}, [
|
||||
collaborativeWorkflow,
|
||||
@ -2127,17 +2133,21 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly() || getWorkflowReadOnly())
|
||||
return
|
||||
|
||||
// Use collaborative redo from Loro
|
||||
collaborationManager.redo()
|
||||
redo()
|
||||
const { edges, nodes } = workflowHistoryStore.getState()
|
||||
if (edges.length === 0 && nodes.length === 0)
|
||||
return
|
||||
const { setNodes, setEdges } = collaborativeWorkflow.getState()
|
||||
|
||||
setEdges(edges)
|
||||
setNodes(nodes)
|
||||
const shouldBroadcast = collaborationManager.isConnected()
|
||||
setEdges(edges, shouldBroadcast)
|
||||
setNodes(nodes, shouldBroadcast)
|
||||
if (shouldBroadcast)
|
||||
collaborationManager.emitHistoryAction('redo')
|
||||
workflowStore.setState({ edgeMenu: undefined })
|
||||
}, [
|
||||
collaborativeWorkflow,
|
||||
redo,
|
||||
workflowStore,
|
||||
workflowHistoryStore,
|
||||
getNodesReadOnly,
|
||||
|
||||
@ -40,6 +40,7 @@ import ReactFlow, {
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { IS_DEV } from '@/config'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import dynamic from '@/next/dynamic'
|
||||
@ -196,6 +197,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
onlineUsers,
|
||||
}) => {
|
||||
const workflowContainerRef = useRef<HTMLDivElement>(null)
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const reactflow = useReactFlow()
|
||||
const store = useStoreApi()
|
||||
@ -266,6 +268,15 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
}
|
||||
})
|
||||
}, [edges, nodes, setEdges, setNodes, store])
|
||||
|
||||
useEffect(() => {
|
||||
return collaborationManager.onHistoryAction((_) => {
|
||||
Toast.notify({
|
||||
type: 'info',
|
||||
message: t('collaboration.historyAction.generic', { ns: 'workflow' }),
|
||||
})
|
||||
})
|
||||
}, [t])
|
||||
const {
|
||||
handleSyncWorkflowDraft,
|
||||
syncWorkflowDraftWhenPageClose,
|
||||
@ -300,7 +311,6 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
const setCommentQuickAdd = useStore(s => s.setCommentQuickAdd)
|
||||
const setPendingCommentState = useStore(s => s.setPendingComment)
|
||||
const isCommentInputActive = Boolean(pendingComment) || isCommentPlacing
|
||||
const { t } = useTranslation()
|
||||
const visibleComments = useMemo(() => {
|
||||
if (showResolvedComments)
|
||||
return comments
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "متغيرات المحادثة",
|
||||
"chatVariable.storedContent": "المحتوى المخزن",
|
||||
"chatVariable.updatedAt": "تم التحديث في ",
|
||||
"collaboration.historyAction.generic": "قام متعاون بعملية تراجع/إعادة",
|
||||
"comments.actions.deleteReply": "حذف الرد",
|
||||
"comments.actions.editReply": "تعديل الرد",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Gesprächsvariablen",
|
||||
"chatVariable.storedContent": "Gespeicherter Inhalt",
|
||||
"chatVariable.updatedAt": "Aktualisiert am ",
|
||||
"collaboration.historyAction.generic": "Ein Mitbearbeiter hat Rückgängig/Wiederholen ausgeführt",
|
||||
"comments.actions.deleteReply": "Antwort löschen",
|
||||
"comments.actions.editReply": "Antwort bearbeiten",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Conversation Variables",
|
||||
"chatVariable.storedContent": "Stored content",
|
||||
"chatVariable.updatedAt": "Updated at ",
|
||||
"collaboration.historyAction.generic": "A collaborator performed an undo/redo action",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Variables de Conversación",
|
||||
"chatVariable.storedContent": "Contenido almacenado",
|
||||
"chatVariable.updatedAt": "Actualizado el ",
|
||||
"collaboration.historyAction.generic": "Un colaborador realizó deshacer/rehacer",
|
||||
"comments.actions.deleteReply": "Eliminar respuesta",
|
||||
"comments.actions.editReply": "Editar respuesta",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "متغیرهای مکالمه",
|
||||
"chatVariable.storedContent": "محتوای ذخیرهشده",
|
||||
"chatVariable.updatedAt": "بهروزرسانی شده در ",
|
||||
"collaboration.historyAction.generic": "یک همکار عملیات برگرداندن/انجام مجدد را انجام داد",
|
||||
"comments.actions.deleteReply": "حذف پاسخ",
|
||||
"comments.actions.editReply": "ویرایش پاسخ",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Variables de Conversation",
|
||||
"chatVariable.storedContent": "Contenu stocké",
|
||||
"chatVariable.updatedAt": "Mis à jour le ",
|
||||
"collaboration.historyAction.generic": "Un collaborateur a effectué une annulation/une réexécution",
|
||||
"comments.actions.deleteReply": "Supprimer la réponse",
|
||||
"comments.actions.editReply": "Modifier la réponse",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "वार्तालाप चर",
|
||||
"chatVariable.storedContent": "संग्रहीत सामग्री",
|
||||
"chatVariable.updatedAt": "अपडेट किया गया ",
|
||||
"collaboration.historyAction.generic": "एक सहयोगी ने Undo/Redo किया",
|
||||
"comments.actions.deleteReply": "जवाब हटाएं",
|
||||
"comments.actions.editReply": "जवाब संपादित करें",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Variabel Percakapan",
|
||||
"chatVariable.storedContent": "Konten yang disimpan",
|
||||
"chatVariable.updatedAt": "Diperbarui pada",
|
||||
"collaboration.historyAction.generic": "Seorang kolaborator melakukan urungkan/ulang",
|
||||
"comments.actions.deleteReply": "Hapus balasan",
|
||||
"comments.actions.editReply": "Edit balasan",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Variabili di Conversazione",
|
||||
"chatVariable.storedContent": "Contenuto memorizzato",
|
||||
"chatVariable.updatedAt": "Aggiornato il ",
|
||||
"collaboration.historyAction.generic": "Un collaboratore ha eseguito annulla/ripristina",
|
||||
"comments.actions.deleteReply": "Elimina risposta",
|
||||
"comments.actions.editReply": "Modifica risposta",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "会話変数",
|
||||
"chatVariable.storedContent": "保存内容",
|
||||
"chatVariable.updatedAt": "最終更新:",
|
||||
"collaboration.historyAction.generic": "共同編集者が元に戻す/やり直しを実行しました",
|
||||
"comments.actions.deleteReply": "返信を削除",
|
||||
"comments.actions.editReply": "返信を編集",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "대화 변수",
|
||||
"chatVariable.storedContent": "저장된 내용",
|
||||
"chatVariable.updatedAt": "업데이트 시간: ",
|
||||
"collaboration.historyAction.generic": "공동 작업자가 실행 취소/다시 실행을 수행했습니다",
|
||||
"comments.actions.deleteReply": "답글 삭제",
|
||||
"comments.actions.editReply": "답글 편집",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Zmienne Konwersacji",
|
||||
"chatVariable.storedContent": "Przechowywana zawartość",
|
||||
"chatVariable.updatedAt": "Zaktualizowano ",
|
||||
"collaboration.historyAction.generic": "Współpracownik wykonał cofnięcie/ponowienie",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Variáveis de Conversação",
|
||||
"chatVariable.storedContent": "Conteúdo armazenado",
|
||||
"chatVariable.updatedAt": "Atualizado em ",
|
||||
"collaboration.historyAction.generic": "Um colaborador realizou desfazer/refazer",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Variabile de Conversație",
|
||||
"chatVariable.storedContent": "Conținut stocat",
|
||||
"chatVariable.updatedAt": "Actualizat la ",
|
||||
"collaboration.historyAction.generic": "Un colaborator a efectuat anulare/refacere",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Переменные разговора",
|
||||
"chatVariable.storedContent": "Сохраненный контент",
|
||||
"chatVariable.updatedAt": "Обновлено в ",
|
||||
"collaboration.historyAction.generic": "Участник выполнил отмену/повтор",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Pogovor Spremenljivke",
|
||||
"chatVariable.storedContent": "Shranjena vsebina",
|
||||
"chatVariable.updatedAt": "Posodobljeno ob",
|
||||
"collaboration.historyAction.generic": "Sodelavec je izvedel razveljavitev/ponovitev",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "ตัวแปรการสนทนา",
|
||||
"chatVariable.storedContent": "เนื้อหาที่เก็บไว้",
|
||||
"chatVariable.updatedAt": "อัพเดทเมื่อ",
|
||||
"collaboration.historyAction.generic": "ผู้ร่วมงานได้ทำการยกเลิก/ทำซ้ำ",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Konuşma Değişkenleri",
|
||||
"chatVariable.storedContent": "Depolanan içerik",
|
||||
"chatVariable.updatedAt": "Güncellenme zamanı: ",
|
||||
"collaboration.historyAction.generic": "Bir işbirlikçi geri alma/yeniden yapma gerçekleştirdi",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Змінні розмови",
|
||||
"chatVariable.storedContent": "Збережений вміст",
|
||||
"chatVariable.updatedAt": "Оновлено ",
|
||||
"collaboration.historyAction.generic": "Співавтор виконав скасування/повторення",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "Biến Hội Thoại",
|
||||
"chatVariable.storedContent": "Nội dung đã lưu",
|
||||
"chatVariable.updatedAt": "Cập nhật lúc ",
|
||||
"collaboration.historyAction.generic": "Một cộng tác viên đã thực hiện hoàn tác/làm lại",
|
||||
"comments.actions.deleteReply": "Delete reply",
|
||||
"comments.actions.editReply": "Edit reply",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "会话变量",
|
||||
"chatVariable.storedContent": "存储内容",
|
||||
"chatVariable.updatedAt": "更新时间 ",
|
||||
"collaboration.historyAction.generic": "协作者执行了撤销/重做操作",
|
||||
"comments.actions.deleteReply": "删除回复",
|
||||
"comments.actions.editReply": "编辑回复",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
"chatVariable.panelTitle": "對話變數",
|
||||
"chatVariable.storedContent": "已儲存內容",
|
||||
"chatVariable.updatedAt": "更新於 ",
|
||||
"collaboration.historyAction.generic": "協作者執行了復原/重做操作",
|
||||
"comments.actions.deleteReply": "刪除回覆",
|
||||
"comments.actions.editReply": "編輯回覆",
|
||||
"comments.actions.addComment": "Add Comment",
|
||||
|
||||
Reference in New Issue
Block a user