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.
| 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 |
exporter.js entry pointhttps://sgapps.io/scripts/modules-webapp/exporter.js bootstraps the entire SGApps client runtime on your page. It:
ApplicationPrototype.js + ApplicationBuilder.js from the SGApps origin.App (the global orchestrator).b2b.* module aliases (window-manager, icons, translate, taskbar, etc.).Application(callback) that fires as soon as App is ready.<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.<!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>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).
Registered by exporter.js via Application.moduleRegister(...). Use these with App.require(["<local> :: <alias>"]):
| 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 |
/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-managerThe 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.taskbarLoads 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.iconsIcon-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.translatei18n translator — only registered when exporter:init:translate (or exporter:configuration:full) was called. Once loaded, App.translate(key, defaultMessage, language?) 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 automaticallySources can be {key: string} maps, Blobs, or URL strings.
sgapps-webapps.b2b.editor.photo-editorA 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-editorSame 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-selectorPurpose-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?), .imageEditor(), .imageEditorOpen(url, opts), .imageEditorOpenSvg(source), .loadMaterials(), .loadSearchTopics(), .selectedImage(), .selectedMaterial(), .renderModel(), .loadResources(), .isVisible(state?), .isLoading(state?, resource?).
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.
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 | {} | 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 / %{filename} substitution in the registry's args template |
top, left | number | random | Initial position |
width, height | number | random | Initial size |
To open by name (run: ["photo-editor", ...]) 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"] });
});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");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?) | Get or set the title bar text |
win.icon(str?) | 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?) / win.height(n?) | Get or set size |
win.top(n?) / win.left(n?) | Get or set position |
win.maximize(bool?) / win.minimize(bool?) | Get or set maximized/minimized state |
win.pinned(node?) | Pin the window to a specific DOM node |
win.restore() | Restore from maximized/pinned |
win.close(force?) | 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?) | Get merged environment (or extend it) |
win.args() | Parsed arg-map: --flag → {flag: true}, --k=v → {k: "v"}, other → _paths[] |
win.helpers() | Utility helpers (adjustToMinSize, toolbar) |
win.dialogs() | Modal-child dialog factory (see Dialogs) |
win.windowManager() | Reference back to the WM |
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 | ({force}) — final teardown; safe place to clean up |
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
});
});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"
});message accepts three shapes — plain string, HTMLElement, or {html: "..."}:
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.
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.
}.close(false) — their own closeRequest subscribers may still abort.alert resolves undefined, confirm resolves false, prompt resolves null.alert, Yes for confirm, submit for prompt.Dialogs use JS defaults 380 × 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.
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.
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>WindowApplication reference (methods, events, lifecycle, patterns)