How To Make A Unit - Part 3
Light And Switch - Undo / Redo

Note
This article is part of a series. If you haven't completed part one or two, we recommend you do so before continuing with this one.
We're going to use the unit you created in the previous chapter and modify it to have Undo / Redo functionality.
How Does Undo / Redo Work
One action a user might perform is an attempt to undo or redo an action. Similar to the Import / Export section described in the previous chapter, much of this process is handled by Alchemy, but aspects related to the configuration of a unit is left to the designer of that unit.
In order to not have to save the entire configuration for every single action, a "Modification" concept is used instead. The idea is that if a designer wishes that a certain action be "undoable", then the performance of that action should return a "modification" object, which contains information on what aspect of the configuration was changed and what the before and after of that aspect was.
Similar to Export/Import, Alchemy only stipulates that the data stored in a Modification object is a string, meaning that any format can be used so long as it is representable in string form. The collection and encoding of this data is the responsibility of the unit's designer, as is the correct decoding and distribution of this data when an Undo or Redo event occurs.
This "modification" is stored and managed by Alchemy. When a user activates the "undo" function, Alchemy runs the "undo" method of the unit, providing the "modification" object. If the user activates the "redo" function, Alchemy runs the "redo" method of the unit and provides the "modification" object.
If a unit has not returned a modification for an action by the time a user attempts to "undo" or "redo" it; then the next modification in the undo/redo timeline is used instead, voiding the unit's opportunity to provide a modification for this action.
Let's Do It
All you have to do is modify the main.ts file to look like this
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import apiConsole from "../typescript-deno-0.3.3-0.1.0/index.ts";
let state = false;
function updateExportableData() {apiConsole.unit.updateExportableData(
JSON.stringify(state)
);
}
apiConsole.unit.setImportListener(({dataString}) => {state = JSON.parse(dataString) as boolean;
apiConsole.interface.glowbox.onOff(0, true, state);
if(state) {apiConsole.interface.checkbox.check(1, true);
} else {apiConsole.interface.checkbox.uncheck(1, true);
}
});
apiConsole.unit.setMouseEventListener(({ event }) => { if(event.type === "checkbox") {state = event.content === "checked";
apiConsole.interface.glowbox.onOff(0, true, state);
updateExportableData();
return apiConsole.createModification(
"light",
JSON.stringify(!state),
JSON.stringify(state)
);
}
});
apiConsole.unit.setUndoListener(({ modification }) => { if(modification.meta === 'light') {let state = JSON.parse(modification.before);
apiConsole.interface.glowbox.onOff(0, true, state);
if(state) {apiConsole.interface.checkbox.check(1, true);
} else {apiConsole.interface.checkbox.uncheck(1, true);
}
}
});
apiConsole.unit.setRedoListener(({ modification }) => { if(modification.meta === 'light') {let state = JSON.parse(modification.after);
apiConsole.interface.glowbox.onOff(0, true, state);
if(state) {apiConsole.interface.checkbox.check(1, true);
} else {apiConsole.interface.checkbox.uncheck(1, true);
}
}
});
Show More
Lets step through the changes and additions made here
Our modifications come in three parts
- setting up the unit to send Modifications
- dealing with Undo events
- dealing with Redo events
Lets start with sending Modifications first
We must send a Modification on ever successful change of the light, so at the sample place where we added the "updateExportableData" command, we also return the Modification. The "setMouseEventListener" listener is already set up to handle the channelling of Modifications, so all we have to do is return the Modification in the callback function.
Lines 26 to 30 cover this. The Typescript-Deno API console comes with a handy "createModification" command to which we send three important pieces of information; the modification type, the before state and the after state. In this case we define this modification as a change to the "light", with the "before" state just being whatever the opposite of the current "state" value is. The "after" state is the current value.
Now for the listeners
Much like the "setImportListener" command, we use a pair of listeners to handle undo and redo events; "setUndoListener" and "setRedoListener". Both take a callback function which will be run when the corresponding event occurs, being supplied with the Modification.
The Undo handler can be seen starting on line 34, and the Redo lister can be seen starting on line 48. Both function in the same way; first checking for the modification type then updating the state, after which the Parts are refreshed to match the new state.
Note how the "before" state is used in the Undo handler, and the "after" state is used in the Redo handler.
Done!
Congratulations! You now have yourself a custom made, externally controlled unit with import/export and undo/redo functionality.
This concludes the Getting Started Tutorials. We hope you found it useful!
You should now have a good grasp on the basics of creating a unit for use in Alchemy. There is of course much more you can do; check out some of the other tutorials for more, and don't forget the reference section which covers all the messages and data structures which can be sent from or received by Alchemy.
Happy Coding!