Adding a feature is done by adding one or more plugins.
In this page, we will go through the steps of creating a new feature, with a dummy
example : Party
mode. This mode could be
toggle from a top bar menu. When enable, this mode will display a text PARTY
in all cells which contains the content party
.
A plugin should extend either CorePlugin
or UIPlugin
depending on its role.
The plugin should also be register in the registry of plugins, in order to load
it at the model startup. (corePluginRegistry
or uiPluginRegistry
). Mode details
about plugins can be found in the plugin section
In our example, we will create two plugins, a new CorePlugin
which will manage
wether the party mode is active, and a new UIPlugin
that will be responsible
to draw the PARTY
text.
const { CorePlugin, UIPlugin } = o_spreadsheet;
class PartyPlugin extends CorePlugin {}
class PartyDrawerPlugin extends UIPlugin {}
// Register the plugins in order to load it at the model startup
corePluginRegistry.add("party_plugin", PartyPlugin);
uiPluginRegistry.add("party_drawer_plugin", PartyDrawerPlugin);
The plugin can have an internal state.
Here, we add an internal state for our plugin
class PartyPlugin extends CorePlugin {
readonly isPartyModeEnabled: boolean = false;
}
The state must be updated with this.history.update
function for the changes to be recorded in the history system (undo/redo). It cannot be changed in any other way!
A good practice is to declare the state readonly.
Data should be persisted via the import
/export
functions.
class PartyPlugin extends CorePlugin {
readonly isPartyModeEnabled: boolean = false;
import(data) {
this.history.update("isPartyModeEnabled", data.isPartyModeEnabled);
}
export(data) {
data.isPartyModeEnabled = this.isPartyModeEnabled;
}
}
Hint: this.history
can be used with multiple level of depth:
class DummyPlugin extends CorePlugin {
readonly records = {
1: {
data: {
1: {
text: "hello"
}
}
}
};
// Replace "hello" by "Bye"
this.history.update("records", 1, "data", 1, "text", "Bye");
// Add a new object in data
this.history.update("records", 1, "data", 2, { text: "Here" });
// Remove entry 1 of data
this.history.update("records", 1, "data", undefined);
}
The plugin can introduce new public getters to make parts of its state available for other plugins or the user interface.
A getter method should only read data and never write anything in the plugin's state nor dispatch any command. In other words, it shouldn't have any side-effect!
class PartyPlugin extends CorePlugin {
static getters = ["isPartyMode"]; // declare the method as a getter.
// getter to check if the party mode is enabled
isPartyMode(): boolean {
return this.isPartyModeEnabled;
}
}
The plugin can handle a command and react to it in order to update its internal
state. Here we introduce a new command "TOGGLE_PARTY_MODE"
. More details about adding
a new command are explained in the command section
const { coreTypes } = o_spreadsheet;
coreTypes.add("TOGGLE_PARTY_MODE"); // declare the command as a core command
class PartyPlugin extends CorePlugin {
handle(cmd) {
switch (cmd.type) {
case "TOGGLE_PARTY_MODE":
// Ensure the change is historized (undo-able and redo-able), using `this.history`.
this.history.update("isPartyModeEnabled", !this.isPartyModeEnabled);
break;
}
}
}
The plugin can also react to commands from other plugins. Let's way want to automatically enable party mode when the user sets the content of a cell to "party"
. We can handle the existing UPDATE_CELL
command.
class PartyPlugin extends CorePlugin {
handle(cmd) {
switch (cmd.type) {
case "TOGGLE_PARTY_MODE":
...
break;
case "UPDATE_CELL":
if (cmd.content === "party") {
this.history.update("isPartyModeEnabled", true);
}
break;
}
}
}
As our core plugin is now able to handle its proper state, we need a way to reflect this state in the UI. This can be done with mainly two different ways:
- Using the
drawLayer
method on UIPlugin
This method will be called in order to draw content directly on the canvas.
- Using a getter in a new component (Side panels, menu item, ...)
This method is explained here
class PartyDrawerPlugin extends UIPlugin {
drawLayer(renderingContext: GridRenderingContext, layer: LAYERS) {
if (layer === LAYERS.Headers) {
// TODO
for (const cell of )
}
}
}