-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(editor): Add undo/redo support for canvas actions (#4787)
* ✨ Added history store and mixin * ✨ Implemented node position change undo/redo * ✨ Implemented move nodes bulk command * ⚡ Not clearing the redo stack after pushing the bulk command * 🔨 Implemented commands using classes * 🔥 Removed unnecessary interfaces and actions * 🔥 Removing unused constants * 🔨 Refactoring classes file * ⚡ Adding eventBus to command obects * ✨ Added undo/redo support for adding and removing nodes * ✨ Implemented initial add/remove connections undo support * ⚡ Covering some corner cases with reconnecting nodes * ⚡ Adding undo support for reconnecting nodes * ⚡ Fixing going back and forward between undo and redo * ✨ Implemented async command revert * ⚡ Preventing push to undo if bulk redo/undo is in progress * ⚡ Handling re-connecting nodes and stopped pushing empty bulk actions to undo stack * ✨ Handling adding a node between two connected nodes * ⚡ Handling the case of removing multiple connections on the same index. Adding debounce to undo/redo keyboard calls * ⚡ Removing unnecessary timeouts, adding missing awaits, refactoring * ⚡ Resetting history when opening new workflow, fixing incorrect bulk recording when inserting node * ✔️ Fixing lint error * ⚡ Minor refactoring + some temporary debugging logs * ⚡ Preserving node properties when undoing it's removal, removing some unused repaint code * ✨ Added undo/redo support for import workflow and node enable/disable * 🔥 Removing some unused constant * ✨ Added undo/redo support for renaming nodes * ⚡ Fixing rename history recording * ✨ Added undo/redo support for duplicating nodes * 📈 Implemented telemetry events * 🔨 A bit of refactoring * ⚡ Fixing edgecases in removing connection and moving nodes * ⚡ Handling case of adding duplicate nodes when going back and forward in history * ⚡ Recording connections added directly to store * ⚡ Moving main history reset after wf is opened * 🔨 Simplifying rename recording * 📈 Adding NDV telemetry event, updating existing event name case * 📈 Updating telemetry events * ⚡ Fixing duplicate connections on undo/redo * ⚡ Stopping undo events from firing constantly on keydown * 📈 Updated telemetry event for hitting undo in NDV * ⚡ Adding undo support for disabling nodes using keyboard shortcuts * ⚡ Preventing adding duplicate connection commands to history * ⚡ Clearing redo stack when new change is added * ⚡ Preventing adding connection actions to undo stack while redoing them * 👌 Addressing PR comments part 1 * 👌 Moving undo logic for disabling nodes to `NodeView` * 👌 Implemented command comparing logic * ⚡ Fix for not clearing redo stack on every user action * ⚡ Fixing recording when moving nodes * ⚡ Fixing undo for moving connections * ⚡ Fixing tracking new nodes after latest merge * ⚡ Fixing broken bulk delete * ⚡ Preventing undo/redo when not on main node view tab * 👌 Addressing PR comments * 👌 Addressing PR comment
- Loading branch information
1 parent
38d7300
commit b2aba48
Showing
13 changed files
with
764 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { MAIN_HEADER_TABS } from './../constants'; | ||
import { useNDVStore } from '@/stores/ndv'; | ||
import { BulkCommand, Undoable } from '@/models/history'; | ||
import { useHistoryStore } from '@/stores/history'; | ||
import { useUIStore } from '@/stores/ui'; | ||
import { useWorkflowsStore } from '@/stores/workflows'; | ||
import { mapStores } from 'pinia'; | ||
import mixins from 'vue-typed-mixins'; | ||
import { Command } from '@/models/history'; | ||
import { debounceHelper } from '@/mixins/debounce'; | ||
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers'; | ||
import Vue from 'vue'; | ||
import { getNodeViewTab } from '@/utils'; | ||
|
||
const UNDO_REDO_DEBOUNCE_INTERVAL = 100; | ||
|
||
export const historyHelper = mixins(debounceHelper, deviceSupportHelpers).extend({ | ||
computed: { | ||
...mapStores( | ||
useNDVStore, | ||
useHistoryStore, | ||
useUIStore, | ||
useWorkflowsStore, | ||
), | ||
isNDVOpen(): boolean { | ||
return this.ndvStore.activeNodeName !== null; | ||
}, | ||
}, | ||
mounted() { | ||
document.addEventListener('keydown', this.handleKeyDown); | ||
}, | ||
destroyed() { | ||
document.removeEventListener('keydown', this.handleKeyDown); | ||
}, | ||
methods: { | ||
handleKeyDown(event: KeyboardEvent) { | ||
const currentNodeViewTab = getNodeViewTab(this.$route); | ||
|
||
if (event.repeat || currentNodeViewTab !== MAIN_HEADER_TABS.WORKFLOW) return; | ||
if (this.isCtrlKeyPressed(event) && event.key === 'z') { | ||
event.preventDefault(); | ||
if (!this.isNDVOpen) { | ||
if (event.shiftKey) { | ||
this.callDebounced('redo', { debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL, trailing: true }); | ||
} else { | ||
this.callDebounced('undo', { debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL, trailing: true }); | ||
} | ||
} else if (!event.shiftKey) { | ||
this.trackUndoAttempt(event); | ||
} | ||
} | ||
}, | ||
async undo() { | ||
const command = this.historyStore.popUndoableToUndo(); | ||
if (!command) { | ||
return; | ||
} | ||
if (command instanceof BulkCommand) { | ||
this.historyStore.bulkInProgress = true; | ||
const commands = command.commands; | ||
const reverseCommands: Command[] = []; | ||
for (let i = commands.length - 1; i >= 0; i--) { | ||
await commands[i].revert(); | ||
reverseCommands.push(commands[i].getReverseCommand()); | ||
} | ||
this.historyStore.pushUndoableToRedo(new BulkCommand(reverseCommands)); | ||
await Vue.nextTick(); | ||
this.historyStore.bulkInProgress = false; | ||
} | ||
if (command instanceof Command) { | ||
await command.revert(); | ||
this.historyStore.pushUndoableToRedo(command.getReverseCommand()); | ||
this.uiStore.stateIsDirty = true; | ||
} | ||
this.trackCommand(command, 'undo'); | ||
}, | ||
async redo() { | ||
const command = this.historyStore.popUndoableToRedo(); | ||
if (!command) { | ||
return; | ||
} | ||
if (command instanceof BulkCommand) { | ||
this.historyStore.bulkInProgress = true; | ||
const commands = command.commands; | ||
const reverseCommands = []; | ||
for (let i = commands.length - 1; i >= 0; i--) { | ||
await commands[i].revert(); | ||
reverseCommands.push(commands[i].getReverseCommand()); | ||
} | ||
this.historyStore.pushBulkCommandToUndo(new BulkCommand(reverseCommands), false); | ||
await Vue.nextTick(); | ||
this.historyStore.bulkInProgress = false; | ||
} | ||
if (command instanceof Command) { | ||
await command.revert(); | ||
this.historyStore.pushCommandToUndo(command.getReverseCommand(), false); | ||
this.uiStore.stateIsDirty = true; | ||
} | ||
this.trackCommand(command, 'redo'); | ||
}, | ||
trackCommand(command: Undoable, type: 'undo'|'redo'): void { | ||
if (command instanceof Command) { | ||
this.$telemetry.track(`User hit ${type}`, { commands_length: 1, commands: [ command.name ] }); | ||
} else if (command instanceof BulkCommand) { | ||
this.$telemetry.track(`User hit ${type}`, { commands_length: command.commands.length, commands: command.commands.map(c => c.name) }); | ||
} | ||
}, | ||
trackUndoAttempt(event: KeyboardEvent) { | ||
if (this.isNDVOpen && !event.shiftKey) { | ||
const activeNode = this.ndvStore.activeNode; | ||
if (activeNode) { | ||
this.$telemetry.track(`User hit undo in NDV`, { node_type: activeNode.type }); | ||
} | ||
} | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.