Window Manager

SGApps' desktop-in-the-browser layer. Every app runs inside a WindowApplication managed by a WindowManager. Two integration paths are supported — pick the one that matches your product.

Two integration modes

Mode Best for How Details
Iframe Embedding one specific app (photo-editor, code-editor, spreadsheet…) with URL-driven state <iframe src="/online/webapp/{app}"> Embedding Apps + API Communication
In-page (native) Building a multi-window desktop inside your own page, opening apps side-by-side, running your own custom apps alongside SGApps' Load exporter.js, mount windowManager.root() into your DOM, call wm.open(...) This document

In-page setup — the exporter.js entry point

https://sgapps.io/scripts/modules-webapp/exporter.js bootstraps the entire SGApps client runtime on your page. It:

  1. Loads ApplicationPrototype.js + ApplicationBuilder.js from the SGApps origin.
  2. Instantiates an App (the global orchestrator).
  3. Registers the b2b.* module aliases (window-manager, icons, translate, taskbar, etc.).
  4. Exposes a global Application(callback) that fires as soon as App is ready.
  5. Detects its own <script src="…"> at runtime so RootHost (the origin serving assets) is always correct — no config needed even when the page runs on your own domain.

Minimal boilerplate

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>My SGApps Desktop</title>
</head>
<body>
    <div id="wm-root" style="position:relative;height:100vh;"></div>

    <script src="https://sgapps.io/scripts/modules-webapp/exporter.js"></script>
    <script>
    Application(function (App) {
        // `exporter:configuration:full` loads the complete stack: styles,
        // core modules, third-party libraries, WindowSocket bridge, GridFS
        // client, and translations. Lighter alternatives are shown below.
        App["exporter:configuration:full"]().then(function () {
            App.require([
                "wm :: sgapps-webapps.b2b.window-manager"
            ]).then(function (libs) {
                // `wm.root()` is the WM's DOM host — mount it wherever
                // you want windows to live. `position: relative` on the
                // container keeps window `position: fixed` centred on it
                // rather than on the viewport.
                document.getElementById("wm-root").appendChild(libs.wm.root());

                // Open a window with a code editor
                var win = libs.wm.open({
                    run: ["sgapps/applications/code-editor"],
                    SKIP_APP_LIBRARY: true,
                    width: 900,
                    height: 600
                });
                win.title("Untitled");
            });
        });
    });
    </script>
</body>
</html>

Piecewise loading (smaller footprint)

exporter:configuration:full bundles seven independent loaders. Many integrations don't need the WindowSocket bridge, the GridFS client, or the translator — load only the ones you actually use:

Application(function (App) {
    // Style baseline (required — window chrome, buttons, dialogs)
    App["exporter:load:style-adjustments"]()
        .then(App["exporter:load:library:modules"])          // Application-Prototype modules (elements, resources)
        .then(App["exporter:load:library:thirdparty"])       // Third-party libs (monaco, reveal, xterm, …)
        .then(App["exporter:load:library:thirdparty-webapp"]) // sgapps-webapps b2b libs (icons, translate, …)
        // Optional: `exporter:init:window-socket` — for iframe-embedded apps
        // Optional: `exporter:init:sgapps-fs`     — for GridFS access
        // Optional: `exporter:init:translate`     — for i18n via translator
        .then(function () {
            return App.require(["wm :: sgapps-webapps.b2b.window-manager"]);
        })
        .then(function (libs) {
            document.body.appendChild(libs.wm.root());
        });
});

Individual loaders return a Promise so you can await, chain, or run them in parallel. Each one is idempotent (a second call resolves without re-fetching).

Available module aliases

Registered by exporter.js via Application.moduleRegister(...). Use these with App.require([&#x22;&#x3C;local&#x3E; :: &#x3C;alias&#x3E;&#x22;]):

Alias What it exports
sgapps-webapps.b2b.window-manager The WM singleton (wm.open, wm.root, wm.dialogs, …)
sgapps-webapps.b2b.window-manager.taskbar Taskbar app constructor (also openable by name once library-registered)
sgapps-webapps.b2b.icons Icon-set helpers used by app descriptors
sgapps-webapps.b2b.translate i18n translator (only when exporter:init:translate was called)
sgapps-webapps.b2b.editor.photo-editor Photo editor constructor
sgapps-webapps.b2b.editor.gcode-editor GCode editor constructor
sgapps-webapps.b2b.cnc-tool.model-selector CNC model selector

Any module registered under /scripts/modules/ is also reachable by path, e.g. sgapps/applications/code-editor — pass SKIP_APP_LIBRARY: true to wm.open() when opening by path.

sgapps-webapps.b2b.window-manager

The WM singleton — the same instance every consumer of this module gets. Mount it into your DOM via wm.root(), then open apps with wm.open(...). Everything the integration needs revolves around this module.

App.require(["wm :: sgapps-webapps.b2b.window-manager"]).then(function (libs) {
    document.body.appendChild(libs.wm.root());
    libs.wm.open({ run: ["sgapps/applications/code-editor"], SKIP_APP_LIBRARY: true });
});

Full API: Opening apps, The WindowApplication API, Dialogs.

sgapps-webapps.b2b.window-manager.taskbar

Loads the taskbar module (path: sgapps/applications/window-manager/taskbar). The WM does not preload it — requiring this alias is the prerequisite for any taskbar usage. To open the taskbar by short name, register it in the WM's applications library; alternatively, open by path with SKIP_APP_LIBRARY: true.

App.require([
    "wm :: sgapps-webapps.b2b.window-manager",
    "taskbar :: sgapps-webapps.b2b.window-manager.taskbar"
]).then(function (libs) {
    // Option A — open by path (no library entry needed)
    libs.wm.open({
        run: ["sgapps/applications/window-manager/taskbar", "--preset=full", "--mode=auto"],
        SKIP_APP_LIBRARY: true
    });

    // Option B — register once, then open by short name (see below for details)
    // libs.wm.applicationsLibrary()["taskbar"] = { path: "sgapps/applications/window-manager/taskbar", ... };
    // libs.wm.open({ run: ["taskbar", "--preset=full", "--mode=auto"] });
});

Full API: Adding a taskbar, Taskbar reference.

sgapps-webapps.b2b.icons

Icon-set resolver used by app descriptors. Also binds Application.icon(path) globally for direct URL resolution — useful when building your own toolbars, menus, or launchers that share SGApps' visual language.

// After the icons module is loaded (part of exporter:configuration:full):
var iconUrl = Application.icon("paper/icons/apps/32/utilities-terminal");
document.querySelector(".my-button img").src = iconUrl;

// Path format: "<icon-set>/<category>/<size?>/<icon-name>"
Application.icon("breeze*book-edit");                         // shortcut form
Application.icon("paper/icons/mimetypes/32/image-x-generic"); // paper set, 32 px mimetype
Application.icon("paper/icons/status/dialog-error");          // status icon (any size)

Available icon sets: paper, breeze, breeze-dark, tango, themes. Browsing by name is easiest via the Icons Manager app running on any SGApps desk.

sgapps-webapps.b2b.translate

i18n translator — only registered when exporter:init:translate (or exporter:configuration:full) was called. Once loaded, App.translate(key, defaultMessage, language&#x3F;) is bound globally.

// One-shot lookup with fallback
App.translate("save.button", "Save");                  // → "Save" (en) / "Salvează" (ro) / "Сохранить" (ru)
App.translate("save.button", "Save", "ru");            // force a language

// Full translator instance (state + loading + reactive text nodes)
var tr = App.translate();
tr.language  = "ro";                                   // active language — property, set via assignment
tr.languages = ["en", "ro", "ru"];                     // supported language list — property, set via assignment
tr.load(mySourceObjOrUrl, { identifier: "my-app", language: "ro" });

// Text nodes that auto-update on language change
var node = tr.trackedTextNode("save.button", "Save");
document.querySelector("button").appendChild(node);
tr.language = "ru";                                    // node's textContent flips automatically

Sources can be &#x7B;key: string&#x7D; maps, Blobs, or URL strings.

sgapps-webapps.b2b.editor.photo-editor

A standalone iframe host for the SGApps photo editor — instantiated as a constructor, NOT opened via wm.open(). Use this when you want the photo editor inside a modal, a sidebar panel, or a page section that isn't managed by the WM.

App.require([
    "photoEditor :: sgapps-webapps.b2b.editor.photo-editor"
]).then(function (libs) {
    var editor = new libs.photoEditor();
    document.getElementById("my-panel").appendChild(editor.node());

    // `editor()` returns a Promise for a client handle once the iframe boots.
    editor.editor().then(function (r) {
        // r.node   — the <iframe>
        // r.window — iframe.contentWindow
        // r.client — WindowSocket handle (postMessage bridge, see api-communication.md)
        // r.visible(true|false|null) — show / hide / z-index-out
        r.client.fire("webapp::instance::request", "open-image", "https://example.com/photo.jpg");
    });
});

The iframe embeds /online/webapp/photo-editor in embed-mode, so window chrome is hidden and the editor fills its container. For the client API, see API Communication.

sgapps-webapps.b2b.editor.gcode-editor

Same shape as editor.photo-editor above, but embeds /online/webapp/gcode-editor. Use it to inline a GCode viewer + CNC simulator anywhere on the page.

App.require([
    "gcodeEditor :: sgapps-webapps.b2b.editor.gcode-editor"
]).then(function (libs) {
    var editor = new libs.gcodeEditor();
    document.body.appendChild(editor.node());
    editor.editor().then(function (r) {
        r.client.fire("webapp::instance::request", "active-file:codeEditor:value", gcodeSource);
    });
});

sgapps-webapps.b2b.cnc-tool.model-selector

Purpose-built tool for the plywood / laser cutting workflow — chooses a CNC model, picks material presets, and drives the full raster → SVG → GCode pipeline. Packages a material-selection UI, an inline image editor, an SVG normalisation stage, and the render pipeline in one module (~1900 lines). Load it only when you're building a CNC integration; other consumers can safely skip it.

App.require([
    "modelSelector :: sgapps-webapps.b2b.cnc-tool.model-selector"
]).then(function (libs) {
    var tool = new libs.modelSelector({
        "ui:auto-scale":              true,
        "ui:show:close-icon":         true,
        "custom:material:fallback":   true,
        // Extensive params override — see the module source for the full schema.
    });
    document.body.appendChild(tool.node());
    tool.loadResources().then(function () {
        tool.isVisible(true);
    });
});

Selected methods: .params(), .scale(n), .node(type&#x3F;), .imageEditor(), .imageEditorOpen(url, opts), .imageEditorOpenSvg(source), .loadMaterials(), .loadSearchTopics(), .selectedImage(), .selectedMaterial(), .renderModel(), .loadResources(), .isVisible(state&#x3F;), .isLoading(state&#x3F;, resource&#x3F;).
Detailed configuration is out of scope for this integration guide. For the full parameter schema and event surface, contact SGApps for a CNC-integration walkthrough — or inspect the module source at public/scripts/modules-webapp/modules--sgapps-web-apps/modelSelector.js.

Opening apps

wm.open() returns a WindowApplication. Options:

Parameter Type Default Description
run string[] — [appName, ...args] — first entry is the app name (from applicationsLibrary()) or a module path when SKIP_APP_LIBRARY: true
env object &#x7B;&#x7D; Merged into the window's env()
SKIP_APP_LIBRARY boolean false When true, run[0] is treated as a module path
SKIP_ARGUMENTS_REPLACE boolean false Skip %U / %&#x7B;filename&#x7D; substitution in the registry's args template
top, left number random Initial position
width, height number random Initial size

Register apps in the library

To open by name (run: [&#x22;photo-editor&#x22;, ...]) the WM must know about the app. Populate wm.applicationsLibrary() after loading:

App.require(["wm :: sgapps-webapps.b2b.window-manager"]).then(function (libs) {
    var lib = libs.wm.applicationsLibrary();

    lib["photo-editor"] = {
        path:  "sgapps/applications/photo-editor",
        name:  "Photo Editor",
        icon:  "paper/icons/mimetypes/32/image-x-generic",
        comment: "Edit images",
        mimetypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
        groups: ["application"],   // ← required for the launcher grid + XDG-open
        args:  ["%U"]              // `%U` is replaced by user-supplied args
    };

    lib["code-editor"] = {
        path:  "sgapps/applications/code-editor",
        name:  "Code Editor",
        icon:  "paper/icons/mimetypes/32/text-x-generic",
        mimetypes: ["text/plain", "text/html", "text/css", "application/json"],
        groups: ["application"],
        args:  ["%U"]
    };

    // Now open by name — the library resolves `path`, `title`, `icon` for you:
    libs.wm.open({ run: ["photo-editor", "https://example.com/photo.jpg"] });
    libs.wm.open({ run: ["code-editor",  "/my/file.js"] });
});

Open with explicit geometry

var win = libs.wm.open({
    run: ["code-editor"],
    top: 60, left: 60,
    width: 1000, height: 700,
    env: { theme: "dark", args: ["--switch-dark-mode"] }
});
win.title("main.js");
win.icon("paper/icons/mimetypes/32/application-javascript");

The WindowApplication API

Full reference: see Window for every method, event, and lifecycle detail plus common patterns (confirm-before-close, api-request bridge, pinning into your own layout).

Every window returned by wm.open() exposes:

Method Description
win.title(str&#x3F;) Get or set the title bar text
win.icon(str&#x3F;) Get or set the icon (path in an icon set)
win.container() Get the content DOM node — attach your own UI here
win.header() / win.footer() Get chrome elements
win.width(n&#x3F;) / win.height(n&#x3F;) Get or set size
win.top(n&#x3F;) / win.left(n&#x3F;) Get or set position
win.maximize(bool&#x3F;) / win.minimize(bool&#x3F;) Get or set maximized/minimized state
win.pinned(node&#x3F;) Pin the window to a specific DOM node
win.restore() Restore from maximized/pinned
win.close(force&#x3F;) Close (soft-close with 500 ms grace unless force: true)
win.focus() Bring to front
win.addClass(str) / win.removeClass(str) Manage CSS classes on the window
win.env(obj&#x3F;) Get merged environment (or extend it)
win.args() Parsed arg-map: --flag → &#x7B;flag: true&#x7D;, --k=v → &#x7B;k: &#x22;v&#x22;&#x7D;, other → _paths[]
win.helpers() Utility helpers (adjustToMinSize, toolbar)
win.dialogs() Modal-child dialog factory (see Dialogs)
win.windowManager() Reference back to the WM

Events

Listen with win.on(event, handler) or wm.on(event, handler):

Event Source Args
window:open wm (win) — right after creation
window:added wm (win) — after DOM insertion
window:close-request wm (win) — soft-close initiated, may be aborted
window:close wm (win) — soft-close committed
focus win — (raised via wm.focusWindow(win))
blur win — (another window took focus)
closeRequest win (abort) — call abort() to prevent close
closed win (&#x7B;force&#x7D;) — final teardown; safe place to clean up

Example — confirm before close:

win.on("closeRequest", function (abort) {
    abort();   // hold the pending soft-close while we ask the user
    win.dialogs().confirm("Unsaved changes. Close anyway?").then(function (r) {
        if (r.result) win.close(true);   // user confirmed — force-close, bypassing this handler
        // if !r.result → do nothing; the initial abort() already cancelled the close
    });
});

Dialogs

Full reference: see Dialogs for every option, keyboard shortcut, cascade rule, and pattern (confirm-before-close, prompt-for-rename, release-notes dialog, Electron parity, and more).

Every window and the WM itself expose .dialogs() — a promise-based alert / confirm / prompt factory (plus a customPopup escape hatch) that opens modal-child windows parented to the caller. Perfect for feedback, confirmations, simple input flows, and — via customPopup — anything that doesn't fit the first three shapes.

// From inside an app
this.window().dialogs().alert("File saved");
     // → Promise<{ window, result: undefined }>

this.window().dialogs().confirm("Delete file?");
     // → Promise<{ window, result: boolean }>

this.window().dialogs().prompt("New name:", "untitled.txt");
     // → Promise<{ window, result: string | null }>

// Escape hatch — custom dialog body, resolve manually.
// Returns the context object synchronously (not a promise).
var ctx = this.window().dialogs().customPopup({ title: "Pick a colour", escResult: null });
// ... populate ctx.container, call ctx.resolve(value) on user action ...
ctx.promise.then(function (r) { /* r.result is whatever you resolved with */ });

// From your integration code
libs.wm.dialogs().alert("Session expired", {
    icon: "paper/icons/status/dialog-warning"
});

Rich content

message accepts three shapes — plain string, HTMLElement, or &#x7B;html: &#x22;...&#x22;&#x7D;:

var el = document.createElement("div");
el.innerHTML = '<h3>Backup done</h3>' +
    '<ul>' +
        '<li><code>database</code> — 12.4 MB</li>' +
        '<li><code>media/</code> — 380 files</li>' +
    '</ul>';
libs.wm.dialogs().alert(el, { title: "Backup complete" });

libs.wm.dialogs().confirm({
    html: '<p><b>Not undoable.</b></p><p>Revoke access for 2 devices and 5 sessions?</p>'
}, { title: "Confirm revoke", destructive: true, yesLabel: "Revoke" });

Baseline typography for h1-h4, p, ul/ol, code, pre, table, a, hr, img/video/svg ships in the WM stylesheet — rich HTML renders consistently across every dialog.

Options

opts = {
    title,          // window title
    icon,           // paper/icons/status/dialog-*
    okLabel,        // default "OK"
    cancelLabel,    // default "Cancel"
    yesLabel,       // (confirm) default "Yes"
    noLabel,        // (confirm) default "No"
    details,        // string | HTMLElement | {html} — collapsible info block
    destructive,    // (confirm) — Yes button turns red
    multiline,      // (prompt) — textarea; Ctrl/Cmd+Enter submits
    rows,           // (prompt multiline) — visible rows
    placeholder,    // (prompt) — input placeholder
    inputType,      // (prompt) — "text" | "password" | "email" | "number"
    escResult,      // (customPopup) — value settled into `result` when the
                    //                 dialog closes without an explicit
                    //                 resolve() call (ESC / parent cascade)
    width, height   // honoured as-is, clamped only to viewport-20px by defaultGeometry
                    //   (safety net). Defaults 380 × 200.
}

Cascade behavior

Dialogs use JS defaults 380 &#xD7; 200 when opts.width / opts.height are omitted, and honour caller-supplied values as-is otherwise. Long content scrolls inside the body while the button bar stays pinned. defaultGeometry clamps oversize values to viewport minus 20px as a safety net — nothing in CSS caps the dimensions.

Adding a taskbar

The taskbar tracks every open window, hosts pluggable widgets (launcher / clock / battery / network / show-desktop), and adapts to viewport size (desktop bar vs. mobile FAB with app grid). Add one to your desktop with:

App.require([
    "wm :: sgapps-webapps.b2b.window-manager",
    "taskbar :: sgapps-webapps.b2b.window-manager.taskbar"
]).then(function (libs) {
    // Register the taskbar in the WM's applications library so it can be
    // opened by short name — the same code path any other app uses (title,
    // icon, and args parsing all flow from this descriptor).
    libs.wm.applicationsLibrary()["taskbar"] = {
        path:  "sgapps/applications/window-manager/taskbar",
        name:  "Taskbar",
        icon:  "paper/icons/categories/applications-other",
        groups: ["application"],
        args:  ["%U"]
    };

    libs.wm.open({
        run: [
            "taskbar",
            "--preset=full",               // launcher + clock + battery + network + show-desktop
            "--mode=auto",                 // desktop bar OR mobile FAB by viewport
            "--position=bottom",
            "--state-storage=localStorage",
            "--state-key=my-app-taskbar"   // your own persistence key
        ]
    });
});

Configuration reference: Taskbar.

Full working example

A self-contained page that boots the WM, registers three apps in the library, opens a taskbar, and wires a "confirm-before-close" prompt to the code editor.

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>My Desktop</title>
    <style>
        body { margin: 0; }
        #wm-root { position: relative; height: 100vh; overflow: hidden; }
    </style>
</head>
<body>
    <div id="wm-root"></div>

    <script src="https://sgapps.io/scripts/modules-webapp/exporter.js"></script>
    <script>
    Application(function (App) {
        App["exporter:configuration:full"]().then(function () {
            App.require([
                "wm :: sgapps-webapps.b2b.window-manager",
                "taskbar :: sgapps-webapps.b2b.window-manager.taskbar"
            ]).then(function (libs) {
                var wm = libs.wm;
                document.getElementById("wm-root").appendChild(wm.root());

                // 1. Register apps in the library so we can open by name
                var lib = wm.applicationsLibrary();
                lib["taskbar"] = {
                    path: "sgapps/applications/window-manager/taskbar",
                    name: "Taskbar",
                    icon: "paper/icons/categories/applications-other",
                    groups: ["application"], args: ["%U"]
                };
                lib["code-editor"] = {
                    path: "sgapps/applications/code-editor",
                    name: "Code Editor",
                    icon: "breeze*book-edit",
                    comment: "Edit text files",
                    mimetypes: ["text/plain", "text/html", "text/css", "text/javascript"],
                    groups: ["application"], args: ["%U"]
                };
                lib["photo-editor"] = {
                    path: "sgapps/applications/photo-editor",
                    name: "Photo Editor",
                    icon: "paper/icons/mimetypes/32/image-x-generic",
                    mimetypes: ["image/jpeg", "image/png", "image/webp"],
                    groups: ["application"], args: ["%U"]
                };

                // 2. Open the taskbar
                wm.open({
                    run: [
                        "taskbar",
                        "--preset=full", "--mode=auto",
                        "--position=bottom", "--state-storage=localStorage",
                        "--state-key=my-desktop-taskbar"
                    ]
                });

                // 3. Open a code-editor window
                var editorWin = wm.open({
                    run: ["code-editor"],
                    top: 40, left: 40,
                    width: 900, height: 600
                });

                // 4. Confirm before close if unsaved changes
                var dirty = false;
                editorWin.on("closeRequest", function (abort) {
                    if (!dirty) return;    // clean → allow soft-close
                    abort();               // hold the close
                    editorWin.dialogs().confirm("Discard unsaved changes?", {
                        destructive: true,
                        yesLabel:  "Discard",
                        noLabel:   "Keep editing"
                    }).then(function (r) {
                        if (r.result) editorWin.close(true);   // force this time
                    });
                });

                // Track dirty flag from the editor
                editorWin.on("api-request::attached", function () {
                    editorWin.emit("api-request::ready", [function (cb) {
                        cb(new Application.Promise(function (res) {
                            editorWin.on("app:content-changed", function () { dirty = true; res(); });
                        }));
                    }]);
                });
            });
        });
    });
    </script>
</body>
</html>

Related

sgapps.io