Loading window: opens at once on start, names the module being loaded (N out of M)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ED1Y4LDd9wWTzrcadwtKVZ
This commit is contained in:
+5
-1
@@ -18,6 +18,10 @@ import { createDock } from "./shell/dock.js";
|
||||
import { hostFor } from "./shell/mounts.js";
|
||||
import paths from "./modules.js";
|
||||
import { composer as c } from "./composer/composer.js";
|
||||
import { startLoading } from "./shell/loading.js";
|
||||
|
||||
// First thing on screen, before the frame exists: the buttons arrive one by one and this says why.
|
||||
const loading = startLoading(c);
|
||||
|
||||
// What the core offers a feature, by name. `on(fn)` hears every change; fn gets "open" when a
|
||||
// different project was opened, "change" for an edit.
|
||||
@@ -38,7 +42,7 @@ const installed = await Promise.race([api.modules(), new Promise((_, no) => setT
|
||||
app.hasModule = (id) => !installed || installed.has(id);
|
||||
const settings = createSettings({ menubar: hostFor("menubar.menus") });
|
||||
app.openSettings = (id) => settings.open(id);
|
||||
const result = await loadFeatures(paths, { app });
|
||||
const result = await loadFeatures(paths, { app, onProgress: loading.progress }).finally(loading.done);
|
||||
settings.addButton();
|
||||
app.project.start().catch((err) => console.warn("could not open a project:", err.message));
|
||||
window.FunPackUI = { frame, settings, app, ...result };
|
||||
|
||||
@@ -24,10 +24,11 @@ function mountOne(hub, has, app) {
|
||||
}
|
||||
|
||||
// `app` is what the core offers features (project, ...). A feature names what it needs; a missing one hides it.
|
||||
export async function loadFeatures(paths, { load = (path) => import(path), app = {}, has = (name) => name in app } = {}) {
|
||||
export async function loadFeatures(paths, { load = (path) => import(path), app = {}, has = (name) => name in app, onProgress = () => {} } = {}) {
|
||||
const mounted = [], hidden = [];
|
||||
const hide = (id, err) => { hidden.push({ id, why: err.message }); console.warn(`[FunPack] ${id} hidden: ${err.message}`); };
|
||||
for (const path of paths) {
|
||||
for (const [i, path] of paths.entries()) {
|
||||
try { onProgress({ n: i + 1, of: paths.length, path }); } catch (err) { console.warn(err); } // a broken progress display must not stop loading
|
||||
let hubs;
|
||||
try { hubs = [].concat((await load(path)).default); } catch (err) { hide(path, err); continue; }
|
||||
for (const hub of hubs) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// The loading window: opens as soon as the page starts, names the module being loaded, closes when all are in.
|
||||
// Built only from the kit. It cannot be dismissed, because the screen behind it is still filling in.
|
||||
const name = (path) => {
|
||||
const parts = path.replace(/^\.\/features\//, "").replace(/\.js$/, "").split("/");
|
||||
if (parts[parts.length - 1] === "index") parts.pop();
|
||||
return parts.join("/") || path;
|
||||
};
|
||||
|
||||
export function startLoading(c) {
|
||||
let win = null, text = null, bar = null;
|
||||
try {
|
||||
text = c.text.md({ text: "Starting FunPack. Please wait for UI to finish loading." });
|
||||
bar = c.progress.bar({ value: 0, max: 100, label: "Loading modules" });
|
||||
win = c.modal.generic({
|
||||
title: "Loading FunPack", size: "md", closeOnOutside: false, closeOnEsc: false,
|
||||
body: c.region.stack({ gap: "lg", children: [text, bar] }),
|
||||
});
|
||||
} catch (err) { console.warn("[FunPack] loading window failed:", err.message); } // no window is better than no app
|
||||
return {
|
||||
progress({ n, of, path }) {
|
||||
if (!win) return;
|
||||
text.setText(`Loading module ${n} out of ${of}: ${name(path)}. Please wait for UI to finish loading.`);
|
||||
bar.setValue(((n - 1) / of) * 100);
|
||||
},
|
||||
done() { if (win) win.close("done"); win = null; },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { startLoading } from "./loading.js";
|
||||
|
||||
const fake = () => {
|
||||
const log = { closed: 0, text: "", pct: -1 };
|
||||
const c = {
|
||||
text: { md: ({ text }) => ({ setText: (t) => { log.text = t; log.text0 = text; } }) },
|
||||
progress: { bar: () => ({ node: {}, setValue: (v) => { log.pct = v; } }) },
|
||||
region: { stack: () => ({}) },
|
||||
modal: { generic: (p) => { log.props = p; return { close: () => { log.closed++; } }; } },
|
||||
};
|
||||
return { c, log };
|
||||
};
|
||||
|
||||
test("the window cannot be dismissed, names the module and its place, and closes once", () => {
|
||||
const { c, log } = fake();
|
||||
const l = startLoading(c);
|
||||
assert.equal(log.props.closeOnEsc, false);
|
||||
assert.equal(log.props.closeOnOutside, false);
|
||||
l.progress({ n: 2, of: 4, path: "./features/timeline/index.js" });
|
||||
assert.equal(log.text, "Loading module 2 out of 4: timeline. Please wait for UI to finish loading.");
|
||||
assert.equal(log.pct, 25);
|
||||
l.progress({ n: 3, of: 4, path: "./features/render/selected.js" });
|
||||
assert.match(log.text, /3 out of 4: render\/selected\./);
|
||||
l.done(); l.done();
|
||||
assert.equal(log.closed, 1);
|
||||
});
|
||||
|
||||
test("a kit that cannot draw the window does not stop the app", () => {
|
||||
const l = startLoading({});
|
||||
l.progress({ n: 1, of: 1, path: "./features/a/index.js" });
|
||||
l.done();
|
||||
});
|
||||
Reference in New Issue
Block a user