enigma-bbs/docs/_docs/modding/menu-modules.md

188 lines
7.6 KiB
Markdown
Raw Permalink Normal View History

2019-02-06 01:42:10 +00:00
---
layout: page
2022-02-04 14:51:03 +00:00
title: Menu Modules
2019-02-06 01:42:10 +00:00
---
## Menu Modules
2022-08-09 20:41:23 +00:00
From initial connection to the screens and mods your users interact with, the entire experience is made up of menu entries — And all menu entries found within [menu.hjson](../configuration/menu-hjson.md) are backed by *Menu Modules*. For basic menus, a standard handler is implemented requiring no code. However, if you would like to create a menu that has custom handling, you will very likely be inheriting from from `MenuModule`. More on this below.
2022-08-07 19:06:47 +00:00
> :information_source: Remember that ENiGMA does not impose any stucture to your system! The "flow" of all `menu.hjson` entries is up to you!
2019-02-06 01:42:10 +00:00
2022-08-09 04:19:20 +00:00
> :bulb: If the `module` entry is not present in a `menu.hjson` entry, the system automatically uses [standard_menu.js](/core/standard_menu.js).
2019-02-06 01:42:10 +00:00
## Creating a New Module
2022-08-07 19:06:47 +00:00
At the highest level, to create a new custom menu or mod, inherit from `MenuModule` and expose it via the `getModule` exported method:
```javascript
// my_fancy_module.js
exports.getModule = class MyFancyModule extends MenuModule {
constructor(options) {
super(options);
}
};
```
2022-08-09 04:19:20 +00:00
Next, override the appropriate methods to add some functionality! Below is an example fragment overriding just `initSequence()`:
```javascript
initSequence() {
async.series(
[
callback => {
// call base method
return this.beforeArt(callback);
},
callback => {
// a private method to display a main "page"
return this._displayMainPage(false, callback);
},
],
() => {
this.finishedLoading();
}
);
}
```
2022-08-09 20:41:23 +00:00
> :bulb: Remember that *all* menus within ENiGMA are created by inheriting from `MenuModule`. Take a look at existing examples such as [WFC](/core/wfc.js), [NUA](/core/nua.js), [MRC](/core/mrc.js) and more!
### ModuleInfo
2022-10-01 05:55:40 +00:00
To register your module with the system, include a `moduleInfo` declaration in your exports. The following members are available:
2022-08-09 20:41:23 +00:00
| Field | Required | Description |
|-------|----------|-------------|
| `name` | :+1: | Short name of the module |
| `desc` | :+1: | Long description of this module |
| `author` | :+1: | Author(s) of module |
| `packageName` | :-1: | Defines a reverse DNS style package name. Can be used in conjunction with the `getModDatabasePath()` call form [database.js](/core/database.js) to interact with a database specific to your module (See example below) |
**Example**:
2022-08-09 04:19:20 +00:00
```javascript
2022-10-01 05:55:40 +00:00
exports.moduleInfo = {
2022-08-09 04:19:20 +00:00
name: 'Super Dope Mod',
desc: '...a super dope mod, duh.',
author: 'You!',
2022-08-09 20:41:23 +00:00
packageName: `com.myname.foo.super-dope-mod`,
2022-08-09 04:19:20 +00:00
};
```
2022-08-09 20:41:23 +00:00
### Per-Mod Databases
2022-10-01 05:55:40 +00:00
Custom mods often need their own data persistence. This can be acheived with `getModDatabsePath()` and your `moduleInfo`'s `packageName`.
2022-08-09 20:41:23 +00:00
**Example**:
```javascript
self.database = getTransactionDatabase(
new sqlite3.Database(getModDatabasePath(moduleInfo), callback)
);
```
Given the `packageName` above, a database will be created at the following location:
```bash
$enigma-bbs/db/mods/com.myname.foo.super-dope-mod.sqlite3
```
### Menu Methods
Form handler methods specified by `@method:someName` in your `menu.hjson` entries map to those found in your module's `menuMethods` object. That is, `this.menuMethods` and have the following signature `(formData, extraArgs, cb)`. For example, consider the following `menu.hjson` fragment:
```hjson
actionKeys: [
{
keys: [ "a", "shift + a" ]
action: @method:toggleAvailable
}
]
```
We can handle this in our module as such:
```javascript
exports.getModule = class MyFancyModule extends MenuModule {
constructor(options) {
super(options);
this.menuMethods = {
toggleAvailable: (formData, extraArgs, cb) => {
// ...do something fancy...
return cb(null);
}
};
}
}
```
## MenuModule Lifecycle
Below is a very high level diagram showing the basic lifecycle of a MenuModule.
2019-02-06 01:42:10 +00:00
2022-08-07 04:51:59 +00:00
![Basic Menu Lifecycle](../../assets/images/basic_menu_lifecycle.png)
2019-02-06 01:42:10 +00:00
2022-08-07 19:06:47 +00:00
Methods indicated above with `()` in their name such as `enter()` are overridable when inheriting form `MenuModule`.
2022-10-01 17:43:56 +00:00
* `enter()` is the first to be called. There is no callback. The default implementation is to simply call `this.initSequence()`.
* `displayQueuedInterruptions(callback)` is called, and if interruptions are allowed for this menu, any that may be queued will be displayed first.
* `beforeArt(callback)` is called before any art is displayed. The default implementation will set emulated baud rate, and clear the screen if either are requested by the menu's `config` block.
* `mciReady(mciData, callback)` is called when art is loaded and MCI codes are initialized. The default implementation of a custom `MenuModule` simply continues. See also [standardMCIReadyHandler](#standardmcireadyhandlermcidata-callback).
2022-08-07 19:06:47 +00:00
## MenuModule Helper Methods
Many helper methods exist and are available to code inheriting from `MenuModule`. Below are some examples. Poke around at [menu_module.js](../../../core/menu_module.js) to discover more!
2022-08-09 02:15:23 +00:00
### Views & View Controller
2022-08-12 20:28:18 +00:00
#### `displayAsset(name | Buffer, options, callback)`:
Display an asset by `name` or by supplying an `Buffer`.
`options` is an optional Object with any of the following properties:
* `clearScreen` (Boolean): Should the screen be cleared first?
* `encoding` (String): Encoding of `Buffer` if used. Defaults to `cp437`.
* `font` (String): SyncTERM style font to use.
* `trailingLF` (Boolean): Should a trailing LF be allowed?
* `startRow` (Number): Row in which to start drawing at
#### `prepViewController(name, formId, mciMap, callback)`:
Prepares the menu's View Controller for a form of `name` and `formId` using the supplied `mciMap`. `callback` has the following siguature: `(err, viewController, created)` where `created` is `true` if a new View Controller was made.
2022-08-07 19:06:47 +00:00
* `prepViewControllerWithArt()`
* `displayArtAndPrepViewController()`
* `setViewText()`
* `getView()`
* `updateCustomViewTextsWithFilter()`
* `refreshPredefinedMciViewsByCode()`
2022-08-09 02:15:23 +00:00
### Validation
2022-08-07 19:06:47 +00:00
* `validateMCIByViewIds()`
* `validateConfigFields()`
2022-08-09 02:15:23 +00:00
### Date/Time Helpers
2022-08-13 01:21:45 +00:00
The following methods take a single input to specify style, defaulting to `short`. If your menu or theme `config` block specifies a cooresponding value such as `dateFormat` or `dateTimeFormat`, that value will be used, else standard fallbacks apply:
2022-08-07 19:06:47 +00:00
* `getDateFormat()`
* `getTimeFormat()`
* `getDateTimeFormat()`
2019-02-06 01:42:10 +00:00
2022-08-09 02:15:23 +00:00
### Misc
* `promptForInput()`
2022-10-01 17:43:56 +00:00
#### `standardMCIReadyHandler(mciData, callback)`:
This is a standard and commonly used `mciReady()` implementation:
2022-08-09 04:19:20 +00:00
```javascript
mciReady(mciData, cb) {
return this.standardMCIReadyHandler(mciData, cb);
}
```
2022-08-13 01:21:45 +00:00
Where `mciData` is a Object mapping [MCI codes](../art/mci.md) such as `TL2` to their properties:
* `SGR`: Graphics rendition
* `focusSGR` (Only present if art contained both, ie: `TL2^[0;mTL2`)
* `position` (Array of Number): Position in [Row, Column] order
* `args` (Array): Any arguments to the MCI code
* `code` (String): The code itself, such as `TL`
* `id` (Number): The MCI code's ID such as `1`
2022-08-07 19:06:47 +00:00
> :information_source: Search the code for the above methods to see how they are used in the base system!
2022-08-09 04:19:20 +00:00
2022-08-09 20:41:23 +00:00
## Custom Mods
Most mods will also derive from `MenuModule`. Some things to be aware of:
* Custom mods that bring in their own dependencies must also include their own `package.json` and other Node requirements
2022-10-01 17:43:56 +00:00
* Be sure to use `packageName` and `getModDatabasePath()` for any (database) peristence needs.
* Custom mods in `mods/the_mod_name/` and the `MenuModule` entry point must be within a file of the same name: `mods/the_mod_name/the_mod_name.js`
* To import ENiGMA modules `require()` from `../../core/`