History
This chapter mainly introduces knowledge related to undo and redo plugin. By reading, you can learn about
- How to implement undo and redo for element operations
- How to configure whether to record additions, removals, and property changes
- How to merge multiple changes into a single history entry
Usage
You can enable undo/redo with the History plugin, for example:
import { Graph, History } from '@antv/x6'
const graph = new Graph({
background: {
color: '#F2F7FA',
},
})
graph.use(
new History({
enabled: true,
}),
)
Demo
- After moving a node freely, the Undo button becomes available.
- Clicking the Undo button restores the node’s position, and then the Redo button becomes available.
- Clicking the Redo button updates the node’s position.
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Button } from 'antd'
import { Graph, History } from '@antv/x6'
interface Props {}
interface State {
canUndo: boolean
canRedo: boolean
}
export default class App extends React.Component<Props, State> {
private container: HTMLDivElement
private graph: Graph
state: State = {
canRedo: false,
canUndo: false,
}
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
graph.use(
new History({
enabled: true,
}),
)
graph.addNode({
x: 100,
y: 60,
width: 100,
height: 40,
label: 'Drag Me',
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
},
})
graph.on('history:change', () => {
this.setState({
canRedo: graph.canRedo(),
canUndo: graph.canUndo(),
})
})
this.graph = graph
}
onUndo = () => {
this.graph.undo()
}
onRedo = () => {
this.graph.redo()
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="history-app">
<div className="app-btns">
<Button.Group>
<Button onClick={this.onUndo} disabled={!this.state.canUndo}>
Undo
</Button>
<Button onClick={this.onRedo} disabled={!this.state.canRedo}>
Redo
</Button>
</Button.Group>
</div>
<div ref={this.refContainer} className="app-content" />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<App />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.history-app {
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-btns {
bottom: 0;
margin: 0;
padding: 0 8px;
}
.app-content {
width: 100%;
height: 240px;
margin: 0;
margin-top: 16px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}Configuration
| Property Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
| stackSize | number | 0 |
A stackSize of 0 means no limit; otherwise only that many entries are kept. |
|
| ignoreAdd | boolean | false |
If ignoreAdd is true, adding elements will not be recorded in the history. |
|
| ignoreRemove | boolean | false |
If ignoreRemove is true, removing elements will not be recorded in the history. |
|
| ignoreChange | boolean | false |
If ignoreChange is true, changes to element properties will not be recorded in the history. |
|
| beforeAddCommand | (event, args) => any |
- | Called before a command is added to the Undo queue; if this method returns false, the command will not be added to the Undo queue. |
|
| afterAddCommand | (event, args, cmd) => any |
- | Called after a command is added to the Undo queue. | |
| executeCommand | (cmd, revert, options) => any |
- | Called when a command is undone or redone; revert is true if the command is undone, otherwise it indicates the command is redone. |
Tip
In actual projects, we often need to undo or redo multiple changes at once. X6 provides the concept of batch, which allows multiple changes to be merged into a single history entry. Here’s how to use it:
// Method 1
graph.startBatch('custom-batch-name')
// Changing the border color of the node and modifying its position will be merged into a single record, allowing for a single undo.
node.attr('body/stroke', 'red')
node.position(30, 30)
graph.stopBatch('custom-batch-name')
// Method 2
graph.batchUpdate(() => {
node.prop('zIndex', 10)
node.attr('label/text', 'hello')
node.attr('label/fill', '#ff0000')
})
API
graph.undo(…)
undo(options?: KeyValue): this
Undo. options will be passed to the event callback.
graph.undoAndCancel(…)
undoAndCancel(options?: KeyValue): this
Undo and do not add to the redo queue; the command cannot be redone. options will be passed to the event callback.
graph.redo(…)
redo(options?: KeyValue): this
Redo. options will be passed to the event callback.
graph.canUndo()
canUndo(): boolean
Returns whether undo is available.
graph.canRedo()
canRedo(): boolean
Returns whether redo is available.
graph.cleanHistory(…)
cleanHistory(options?: KeyValue): this
Clear the history queue. options will be passed to the event callback.
graph.getHistoryStackSize(…)
getHistoryStackSize(): number
Get the size of the history stack.
graph.getUndoRemainSize(…)
getUndoRemainSize(): number
Get the remaining size of the history undo stack.
graph.getUndoStackSize(…)
getUndoStackSize(): number
Get the size of the history undo stack.
graph.getRedoStackSize(…)
getRedoStackSize(): number
Get the size of the history redo stack.
graph.isHistoryEnabled()
isHistoryEnabled(): boolean
Returns whether history is enabled.
graph.enableHistory()
enableHistory(): this
Enable history state.
graph.disableHistory()
disableHistory(): this
Disable history state.
graph.toggleHistory(…)
toggleHistory(enabled?: boolean): this
Toggle the enabled state of history. Parameters are as follows:
| Name | Type | Required | Default Value | Description |
|---|---|---|---|---|
| enabled | boolean | - | Whether to enable history state; defaults to toggling the enabled state of history. |
Events
| Event Name | Parameter Type | Description |
|---|---|---|
history:undo |
{ cmds: Command[], options: KeyValue } |
Triggered when a command is undone. |
history:redo |
{ cmds: Command[], options: KeyValue } |
Triggered when a command is redone. |
history:cancel |
{ cmds: Command[], options: KeyValue } |
Triggered when a command is canceled. |
history:add |
{ cmds: Command[], options: KeyValue } |
Triggered when a command is added to the queue. |
history:clean |
{ cmds: Command[] | null, options: KeyValue } |
Triggered when the history queue is cleared. |
history:change |
{ cmds: Command[] | null, options: KeyValue } |
Triggered when the history queue changes. |
history:batch |
{ cmds: Command, options: KeyValue } |
Triggered when a batch command is received. |
graph.on('history:undo', ({ cmds }) => {
console.log(cmds)
})
// We can also listen to events on the plugin instance
history.on('undo', ({ cmds }) => {
console.log(cmds)
})