@loomweaver/plugin-sdk 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/package.json +45 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +23 -0
- package/src/index.js.map +1 -0
- package/src/lib/auth.d.ts +60 -0
- package/src/lib/auth.js +35 -0
- package/src/lib/auth.js.map +1 -0
- package/src/lib/bar-item.d.ts +67 -0
- package/src/lib/bar-item.js +2 -0
- package/src/lib/bar-item.js.map +1 -0
- package/src/lib/button.d.ts +4 -0
- package/src/lib/button.js +2 -0
- package/src/lib/button.js.map +1 -0
- package/src/lib/capability.d.ts +38 -0
- package/src/lib/capability.js +32 -0
- package/src/lib/capability.js.map +1 -0
- package/src/lib/command.d.ts +199 -0
- package/src/lib/command.js +2 -0
- package/src/lib/command.js.map +1 -0
- package/src/lib/container-handle.d.ts +36 -0
- package/src/lib/container-handle.js +4 -0
- package/src/lib/container-handle.js.map +1 -0
- package/src/lib/content-route.d.ts +254 -0
- package/src/lib/content-route.js +2 -0
- package/src/lib/content-route.js.map +1 -0
- package/src/lib/contribution.d.ts +4 -0
- package/src/lib/contribution.js +2 -0
- package/src/lib/contribution.js.map +1 -0
- package/src/lib/dialog-ref.d.ts +25 -0
- package/src/lib/dialog-ref.js +37 -0
- package/src/lib/dialog-ref.js.map +1 -0
- package/src/lib/dialog.d.ts +105 -0
- package/src/lib/dialog.js +2 -0
- package/src/lib/dialog.js.map +1 -0
- package/src/lib/dirty-surface.d.ts +48 -0
- package/src/lib/dirty-surface.js +2 -0
- package/src/lib/dirty-surface.js.map +1 -0
- package/src/lib/menu.d.ts +48 -0
- package/src/lib/menu.js +2 -0
- package/src/lib/menu.js.map +1 -0
- package/src/lib/notification.d.ts +28 -0
- package/src/lib/notification.js +2 -0
- package/src/lib/notification.js.map +1 -0
- package/src/lib/pane-area.d.ts +29 -0
- package/src/lib/pane-area.js +2 -0
- package/src/lib/pane-area.js.map +1 -0
- package/src/lib/plugin-state.d.ts +52 -0
- package/src/lib/plugin-state.js +2 -0
- package/src/lib/plugin-state.js.map +1 -0
- package/src/lib/plugin.d.ts +259 -0
- package/src/lib/plugin.js +2 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/product-identity.d.ts +25 -0
- package/src/lib/product-identity.js +17 -0
- package/src/lib/product-identity.js.map +1 -0
- package/src/lib/rail-item.d.ts +61 -0
- package/src/lib/rail-item.js +2 -0
- package/src/lib/rail-item.js.map +1 -0
- package/src/lib/settings-model.d.ts +164 -0
- package/src/lib/settings-model.js +2 -0
- package/src/lib/settings-model.js.map +1 -0
- package/src/lib/surface.d.ts +220 -0
- package/src/lib/surface.js +2 -0
- package/src/lib/surface.js.map +1 -0
- package/src/lib/view-state.d.ts +38 -0
- package/src/lib/view-state.js +9 -0
- package/src/lib/view-state.js.map +1 -0
- package/src/lib/view.d.ts +103 -0
- package/src/lib/view.js +2 -0
- package/src/lib/view.js.map +1 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A handle on one key of your plugin's own store. Reads are Signal-shaped, exactly like
|
|
3
|
+
* {@link ViewState} and `ctx.session`, so a template re-reads reactively without subscription
|
|
4
|
+
* bookkeeping.
|
|
5
|
+
*/
|
|
6
|
+
export interface StateHandle<T = unknown> {
|
|
7
|
+
/** The stored value, reactive; `undefined` when nothing is stored (apply your own default). */
|
|
8
|
+
readonly value: () => T | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the store has answered yet. Check it before you apply a default, otherwise a
|
|
11
|
+
* network-backed store lands its value **after** the user has started typing and overwrites the
|
|
12
|
+
* input. With a local store this is true immediately; the gate costs nothing there and is the
|
|
13
|
+
* difference between working and losing input everywhere else.
|
|
14
|
+
*/
|
|
15
|
+
readonly loaded: () => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Replace the value under this key. It replaces the **whole** value — nothing is merged — so spread
|
|
18
|
+
* the current one when you change a field. Writes are debounced; call it per keystroke if you like.
|
|
19
|
+
* Values are JSON: the store is string-valued and crosses a process boundary, so promising
|
|
20
|
+
* structured clone would be a lie.
|
|
21
|
+
*/
|
|
22
|
+
set(next: T): void;
|
|
23
|
+
/** Remove the key from the store. */
|
|
24
|
+
clear(): void;
|
|
25
|
+
/** Stop watching. Any pending write is flushed first. */
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Your plugin's **own** keyed store — `ctx.state`. Every surface of your plugin sees the
|
|
30
|
+
* same store, whichever dock it sits in, however many instances are open and in every browser window,
|
|
31
|
+
* which makes it both your persistence and the only channel between your own surfaces. It is
|
|
32
|
+
* plugin-private by construction: the host prefixes every key with your plugin id and you cannot
|
|
33
|
+
* leave that namespace, so there is nothing foreign to reach and no capability to grant.
|
|
34
|
+
*
|
|
35
|
+
* It holds **working state**, not settings. Settings have their own path precisely because the user
|
|
36
|
+
* can *see* them in the settings dialog; a free-form settings store would be a back door around that
|
|
37
|
+
* transparency. Uninstalling your plugin deletes this store (a settings section survives, an
|
|
38
|
+
* abandoned draft is litter).
|
|
39
|
+
*
|
|
40
|
+
* The authoring rule that follows from "set replaces the whole value": **one key per unit of
|
|
41
|
+
* editing** — a wizard step, not the whole form — and where a surface can exist more than once, key
|
|
42
|
+
* by instance as well. Two windows editing two keys converge; two windows replacing one key means
|
|
43
|
+
* last write wins, which is fine for convergence and not fine for the user's typing.
|
|
44
|
+
*/
|
|
45
|
+
export interface PluginState {
|
|
46
|
+
/**
|
|
47
|
+
* Watch one key and get a handle on it. This is also how the host learns which keys you care
|
|
48
|
+
* about: with free-form keys it cannot push everything, so the call that hands out the handle is
|
|
49
|
+
* the interest registration. Call {@link StateHandle.dispose} when your surface goes away.
|
|
50
|
+
*/
|
|
51
|
+
watch<T = unknown>(key: string): StateHandle<T>;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-state.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/plugin-state.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { PluginState } from './plugin-state.js';
|
|
2
|
+
import { Type } from '@angular/core';
|
|
3
|
+
import { Capability } from './capability.js';
|
|
4
|
+
import { Command, CommandArguments, CommandOutcome, InvocableCommand } from './command.js';
|
|
5
|
+
import { MenuItem } from './menu.js';
|
|
6
|
+
import { Disposable } from './contribution.js';
|
|
7
|
+
import { Surface } from './surface.js';
|
|
8
|
+
import { OpenTabInput } from './content-route.js';
|
|
9
|
+
import { BarItem } from './bar-item.js';
|
|
10
|
+
import { RailItem } from './rail-item.js';
|
|
11
|
+
import { SettingsSection } from './settings-model.js';
|
|
12
|
+
import { DialogRef } from './dialog-ref.js';
|
|
13
|
+
import { AlertOptions, ConfirmOptions, OpenOptions, ProgressHandle, ProgressOptions, PromptOptions } from './dialog.js';
|
|
14
|
+
import { NotificationInput } from './notification.js';
|
|
15
|
+
/** What a plugin declares about itself (grows: activation events, version, …). */
|
|
16
|
+
export interface PluginManifest {
|
|
17
|
+
/** Stable plugin id. */
|
|
18
|
+
readonly id: string;
|
|
19
|
+
/** Human-readable name. */
|
|
20
|
+
readonly name?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Capabilities the plugin declares it needs. This is the *request*; the
|
|
23
|
+
* distribution/tenant *grants* (default-deny, see `provideCapabilityGrants`). The effective set
|
|
24
|
+
* is what was granted — a declaration alone grants nothing. The declaration is what the install
|
|
25
|
+
* consent dialog shows and what the Permissions settings manage.
|
|
26
|
+
*/
|
|
27
|
+
readonly capabilities?: readonly Capability[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* One row of an ad-hoc context menu opened via {@link PluginUi.openMenu}: a display `label` (a literal —
|
|
31
|
+
* the weaver localises its own body text), an optional leading `icon` (a host icon-registry name), and an
|
|
32
|
+
* in-process `run` handler invoked when the row is chosen. Trusted-rung only: `run` is a function, so it
|
|
33
|
+
* does not cross the sandbox RPC boundary — a sandboxed plugin draws its own `<lw-menu>` instead.
|
|
34
|
+
*/
|
|
35
|
+
export interface UiMenuItem {
|
|
36
|
+
readonly label: string;
|
|
37
|
+
readonly icon?: string;
|
|
38
|
+
readonly run: () => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Host UI services a plugin reaches through `ctx.ui`: modal
|
|
42
|
+
* dialogs + transient toasts. This is the brokered path that replaces injecting the host
|
|
43
|
+
* services directly; the `ui` capability gate (default-deny) is enforced in front
|
|
44
|
+
* of it — a call without the grant throws.
|
|
45
|
+
*/
|
|
46
|
+
export interface PluginUi {
|
|
47
|
+
confirm(options: ConfirmOptions): Promise<boolean>;
|
|
48
|
+
alert(options: AlertOptions): Promise<void>;
|
|
49
|
+
prompt(options: PromptOptions): Promise<string | null>;
|
|
50
|
+
open<R = unknown>(component: Type<unknown>, options?: OpenOptions): DialogRef<R>;
|
|
51
|
+
progress(options: ProgressOptions): ProgressHandle;
|
|
52
|
+
withProgress<T>(options: ProgressOptions, work: Promise<T>): Promise<T>;
|
|
53
|
+
toast(input: NotificationInput): string;
|
|
54
|
+
/** Opens the host settings surface; the host renders the registered sections. */
|
|
55
|
+
openSettings(): DialogRef;
|
|
56
|
+
/**
|
|
57
|
+
* Opens an ad-hoc context menu at a viewport point — for a right-click on the weaver's own view body
|
|
58
|
+
* (a Library row, a canvas node, …). The host draws it with the same `<lw-menu>` popover mechanics as
|
|
59
|
+
* its own menus (positioning, Escape/outside-pointer dismiss, focus restore). Trusted-rung only: the
|
|
60
|
+
* items' `run` handlers do not cross the sandbox boundary (a sandboxed plugin self-draws a `<lw-menu>`).
|
|
61
|
+
*/
|
|
62
|
+
openMenu(items: readonly UiMenuItem[], at: {
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
}): void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read-only host facts + app-lifecycle a plugin reaches through `ctx.host` —
|
|
69
|
+
* so a plugin's About surface gets the version + update state through the brokered `ctx`
|
|
70
|
+
* instead of importing host services directly. `version`/`updateAvailable` are signal-shaped
|
|
71
|
+
* (`() => T`) so a template re-reads them reactively.
|
|
72
|
+
*/
|
|
73
|
+
export interface PluginHost {
|
|
74
|
+
/** The running app version (e.g. "1.2.3"). */
|
|
75
|
+
readonly version: () => string;
|
|
76
|
+
/** True once a new version is downloaded and ready to activate. */
|
|
77
|
+
readonly updateAvailable: () => boolean;
|
|
78
|
+
/** Whether update checks are possible (a service worker is registered and enabled). */
|
|
79
|
+
readonly updatesEnabled: boolean;
|
|
80
|
+
/** Manually check for a new version. */
|
|
81
|
+
checkForUpdate(): Promise<void>;
|
|
82
|
+
/** Activate the downloaded version and reload into it. */
|
|
83
|
+
activateUpdate(): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read-only session facts a plugin reaches through `ctx.session` — so a plugin can gate its
|
|
87
|
+
* **own** surface/logic by the signed-in user's login state and roles, the imperative counterpart to the
|
|
88
|
+
* declarative `access` on contributions. LoomWeaver owns no auth: this only reflects the snapshot the
|
|
89
|
+
* distribution supplied (`provideAuthSource`). Signal-shaped (`() => T`) so a template re-reads reactively;
|
|
90
|
+
* roles are opaque strings — match, do not interpret. The snapshot's claim bag does not arrive here:
|
|
91
|
+
* a plugin is told the login state and the roles, and nothing else. Client-side gating is
|
|
92
|
+
* presentation, not security. Gated by the `session` capability (default-deny).
|
|
93
|
+
*/
|
|
94
|
+
export interface PluginSession {
|
|
95
|
+
/** Whether someone is signed in. */
|
|
96
|
+
readonly authenticated: () => boolean;
|
|
97
|
+
/** The current principal's roles (empty when anonymous). */
|
|
98
|
+
readonly roles: () => readonly string[];
|
|
99
|
+
/** Convenience: whether the current principal holds `role`. */
|
|
100
|
+
hasRole(role: string): boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The content the URL pane currently shows — the read side of the content area a plugin reaches
|
|
104
|
+
* through `ctx.activeContent`. Path parameters are extracted against the matched
|
|
105
|
+
* route's pattern (`ask/:id` on `ask/abc` → `{ id: 'abc' }`), so a plugin never parses URLs.
|
|
106
|
+
*/
|
|
107
|
+
export interface ActiveContent {
|
|
108
|
+
/** The matched surface's id, or `null` when the matched route carries none. */
|
|
109
|
+
readonly surfaceId: string | null;
|
|
110
|
+
/** The full active content path (no leading slash), including any sub-route segment. */
|
|
111
|
+
readonly path: string;
|
|
112
|
+
/** The `:param` values of the matched route pattern (empty for parameter-less routes). */
|
|
113
|
+
readonly params: Readonly<Record<string, string>>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The `ctx` a plugin uses to contribute to the host — one uniform contract. Trusted in-process
|
|
117
|
+
* plugins get a host-backed implementation; sandboxed plugins get the same surface over RPC.
|
|
118
|
+
*/
|
|
119
|
+
export interface PluginContext {
|
|
120
|
+
/**
|
|
121
|
+
* Registers a named, invocable {@link Command}. Triggers — rail/bar/
|
|
122
|
+
* view-action items (via `command: <id>`), keybindings, and the command palette — reference it
|
|
123
|
+
* by id, so one behaviour has many entry points.
|
|
124
|
+
*/
|
|
125
|
+
registerCommand(command: Command): Disposable;
|
|
126
|
+
/**
|
|
127
|
+
* Registers a {@link Surface} — the **one** author contract for anything the host renders.
|
|
128
|
+
* Declare *what the surface can do* (`routable`, `instanceable`, `docks`) rather than *where it lives*;
|
|
129
|
+
* the host places it (a routable surface into the content area, else into its home dock) and the user
|
|
130
|
+
* re-arranges it from there.
|
|
131
|
+
*
|
|
132
|
+
* Replaced `registerView` + `registerRoute`, removed in 0.5.0:
|
|
133
|
+
* - a panel view → a non-routable surface: `registerSurface({ id, title, docks: ['<regionId>'], component })`
|
|
134
|
+
* - a content route → a routable surface: `registerSurface({ id, title, routable: { path }, component })`
|
|
135
|
+
*
|
|
136
|
+
* Both migrations need an `id` and a `title`, which the old contracts did not carry.
|
|
137
|
+
*/
|
|
138
|
+
registerSurface(surface: Surface): Disposable;
|
|
139
|
+
registerBarItem(item: BarItem): Disposable;
|
|
140
|
+
registerRailItem(item: RailItem): Disposable;
|
|
141
|
+
/** Contributes a section to the host settings surface. */
|
|
142
|
+
registerSettingsSection(section: SettingsSection): Disposable;
|
|
143
|
+
/**
|
|
144
|
+
* Contributes an item to a named menu slot — e.g. add an action to the tab context menu
|
|
145
|
+
* (`'content/tab/context'`) or a plugin's own slot. The item names a {@link Command} by id (invoked with
|
|
146
|
+
* the menu's {@link MenuContext}) and may declare a coarse `when` filter. The host draws the menu.
|
|
147
|
+
*/
|
|
148
|
+
registerMenuItem(item: MenuItem): Disposable;
|
|
149
|
+
/**
|
|
150
|
+
* Contributes custom icon names the weaver can then reference in its contributions
|
|
151
|
+
* (`Command.icon` / `View.icon` / `RailItem.icon` / `BarButtonItem.icon`). Each value
|
|
152
|
+
* is a raw SVG string (an `@ng-icons` export or hand-authored markup). Names are flat and
|
|
153
|
+
* collision-safe: a name already registered by the shell or another plugin is ignored (first-wins,
|
|
154
|
+
* dev-warned), so pick unique names. The returned {@link Disposable} removes exactly these names.
|
|
155
|
+
*/
|
|
156
|
+
contributeIcons(icons: Readonly<Record<string, string>>): Disposable;
|
|
157
|
+
/**
|
|
158
|
+
* Contributes design tokens (`--lw-*` custom properties) that re-skin the whole app — host chrome and
|
|
159
|
+
* every other plugin, since all read the same tokens. The vocabulary covers colors and the
|
|
160
|
+
* UI font (`--lw-font-sans` / `--lw-font-mono`); font *size* is a user preference, not a theme token.
|
|
161
|
+
* Requires the `theme` capability. Only
|
|
162
|
+
* whitelisted `--lw-*` names apply; unknown names are ignored. Contributed tokens sit **below** any
|
|
163
|
+
* tenant/distribution branding in the cascade (Tenant > Plugin > Product), so a plugin themes freely
|
|
164
|
+
* but never overrides a token the tenant explicitly set. Collisions across plugins are first-wins:
|
|
165
|
+
* the plugin that contributes a token first owns it in **both** schemes, so a later plugin cannot
|
|
166
|
+
* take over just its dark value. The returned {@link Disposable} removes exactly these tokens and
|
|
167
|
+
* the app reverts.
|
|
168
|
+
*
|
|
169
|
+
* `tokens` apply in both light and dark mode. Pass the optional `dark` map to override specific
|
|
170
|
+
* tokens only when dark mode is active — e.g. warm dark surfaces while `tokens` carries
|
|
171
|
+
* the light palette. Dark overrides win over `tokens` in dark mode; tokens absent from `dark` keep
|
|
172
|
+
* their `tokens` value across both modes.
|
|
173
|
+
*/
|
|
174
|
+
contributeTheme(tokens: Readonly<Record<string, string>>, dark?: Readonly<Record<string, string>>): Disposable;
|
|
175
|
+
/** Navigates the content area to a route path — switches perspective / full-area view (`navigation`). */
|
|
176
|
+
navigateContent(path: string): void;
|
|
177
|
+
/**
|
|
178
|
+
* Opens a titled **dynamic** tab (e.g. an open document) in the content group of the path's
|
|
179
|
+
* matched route (navigating there activates that group) and navigates to it; opening the same
|
|
180
|
+
* path again just re-activates it (`navigation`). Set `titleIsLiteral` for a
|
|
181
|
+
* non-translatable title, `onClose` to free per-tab state when the tab is closed, and `preview` to
|
|
182
|
+
* open it as a single reused *preview* slot (see {@link OpenTabInput}).
|
|
183
|
+
*/
|
|
184
|
+
openContentTab(input: OpenTabInput): void;
|
|
185
|
+
/**
|
|
186
|
+
* Promotes the **preview** tab rooted at `path` to a permanent tab — the programmatic
|
|
187
|
+
* "Keep Open", e.g. bound to a double-click or fired when the content is edited. A no-op if the tab
|
|
188
|
+
* is already permanent or not open (`navigation`).
|
|
189
|
+
*/
|
|
190
|
+
keepContentTab(path: string): void;
|
|
191
|
+
/**
|
|
192
|
+
* **Pins** the dynamic tab rooted at `path`: the host sorts it to the front of its
|
|
193
|
+
* group and guards it against accidental close (its close control becomes an unpin control).
|
|
194
|
+
* Pinning also promotes a preview tab. A no-op if the tab is not open (`navigation`).
|
|
195
|
+
*/
|
|
196
|
+
pinContentTab(path: string): void;
|
|
197
|
+
/** Unpins the tab rooted at `path` — it returns to a normal, closable tab. No-op otherwise (`navigation`). */
|
|
198
|
+
unpinContentTab(path: string): void;
|
|
199
|
+
/** Closes a dynamic content tab by path; the host activates a neighbour (`navigation`). */
|
|
200
|
+
closeContentTab(path: string): void;
|
|
201
|
+
/**
|
|
202
|
+
* Reveals an already-**docked** surface by id (finding #29): activates its tab wherever the user
|
|
203
|
+
* has placed it — a sidebar panel (expanding a collapsed one) or a content pane — so a command
|
|
204
|
+
* like "Focus Library" can bring a docked view to the front. Routable surfaces are reached via
|
|
205
|
+
* {@link navigateContent} instead; container-only children (`docks: []`) live inside their
|
|
206
|
+
* container and are not revealed from here. A no-op for an unknown or un-placed id (`navigation`).
|
|
207
|
+
*/
|
|
208
|
+
revealSurface(id: string): void;
|
|
209
|
+
/**
|
|
210
|
+
* Signal-shaped read of the {@link ActiveContent} — which routable surface the URL pane
|
|
211
|
+
* currently shows, with its path parameters (`navigation`). The read side of the content area
|
|
212
|
+
*: a panel that reacts to "which tab is focused" (an inspector, a details view) reads
|
|
213
|
+
* this instead of injecting the host's router and parsing URLs, so it stays stable across host
|
|
214
|
+
* URL-shape changes. `null` when no content route matches. Trusted rung only (like `ui.openMenu`,
|
|
215
|
+
* it does not cross the sandbox RPC boundary — a sandboxed surface already receives its own state
|
|
216
|
+
* over the surface channel).
|
|
217
|
+
*/
|
|
218
|
+
readonly activeContent: () => ActiveContent | null;
|
|
219
|
+
/**
|
|
220
|
+
* Runs a registered command by id and answers what it did (`automation`, unless the command is
|
|
221
|
+
* one this plugin registered itself — its own need no grant).
|
|
222
|
+
*
|
|
223
|
+
* Unlike every other member here this **answers a refusal instead of throwing one**, because a
|
|
224
|
+
* caller working through a list of actions has to handle "you may not" as an outcome rather than as
|
|
225
|
+
* an exception. The user is still told, exactly as they are for every other refused route.
|
|
226
|
+
*
|
|
227
|
+
* A refusal reads `unavailable` for every reason a command cannot be reached — no such id, not
|
|
228
|
+
* {@link Command.callable}, the session does not meet its {@link Command.access}, this window does
|
|
229
|
+
* not host it, the grant is missing — so that invoking ids and reading the reason back cannot map
|
|
230
|
+
* what is installed.
|
|
231
|
+
*/
|
|
232
|
+
invokeCommand(id: string, args?: CommandArguments): Promise<CommandOutcome>;
|
|
233
|
+
/**
|
|
234
|
+
* The commands this plugin may invoke that it did not register itself (`automation`) — the
|
|
235
|
+
* workbench's own account, already narrowed by everything that would refuse the invocation, with
|
|
236
|
+
* every text resolved to the active language. Empty without the grant, whatever is installed.
|
|
237
|
+
*
|
|
238
|
+
* Read it instead of keeping a list of your own: a second list is a second answer to "may this
|
|
239
|
+
* run", and it is not the one the user can see and withdraw.
|
|
240
|
+
*/
|
|
241
|
+
readonly invocableCommands: () => readonly InvocableCommand[];
|
|
242
|
+
/** Host UI services (dialogs, toasts). */
|
|
243
|
+
readonly ui: PluginUi;
|
|
244
|
+
/** Read-only host facts + app-lifecycle (version, update). */
|
|
245
|
+
readonly host: PluginHost;
|
|
246
|
+
/** Read-only session facts (login state + roles) for self-gating (gated by `session`). */
|
|
247
|
+
readonly session: PluginSession;
|
|
248
|
+
/**
|
|
249
|
+
* Your plugin's own keyed store — working state shared by all your surfaces, in every window
|
|
250
|
+
*. Ungated: it is plugin-private by construction, so there is nothing foreign to reach.
|
|
251
|
+
*/
|
|
252
|
+
readonly state: PluginState;
|
|
253
|
+
}
|
|
254
|
+
/** A LoomWeaver plugin: declares itself and contributes on activation. */
|
|
255
|
+
export interface Plugin {
|
|
256
|
+
readonly manifest: PluginManifest;
|
|
257
|
+
activate(ctx: PluginContext): void | Promise<void>;
|
|
258
|
+
deactivate?(): void;
|
|
259
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/plugin.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { InjectionToken, Provider } from '@angular/core';
|
|
2
|
+
/**
|
|
3
|
+
* The product a LoomWeaver installation presents as (name, tagline, logo).
|
|
4
|
+
*
|
|
5
|
+
* Branding lives in the distribution (composition root), never in the platform
|
|
6
|
+
* core: a distribution provides its identity, the neutral shell renders it. The
|
|
7
|
+
* bare platform falls back to {@link LOOMWEAVER_IDENTITY}.
|
|
8
|
+
*/
|
|
9
|
+
export interface ProductIdentity {
|
|
10
|
+
/** Literal brand name, e.g. `Acme Studio`. Not localized. */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** Transloco key for the tagline (a literal renders as-is if it is no key). */
|
|
13
|
+
readonly tagline: string;
|
|
14
|
+
/** URL/path to the square product logo. */
|
|
15
|
+
readonly logoUrl: string;
|
|
16
|
+
}
|
|
17
|
+
/** Bare-platform fallback used when no distribution configures an identity. */
|
|
18
|
+
export declare const LOOMWEAVER_IDENTITY: ProductIdentity;
|
|
19
|
+
/**
|
|
20
|
+
* Active product identity. Defaults to {@link LOOMWEAVER_IDENTITY}; a
|
|
21
|
+
* distribution overrides it via {@link provideProductIdentity}.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PRODUCT_IDENTITY: InjectionToken<ProductIdentity>;
|
|
24
|
+
/** Provider a distribution uses to declare its product identity. */
|
|
25
|
+
export declare function provideProductIdentity(identity: ProductIdentity): Provider;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
/** Bare-platform fallback used when no distribution configures an identity. */
|
|
3
|
+
export const LOOMWEAVER_IDENTITY = {
|
|
4
|
+
name: 'LoomWeaver',
|
|
5
|
+
tagline: 'shell.tagline',
|
|
6
|
+
logoUrl: 'loom-icon-64.png',
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Active product identity. Defaults to {@link LOOMWEAVER_IDENTITY}; a
|
|
10
|
+
* distribution overrides it via {@link provideProductIdentity}.
|
|
11
|
+
*/
|
|
12
|
+
export const PRODUCT_IDENTITY = new InjectionToken('PRODUCT_IDENTITY', { providedIn: 'root', factory: () => LOOMWEAVER_IDENTITY });
|
|
13
|
+
/** Provider a distribution uses to declare its product identity. */
|
|
14
|
+
export function provideProductIdentity(identity) {
|
|
15
|
+
return { provide: PRODUCT_IDENTITY, useValue: identity };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=product-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-identity.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/product-identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAY,MAAM,eAAe,CAAC;AAkBzD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAoB;IAClD,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,kBAAkB;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,cAAc,CAChD,kBAAkB,EAClB,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAC3D,CAAC;AAEF,oEAAoE;AACpE,MAAM,UAAU,sBAAsB,CAAC,QAAyB;IAC9D,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AccessRequirement } from './auth.js';
|
|
2
|
+
/**
|
|
3
|
+
* A Rail/Ribbon item: an **independent command** triggered from the far
|
|
4
|
+
* rail — not a view switcher (switching views is the panel's tab bar). Mirrors
|
|
5
|
+
* Obsidian's ribbon (`addRibbonIcon`).
|
|
6
|
+
*/
|
|
7
|
+
export interface RailItem {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
/** Target Rail region id. */
|
|
10
|
+
readonly rail: string;
|
|
11
|
+
/** Icon name — resolved by the host icon registry (a plain string). */
|
|
12
|
+
readonly icon: string;
|
|
13
|
+
/**
|
|
14
|
+
* One or two letters the host draws **instead of** the icon, for an entry whose name is the user's
|
|
15
|
+
* rather than yours (a saved workspace, a project, an account) — a fixed glyph would make every
|
|
16
|
+
* such entry look alike. {@link icon} stays required as the fallback for a name too short or too
|
|
17
|
+
* foreign to abbreviate.
|
|
18
|
+
*
|
|
19
|
+
* Keep it to two characters: the rail draws them at icon size, and three no longer read at a
|
|
20
|
+
* glance. The host renders them in the app font and in the same colour the icon would have taken,
|
|
21
|
+
* so hover and the active marker behave exactly as elsewhere.
|
|
22
|
+
*/
|
|
23
|
+
readonly initials?: string;
|
|
24
|
+
/** Transloco key (or literal) for the tooltip/label. */
|
|
25
|
+
readonly title: string;
|
|
26
|
+
/** Lower renders first within its anchor group (default 0). */
|
|
27
|
+
readonly order?: number;
|
|
28
|
+
/** Pinned to the top (default) or the bottom of the rail (e.g. settings, VS Code style). */
|
|
29
|
+
readonly anchor?: 'top' | 'bottom';
|
|
30
|
+
/**
|
|
31
|
+
* Id of a menu slot to open as this item's **context menu** on right-click — region-agnostic:
|
|
32
|
+
* the host wires the right-click uniformly and passes a serialisable context (`{ targetKind, id, region }`).
|
|
33
|
+
* Contribute items to the slot with `ctx.registerMenuItem({ menu, … })`. Omit for no context menu.
|
|
34
|
+
*/
|
|
35
|
+
readonly menu?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Id of a registered {@link Command} this item triggers. Provide this **or** {@link run}; when
|
|
38
|
+
* set, the host runs that command (so a keybinding/palette can share the same behaviour).
|
|
39
|
+
*/
|
|
40
|
+
readonly command?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Declarative auth gating: the host hides (default) or disables this item when the
|
|
43
|
+
* current session does not meet the requirement. Presentation only — real enforcement is
|
|
44
|
+
* server-side. Omit for an item everyone sees.
|
|
45
|
+
*/
|
|
46
|
+
readonly access?: AccessRequirement;
|
|
47
|
+
/**
|
|
48
|
+
* Id of a workspace this item switches to. The host performs the switch itself and
|
|
49
|
+
* marks the item as the current one while that workspace is active, so a rail entry for a
|
|
50
|
+
* workspace needs no command of its own. Provide this **instead of** {@link command}/{@link run};
|
|
51
|
+
* when it is set those are ignored. An id no workspace answers to warns in development.
|
|
52
|
+
*/
|
|
53
|
+
readonly workspace?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Inline behaviour, for an item that is not backed by a registered command. May be async; the
|
|
56
|
+
* host fires it fire-and-forget. Typed `() => void` so a one-expression arrow whose handler
|
|
57
|
+
* happens to return a value (e.g. `() => ctx.ui.openSettings()`) still assigns — the return is
|
|
58
|
+
* ignored either way.
|
|
59
|
+
*/
|
|
60
|
+
run?(): void;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rail-item.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/rail-item.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Type } from '@angular/core';
|
|
2
|
+
import { LwButtonVariant } from './button.js';
|
|
3
|
+
/**
|
|
4
|
+
* Settings vocabulary (schema-driven). The host renders a settings
|
|
5
|
+
* surface from these declarations; each contributor (shell or plugin) supplies its own
|
|
6
|
+
* value accessors, so the *owner* keeps responsibility for storage. The host only reads
|
|
7
|
+
* `value()` and calls `set()`. The control vocabulary grows demand-driven (YAGNI):
|
|
8
|
+
* `select` (single choice) · `toggle` (on/off) · `text` (a string field) · `slider` (a number) ·
|
|
9
|
+
* `button` (an action) · `component` (embed a component).
|
|
10
|
+
*/
|
|
11
|
+
/** An option in a {@link SettingSelect}. `label` is a Transloco key. */
|
|
12
|
+
export interface SelectOption {
|
|
13
|
+
readonly value: string;
|
|
14
|
+
readonly label: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A single-choice dropdown control. It owns its value binding — the contributor supplies
|
|
18
|
+
* `value()`/`set()`, so the *owner* keeps responsibility for storage (the host only reads
|
|
19
|
+
* and writes through them). Changes save immediately (the host calls `set()` on change).
|
|
20
|
+
*/
|
|
21
|
+
export interface SettingSelect {
|
|
22
|
+
readonly kind: 'select';
|
|
23
|
+
readonly options: readonly SelectOption[];
|
|
24
|
+
/** Reads the current value — a signal works directly (it is a `() => string`). */
|
|
25
|
+
readonly value: () => string;
|
|
26
|
+
/** Persists a new value; the owner decides how and where. */
|
|
27
|
+
readonly set: (value: string) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* An on/off toggle (switch) control. Like {@link SettingSelect} it owns its value binding — the contributor
|
|
31
|
+
* supplies `value()`/`set()` (boolean), so the *owner* keeps storage. Changes save immediately.
|
|
32
|
+
*/
|
|
33
|
+
export interface SettingToggle {
|
|
34
|
+
readonly kind: 'toggle';
|
|
35
|
+
readonly value: () => boolean;
|
|
36
|
+
readonly set: (value: boolean) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A single-line text field control. Owns its value binding (`value()`/`set()` — a string). `inputType`
|
|
40
|
+
* picks the native input type (default `text`; e.g. `date`/`email`/`number`); `placeholder` is a Transloco
|
|
41
|
+
* key. Changes save immediately.
|
|
42
|
+
*/
|
|
43
|
+
export interface SettingText {
|
|
44
|
+
readonly kind: 'text';
|
|
45
|
+
readonly value: () => string;
|
|
46
|
+
readonly set: (value: string) => void;
|
|
47
|
+
readonly inputType?: 'text' | 'date' | 'email' | 'number' | 'password';
|
|
48
|
+
readonly placeholder?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A numeric slider control. Owns its value binding (`value()`/`set()` — a number). `min`/`max`/`step` bound
|
|
52
|
+
* and quantise it (defaults 0/100/1). Changes save immediately.
|
|
53
|
+
*/
|
|
54
|
+
export interface SettingSlider {
|
|
55
|
+
readonly kind: 'slider';
|
|
56
|
+
readonly value: () => number;
|
|
57
|
+
readonly set: (value: number) => void;
|
|
58
|
+
readonly min?: number;
|
|
59
|
+
readonly max?: number;
|
|
60
|
+
readonly step?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* An action button control — no stored value. Provide a registered {@link Command} id via
|
|
64
|
+
* `command` (so the palette/keybindings can share the behaviour), **or** an inline `run()` for a
|
|
65
|
+
* one-off action (e.g. opening a dialog). `label` is a Transloco key.
|
|
66
|
+
*/
|
|
67
|
+
export interface SettingButton {
|
|
68
|
+
readonly kind: 'button';
|
|
69
|
+
readonly label: string;
|
|
70
|
+
readonly variant?: LwButtonVariant;
|
|
71
|
+
/** Id of a registered command to run on click. Provide this **or** {@link run}. */
|
|
72
|
+
readonly command?: string;
|
|
73
|
+
/** Inline behaviour for a button not backed by a registered command. Provide this **or** {@link command}. */
|
|
74
|
+
readonly run?: () => void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Embeds an existing component as the control — lets a
|
|
78
|
+
* setting reuse a host or plugin widget directly instead of re-describing it. The component
|
|
79
|
+
* reads/writes its own state via its own services.
|
|
80
|
+
*/
|
|
81
|
+
export interface SettingComponent {
|
|
82
|
+
readonly kind: 'component';
|
|
83
|
+
readonly component: Type<unknown>;
|
|
84
|
+
/**
|
|
85
|
+
* Renders the component as a full-width block instead of in the row's right-hand control slot — for
|
|
86
|
+
* a self-contained widget that owns the whole row (e.g. a table or a list), not a compact control
|
|
87
|
+
* sitting beside a label. The row's `label`/`description` are then not painted; the component
|
|
88
|
+
* provides its own heading.
|
|
89
|
+
*/
|
|
90
|
+
readonly fullWidth?: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** A settings control — the host renders each kind with the matching primitive. */
|
|
93
|
+
export type SettingControl = SettingSelect | SettingToggle | SettingText | SettingSlider | SettingButton | SettingComponent;
|
|
94
|
+
/**
|
|
95
|
+
* One row in a settings section: a label/description the host paints and a control it
|
|
96
|
+
* renders. Value binding (for value controls) lives on the control itself.
|
|
97
|
+
*/
|
|
98
|
+
export interface SettingRow {
|
|
99
|
+
readonly id: string;
|
|
100
|
+
/** Transloco key for the row label. */
|
|
101
|
+
readonly label: string;
|
|
102
|
+
/** Optional Transloco key for a secondary description line. */
|
|
103
|
+
readonly description?: string;
|
|
104
|
+
readonly control: SettingControl;
|
|
105
|
+
}
|
|
106
|
+
/** A contributed group of rows. The host renders sections ordered by `order` (default 0). */
|
|
107
|
+
export interface SettingsSection {
|
|
108
|
+
readonly id: string;
|
|
109
|
+
/** Transloco key for the section heading (and its left-nav entry). */
|
|
110
|
+
readonly title: string;
|
|
111
|
+
/** Transloco key for the left-nav group header this section sits under (e.g. Options vs Plugins). */
|
|
112
|
+
readonly group?: string;
|
|
113
|
+
readonly order?: number;
|
|
114
|
+
readonly rows: readonly SettingRow[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The **data-only** control form a *sandboxed* plugin declares over the RPC boundary (the VS Code
|
|
118
|
+
* `contributes.configuration` model): the declaration carries the control kind and its
|
|
119
|
+
* **default value** instead of `value()`/`set()` callbacks, which cannot cross the wire. The host
|
|
120
|
+
* renders the control, **owns the storage** (user-local through the distribution's `SETTINGS_STORE` port)
|
|
121
|
+
* and pushes the current values back to the plugin — once after registration and on every change —
|
|
122
|
+
* by calling the `settingsChanged(sectionId, values)` method the plugin exposes on its RPC channel.
|
|
123
|
+
*/
|
|
124
|
+
export type FrameSettingControl = {
|
|
125
|
+
readonly kind: 'toggle';
|
|
126
|
+
readonly value: boolean;
|
|
127
|
+
} | {
|
|
128
|
+
readonly kind: 'text';
|
|
129
|
+
readonly value: string;
|
|
130
|
+
readonly inputType?: 'text' | 'date' | 'email' | 'number' | 'password';
|
|
131
|
+
readonly placeholder?: string;
|
|
132
|
+
} | {
|
|
133
|
+
readonly kind: 'select';
|
|
134
|
+
readonly value: string;
|
|
135
|
+
readonly options: readonly SelectOption[];
|
|
136
|
+
} | {
|
|
137
|
+
readonly kind: 'slider';
|
|
138
|
+
readonly value: number;
|
|
139
|
+
readonly min?: number;
|
|
140
|
+
readonly max?: number;
|
|
141
|
+
readonly step?: number;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* One declared row of a sandboxed plugin's settings. `label`/`description` may be plain literals — a
|
|
145
|
+
* sandboxed plugin cannot contribute translations, and the host renders an unknown key as-is.
|
|
146
|
+
*/
|
|
147
|
+
export interface FrameSettingRow {
|
|
148
|
+
readonly id: string;
|
|
149
|
+
readonly label: string;
|
|
150
|
+
readonly description?: string;
|
|
151
|
+
readonly control: FrameSettingControl;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A sandboxed plugin's settings section. Registered over RPC via
|
|
155
|
+
* `ctx.registerSettingsSection`; the host decides the nav **group** (never the plugin): an
|
|
156
|
+
* *installed* plugin's section appears under **Community plugins**, a composed frame plugin's
|
|
157
|
+
* under **App plugins** — a plugin cannot masquerade as part of the app.
|
|
158
|
+
*/
|
|
159
|
+
export interface FrameSettingsSection {
|
|
160
|
+
readonly id: string;
|
|
161
|
+
readonly title: string;
|
|
162
|
+
readonly order?: number;
|
|
163
|
+
readonly rows: readonly FrameSettingRow[];
|
|
164
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-model.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/settings-model.ts"],"names":[],"mappings":""}
|