Skip to content

Commit fc0d73d

Browse files
authored
Merge pull request #16 from wiredjs/undo
basic undo-redo support
2 parents 8b036c7 + 9b4902e commit fc0d73d

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

src/components/undo-redo.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { BaseElement, element } from '../base-element.js';
2+
import UndoRedo from 'udrd';
3+
import { Op, UndoableOp } from '../ops.js';
4+
5+
@element('undo-redo')
6+
export class UndoRedoElement extends BaseElement {
7+
private readonly undoRedo = new UndoRedo<Op>();
8+
private canRedo = false;
9+
private canUndo = false;
10+
11+
push(uop: UndoableOp) {
12+
this.undoRedo.push(uop.do, uop.undo);
13+
this.refreshCans();
14+
}
15+
16+
undo() {
17+
const op = this.undoRedo.undo();
18+
if (op) {
19+
this.fireEvent('do-op', op);
20+
this.refreshCans();
21+
}
22+
}
23+
24+
redo() {
25+
const op = this.undoRedo.redo();
26+
if (op) {
27+
this.fireEvent('do-op', op);
28+
this.refreshCans();
29+
}
30+
}
31+
32+
private refreshCans() {
33+
let fire = false;
34+
if (this.canRedo !== this.undoRedo.canRedo()) {
35+
this.canRedo = !this.canRedo;
36+
fire = true;
37+
}
38+
if (this.canUndo !== this.undoRedo.canUndo()) {
39+
this.canUndo = !this.canUndo;
40+
fire = true;
41+
}
42+
if (fire) {
43+
this.fireEvent('undo-state-change', {
44+
canRedo: this.canRedo,
45+
canUndo: this.canUndo
46+
});
47+
}
48+
}
49+
}

src/designer/design-section.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,31 @@ export class DesignSection extends BaseElement {
6565
private handleOp(e: CustomEvent) {
6666
const uop = e.detail as UndoableOp;
6767
if (uop) {
68-
this.doOp(uop.do);
68+
this.doOp(uop.do, true);
6969
}
7070
}
7171

72-
private doOp(op: Op) {
72+
doOp(op: Op, skipSelection?: boolean) {
73+
let s: Shape | null = op.shape;
7374
switch (op.type) {
7475
case 'add':
75-
this.canvas.addShape(op.shape);
76+
this.canvas.addShape(s);
7677
break;
7778
case 'delete':
7879
this.selectedShape = null;
79-
this.canvas.deleteShape(op.shape);
80+
this.canvas.deleteShape(s);
81+
s = null;
8082
break;
8183
case 'update':
82-
this.canvas.updateShape(op.shape);
84+
this.canvas.updateShape(s);
8385
break;
8486
default:
87+
s = null;
8588
break;
8689
}
90+
if (s && (!skipSelection)) {
91+
this.selectedShape = s;
92+
}
8793
}
8894

8995
private onSelect(e: CustomEvent) {

src/main-app.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { BaseElement, html, element, property } from './base-element.js';
22
import { flexStyles } from './flex-styles.js';
3+
import { UndoableOp, Op } from './ops.js';
4+
import { UndoRedoElement } from './components/undo-redo.js';
5+
import { DesignSection } from './designer/design-section';
36
import './components/dac-tab-bar';
47
import './components/dac-tab';
58
import './components/dac-icon';
69
import './designer/design-section';
10+
import './components/undo-redo.js';
711

812
@element('main-app')
913
export class MainApp extends BaseElement {
@@ -54,11 +58,11 @@ export class MainApp extends BaseElement {
5458
<div id="toolbar" class="horizontal layout center">
5559
<div class="flex">Draw A Component</div>
5660
<div id="appControls">
57-
<button id="undoBtn" disabled title="Undo">
61+
<button id="undoBtn" disabled title="Undo" @click="${() => this.ur.undo()}">
5862
<dac-icon icon="undo"></dac-icon>
5963
<div>Undo</div>
6064
</button>
61-
<button id="redoBtn" disabled title="Undo">
65+
<button id="redoBtn" disabled title="Redo" @click="${() => this.ur.redo()}">
6266
<dac-icon icon="redo"></dac-icon>
6367
<div>Redo</div>
6468
</button>
@@ -75,15 +79,44 @@ export class MainApp extends BaseElement {
7579
</dac-tab-bar>
7680
</div>
7781
<main class="flex horizontal layout">
78-
<design-section class="flex" style="${this.selectedTab === 'design' ? '' : 'display: none;'}"></design-section>
82+
<design-section class="flex" style="${this.selectedTab === 'design' ? '' : 'display: none;'}" @op="${this.onOp}"></design-section>
7983
<div class="flex" style="${this.selectedTab === 'preview' ? '' : 'display: none;'}">
8084
<p>Preview goes here</p>
8185
</div>
8286
</div>
87+
<undo-redo @do-op="${this.doOp}" @undo-state-change="${this.updateUndoState}"></undo-redo>
8388
`;
8489
}
8590

91+
private get ur(): UndoRedoElement {
92+
return this.$$('undo-redo') as UndoRedoElement;
93+
}
94+
95+
private get designSection(): DesignSection {
96+
return this.$$('design-section') as DesignSection;
97+
}
98+
8699
private tabClick(e: Event) {
87100
this.selectedTab = (e.currentTarget as HTMLElement).getAttribute('name') || 'design';
88101
}
102+
103+
private onOp(e: CustomEvent) {
104+
const uop = e.detail as UndoableOp;
105+
if (uop) {
106+
this.ur.push(uop);
107+
}
108+
}
109+
110+
private doOp(e: CustomEvent) {
111+
const op = e.detail as Op;
112+
if (op) {
113+
this.designSection.doOp(op);
114+
}
115+
}
116+
117+
private updateUndoState(e: CustomEvent) {
118+
const detail = e.detail;
119+
(this.$('undoBtn') as HTMLButtonElement).disabled = !detail.canUndo;
120+
(this.$('redoBtn') as HTMLButtonElement).disabled = !detail.canRedo;
121+
}
89122
}

0 commit comments

Comments
 (0)