Plugins can be switched on and off in the Plugins tab (#114)

admin committed 2026-09-17 20:15:54 +02:00
1 parent 0576bbaccb
commit de58de1d95
5 files changed
+39 -16

No files matched your search

+3 -2
@@ -16,8 +16,9 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
| GET | `sounds` | `[{file,name,bytes,updatedAt}]` — what the sound picker offers |
| POST | `sounds&name=jump.wav` | **raw sound bytes** → `{file,name,bytes}` (201) |
| DELETE | `sounds/{file}` | 204 |
| GET | `plugins` | `[{file,bytes,updatedAt}]` — every `.js` in `data/plugins/`, sorted by file name |
| GET | `plugins/{file}` | the module source as `text/javascript` (`nosniff`, `no-cache`); 404 for anything but a bare file name there — see [Plugins](Plugins) |
| GET | `plugins` | `[{file,bytes,updatedAt,active}]` — every `.js` in `data/plugins/`, sorted by file name, switched on or not |
| GET | `plugins/{file}` | the module source as `text/javascript` (`nosniff`, `no-cache`); 403 while the plugin is switched off, 404 for anything but a bare file name there — see [Plugins](Plugins) |
| PUT | `plugins/{file}` | `{active}` → the entry as above. Switches a plugin on or off, server-wide, stored in `data/plugins/plugins.json`; 422 when `active` is not a boolean |
Every project document carries `formatVersion` (`PROJECT_FORMAT_VERSION` in `core/model/Project.ts`,
currently 2). There are no load-time migrations: `serialize.ts` reads what it recognises and drops
+2 -2
@@ -89,7 +89,7 @@ src/editor/ App → Editor (layout + ticker), PlaySession (the run),
FilePicker (the shared "+ → Texture / Font / Sound" dialog; ImagePicker/FontPicker/SoundPicker only describe their row),
framing (fit/marquee/snap maths), FormDialog (the small modal forms: label column, Cancel + confirm, one open at a time), AssetTabEditor (the shared shell of the asset tabs),
TabStrip + tabRow (the tab row and what it does when the tabs outgrow it; plugin tabs are appended at run time)
src/editor/plugins/ startPlugins (list, import, host) → loader (order, checks, what became of each file), pluginApi (API v1), NodeBoard (api.ui)
src/editor/plugins/ startPlugins (list, import, host) → loader (PluginManager: order, checks, switching on and off, what became of each file), pluginApi (API v1, the per-plugin scope), PluginsPane (the Plugins tab), NodeBoard (api.ui)
src/editor/viewport/ WorldViewport base → FreeViewport (a camera of its own: fit, pan, wheel zoom) → SceneViewport, PreviewCanvas;
GameViewport follows the game's camera instead — editing tools, hence not in src/viewport/
src/editor/inspector/ Inspector panel, host (what a section builds into), field factory, previewHandlers (scrub → preview → command), valueSpecs
@@ -113,7 +113,7 @@ src/editor/list/ ListEditor (a row per item), slot (which list is being ed
src/editor/console/ log (ring buffer, argument formatting, the console.* capture), ConsolePanel (the tail under the tabs)
api/ PHP backend: index.php (routing), lib/{Store,TextureStore,FontStore,SoundStore,PluginStore,OriginalName,Response}.php
data/projects/ persisted projects, data/textures/ + data/fonts/ + data/sounds/ content-addressed uploads,
data/plugins/ editor plugins (filled from plugins/ by the build and the seeder)
data/plugins/ editor plugins and plugins.json (which are switched on); filled from plugins/ by the build and the seeder
tests/ Vitest unit tests tools/ seed-*, e2e (steps in tools/e2e/, rig in tools/e2e/lib/), smoke-*, lib/, assets/ (the walk demo's sheet)
```
+2 -2
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
node tools/seed-e2e.mjs && node tools/e2e.mjs # 963 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 969 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
@@ -232,4 +232,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
WebGL 2.0, and the same 963 checks as here.
WebGL 2.0, and the same 969 checks as here.
+1 -1
@@ -31,7 +31,7 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
| [Architecture](Architecture) | The stack, the source layout, the rules that keep it organised |
| [Development](Development) | Commands, the GPU requirement, the e2e suite, the three example games, continuous integration |
| [API](API) | The PHP JSON API and the project document |
| [Plugins](Plugins) | Extending the editor: the plugin file, how it is loaded, API v1, the demo plugin |
| [Plugins](Plugins) | Extending the editor: the plugin file, how it is loaded, switching one on and off, API v1, the demo plugin |
## Ideas, gaps and bugs
+31 -9
@@ -2,11 +2,12 @@ A **plugin** extends the editor. It is one JavaScript file – an ES module –
server, and the editor loads every file there when it starts. v1 is deliberately small: it offers what
the demo plugin needs (read the assets, add a tab, show nodes on a board) and nothing it does not.
**Where plugins come from.** Every `.js` file in `data/plugins/` is active; there is no switch yet
(#114) and no upload (#116) – a file is put there by hand. The plugins that ship with the repository
**Where plugins come from.** A file is put into `data/plugins/` by hand; there is no upload yet
(#116). A file put there is **off** until someone switches it on in the **Plugins** tab (#114). The plugins that ship with the repository
live in `plugins/` and are copied to `data/plugins/` by `npm run build` and by `tools/seed-e2e.mjs`;
a file of the same name is replaced, anything else in the folder is left alone. `data/plugins/` is
not in the repository. The setting is **server-wide**: every project on the server sees the same
a file of the same name is replaced, anything else in the folder is left alone, and a shipped
plugin is switched on – the build only when the state does not name it yet, the seeder always.
`data/plugins/` is not in the repository. The setting is **server-wide**: every project on the server sees the same
plugins. Plugins run **in the editor only**, never in a running game or an export.
## The format
@@ -25,7 +26,7 @@ export function activate(api) {
| `meta.name`, `meta.version` | Shown in reports; the name falls back to the id. |
| `meta.api` | The plugin API version the plugin was written for. Anything but `1` is not activated. |
| `activate(api)` | Required. Called once at start-up; may be `async`, and the next plugin waits for it. |
| `deactivate()` | Optional. Nothing calls it yet – it is for switching a plugin off (#114). |
| `deactivate()` | Optional. Called when the plugin is switched off, before what it registered is taken away. |
**A plugin imports nothing.** Everything comes in through `api`. The editor is one WebGL canvas
with one Pixi instance: a plugin that brought its own Pixi would draw into a second renderer. For
@@ -33,9 +34,9 @@ the same reason `api.tabs.add` accepts only widgets from `api.ui`.
## Loading
The editor asks `GET ?r=plugins` for the list, then imports each file through
The editor asks `GET ?r=plugins` for the list, then imports each **switched-on** file through
`GET ?r=plugins/{file}` (see [API](API)) – **in file-name order**, one after the other, after the
project has loaded. Nothing a plugin does wrong stops the editor or the other plugins. Each of these
project has loaded. A plugin that is off is never fetched, so none of its code runs. Nothing a plugin does wrong stops the editor or the other plugins. Each of these
leaves the plugin inactive, with a line in the console and a notice in the toolbar:
- the file does not load (a syntax error, a failed request),
@@ -44,7 +45,28 @@ leaves the plugin inactive, with a line in the console and a notice in the toolb
- a plugin with the same `meta.id` is active already,
- `activate` throws (or its promise rejects).
What `activate` registered before it threw stays registered.
What `activate` registered before it threw is taken away again, so a half-built plugin leaves
no tab behind.
## Switching a plugin on and off
The **Plugins** tab lists every file in `data/plugins/` with its name, version, file and state, each
with a switch. The state is stored on the server in `data/plugins/plugins.json`
(`{"demo-plugin.js": {"active": true}}`), so it holds for everyone who opens this editor, for every
project.
A switch takes effect at once, without reloading the editor:
- **On** stores it, then loads and activates the plugin – after every plugin that is running
already, whatever the file names say.
- **Off** stores it, calls the plugin's `deactivate()`, and then takes away what it registered
through the API: its tabs, their content and its `change` listeners. Whoever was looking at one
of its tabs lands on Scene.
- A plugin that is off has no name on its row until it has run once in this session: reading a name
out of it would mean running it.
- What the plugin did outside the API – a timer it started, something it wrote to the server – the
editor cannot take back. That is the same right-to-everything as in **Limits** below.
- When the server does not store the switch, nothing changes and the toolbar says why.
## API v1
@@ -69,4 +91,4 @@ creating, renaming or deleting an asset shows at once. e2e step 38 checks exactl
for v1, open in #115.
- No writing to the project, no node types of its own, no asset types, no inspector rows, no hooks
into a running game. Those come when a plugin needs them.
- No switch to turn a plugin off (#114), no upload (#116).
- No upload (#116): a file is put into `data/plugins/` by hand.