introduction
snapshot
is translated as a snapshot, which is used to directly obtain the status of the page at a certain runtime. The snapshot before and after the operation is stored, so that the page state can be redone and undone easily.
this article mainly introduces the principle of snapshot
tool implementation and its use in the project.
Design
to implement the history, redo and undo of page state, you need to support the following properties and methods
Properties
- History: stores the history of the page state, including the page initialization state to the previous page state
- undo record: stores each redone operation record, which is used to recover after undo
- current record: temporarily stores the current page state, which is mainly used to store it in the history after the next operation
- Last time to insert data: the insertion interval is too small and additional processing is required
// History
recordList: string[] = []
// cancel the record, which is used to redo
redoList: string[] = []
// the current record is temporarily stored in the currentRecord variable, and then stored in recordList when the user modifies it.
currentRecord = ''
// time when the data was last inserted
time = 0
method
Storage history push
when the user acts, the history is updated. The following points need to be considered.
- when the current operation time is less than 100 ms from the last insertion time, the current record is replaced and the add is canceled
- if the current record has a value, the last operation was manually inserted, the previously cached current record was pushed into
recordList
, and the redo record needs to be emptied - Save the current state to the current record
- set the maximum history capacity. When exceeded, delete the first inserted data
push(record: PageData) {
const nowTime = Date.now()
// prevent adding repetitive time. When the adding interval is less than 100ms, replace the current record and cancel the add.
if (this.time + 100 > nowTime) {
try {
// convert json to string storage
this.currentRecord = JSON.stringify(record)
} catch (error) {
return false
}
return false
}
this.time = nowTime
// determine whether the currentRecord record already exists, and store it in recordList.
if (this.currentRecord) {
this.recordList.push(this.currentRecord)
// after adding records, the redo records should be emptied.
this.redoList.splice(0, this.redoList.length)
}
// Save the current record
this.currentRecord = JSON.stringify(record)
// store a maximum of 30 records. If more than that, delete the previous records.
if (this.recordList.length > 30) {
this.recordList.unshift()
}
return true
}
undo operation undo
when the user relies on the history list stored in push
after the user's operation, and returns the page state to the previous state, you should pay attention to the following points:
- when no history is available, return
- retrieve the last stored data from the history
- if the current record exists, it needs to be stored in the redo record list
- the current record needs to be cleared to prevent repeated additions, because after undo, the
push
storage history method
directly.
will also be executed.
undo() {
// if there is no record, return false
if (this.recordList.length === 0) {
return null
}
const record = this.recordList.pop()
// add the current record to the redo record
if (this.currentRecord) {
this.redoList.push(this.currentRecord)
}
// discard the current record to prevent repeated additions
this.currentRecord = ''
return JSON.parse(record as string) as PageData
}
redo redo
when a user acts, relies on the redoList
list to return the page state to the state before undo, you should pay attention to the following points:
- when the redo record is not available, return
- retrieve the last stored data from the redo record
- if the current record has a value, you need to put it in the history list
- the current record needs to be cleared to prevent repeated additions, because after redoing, the
push
storage history method
directly
will also be executed.
redo() {
//return false if there is no redo record
if (this.redoList.length === 0) {
return null
}
const record = this.redoList.pop()
// add to the redo record
if (this.currentRecord) {
this.recordList.push(this.currentRecord)
}
// discard the current record to prevent repeated additions
this.currentRecord = ''
return JSON.parse(record as string) as PageData
}
process demonstration
suppose the data list is [1, 2, 3, 4]
, and the current attribute values are:
recordList = [1, 2, 3]
redoList = []
currentRecord = 4
1, add 5 manually, the push
method will be executed, and the attribute value will be
after execution.
recordList = [1, 2, 3, 4]
redoList = []
currentRecord = 5
2. Perform undo once,then undo will
be executed first, and the attribute values after execution are respectively
recordList = [1, 2, 3]
redoList = [5]
currentRecord = ''
Then executepush
, put 4push
in, and the attribute values after execution are respectively
recordList = [1, 2, 3]
redoList = [5]
currentRecord = 4
3. If the second undo is executed, undo
will be executed first, and the attribute value will be
after execution.
recordList = [1, 2]
redoList = [5, 4]
currentRecord = ''
then execute push
, insert 3 push
, and the attribute value is
after execution.
recordList = [1, 2]
redoList = [5, 4]
currentRecord = 3
4. If a redo is performed once, redo
will be executed first, and the attribute value will be
after execution.
recordList = [1, 2, 3]
redoList = [5]
currentRecord = ''
then execute push
, enter 4 push
, and the attribute value is
after execution.
recordList = [1, 2, 3]
redoList = [5]
currentRecord = 4
5. Manually add 6,the push
method will be executed, and the attribute values after execution are respectively
recordList = [1, 2, 3, 4]
redoList = []
currentRecord = 6
complete code
export default class Snapshot {
//History
recordList: string[] = []
// cancel the record, which is used to redo
redoList: string[] = []
//The current record is temporarily stored in the currentRecord variable. When the user modifies it, it is stored in recordList.
currentRecord = ''
// time when the data was last inserted
time = 0
push(record: PageData) {
const nowTime = Date.now()
// prevent adding repetitive time. When the adding interval is less than 100ms, replace the current record and cancel the add.
if (this.time + 100 > nowTime) {
try {
// convert json to string storage
this.currentRecord = JSON.stringify(record)
} catch (error) {
return false
}
return false
}
this.time = nowTime
// determine whether the currentRecord record already exists, and store it in recordList.
if (this.currentRecord) {
this.recordList.push(this.currentRecord)
// after adding records, the redo records should be emptied.
this.redoList.splice(0, this.redoList.length)
}
try {
// convert json to string storage
this.currentRecord = JSON.stringify(record)
} catch (error) {
return
}
// store a maximum of 30 records. If more than that, delete the previous records.
if (this.recordList.length > 30) {
this.recordList.unshift()
}
return true
}
undo() {
// if there is no record, return false
if (this.recordList.length === 0) {
return null
}
const record = this.recordList.pop()
// add the current record to the redo record
if (this.currentRecord) {
this.redoList.push(this.currentRecord)
}
// discard the current record to prevent repeated additions
this.currentRecord = ''
return JSON.parse(record as string) as PageData
}
redo() {
// if no record is redone, return false
if (this.redoList.length === 0) {
return null
}
const record = this.redoList.pop()
// add to the redo record
if (this.currentRecord) {
this.recordList.push(this.currentRecord)
}
// discard the current record to prevent repeated additions
this.currentRecord = ''
return JSON.parse(record as string) as PageData
}
}