The columns were a share of the viewport, which is not a measurement of anything. A 900x900 window -- an ordinary shape -- gave the properties column 198px, in which "Loosen the starting image" wrapped over three lines and a section heading broke mid-word; 375px put three columns side by side with clipped headings and a preview no wider than a thumbnail. A panel is now as wide as a settings row needs, and below 760px it stops taking space from the centre and overlays it instead. The element decides what is OPEN and the stylesheet decides how it is DRAWN, so entering that mode closes both panels and only one opens at a time -- two overlays over a narrow centre is the same problem with an extra step. What was open is restored on the way out, and none of it touches the remembered preference: a window being small is not the user changing their mind. The rails paint above the overlay. Without that the panel covered the only control that closes it, which is the exact fault this arrangement exists to avoid, arriving by a different route the moment the panel stopped taking its own space. Also: the prompt was holding 38% of the height for two lines of text. The result gets the room; the divider is still draggable.
99 lines
4.2 KiB
JavaScript
99 lines
4.2 KiB
JavaScript
// The app's regions, and the mount points each one offers.
|
|
//
|
|
// A region declares what it can host. Core never learns these names, and no
|
|
// list of them exists anywhere else -- a module asks for one by name, and gets
|
|
// it or does not.
|
|
//
|
|
// The shape is the one v4 called Simple mode, built the other way round. v4's
|
|
// Simple mode was the Editor with about ten `display: none !important` rules
|
|
// over it, which is not a smaller app but the same app wearing a mask -- and it
|
|
// broke: a collapsed column was marked [hidden], and nothing can slide a
|
|
// display:none element into view, so a panel button did nothing at all and said
|
|
// nothing. So here a region that does not exist yet is ABSENT. The timeline, the
|
|
// media bin's view modes, the pipeline window and the wheel are not hidden
|
|
// below; they have not been built, and adding one is adding a region rather
|
|
// than removing a rule.
|
|
|
|
import { composer } from "../composer/composer.js";
|
|
import { offer } from "./mounts.js";
|
|
import { createTransport } from "./transport.js";
|
|
|
|
export function build(root, handlers = {}) {
|
|
// Left: what a project is made of. Empty until the media bin exists, and an
|
|
// empty state rather than an absent panel, because "there is nothing here
|
|
// yet" and "this part of the app is missing" must not look the same.
|
|
const assets = composer.panel.default({
|
|
title: "Assets",
|
|
body: composer.emptyState.default({
|
|
icon: "▤",
|
|
title: "Nothing in the bin",
|
|
hint: "Media you add to the project appears here.",
|
|
}),
|
|
});
|
|
|
|
// Centre: the result, and the prompt under it. The prompt is the one control
|
|
// that is always on screen, because it is the one always being edited.
|
|
const viewer = composer.viewer.media({ empty: "The result of the last run appears here." });
|
|
const preview = composer.panel.default({ title: "Preview", body: viewer });
|
|
const prompt = composer.panel.default({
|
|
title: "Prompt",
|
|
body: composer.hint.default({ text: "Modules that announced generation.prompt." }),
|
|
});
|
|
// The result gets the room. The prompt is a few lines and a button's worth of
|
|
// controls; at 38% of the height it was mostly empty space taken from the one
|
|
// thing on the page anybody is looking at. Draggable, so anyone writing a long
|
|
// prompt can take it back.
|
|
const centre = composer.splitPane.v({
|
|
size: 74,
|
|
label: "Preview and prompt",
|
|
panes: [preview, prompt],
|
|
});
|
|
|
|
// Right: everything about the run. Generation is open, settings folded away
|
|
// -- one is read constantly and the other rarely, and a fold says which is
|
|
// which without needing a second place to look. A settings WINDOW is a
|
|
// separate thing that has not been built; this is not a stand-in for it.
|
|
const generation = composer.panel.default({
|
|
title: "Generation",
|
|
body: composer.hint.default({ text: "Modules that announced a generation.* mount point." }),
|
|
});
|
|
const settings = composer.panel.default({
|
|
body: composer.hint.default({ text: "Modules that announced settings.general." }),
|
|
});
|
|
const properties = composer.group.default({
|
|
rows: [generation, composer.collapsible.default({ label: "Settings", body: settings })],
|
|
});
|
|
|
|
const workspace = composer.workspace.docked({
|
|
id: "main",
|
|
leftLabel: "Assets",
|
|
rightLabel: "Properties",
|
|
left: assets,
|
|
centre,
|
|
right: properties,
|
|
});
|
|
|
|
// The transport. One row, always visible, and the place a run is started and
|
|
// reported -- an inert setting is said HERE, next to Generate, not in the
|
|
// panel that would have enabled it.
|
|
const transport = createTransport(handlers);
|
|
|
|
const page = composer.frame.app({ main: workspace, footer: transport });
|
|
|
|
root.replaceChildren(page.node);
|
|
|
|
// The mount point IS the contract with modules; the region behind it can be
|
|
// rearranged freely as long as the name survives.
|
|
offer("assets.library", assets.body);
|
|
offer("generation.prompt", prompt.body);
|
|
offer("settings.general", settings.body);
|
|
offer("generation.model", generation.body);
|
|
offer("generation.latent", generation.body);
|
|
offer("generation.sampling", generation.body);
|
|
offer("generation.timing", generation.body);
|
|
offer("generation.post", generation.body);
|
|
|
|
return { workspace, assets, preview, viewer, prompt, generation, settings,
|
|
properties, transport, page };
|
|
}
|