Skip to content

Commit 4175ff0

Browse files
committed
feat(history): can create an alternative branch by ignoring one given command
1 parent 26a52e1 commit 4175ff0

3 files changed

Lines changed: 96 additions & 13 deletions

File tree

src/api/history/TreeHistory.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@ export abstract class TreeHistory implements LinearHistoryBase {
153153
*/
154154
public abstract deleteFrom(id: number): void;
155155

156+
/**
157+
* Creates an alternative branch that does not contain the targeted node.
158+
* If the targeted node has no child, nothing happens.
159+
* Otherwise, the history creates a new branch at the parent level without the targeted node.
160+
* Does not delete anything, so compatible with the usage path (see path()).
161+
* @param id - The node ID to remove.
162+
*/
163+
public abstract deleteNode(id: number): void;
164+
156165
/**
157166
* Retrieves the modifiable attributes of the undoable node identified by the given id.
158167
* See the method `getModifiableCmdAttributes` in the class `ModifiableCommand`.

src/impl/history/TreeHistoryImpl.ts

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,10 @@ export class TreeHistoryImpl extends TreeHistory {
197197
}
198198

199199
public deleteFrom(id: number): void {
200-
// Cannot delete if keeping path
201-
if (this.keepPath) {
202-
return;
203-
}
204-
205200
const node = this.undoableNodes[id];
206201

207-
if (node === undefined) {
202+
// Cannot delete if keeping path
203+
if (this.keepPath || node === undefined) {
208204
return;
209205
}
210206

@@ -246,6 +242,26 @@ export class TreeHistoryImpl extends TreeHistory {
246242
return getModifiableCmdAttributes(node.undoable);
247243
}
248244

245+
public deleteNode(id: number): void {
246+
const node = this.undoableNodes[id];
247+
const parent = node?.parent;
248+
249+
// Cannot delete if keeping path.
250+
// If the node to delete has no child, just a simple removal.
251+
if (this.keepPath || parent === undefined || node === undefined || node.children.length === 0) {
252+
return;
253+
}
254+
255+
const currentId = this.currentNode.id;
256+
this.goTo(parent.id);
257+
// Cloning the child trees of the node to remove.
258+
// The goal is to create new branches at the parent level without the node to remove.
259+
const clonedTree = this.cloneSubtree(node);
260+
this.addClonedSubtree(clonedTree);
261+
// Going back to the original position
262+
this.goTo(currentId);
263+
}
264+
249265
public applyModifiedAttributesOn(id: number, data: object): void {
250266
// not `at(i)` since at considers -1 as last one
251267
const node = this.undoableNodes[id];
@@ -279,19 +295,28 @@ export class TreeHistoryImpl extends TreeHistory {
279295
}
280296
}
281297

282-
private proceedModifiedNode(currentNode: UndoableTreeNode): void {
298+
/**
299+
* Takes a modified command and re-processes it within the history
300+
* @param node - The modified command to re-executed
301+
*/
302+
private proceedModifiedNode(node: UndoableTreeNode): void {
283303
// Executing the cloned undoable object
284-
currentNode.undoable.redo();
304+
node.undoable.redo();
285305
// Must refresh the visual snapshot cache
286-
if (currentNode.undoable instanceof UndoableCommand) {
287-
currentNode.undoable.refreshCache();
306+
if (node.undoable instanceof UndoableCommand) {
307+
node.undoable.refreshCache();
288308
}
289309
// Adding the novel commands to the history
290-
this.add(currentNode.undoable);
310+
this.add(node.undoable);
291311
}
292312

293-
private addClonedSubtree(currentNode: UndoableTreeNode): void {
294-
for (const child of currentNode.children) {
313+
/**
314+
* Adds the given node and its child trees in the history.
315+
* Re-execute the added nodes.
316+
* @param node - The node to add (and its children)
317+
*/
318+
private addClonedSubtree(node: UndoableTreeNode): void {
319+
for (const child of node.children) {
295320
this.proceedModifiedNode(child);
296321
this.undo();
297322
this.addClonedSubtree(child);

test/history/TreeHistory.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,55 @@ describe("using a tree-based history", () => {
11011101
});
11021102
});
11031103

1104+
describe("when deleting nodes", () => {
1105+
beforeEach(() => {
1106+
history = new TreeHistoryImpl(false, true);
1107+
history.add(undoable0);
1108+
history.add(undoable1);
1109+
// A - B*
1110+
});
1111+
1112+
test("no effect when using bad id", () => {
1113+
history.goTo(history.root.children[0].id);
1114+
history.deleteNode(10);
1115+
// A* - B
1116+
expect(history.size()).toBe(2);
1117+
});
1118+
1119+
test("does nothing the node if it has no child", () => {
1120+
history.goTo(history.root.children[0].id);
1121+
history.deleteNode(history.root.children[0].children[0].id);
1122+
// A* - B
1123+
expect(history.size()).toBe(2);
1124+
expect(history.currentNode.undoable).toBe(undoable0);
1125+
});
1126+
1127+
test("no effect if trying to remove the root", () => {
1128+
history.deleteNode(history.root.id);
1129+
// A - B*
1130+
expect(history.size()).toBe(2);
1131+
});
1132+
1133+
test("creates a branch with correct undoable objects", () => {
1134+
history.add(new CmdModifiableDouble());
1135+
history.undo();
1136+
history.add(new CmdModifiableDouble3());
1137+
// A - B - C|D*
1138+
history.deleteNode(1);
1139+
1140+
// A - B - C|D*
1141+
// - C'
1142+
// - D'
1143+
expect(history.size()).toBe(6);
1144+
expect(history.root.children).toHaveLength(1);
1145+
expect(history.root.children[0].children).toHaveLength(3);
1146+
expect(history.root.children[0].children[0].children).toHaveLength(2);
1147+
expect(history.root.children[0].children[1].undoable).toBeInstanceOf(CmdModifiableDouble);
1148+
expect(history.root.children[0].children[2].undoable).toBeInstanceOf(CmdModifiableDouble3);
1149+
expect(history.root.children[0].children[0].undoable).toBe(undoable1);
1150+
});
1151+
});
1152+
11041153
describe("using a standard tree history storing modifiable commands", () => {
11051154
let cmd: CmdModifiableDouble;
11061155

0 commit comments

Comments
 (0)