Something went wrong. Try again.
Rewild Your Web
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138// SPDX-License-Identifier: AGPL-3.0-or-later
/** * Abstract base class for per-shell layout managers. * * The shared services in `ui/system/services/` (notifications, channels, * media_session, shortcuts, keyboard) all consume a `layoutManager` of * this shape. Each shell extends this class and supplies its own * placement strategy: * * - Desktop: tiled desks with a binary split tree. * - Mobile: a one-deep carousel of full-screen panels. * - Mediacenter: a flat list of pages addressed by a state machine. * * The contract is intentionally narrow. Anything richer than this lives * on the subclass, behind shell-specific names. * * ## What the subclass must populate * * The base provides default implementations of the read-side API * (`webviews`, `activeWebviewId`) that walk an internal `Map<id, View>` * the subclass keeps in sync. Override the abstract methods to do the * actual placement work; remember to call `this._views.set(id, view)` / * `this._views.delete(id)` / `this._activeId = id` so the read-side * stays consistent. * * ## What it deliberately does NOT model * * - Surface layout (tiled / floating / panels / state machines). * - Persistence (`store.js` is per-shell). * - Render/render-orchestration (each shell decides how to translate * layout changes into DOM updates). */export class LayoutManager { constructor() { /** * Internal map of every hydrated view, keyed by id. Subclasses * populate via `addWebView` / `removeWebView`. The default * `webviews` getter walks this. * @type {Map<string, import("./view.js").View>} */ this._views = new Map();
/** * Id of the currently active view, or null if no view is active. * Subclasses write this in `setActiveWebView`. * @type {string|null} */ this._activeId = null;
/** * Id of the shell's homescreen webview, if any. Some services use * this to special-case the "home" tab. Set by the shell at boot. * @type {string|null} */ this.homescreenWebviewId = null; }
// --------------------------------------------------------------------------- // Read-side API: default implementations, override only if your shell // genuinely keeps state in a non-Map shape.
/** * Snapshot of every hydrated webview, keyed by id. Each entry is * `{ webview }` : a thin wrapper matching the legacy desktop / mobile * shape that the shared services already expect. Returns a fresh Map * so iterators can't observe mutations mid-walk. * * @returns {Map<string, {webview: object}>} */ get webviews() { const out = new Map(); for (const view of this._views.values()) { if (!view.webView) continue; out.set(view.id, { webview: view.webView }); } return out; }
/** * Id of the currently active view, or null if no view is active. * @returns {string|null} */ get activeWebviewId() { return this._activeId; }
// --------------------------------------------------------------------------- // Write-side API, abstract. Subclasses MUST override.
/** * Wrap a freshly-created `<web-view>` element as a `View` and place it * in the shell's layout. The shell decides where (desktop's tree * intent, mobile's carousel-append, mediacenter's pages-push). * * After placing, the subclass MUST: * - register the view in `this._views` * - set `this._activeId = view.id` if the new view should be active * * @param {object} _webView The `<web-view>` element to adopt. * @param {object} [_intent] Shell-specific placement hint (e.g. * `{ direction, anchorViewId, mode }` on desktop). * @returns {void} */ addWebView(_webView, _intent) { throw new Error("LayoutManager.addWebView: not implemented"); }
/** * Make the view with `id` the active one. The shell decides what * "active" means (focused tile, current carousel panel, foregrounded * page). * * After activating, the subclass MUST set `this._activeId = id`. * * @param {string} _id * @returns {void} */ setActiveWebView(_id) { throw new Error("LayoutManager.setActiveWebView: not implemented"); }
/** * Remove the view with `id` from the layout entirely. The shell is * responsible for any animation / focus reassignment. * * After removing, the subclass MUST: * - delete `this._views` entry * - clear `this._activeId` if it pointed at the removed view * * @param {string} _id * @returns {void} */ removeWebView(_id) { throw new Error("LayoutManager.removeWebView: not implemented"); }}