@things-factory/figure-ui 10.1.79 → 10.1.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/figure-assistant.ts +2 -2
- package/client/graphql/index.ts +99 -10
- package/client/graphql/reached.ts +25 -0
- package/client/graphql/store.ts +27 -4
- package/client/modeller/ai-proposal.ts +74 -9
- package/client/modeller/align.ts +2 -3
- package/client/modeller/canvas.ts +18 -5
- package/client/modeller/command-preview.ts +113 -0
- package/client/modeller/editor.ts +826 -54
- package/client/modeller/fields.ts +12 -4
- package/client/modeller/figure-facts.ts +2 -2
- package/client/modeller/figure-first-save.ts +36 -14
- package/client/modeller/figure-history-panel.ts +133 -14
- package/client/modeller/figure-links.ts +385 -68
- package/client/modeller/figure-parts.ts +56 -2
- package/client/modeller/figure-preview.ts +19 -1
- package/client/modeller/figure-properties.ts +3 -3
- package/client/modeller/figure-revert-choice.ts +49 -0
- package/client/modeller/first-save.ts +16 -0
- package/client/modeller/gizmo.ts +0 -16
- package/client/modeller/links.ts +142 -15
- package/client/modeller/motion-ends.ts +83 -0
- package/client/modeller/occupancy.ts +28 -2
- package/client/modeller/product-session.ts +154 -18
- package/client/modeller/refusals.ts +15 -0
- package/client/modeller/revert-request.ts +26 -0
- package/client/modeller/side-panel-styles.ts +12 -0
- package/client/modeller/stage-model.ts +25 -3
- package/client/pages/figure-modeller-page.ts +68 -29
- package/dist-client/figure-assistant.js +2 -2
- package/dist-client/figure-assistant.js.map +1 -1
- package/dist-client/graphql/index.d.ts +39 -2
- package/dist-client/graphql/index.js +63 -9
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/graphql/reached.d.ts +10 -0
- package/dist-client/graphql/reached.js +28 -0
- package/dist-client/graphql/reached.js.map +1 -0
- package/dist-client/graphql/store.js +27 -4
- package/dist-client/graphql/store.js.map +1 -1
- package/dist-client/modeller/ai-proposal.d.ts +31 -2
- package/dist-client/modeller/ai-proposal.js +65 -6
- package/dist-client/modeller/ai-proposal.js.map +1 -1
- package/dist-client/modeller/align.d.ts +1 -1
- package/dist-client/modeller/align.js +0 -2
- package/dist-client/modeller/align.js.map +1 -1
- package/dist-client/modeller/canvas.d.ts +11 -0
- package/dist-client/modeller/canvas.js +33 -4
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/command-preview.d.ts +35 -0
- package/dist-client/modeller/command-preview.js +95 -0
- package/dist-client/modeller/command-preview.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +113 -1
- package/dist-client/modeller/editor.js +850 -50
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fields.d.ts +3 -2
- package/dist-client/modeller/fields.js +10 -3
- package/dist-client/modeller/fields.js.map +1 -1
- package/dist-client/modeller/figure-facts.js +2 -2
- package/dist-client/modeller/figure-facts.js.map +1 -1
- package/dist-client/modeller/figure-first-save.d.ts +3 -4
- package/dist-client/modeller/figure-first-save.js +41 -14
- package/dist-client/modeller/figure-first-save.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +31 -0
- package/dist-client/modeller/figure-history-panel.js +130 -14
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +51 -1
- package/dist-client/modeller/figure-links.js +392 -72
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +7 -0
- package/dist-client/modeller/figure-parts.js +55 -2
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +6 -0
- package/dist-client/modeller/figure-preview.js +22 -1
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-properties.js +3 -3
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/figure-revert-choice.d.ts +17 -0
- package/dist-client/modeller/figure-revert-choice.js +59 -0
- package/dist-client/modeller/figure-revert-choice.js.map +1 -0
- package/dist-client/modeller/first-save.d.ts +7 -0
- package/dist-client/modeller/first-save.js +17 -0
- package/dist-client/modeller/first-save.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +0 -6
- package/dist-client/modeller/gizmo.js +0 -18
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/links.d.ts +49 -4
- package/dist-client/modeller/links.js +141 -15
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/motion-ends.d.ts +12 -0
- package/dist-client/modeller/motion-ends.js +65 -0
- package/dist-client/modeller/motion-ends.js.map +1 -0
- package/dist-client/modeller/occupancy.d.ts +14 -1
- package/dist-client/modeller/occupancy.js +14 -1
- package/dist-client/modeller/occupancy.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +43 -2
- package/dist-client/modeller/product-session.js +146 -12
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +13 -0
- package/dist-client/modeller/refusals.js +16 -0
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/modeller/revert-request.d.ts +16 -0
- package/dist-client/modeller/revert-request.js +11 -0
- package/dist-client/modeller/revert-request.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.js +12 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -1
- package/dist-client/modeller/stage-model.d.ts +18 -2
- package/dist-client/modeller/stage-model.js +21 -3
- package/dist-client/modeller/stage-model.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +8 -1
- package/dist-client/pages/figure-modeller-page.js +72 -34
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/ai-proposal.test.ts +2 -2
- package/test/align.test.ts +50 -5
- package/test/browser/acceptance-page.js +187 -0
- package/test/browser/build.mjs +5 -1
- package/test/browser/revert-page.js +47 -0
- package/test/browser/serve-acceptance.mjs +85 -0
- package/test/browser/serve-revert.mjs +119 -0
- package/test/clip-ai-proposal.test.ts +104 -0
- package/test/clip-consent.test.ts +88 -0
- package/test/command-preview.test.ts +288 -0
- package/test/fields.test.ts +4 -3
- package/test/figure-assistant-surface.test.ts +4 -1
- package/test/first-minute.test.ts +96 -0
- package/test/first-save-suggest.test.ts +42 -0
- package/test/gizmo.test.ts +20 -16
- package/test/joint-fan.test.ts +2 -1
- package/test/layout.test.ts +9 -2
- package/test/links.test.ts +7 -4
- package/test/proposal-record.test.ts +126 -0
- package/test/release-entry.test.ts +28 -0
- package/test/release-panel.test.ts +3 -2
- package/test/resize-with-placement.test.ts +225 -0
- package/test/revert-choice.test.ts +39 -0
- package/test/revert-keeping.test.ts +102 -0
- package/test/save-landed.test.ts +198 -0
- package/translations/en.json +222 -10
- package/translations/ja.json +223 -11
- package/translations/ko.json +223 -11
- package/translations/ms.json +222 -10
- package/translations/zh.json +223 -11
|
@@ -2,10 +2,13 @@ import { LitElement, type PropertyValues } from 'lit';
|
|
|
2
2
|
import type { GizmoReach } from './gizmo.js';
|
|
3
3
|
import type { FigureRenderPart } from './renderer.js';
|
|
4
4
|
import { type ViewSettings } from './figure-view.js';
|
|
5
|
+
import { type ScaleAnchor } from './stage-model.js';
|
|
5
6
|
export declare class FigureCanvas extends LitElement {
|
|
6
7
|
static styles: import("lit").CSSResult;
|
|
7
8
|
parts: FigureRenderPart[];
|
|
8
9
|
palette: Record<string, string>;
|
|
10
|
+
/** The face a size drag keeps, per part (figure-model's `figureScaleAnchorOf`), so the drag shows what is written. */
|
|
11
|
+
anchors: Record<string, ScaleAnchor>;
|
|
9
12
|
/** Which figure is open. Another one stands the board up again. */
|
|
10
13
|
context: string;
|
|
11
14
|
selected: string;
|
|
@@ -18,8 +21,16 @@ export declare class FigureCanvas extends LitElement {
|
|
|
18
21
|
* would reach the editor as an edit.
|
|
19
22
|
*/
|
|
20
23
|
viewOnly: boolean;
|
|
24
|
+
/** The editor shows a new figure's start card over the canvas: the canvas's own one-line hint gives way to it. */
|
|
25
|
+
startShown: boolean;
|
|
21
26
|
/** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */
|
|
22
27
|
fan: FigureRenderPart | null;
|
|
28
|
+
/**
|
|
29
|
+
* A command seen before it is done (ADR-0097 §5.5): the bodies it would move, where they would stand, translucent,
|
|
30
|
+
* and the face they would be fastened to, lit. Shown, never picked, never saved; gone when the command is.
|
|
31
|
+
*/
|
|
32
|
+
ghost: FigureRenderPart[];
|
|
33
|
+
highlight: FigureRenderPart | null;
|
|
23
34
|
private stage;
|
|
24
35
|
private scene?;
|
|
25
36
|
private board?;
|
|
@@ -6,6 +6,7 @@ import { create, Model, niceGridStep } from '@hatiolab/things-scene';
|
|
|
6
6
|
import { DEFAULT_VIEW, sceneLook, WORKBENCH } from './figure-view.js';
|
|
7
7
|
import { boardFor, changedKeys, componentOf, gizmoResultOf } from './stage-model.js';
|
|
8
8
|
import { JOINT_FAN } from './joint-fan.js';
|
|
9
|
+
import { PREVIEW, TARGET_FACE } from './command-preview.js';
|
|
9
10
|
import { ownerOf } from './fields.js';
|
|
10
11
|
/* The fan's colour when the figure's palette names no accent. */
|
|
11
12
|
const FAN_COLOUR = '#ff8a00';
|
|
@@ -16,6 +17,8 @@ let FigureCanvas = class FigureCanvas extends LitElement {
|
|
|
16
17
|
super(...arguments);
|
|
17
18
|
this.parts = [];
|
|
18
19
|
this.palette = {};
|
|
20
|
+
/** The face a size drag keeps, per part (figure-model's `figureScaleAnchorOf`), so the drag shows what is written. */
|
|
21
|
+
this.anchors = {};
|
|
19
22
|
/** Which figure is open. Another one stands the board up again. */
|
|
20
23
|
this.context = '';
|
|
21
24
|
this.selected = '';
|
|
@@ -28,8 +31,16 @@ let FigureCanvas = class FigureCanvas extends LitElement {
|
|
|
28
31
|
* would reach the editor as an edit.
|
|
29
32
|
*/
|
|
30
33
|
this.viewOnly = false;
|
|
34
|
+
/** The editor shows a new figure's start card over the canvas: the canvas's own one-line hint gives way to it. */
|
|
35
|
+
this.startShown = false;
|
|
31
36
|
/** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */
|
|
32
37
|
this.fan = null;
|
|
38
|
+
/**
|
|
39
|
+
* A command seen before it is done (ADR-0097 §5.5): the bodies it would move, where they would stand, translucent,
|
|
40
|
+
* and the face they would be fastened to, lit. Shown, never picked, never saved; gone when the command is.
|
|
41
|
+
*/
|
|
42
|
+
this.ghost = [];
|
|
43
|
+
this.highlight = null;
|
|
33
44
|
/** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */
|
|
34
45
|
this.writing = false;
|
|
35
46
|
/** Components the gizmo changed since the last commit, by part name. */
|
|
@@ -71,7 +82,7 @@ let FigureCanvas = class FigureCanvas extends LitElement {
|
|
|
71
82
|
}
|
|
72
83
|
`; }
|
|
73
84
|
render() {
|
|
74
|
-
return html `<div stage></div>${this.parts.length ? nothing : html `<p>${i18next.t('figure.text.add-a-shape-to-start')}</p>`}`;
|
|
85
|
+
return html `<div stage></div>${this.parts.length || this.startShown ? nothing : html `<p>${i18next.t('figure.text.add-a-shape-to-start')}</p>`}`;
|
|
75
86
|
}
|
|
76
87
|
firstUpdated() {
|
|
77
88
|
this.standUp();
|
|
@@ -81,7 +92,7 @@ let FigureCanvas = class FigureCanvas extends LitElement {
|
|
|
81
92
|
updated(changed) {
|
|
82
93
|
if (!this.hasUpdated)
|
|
83
94
|
return;
|
|
84
|
-
if (
|
|
95
|
+
if (['parts', 'palette', 'context', 'fan', 'anchors', 'ghost', 'highlight'].some(key => changed.has(key)))
|
|
85
96
|
this.standUp();
|
|
86
97
|
if (changed.has('view'))
|
|
87
98
|
this.applyView();
|
|
@@ -177,10 +188,12 @@ let FigureCanvas = class FigureCanvas extends LitElement {
|
|
|
177
188
|
}
|
|
178
189
|
}
|
|
179
190
|
components() {
|
|
180
|
-
const parts = this.parts.map(part => componentOf(part, this.palette, this.board)).filter((c) => !!c);
|
|
191
|
+
const parts = this.parts.map(part => componentOf(part, this.palette, this.board, this.anchors[ownerOf(part.id)])).filter((c) => !!c);
|
|
181
192
|
/* The fan in the accent colour, translucent; `capturable: false` keeps clicks, the marquee and the gizmo off it. */
|
|
182
193
|
const fan = this.fan ? componentOf(this.fan, { ...this.palette, [JOINT_FAN]: this.palette['palette.accent'] ?? FAN_COLOUR }, this.board) : null;
|
|
183
|
-
|
|
194
|
+
const accent = this.palette['palette.accent'] ?? FAN_COLOUR;
|
|
195
|
+
const shown = [...(fan ? [fan] : []), ...this.ghost.map(part => componentOf(part, { ...this.palette, [PREVIEW]: accent, [TARGET_FACE]: accent }, this.board)), ...(this.highlight ? [componentOf(this.highlight, { ...this.palette, [TARGET_FACE]: accent }, this.board)] : [])];
|
|
196
|
+
return [...parts, ...shown.filter((c) => !!c).map(c => ({ ...c, capturable: false, locked: true }))];
|
|
184
197
|
}
|
|
185
198
|
/** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */
|
|
186
199
|
standing() {
|
|
@@ -344,6 +357,10 @@ __decorate([
|
|
|
344
357
|
property({ attribute: false }),
|
|
345
358
|
__metadata("design:type", Object)
|
|
346
359
|
], FigureCanvas.prototype, "palette", void 0);
|
|
360
|
+
__decorate([
|
|
361
|
+
property({ attribute: false }),
|
|
362
|
+
__metadata("design:type", Object)
|
|
363
|
+
], FigureCanvas.prototype, "anchors", void 0);
|
|
347
364
|
__decorate([
|
|
348
365
|
property(),
|
|
349
366
|
__metadata("design:type", Object)
|
|
@@ -364,10 +381,22 @@ __decorate([
|
|
|
364
381
|
property({ type: Boolean }),
|
|
365
382
|
__metadata("design:type", Object)
|
|
366
383
|
], FigureCanvas.prototype, "viewOnly", void 0);
|
|
384
|
+
__decorate([
|
|
385
|
+
property({ type: Boolean }),
|
|
386
|
+
__metadata("design:type", Object)
|
|
387
|
+
], FigureCanvas.prototype, "startShown", void 0);
|
|
367
388
|
__decorate([
|
|
368
389
|
property({ attribute: false }),
|
|
369
390
|
__metadata("design:type", Object)
|
|
370
391
|
], FigureCanvas.prototype, "fan", void 0);
|
|
392
|
+
__decorate([
|
|
393
|
+
property({ attribute: false }),
|
|
394
|
+
__metadata("design:type", Array)
|
|
395
|
+
], FigureCanvas.prototype, "ghost", void 0);
|
|
396
|
+
__decorate([
|
|
397
|
+
property({ attribute: false }),
|
|
398
|
+
__metadata("design:type", Object)
|
|
399
|
+
], FigureCanvas.prototype, "highlight", void 0);
|
|
371
400
|
__decorate([
|
|
372
401
|
query('div[stage]'),
|
|
373
402
|
__metadata("design:type", HTMLDivElement)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../client/modeller/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport { create, Model, niceGridStep } from '@hatiolab/things-scene'\n\nimport type { GizmoReach } from './gizmo.js'\nimport type { FigureRenderPart } from './renderer.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from './figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, type Board } from './stage-model.js'\nimport { JOINT_FAN } from './joint-fan.js'\nimport { ownerOf } from './fields.js'\n\n/*\n * The modeller's canvas -- a things-scene board in editing mode.\n *\n * Each body figure-model evaluates stands on the board as a `figure-part` component, so everything the canvas\n * has comes from the scene, not from code of our own: picking and the orange outline, the gizmo with its snapping and\n * live readout, box and shift selection, the camera (damping, zoom toward the cursor, double-click to fit, the six\n * view presets and their keys), the studio environment, the coordinate ground, lights and shadows, supersampling.\n * An earlier canvas drew the bodies with three.js directly and lost all of that (user, 2026-09-24: \"why nerf it?\").\n *\n * What the gizmo writes back to a component comes out as `part-transformed` in the inputs' terms (`stage-model`),\n * and the editor turns it into one step of the history; the next evaluation puts the component where the inputs say,\n * so a change a part cannot take (a fastened part moved) springs back.\n */\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown>) => void\n dispose?: () => void\n}\ninterface SceneLayer extends SceneComponent {\n components?: SceneComponent[]\n root?: { on?: (event: string, handler: (...args: unknown[]) => void) => void; selected?: SceneComponent[] }\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n addComponent?: (component: SceneComponent) => void\n removeComponent?: (component: SceneComponent) => void\n invalidate?: () => void\n fitCameraToModel?: () => void\n setGizmoMode?: (mode: 'translate' | 'rotate' | 'scale') => void\n app?: unknown\n}\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: SceneLayer\n}\n\n/* The fan's colour when the figure's palette names no accent. */\nconst FAN_COLOUR = '#ff8a00'\n\n/* The 2D side of the board, which the author never sees on this page but the scene paints. */\nconst PAPER = '#ffffff'\n\n@customElement('figure-canvas')\nexport class FigureCanvas extends LitElement {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n min-height: 0;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n /* The scene sets position: relative inline on its target; the size is given so it still fills the host. */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n p {\n position: absolute;\n inset: 45% 1rem auto;\n margin: 0;\n text-align: center;\n pointer-events: none;\n color: #f4f4f4;\n }\n `\n @property({ attribute: false }) parts: FigureRenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n /** Which figure is open. Another one stands the board up again. */\n @property() context = ''\n @property() selected = ''\n /** What the gizmo may write for the selected part; a change beyond it is not applied and the part springs back. */\n @property({ attribute: false }) reach: GizmoReach = { translate: false, rotate: false, scale: false }\n @property({ attribute: false }) view: ViewSettings = DEFAULT_VIEW\n /**\n * View only: nothing that happens on the board is told out. The card's picture is taken from a second canvas stood up\n * off screen (`thumbnail.ts`); the scene settles its components as it builds them, and a change told from there\n * would reach the editor as an edit.\n */\n @property({ type: Boolean }) viewOnly = false\n /** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */\n @property({ attribute: false }) fan: FigureRenderPart | null = null\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n private scene?: SceneHandle\n private board?: Board & { depth: number }\n private standingFor?: string\n private watcher?: ResizeObserver\n /** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */\n private writing = false\n\n render() {\n return html`<div stage></div>${this.parts.length ? nothing : html`<p>${i18next.t('figure.text.add-a-shape-to-start')}</p>`}`\n }\n\n firstUpdated() {\n this.standUp()\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n protected updated(changed: PropertyValues) {\n if (!this.hasUpdated) return\n if (changed.has('parts') || changed.has('palette') || changed.has('context') || changed.has('fan')) this.standUp()\n if (changed.has('view')) this.applyView()\n if (changed.has('selected')) this.showSelected()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * The board as a picture, for the card: the 3D view at this size, or nothing when it cannot be taken. The scene's\n * `toDataURL` renders the 2D board (a white sheet), and the 3D renderer keeps no drawing buffer, so the\n * scene's own `snapshot3d` draws and reads in one task. A picture that cannot be taken is not an error and is not\n * faked (user, 2026-09-24: no card had a picture after this canvas moved onto the scene).\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n const layer = this.scene?.root as (SceneLayer & { snapshot3d?: (w?: number, h?: number, type?: string, quality?: number) => Promise<string | undefined> }) | undefined\n if (!layer?.snapshot3d) return undefined\n try {\n /* webp: a card needs no alpha, and png is three or four times heavier in every list response. */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n console.warn(`[thumbnail] could not take the picture -- ${(e as Error).message}`)\n return undefined\n }\n }\n\n /** The whole figure in view: the board's fit, then the 3D camera's own. */\n fit() {\n this.scene?.fit?.('ratio')\n this.scene?.root?.fitCameraToModel?.()\n }\n\n /** The gizmo in a mode on the selected part, as W, E and R do with the canvas focused -- the scene's toggle. */\n gizmo(mode: 'translate' | 'rotate' | 'scale') {\n this.scene?.root?.setGizmoMode?.(mode)\n }\n\n /**\n * Stand the board up for this figure, or bring the one standing up to date. A new board only for another figure or\n * one that has outgrown it -- standing up again loses the selection and the camera.\n */\n private standUp() {\n if (!this.stage) return\n const needed = boardFor(this.parts, this.board)\n const outgrown = !this.board || needed.width > this.board.width || needed.height > this.board.height || needed.depth > this.board.depth\n if (this.scene && this.standingFor === this.context) {\n /*\n A part dragged past the board's edge grows the board where it stands. Standing it up again lost the selection,\n the gizmo and the camera after every such drag -- the gizmo seemed not to work at all (user, 2026-09-24).\n */\n if (outgrown) this.grow(needed)\n this.sync()\n return\n }\n this.teardown()\n if (!this.parts.length) return\n this.board = needed\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: needed.width,\n height: needed.height,\n depth: needed.depth,\n threed: true,\n /* Two samples a pixel: MSAA alone leaves steps on the silhouettes of one asset shown large. */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n ...sceneLook(this.view ?? DEFAULT_VIEW),\n gridStep: niceGridStep(Math.max(needed.width, needed.height)),\n sky: 'color',\n fillStyle: PAPER,\n components: this.components()\n },\n /* Editing mode: the camera view buttons, gizmo modes, help and keys. */\n mode: 1\n }) as SceneHandle\n this.standingFor = this.context\n this.watchScene()\n this.scene.resize?.()\n this.fit()\n this.showSelected()\n } catch (error) {\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n\n private components(): Record<string, unknown>[] {\n const parts = this.parts.map(part => componentOf(part, this.palette, this.board!)).filter((c): c is Record<string, unknown> => !!c)\n /* The fan in the accent colour, translucent; `capturable: false` keeps clicks, the marquee and the gizmo off it. */\n const fan = this.fan ? componentOf(this.fan, { ...this.palette, [JOINT_FAN]: this.palette['palette.accent'] ?? FAN_COLOUR }, this.board!) : null\n return fan ? [...parts, { ...fan, capturable: false, locked: true }] : parts\n }\n\n /** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */\n private standing(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /** A larger board under the same scene: its size, and the grid that suits it. Components are re-placed by `sync`. */\n private grow(needed: Board & { depth: number }) {\n const layer = this.scene?.root\n this.board = needed\n if (!layer) return\n this.writing = true\n try {\n layer.set({ width: needed.width, height: needed.height, depth: needed.depth, gridStep: niceGridStep(Math.max(needed.width, needed.height)) })\n } finally {\n this.writing = false\n }\n }\n\n /** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */\n private sync() {\n const layer = this.scene?.root\n if (!layer || !this.board) return\n const wanted = new Map(this.components().map(c => [c.name as string, c]))\n const standing = this.standing()\n this.writing = true\n try {\n for (const [name, component] of standing) {\n if (wanted.has(name)) continue\n layer.removeComponent?.(component)\n component.dispose?.()\n }\n for (const [name, state] of wanted) {\n const found = standing.get(name)\n if (found) {\n const diff = changedKeys(found.state, state)\n if (diff) found.set(diff)\n continue\n }\n const made = Model.compile({ ...state }, layer.app as never) as SceneComponent | null\n if (made) layer.addComponent?.(made)\n }\n } finally {\n this.writing = false\n }\n layer.invalidate?.()\n }\n\n /** Selection and drags, told out by the part's name -- never by its place in the list, which may change meanwhile. */\n private watchScene() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root?.on || this.viewOnly) return\n root.on('selected', (after: unknown) => {\n const name = (after as SceneComponent[] | undefined)?.[0]?.state?.name\n const id = typeof name === 'string' ? name : ''\n if (ownerOf(id) !== this.selected) this.dispatchEvent(new CustomEvent('part-selected', { detail: id, bubbles: true, composed: true }))\n })\n /*\n A change comes up to the board with the component in the last argument's `origin` (the scene appends\n { origin, name, deliverer } as it bubbles). Reading the first argument as the component does nothing.\n */\n /*\n A drag is written once, when it ends. The scene's gizmo writes the component on every move -- a size anchored on\n the opposite face, from the scale it captured when the drag began -- and writing each move back through the\n editor (and the evaluation back onto the component) fought it: the scale gizmo did not work (user, 2026-09-24).\n While a pointer is down the changes are only kept; on release the last one is told, and the board is brought to\n the evaluation. A change with no pointer down (the arrow keys) is told after a short pause.\n */\n layer.on?.('change', (...args: unknown[]) => {\n if (this.writing) return\n const origin = (args[args.length - 1] as { origin?: SceneComponent } | undefined)?.origin\n const name = origin?.state?.name\n if (typeof name !== 'string' || !this.parts.some(p => p.id === name)) return\n this.pending.set(name, origin!)\n if (!this.pointerDown) this.commitSoon(80)\n })\n this.stage.addEventListener('pointerdown', this.onPointerDown, true)\n window.addEventListener('pointerup', this.onPointerUp, true)\n window.addEventListener('pointercancel', this.onPointerUp, true)\n }\n\n /** Components the gizmo changed since the last commit, by part name. */\n private pending = new Map<string, SceneComponent>()\n private pointerDown = false\n private commitTimer?: ReturnType<typeof setTimeout>\n private onPointerDown = () => {\n this.pointerDown = true\n }\n private onPointerUp = () => {\n if (!this.pointerDown) return\n this.pointerDown = false\n /* After the scene's own drag end (it settles the depth and rebuilds the geometry on release). */\n this.commitSoon(0)\n }\n private commitSoon(delay: number) {\n clearTimeout(this.commitTimer)\n this.commitTimer = setTimeout(() => this.commit(), delay)\n }\n /** Tell each changed part once, in the inputs' terms; then put the board where the inputs say (a refusal springs back). */\n private commit() {\n /*\n Not while a pointer is down: a pause set by a change just before the press (the arrow keys, the scene settling\n the pick) fired in the middle of the drag and wrote where the part was then -- a drag on a part placed by a rule\n became two steps, the first one a few millimetres in (browser check, 2026-09-25). What is pending waits for the release.\n */\n if (this.pointerDown || !this.board || !this.pending.size) return\n const changed = [...this.pending]\n this.pending.clear()\n for (const [name, component] of changed) {\n const part = this.parts.find(p => p.id === name)\n if (!part) continue\n const result = gizmoResultOf(part, component.state, this.board)\n if (!result.position && !result.rotation && !result.scale) continue\n this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: part.id, result }, bubbles: true, composed: true }))\n }\n setTimeout(() => this.sync())\n }\n\n private showSelected() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root) return\n /* A repeat is selected whole: every copy of it (the editor selects the part the author made). */\n const wanted = this.selected ? [...this.standing()].filter(([name]) => ownerOf(name) === this.selected).map(([, component]) => component) : []\n const now = root.selected ?? []\n if (now.length === wanted.length && wanted.every((one, i) => now[i] === one)) return\n root.selected = wanted\n layer.invalidate?.()\n }\n\n /** View settings go into the scene as they are: they are the board's own state names. Nothing is stood up again. */\n private applyView() {\n const layer = this.scene?.root\n if (!layer) return\n layer.set(sceneLook(this.view ?? DEFAULT_VIEW))\n layer.invalidate?.()\n }\n\n private teardown() {\n clearTimeout(this.commitTimer)\n this.pending.clear()\n this.stage?.removeEventListener('pointerdown', this.onPointerDown, true)\n window.removeEventListener('pointerup', this.onPointerUp, true)\n window.removeEventListener('pointercancel', this.onPointerUp, true)\n this.scene?.dispose?.()\n this.scene = undefined\n this.standingFor = undefined\n this.board = undefined\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../client/modeller/canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport { create, Model, niceGridStep } from '@hatiolab/things-scene'\n\nimport type { GizmoReach } from './gizmo.js'\nimport type { FigureRenderPart } from './renderer.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from './figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, type Board, type ScaleAnchor } from './stage-model.js'\nimport { JOINT_FAN } from './joint-fan.js'\nimport { PREVIEW, TARGET_FACE } from './command-preview.js'\nimport { ownerOf } from './fields.js'\n\n/*\n * The modeller's canvas -- a things-scene board in editing mode.\n *\n * Each body figure-model evaluates stands on the board as a `figure-part` component, so everything the canvas\n * has comes from the scene, not from code of our own: picking and the orange outline, the gizmo with its snapping and\n * live readout, box and shift selection, the camera (damping, zoom toward the cursor, double-click to fit, the six\n * view presets and their keys), the studio environment, the coordinate ground, lights and shadows, supersampling.\n * An earlier canvas drew the bodies with three.js directly and lost all of that (user, 2026-09-24: \"why nerf it?\").\n *\n * What the gizmo writes back to a component comes out as `part-transformed` in the inputs' terms (`stage-model`),\n * and the editor turns it into one step of the history; the next evaluation puts the component where the inputs say,\n * so a change a part cannot take (a fastened part moved) springs back.\n */\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown>) => void\n dispose?: () => void\n}\ninterface SceneLayer extends SceneComponent {\n components?: SceneComponent[]\n root?: { on?: (event: string, handler: (...args: unknown[]) => void) => void; selected?: SceneComponent[] }\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n addComponent?: (component: SceneComponent) => void\n removeComponent?: (component: SceneComponent) => void\n invalidate?: () => void\n fitCameraToModel?: () => void\n setGizmoMode?: (mode: 'translate' | 'rotate' | 'scale') => void\n app?: unknown\n}\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: SceneLayer\n}\n\n/* The fan's colour when the figure's palette names no accent. */\nconst FAN_COLOUR = '#ff8a00'\n\n/* The 2D side of the board, which the author never sees on this page but the scene paints. */\nconst PAPER = '#ffffff'\n\n@customElement('figure-canvas')\nexport class FigureCanvas extends LitElement {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n min-height: 0;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n /* The scene sets position: relative inline on its target; the size is given so it still fills the host. */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n p {\n position: absolute;\n inset: 45% 1rem auto;\n margin: 0;\n text-align: center;\n pointer-events: none;\n color: #f4f4f4;\n }\n `\n @property({ attribute: false }) parts: FigureRenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n /** The face a size drag keeps, per part (figure-model's `figureScaleAnchorOf`), so the drag shows what is written. */\n @property({ attribute: false }) anchors: Record<string, ScaleAnchor> = {}\n /** Which figure is open. Another one stands the board up again. */\n @property() context = ''\n @property() selected = ''\n /** What the gizmo may write for the selected part; a change beyond it is not applied and the part springs back. */\n @property({ attribute: false }) reach: GizmoReach = { translate: false, rotate: false, scale: false }\n @property({ attribute: false }) view: ViewSettings = DEFAULT_VIEW\n /**\n * View only: nothing that happens on the board is told out. The card's picture is taken from a second canvas stood up\n * off screen (`thumbnail.ts`); the scene settles its components as it builds them, and a change told from there\n * would reach the editor as an edit.\n */\n @property({ type: Boolean }) viewOnly = false\n /** The editor shows a new figure's start card over the canvas: the canvas's own one-line hint gives way to it. */\n @property({ type: Boolean }) startShown = false\n /** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */\n @property({ attribute: false }) fan: FigureRenderPart | null = null\n /**\n * A command seen before it is done (ADR-0097 §5.5): the bodies it would move, where they would stand, translucent,\n * and the face they would be fastened to, lit. Shown, never picked, never saved; gone when the command is.\n */\n @property({ attribute: false }) ghost: FigureRenderPart[] = []\n @property({ attribute: false }) highlight: FigureRenderPart | null = null\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n private scene?: SceneHandle\n private board?: Board & { depth: number }\n private standingFor?: string\n private watcher?: ResizeObserver\n /** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */\n private writing = false\n\n render() {\n return html`<div stage></div>${this.parts.length || this.startShown ? nothing : html`<p>${i18next.t('figure.text.add-a-shape-to-start')}</p>`}`\n }\n\n firstUpdated() {\n this.standUp()\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n protected updated(changed: PropertyValues) {\n if (!this.hasUpdated) return\n if (['parts', 'palette', 'context', 'fan', 'anchors', 'ghost', 'highlight'].some(key => changed.has(key))) this.standUp()\n if (changed.has('view')) this.applyView()\n if (changed.has('selected')) this.showSelected()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * The board as a picture, for the card: the 3D view at this size, or nothing when it cannot be taken. The scene's\n * `toDataURL` renders the 2D board (a white sheet), and the 3D renderer keeps no drawing buffer, so the\n * scene's own `snapshot3d` draws and reads in one task. A picture that cannot be taken is not an error and is not\n * faked (user, 2026-09-24: no card had a picture after this canvas moved onto the scene).\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n const layer = this.scene?.root as (SceneLayer & { snapshot3d?: (w?: number, h?: number, type?: string, quality?: number) => Promise<string | undefined> }) | undefined\n if (!layer?.snapshot3d) return undefined\n try {\n /* webp: a card needs no alpha, and png is three or four times heavier in every list response. */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n console.warn(`[thumbnail] could not take the picture -- ${(e as Error).message}`)\n return undefined\n }\n }\n\n /** The whole figure in view: the board's fit, then the 3D camera's own. */\n fit() {\n this.scene?.fit?.('ratio')\n this.scene?.root?.fitCameraToModel?.()\n }\n\n /** The gizmo in a mode on the selected part, as W, E and R do with the canvas focused -- the scene's toggle. */\n gizmo(mode: 'translate' | 'rotate' | 'scale') {\n this.scene?.root?.setGizmoMode?.(mode)\n }\n\n /**\n * Stand the board up for this figure, or bring the one standing up to date. A new board only for another figure or\n * one that has outgrown it -- standing up again loses the selection and the camera.\n */\n private standUp() {\n if (!this.stage) return\n const needed = boardFor(this.parts, this.board)\n const outgrown = !this.board || needed.width > this.board.width || needed.height > this.board.height || needed.depth > this.board.depth\n if (this.scene && this.standingFor === this.context) {\n /*\n A part dragged past the board's edge grows the board where it stands. Standing it up again lost the selection,\n the gizmo and the camera after every such drag -- the gizmo seemed not to work at all (user, 2026-09-24).\n */\n if (outgrown) this.grow(needed)\n this.sync()\n return\n }\n this.teardown()\n if (!this.parts.length) return\n this.board = needed\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: needed.width,\n height: needed.height,\n depth: needed.depth,\n threed: true,\n /* Two samples a pixel: MSAA alone leaves steps on the silhouettes of one asset shown large. */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n ...sceneLook(this.view ?? DEFAULT_VIEW),\n gridStep: niceGridStep(Math.max(needed.width, needed.height)),\n sky: 'color',\n fillStyle: PAPER,\n components: this.components()\n },\n /* Editing mode: the camera view buttons, gizmo modes, help and keys. */\n mode: 1\n }) as SceneHandle\n this.standingFor = this.context\n this.watchScene()\n this.scene.resize?.()\n this.fit()\n this.showSelected()\n } catch (error) {\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n\n private components(): Record<string, unknown>[] {\n const parts = this.parts.map(part => componentOf(part, this.palette, this.board!, this.anchors[ownerOf(part.id)])).filter((c): c is Record<string, unknown> => !!c)\n /* The fan in the accent colour, translucent; `capturable: false` keeps clicks, the marquee and the gizmo off it. */\n const fan = this.fan ? componentOf(this.fan, { ...this.palette, [JOINT_FAN]: this.palette['palette.accent'] ?? FAN_COLOUR }, this.board!) : null\n const accent = this.palette['palette.accent'] ?? FAN_COLOUR\n const shown = [...(fan ? [fan] : []), ...this.ghost.map(part => componentOf(part, { ...this.palette, [PREVIEW]: accent, [TARGET_FACE]: accent }, this.board!)), ...(this.highlight ? [componentOf(this.highlight, { ...this.palette, [TARGET_FACE]: accent }, this.board!)] : [])]\n return [...parts, ...shown.filter((c): c is Record<string, unknown> => !!c).map(c => ({ ...c, capturable: false, locked: true }))]\n }\n\n /** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */\n private standing(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /** A larger board under the same scene: its size, and the grid that suits it. Components are re-placed by `sync`. */\n private grow(needed: Board & { depth: number }) {\n const layer = this.scene?.root\n this.board = needed\n if (!layer) return\n this.writing = true\n try {\n layer.set({ width: needed.width, height: needed.height, depth: needed.depth, gridStep: niceGridStep(Math.max(needed.width, needed.height)) })\n } finally {\n this.writing = false\n }\n }\n\n /** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */\n private sync() {\n const layer = this.scene?.root\n if (!layer || !this.board) return\n const wanted = new Map(this.components().map(c => [c.name as string, c]))\n const standing = this.standing()\n this.writing = true\n try {\n for (const [name, component] of standing) {\n if (wanted.has(name)) continue\n layer.removeComponent?.(component)\n component.dispose?.()\n }\n for (const [name, state] of wanted) {\n const found = standing.get(name)\n if (found) {\n const diff = changedKeys(found.state, state)\n if (diff) found.set(diff)\n continue\n }\n const made = Model.compile({ ...state }, layer.app as never) as SceneComponent | null\n if (made) layer.addComponent?.(made)\n }\n } finally {\n this.writing = false\n }\n layer.invalidate?.()\n }\n\n /** Selection and drags, told out by the part's name -- never by its place in the list, which may change meanwhile. */\n private watchScene() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root?.on || this.viewOnly) return\n root.on('selected', (after: unknown) => {\n const name = (after as SceneComponent[] | undefined)?.[0]?.state?.name\n const id = typeof name === 'string' ? name : ''\n if (ownerOf(id) !== this.selected) this.dispatchEvent(new CustomEvent('part-selected', { detail: id, bubbles: true, composed: true }))\n })\n /*\n A change comes up to the board with the component in the last argument's `origin` (the scene appends\n { origin, name, deliverer } as it bubbles). Reading the first argument as the component does nothing.\n */\n /*\n A drag is written once, when it ends. The scene's gizmo writes the component on every move -- a size anchored on\n the opposite face, from the scale it captured when the drag began -- and writing each move back through the\n editor (and the evaluation back onto the component) fought it: the scale gizmo did not work (user, 2026-09-24).\n While a pointer is down the changes are only kept; on release the last one is told, and the board is brought to\n the evaluation. A change with no pointer down (the arrow keys) is told after a short pause.\n */\n layer.on?.('change', (...args: unknown[]) => {\n if (this.writing) return\n const origin = (args[args.length - 1] as { origin?: SceneComponent } | undefined)?.origin\n const name = origin?.state?.name\n if (typeof name !== 'string' || !this.parts.some(p => p.id === name)) return\n this.pending.set(name, origin!)\n if (!this.pointerDown) this.commitSoon(80)\n })\n this.stage.addEventListener('pointerdown', this.onPointerDown, true)\n window.addEventListener('pointerup', this.onPointerUp, true)\n window.addEventListener('pointercancel', this.onPointerUp, true)\n }\n\n /** Components the gizmo changed since the last commit, by part name. */\n private pending = new Map<string, SceneComponent>()\n private pointerDown = false\n private commitTimer?: ReturnType<typeof setTimeout>\n private onPointerDown = () => {\n this.pointerDown = true\n }\n private onPointerUp = () => {\n if (!this.pointerDown) return\n this.pointerDown = false\n /* After the scene's own drag end (it settles the depth and rebuilds the geometry on release). */\n this.commitSoon(0)\n }\n private commitSoon(delay: number) {\n clearTimeout(this.commitTimer)\n this.commitTimer = setTimeout(() => this.commit(), delay)\n }\n /** Tell each changed part once, in the inputs' terms; then put the board where the inputs say (a refusal springs back). */\n private commit() {\n /*\n Not while a pointer is down: a pause set by a change just before the press (the arrow keys, the scene settling\n the pick) fired in the middle of the drag and wrote where the part was then -- a drag on a part placed by a rule\n became two steps, the first one a few millimetres in (browser check, 2026-09-25). What is pending waits for the release.\n */\n if (this.pointerDown || !this.board || !this.pending.size) return\n const changed = [...this.pending]\n this.pending.clear()\n for (const [name, component] of changed) {\n const part = this.parts.find(p => p.id === name)\n if (!part) continue\n const result = gizmoResultOf(part, component.state, this.board)\n if (!result.position && !result.rotation && !result.scale) continue\n this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: part.id, result }, bubbles: true, composed: true }))\n }\n setTimeout(() => this.sync())\n }\n\n private showSelected() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root) return\n /* A repeat is selected whole: every copy of it (the editor selects the part the author made). */\n const wanted = this.selected ? [...this.standing()].filter(([name]) => ownerOf(name) === this.selected).map(([, component]) => component) : []\n const now = root.selected ?? []\n if (now.length === wanted.length && wanted.every((one, i) => now[i] === one)) return\n root.selected = wanted\n layer.invalidate?.()\n }\n\n /** View settings go into the scene as they are: they are the board's own state names. Nothing is stood up again. */\n private applyView() {\n const layer = this.scene?.root\n if (!layer) return\n layer.set(sceneLook(this.view ?? DEFAULT_VIEW))\n layer.invalidate?.()\n }\n\n private teardown() {\n clearTimeout(this.commitTimer)\n this.pending.clear()\n this.stage?.removeEventListener('pointerdown', this.onPointerDown, true)\n window.removeEventListener('pointerup', this.onPointerUp, true)\n window.removeEventListener('pointercancel', this.onPointerUp, true)\n this.scene?.dispose?.()\n this.scene = undefined\n this.standingFor = undefined\n this.board = undefined\n }\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FigureBodyMove, FigureClipLoss, FigureFaceArea, FigureRowReading } from '@hatiolab/figure-model';
|
|
2
|
+
import type { FigureRenderPart } from './renderer.js';
|
|
3
|
+
export declare const PREVIEW = "__preview";
|
|
4
|
+
export declare const TARGET_FACE = "__target-face";
|
|
5
|
+
export type Say = (key: string, params?: Record<string, string>) => string;
|
|
6
|
+
/** A preview as the editor holds it: bound to the figure, version and command it was worked out for. */
|
|
7
|
+
export interface HeldPreview {
|
|
8
|
+
context: string;
|
|
9
|
+
revision: number;
|
|
10
|
+
/** The command, as text: a preview for another command is not this one's. */
|
|
11
|
+
key: string;
|
|
12
|
+
geometry?: unknown[];
|
|
13
|
+
moves?: FigureBodyMove[];
|
|
14
|
+
/** Bodies it would draw that are not drawn now: a row's copies. */
|
|
15
|
+
added?: string[];
|
|
16
|
+
/** The row it would make, read on the preview's figure. */
|
|
17
|
+
row?: FigureRowReading;
|
|
18
|
+
clipsWouldGo?: FigureClipLoss[];
|
|
19
|
+
face?: FigureFaceArea;
|
|
20
|
+
/** Whether a face was asked for and could not be shown. */
|
|
21
|
+
faceUnknown?: boolean;
|
|
22
|
+
/** Why the command would be refused; nothing else is shown then. */
|
|
23
|
+
refused?: string;
|
|
24
|
+
}
|
|
25
|
+
/** The moving bodies where they would stand, drawn translucent. The ones that stay are not drawn twice. */
|
|
26
|
+
export declare function ghostPartsOf(preview: HeldPreview | undefined): FigureRenderPart[];
|
|
27
|
+
/** The face the part would meet, lit: a thin plate over a flat face, a thin disc over a round one. */
|
|
28
|
+
export declare function faceHighlightOf(face: FigureFaceArea | undefined): FigureRenderPart | null;
|
|
29
|
+
/**
|
|
30
|
+
* What the command would do, in words: the part's own move and turn, what goes with it, the clips it would stop, and
|
|
31
|
+
* why nothing can be shown when the command would be refused.
|
|
32
|
+
*/
|
|
33
|
+
export declare function previewWordsOf(preview: HeldPreview | undefined, part: string, say: Say): string[];
|
|
34
|
+
/** A row in words, as figure-model reads it from what is drawn: how many, how far apart, what is left at the ends, what rides it. */
|
|
35
|
+
export declare function rowWordsOf(row: FigureRowReading | undefined, say: Say): string[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A command seen before it is done, as the modeller shows it (ADR-0097 §5.5, designer 2026-09-27): the bodies it would
|
|
3
|
+
* move, translucent where they would stand; the face they would meet, lit; and in words, how far and how each turns --
|
|
4
|
+
* the turn as figure-model gives it, one axis and one angle in the figure's frame.
|
|
5
|
+
*/
|
|
6
|
+
/* The material tokens of what a command would do: the bodies where they would stand, and the face they would meet. */
|
|
7
|
+
export const PREVIEW = '__preview';
|
|
8
|
+
export const TARGET_FACE = '__target-face';
|
|
9
|
+
/** The moving bodies where they would stand, drawn translucent. The ones that stay are not drawn twice. */
|
|
10
|
+
export function ghostPartsOf(preview) {
|
|
11
|
+
if (!preview?.geometry || !(preview.moves?.length || preview.added?.length))
|
|
12
|
+
return [];
|
|
13
|
+
const moving = new Set([...(preview.moves ?? []).map(m => m.body), ...(preview.added ?? [])]);
|
|
14
|
+
return preview.geometry
|
|
15
|
+
.filter(body => body && typeof body === 'object' && 'shape' in body && 'pose' in body && moving.has(body.id))
|
|
16
|
+
.map(body => ({ ...body, id: `${PREVIEW}:${body.id}`, material: { token: PREVIEW, transparent: true } }));
|
|
17
|
+
}
|
|
18
|
+
/** How thick the lit face is, and how far off the face it floats, so it neither hides nor flickers with it. */
|
|
19
|
+
const THICKNESS = 2;
|
|
20
|
+
const LIFT = 1;
|
|
21
|
+
/** The face the part would meet, lit: a thin plate over a flat face, a thin disc over a round one. */
|
|
22
|
+
export function faceHighlightOf(face) {
|
|
23
|
+
if (!face)
|
|
24
|
+
return null;
|
|
25
|
+
/* Its own x · y · z are the face's two sides and its normal, as the kernel gives them. */
|
|
26
|
+
const r = [0, 1, 2].map(i => [face.u[i], face.normal[i], face.v[i]]);
|
|
27
|
+
const t = [0, 1, 2].map(i => face.centre[i] + face.normal[i] * (LIFT + THICKNESS / 2));
|
|
28
|
+
const shape = face.round
|
|
29
|
+
? { provider: 'cylinder', dimensions: { radius: face.size.u / 2, length: THICKNESS } }
|
|
30
|
+
: { provider: 'box', dimensions: { width: face.size.u, height: THICKNESS, depth: face.size.v } };
|
|
31
|
+
/* Solid: a translucent plate over a light face was hardly seen (2026-09-27, :3391); it covers the face it names. */
|
|
32
|
+
return { id: TARGET_FACE, shape, pose: { r, t }, material: { token: TARGET_FACE } };
|
|
33
|
+
}
|
|
34
|
+
const mm = (v) => `${v > 0 ? '+' : v < 0 ? '−' : ''}${Math.abs(Math.round(v * 10) / 10).toLocaleString()}`;
|
|
35
|
+
/**
|
|
36
|
+
* What the command would do, in words: the part's own move and turn, what goes with it, the clips it would stop, and
|
|
37
|
+
* why nothing can be shown when the command would be refused.
|
|
38
|
+
*/
|
|
39
|
+
export function previewWordsOf(preview, part, say) {
|
|
40
|
+
if (!preview)
|
|
41
|
+
return [];
|
|
42
|
+
if (preview.refused)
|
|
43
|
+
return [say('figure.text.preview-refused', { reason: preview.refused })];
|
|
44
|
+
/* A row: what it would be, read as drawn; its part does not move, it is copied. */
|
|
45
|
+
if (preview.row)
|
|
46
|
+
return [...rowWordsOf(preview.row, say), ...(preview.clipsWouldGo?.length ? [say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') })] : [])];
|
|
47
|
+
const words = [];
|
|
48
|
+
const own = preview.moves?.find(m => m.body === part);
|
|
49
|
+
if (!own)
|
|
50
|
+
words.push(say('figure.text.preview-stays'));
|
|
51
|
+
else {
|
|
52
|
+
const axes = ['x', 'y', 'z'].map((axis, i) => [axis, own.translate[i]]).filter(([, v]) => Math.abs(v) >= 0.05);
|
|
53
|
+
if (axes.length)
|
|
54
|
+
words.push(say('figure.text.preview-moves', { by: axes.map(([axis, v]) => `${axis.toUpperCase()} ${mm(v)}`).join(' · ') }));
|
|
55
|
+
if (own.turn) {
|
|
56
|
+
/* Signed by the right hand about the axis, as the figure's frame has it. */
|
|
57
|
+
const degrees = mm(own.turn.degrees);
|
|
58
|
+
words.push(typeof own.turn.axis === 'string'
|
|
59
|
+
? say('figure.text.preview-turns-named', { axis: own.turn.axis.toUpperCase(), degrees })
|
|
60
|
+
: say('figure.text.preview-turns-slanted', { axis: own.turn.axis.map(v => v.toFixed(2)).join(', '), degrees }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/* What goes with it: parts fastened to it, each row once, with its count. */
|
|
64
|
+
const others = new Map();
|
|
65
|
+
for (const m of preview.moves ?? [])
|
|
66
|
+
if (m.part !== part)
|
|
67
|
+
others.set(m.part, (others.get(m.part) ?? 0) + 1);
|
|
68
|
+
if (others.size)
|
|
69
|
+
words.push(say('figure.text.preview-carries', { parts: [...others].map(([name, n]) => (n > 1 ? say('figure.text.preview-copies', { part: name, n: String(n) }) : name)).join(', ') }));
|
|
70
|
+
if (preview.clipsWouldGo?.length)
|
|
71
|
+
words.push(say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') }));
|
|
72
|
+
if (preview.faceUnknown)
|
|
73
|
+
words.push(say('figure.text.preview-face-unknown'));
|
|
74
|
+
return words;
|
|
75
|
+
}
|
|
76
|
+
/** A row in words, as figure-model reads it from what is drawn: how many, how far apart, what is left at the ends, what rides it. */
|
|
77
|
+
export function rowWordsOf(row, say) {
|
|
78
|
+
if (!row)
|
|
79
|
+
return [];
|
|
80
|
+
const words = [
|
|
81
|
+
row.pitch !== undefined
|
|
82
|
+
? say('figure.text.row-count-pitch', { part: row.part, count: String(row.count), pitch: String(row.pitch), axis: row.axis.toUpperCase() })
|
|
83
|
+
: say('figure.text.row-count-uneven', { part: row.part, count: String(row.count), axis: row.axis.toUpperCase() })
|
|
84
|
+
];
|
|
85
|
+
if (!row.margins)
|
|
86
|
+
words.push(say('figure.text.row-ends-unknown'));
|
|
87
|
+
else if (row.centred)
|
|
88
|
+
words.push(say('figure.text.row-ends-centred', { mm: String(row.margins.low) }));
|
|
89
|
+
else
|
|
90
|
+
words.push(say('figure.text.row-ends', { low: String(row.margins.low), high: String(row.margins.high) }));
|
|
91
|
+
if (row.companions.length)
|
|
92
|
+
words.push(say('figure.text.row-companions', { parts: row.companions.map(c => say('figure.text.preview-copies', { part: c.part, n: String(c.count) })).join(', ') }));
|
|
93
|
+
return words;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=command-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-preview.js","sourceRoot":"","sources":["../../client/modeller/command-preview.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FigureBodyMove, FigureClipLoss, FigureFaceArea, FigureRowReading } from '@hatiolab/figure-model'\n\nimport type { FigureRenderPart } from './renderer.js'\n\n/*\n * A command seen before it is done, as the modeller shows it (ADR-0097 §5.5, designer 2026-09-27): the bodies it would\n * move, translucent where they would stand; the face they would meet, lit; and in words, how far and how each turns --\n * the turn as figure-model gives it, one axis and one angle in the figure's frame.\n */\n\n/* The material tokens of what a command would do: the bodies where they would stand, and the face they would meet. */\nexport const PREVIEW = '__preview'\nexport const TARGET_FACE = '__target-face'\n\nexport type Say = (key: string, params?: Record<string, string>) => string\n\n/** A preview as the editor holds it: bound to the figure, version and command it was worked out for. */\nexport interface HeldPreview {\n context: string\n revision: number\n /** The command, as text: a preview for another command is not this one's. */\n key: string\n geometry?: unknown[]\n moves?: FigureBodyMove[]\n /** Bodies it would draw that are not drawn now: a row's copies. */\n added?: string[]\n /** The row it would make, read on the preview's figure. */\n row?: FigureRowReading\n clipsWouldGo?: FigureClipLoss[]\n face?: FigureFaceArea\n /** Whether a face was asked for and could not be shown. */\n faceUnknown?: boolean\n /** Why the command would be refused; nothing else is shown then. */\n refused?: string\n}\n\n/** The moving bodies where they would stand, drawn translucent. The ones that stay are not drawn twice. */\nexport function ghostPartsOf(preview: HeldPreview | undefined): FigureRenderPart[] {\n if (!preview?.geometry || !(preview.moves?.length || preview.added?.length)) return []\n const moving = new Set([...(preview.moves ?? []).map(m => m.body), ...(preview.added ?? [])])\n return (preview.geometry as FigureRenderPart[])\n .filter(body => body && typeof body === 'object' && 'shape' in body && 'pose' in body && moving.has(body.id))\n .map(body => ({ ...body, id: `${PREVIEW}:${body.id}`, material: { token: PREVIEW, transparent: true } }))\n}\n\n/** How thick the lit face is, and how far off the face it floats, so it neither hides nor flickers with it. */\nconst THICKNESS = 2\nconst LIFT = 1\n\n/** The face the part would meet, lit: a thin plate over a flat face, a thin disc over a round one. */\nexport function faceHighlightOf(face: FigureFaceArea | undefined): FigureRenderPart | null {\n if (!face) return null\n /* Its own x · y · z are the face's two sides and its normal, as the kernel gives them. */\n const r = [0, 1, 2].map(i => [face.u[i]!, face.normal[i]!, face.v[i]!])\n const t = [0, 1, 2].map(i => face.centre[i]! + face.normal[i]! * (LIFT + THICKNESS / 2))\n const shape: FigureRenderPart['shape'] = face.round\n ? { provider: 'cylinder', dimensions: { radius: face.size.u / 2, length: THICKNESS } }\n : { provider: 'box', dimensions: { width: face.size.u, height: THICKNESS, depth: face.size.v } }\n /* Solid: a translucent plate over a light face was hardly seen (2026-09-27, :3391); it covers the face it names. */\n return { id: TARGET_FACE, shape, pose: { r, t }, material: { token: TARGET_FACE } }\n}\n\nconst mm = (v: number) => `${v > 0 ? '+' : v < 0 ? '−' : ''}${Math.abs(Math.round(v * 10) / 10).toLocaleString()}`\n\n/**\n * What the command would do, in words: the part's own move and turn, what goes with it, the clips it would stop, and\n * why nothing can be shown when the command would be refused.\n */\nexport function previewWordsOf(preview: HeldPreview | undefined, part: string, say: Say): string[] {\n if (!preview) return []\n if (preview.refused) return [say('figure.text.preview-refused', { reason: preview.refused })]\n /* A row: what it would be, read as drawn; its part does not move, it is copied. */\n if (preview.row) return [...rowWordsOf(preview.row, say), ...(preview.clipsWouldGo?.length ? [say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') })] : [])]\n const words: string[] = []\n const own = preview.moves?.find(m => m.body === part)\n if (!own) words.push(say('figure.text.preview-stays'))\n else {\n const axes = (['x', 'y', 'z'] as const).map((axis, i) => [axis, own.translate[i]!] as const).filter(([, v]) => Math.abs(v) >= 0.05)\n if (axes.length) words.push(say('figure.text.preview-moves', { by: axes.map(([axis, v]) => `${axis.toUpperCase()} ${mm(v)}`).join(' · ') }))\n if (own.turn) {\n /* Signed by the right hand about the axis, as the figure's frame has it. */\n const degrees = mm(own.turn.degrees)\n words.push(\n typeof own.turn.axis === 'string'\n ? say('figure.text.preview-turns-named', { axis: own.turn.axis.toUpperCase(), degrees })\n : say('figure.text.preview-turns-slanted', { axis: own.turn.axis.map(v => v.toFixed(2)).join(', '), degrees })\n )\n }\n }\n /* What goes with it: parts fastened to it, each row once, with its count. */\n const others = new Map<string, number>()\n for (const m of preview.moves ?? []) if (m.part !== part) others.set(m.part, (others.get(m.part) ?? 0) + 1)\n if (others.size)\n words.push(say('figure.text.preview-carries', { parts: [...others].map(([name, n]) => (n > 1 ? say('figure.text.preview-copies', { part: name, n: String(n) }) : name)).join(', ') }))\n if (preview.clipsWouldGo?.length) words.push(say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') }))\n if (preview.faceUnknown) words.push(say('figure.text.preview-face-unknown'))\n return words\n}\n\n/** A row in words, as figure-model reads it from what is drawn: how many, how far apart, what is left at the ends, what rides it. */\nexport function rowWordsOf(row: FigureRowReading | undefined, say: Say): string[] {\n if (!row) return []\n const words = [\n row.pitch !== undefined\n ? say('figure.text.row-count-pitch', { part: row.part, count: String(row.count), pitch: String(row.pitch), axis: row.axis.toUpperCase() })\n : say('figure.text.row-count-uneven', { part: row.part, count: String(row.count), axis: row.axis.toUpperCase() })\n ]\n if (!row.margins) words.push(say('figure.text.row-ends-unknown'))\n else if (row.centred) words.push(say('figure.text.row-ends-centred', { mm: String(row.margins.low) }))\n else words.push(say('figure.text.row-ends', { low: String(row.margins.low), high: String(row.margins.high) }))\n if (row.companions.length) words.push(say('figure.text.row-companions', { parts: row.companions.map(c => say('figure.text.preview-copies', { part: c.part, n: String(c.count) })).join(', ') }))\n return words\n}\n"]}
|
|
@@ -18,6 +18,11 @@ import type { FirstSave } from './first-save.js';
|
|
|
18
18
|
export declare class FigureEditor extends LitElement {
|
|
19
19
|
static styles: import("lit").CSSResult[];
|
|
20
20
|
session?: FigureProductSession;
|
|
21
|
+
/** The saved figure's state and version, as the server gave them (the page reads the row). */
|
|
22
|
+
releaseState?: {
|
|
23
|
+
state?: 'draft' | 'released';
|
|
24
|
+
version: number;
|
|
25
|
+
};
|
|
21
26
|
/** The recipe templates (ADR-0095), read by the page from the server. */
|
|
22
27
|
templates: RecipeTemplate[];
|
|
23
28
|
/** Reads a template's guide; given by the page. */
|
|
@@ -62,9 +67,17 @@ export declare class FigureEditor extends LitElement {
|
|
|
62
67
|
* Parts hidden on the workbench — the author's view while working, not the asset's visibility. Not saved: a part
|
|
63
68
|
* hidden here is still in the figure, still checked, still drawn on a board.
|
|
64
69
|
*/
|
|
70
|
+
/** The page offers an AI draft for a new figure (the modeller page, through the app's assistant dock). */
|
|
71
|
+
aiDraft: boolean;
|
|
65
72
|
private hiddenParts;
|
|
66
73
|
/** A value in hand -- typed, held on a spinner, scrubbed, dragged -- drawn at once and recorded on release. */
|
|
67
74
|
private live;
|
|
75
|
+
/**
|
|
76
|
+
* A command seen before it is done (ADR-0097 §5.5): worked out on a copy for the figure, version, part and command it
|
|
77
|
+
* names, and shown only while all four are still the ones on screen. Nothing of it is saved or undone.
|
|
78
|
+
*/
|
|
79
|
+
private preview?;
|
|
80
|
+
private previewDrawn?;
|
|
68
81
|
private lastContext;
|
|
69
82
|
/**
|
|
70
83
|
* Move, turn and size on the canvas, in the bar's tools: the gizmo the W · E · R keys give, found without knowing the
|
|
@@ -76,6 +89,11 @@ export declare class FigureEditor extends LitElement {
|
|
|
76
89
|
private onKey;
|
|
77
90
|
connectedCallback(): void;
|
|
78
91
|
disconnectedCallback(): void;
|
|
92
|
+
private static readonly FOLD_START_UNDER;
|
|
93
|
+
private foldedForWidth;
|
|
94
|
+
private keptOpenByAuthor;
|
|
95
|
+
private widthWatch;
|
|
96
|
+
private fitPanesTo;
|
|
79
97
|
/**
|
|
80
98
|
* Refusals and notices go to the app's shared surfaces, as every screen's do (user, 2026-09-27: 「왜 모두가 사용하는
|
|
81
99
|
* notification 오류 표현 방법을 사용하지 않고」 · 「혹은 oxprompt 에서 확인하고 진행하던지」). A refusal with a way on --
|
|
@@ -84,7 +102,13 @@ export declare class FigureEditor extends LitElement {
|
|
|
84
102
|
*/
|
|
85
103
|
private toldViewError;
|
|
86
104
|
private tell;
|
|
105
|
+
private askClipLoss;
|
|
106
|
+
private confirmClipLoss;
|
|
87
107
|
private ask;
|
|
108
|
+
private panelMemory;
|
|
109
|
+
private restorePanelFor?;
|
|
110
|
+
private propertiesPanel;
|
|
111
|
+
private foldsOf;
|
|
88
112
|
protected willUpdate(_changed: PropertyValues): void;
|
|
89
113
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
90
114
|
private useGizmo;
|
|
@@ -131,12 +155,25 @@ export declare class FigureEditor extends LitElement {
|
|
|
131
155
|
private designValues;
|
|
132
156
|
/** What the gizmo may write for the selected part — read off the asset being edited. */
|
|
133
157
|
private reachOf;
|
|
158
|
+
/** Whether a size drag on this part could not show what is written: figure-model gives no face it keeps. */
|
|
159
|
+
private sizeDragUnsure;
|
|
134
160
|
/** A drag, written back as the part's own inputs — one step of the history, like typing the numbers. */
|
|
135
161
|
private transformed;
|
|
136
162
|
/** 맞추기 — one tool, one step of the history, through the same inputs as the gizmo. */
|
|
137
163
|
private align;
|
|
138
164
|
/** A mirrored copy: duplicate, then place the copy on the other side — two steps. */
|
|
139
165
|
private mirror;
|
|
166
|
+
/**
|
|
167
|
+
* The one-off "put it on the plane", by the figure's placement (designer 2026-09-27): figure-model's rule
|
|
168
|
+
* (`figurePlaneAlignChanges`) -- a floor figure's part to the floor by its bottom, a ceiling one's to the ceiling by
|
|
169
|
+
* its top, a wall one's to the wall by its back, a centred one's centre to the origin's height. Once: nothing is
|
|
170
|
+
* fastened. Refused, it says why.
|
|
171
|
+
*/
|
|
172
|
+
private keptExtremeWord;
|
|
173
|
+
private planeContact?;
|
|
174
|
+
private planeContactOf;
|
|
175
|
+
private renderPlaneAlign;
|
|
176
|
+
private alignToPlane;
|
|
140
177
|
private renderTools;
|
|
141
178
|
/**
|
|
142
179
|
* A part's name. It renames every value the part owns, and what refers to them follows
|
|
@@ -150,8 +187,21 @@ export declare class FigureEditor extends LitElement {
|
|
|
150
187
|
private renamePart;
|
|
151
188
|
/** Size response, fastening and motion for the selected part, as authoring commands. */
|
|
152
189
|
private renderLinks;
|
|
190
|
+
/**
|
|
191
|
+
* The face a size drag keeps for the selected part, figure-model's rule (`figureScaleAnchorOf`): the gizmo shows it
|
|
192
|
+
* while dragging and the commit keeps it, so nothing jumps on release. A part whose fastening is turned has none the
|
|
193
|
+
* rule can say, and the gizmo keeps its own (the face across from the handle).
|
|
194
|
+
*/
|
|
195
|
+
private anchorsOf;
|
|
196
|
+
private lastAnchors;
|
|
153
197
|
/** The figure's controls: try a value, name it, set its range and travel time, keep the pose. */
|
|
154
198
|
private renderControls;
|
|
199
|
+
/**
|
|
200
|
+
* "Follow the size and keep where the figure is placed" (ADR-0097): figure-model plans it from the figure as it is now,
|
|
201
|
+
* and the whole plan is one step of the history. A refusal with a way on -- move the part onto the plane, replace how
|
|
202
|
+
* its position is worked out -- asks in the app's prompt and plans again with that choice; any other says why.
|
|
203
|
+
*/
|
|
204
|
+
private resizeWithPlacement;
|
|
155
205
|
/** One command, one step of the history. A refusal a person can meet here is said in their words. */
|
|
156
206
|
private linkCommand;
|
|
157
207
|
private changeField;
|
|
@@ -184,6 +234,8 @@ export declare class FigureEditor extends LitElement {
|
|
|
184
234
|
* time); a row of a fixed count says its pitch cannot be set yet (figure-model refuses it the same way).
|
|
185
235
|
*/
|
|
186
236
|
private renderRepeat;
|
|
237
|
+
/** The selected row as it is drawn now, read by figure-model: nothing when it cannot be read. */
|
|
238
|
+
private rowNow;
|
|
187
239
|
/** The design input a row's pitch is read from, when it is read from one (ADR-0096). */
|
|
188
240
|
private pitchInputOf;
|
|
189
241
|
/**
|
|
@@ -198,15 +250,75 @@ export declare class FigureEditor extends LitElement {
|
|
|
198
250
|
*/
|
|
199
251
|
private setPitch;
|
|
200
252
|
/** Choose a part, from the list, the canvas or a place row. A figure-level tab gives way to the part's properties. */
|
|
253
|
+
private releaseWords;
|
|
254
|
+
/** To the release that exists: the history tab's release section, not a second one here. */
|
|
255
|
+
private goRelease;
|
|
256
|
+
/** The fastening form's command, seen before it is done; null clears what was shown. */
|
|
257
|
+
private previewCommand;
|
|
258
|
+
/** The preview, while it still speaks of what is on screen: this figure, this version, this part. */
|
|
259
|
+
private currentPreview;
|
|
260
|
+
private detachSaid?;
|
|
261
|
+
private detachWords;
|
|
262
|
+
private overhangSaid?;
|
|
263
|
+
/** Wider than its face: a fact of the relation, said in its details, not as a warning (designer 2026-09-27). */
|
|
264
|
+
private overhangNote;
|
|
265
|
+
private overhangWords;
|
|
266
|
+
/** The selected part's motion at both ends of its range, made once per figure, version and part. */
|
|
267
|
+
private motionEndsSaid?;
|
|
268
|
+
private motionEnds;
|
|
269
|
+
/** "Fasten it first", from the motion tab: to the properties tab, the fastening form open on this part. */
|
|
270
|
+
private goFasten;
|
|
271
|
+
/** What the canvas draws of it, made once per preview: a new list every render would stand the board up each time. */
|
|
272
|
+
private previewShown;
|
|
273
|
+
/**
|
|
274
|
+
* What the author has in view, for the AI dock to hear with a request (ADR-0097 §5.6 item 5): the part selected and
|
|
275
|
+
* what the preview is trying. Read at the moment of sending, never cached, and never a reason to start a new
|
|
276
|
+
* conversation -- selecting is not a new subject.
|
|
277
|
+
*/
|
|
278
|
+
assistantFocus(): {
|
|
279
|
+
part?: string;
|
|
280
|
+
trying?: {
|
|
281
|
+
clips?: Record<string, number>;
|
|
282
|
+
controls?: Record<string, number>;
|
|
283
|
+
};
|
|
284
|
+
};
|
|
201
285
|
private choose;
|
|
202
286
|
private asset;
|
|
287
|
+
/** A side tab says what it is in words under its icon: the icons alone had to be learnt (designer 2026-09-27, U05). */
|
|
203
288
|
private renderTab;
|
|
204
289
|
updated(changed: PropertyValues<this>): void;
|
|
205
|
-
/**
|
|
290
|
+
/**
|
|
291
|
+
* A new figure's first question, on the canvas where the work will be (designer 2026-09-27, U04): a basic shape, a
|
|
292
|
+
* recipe, or an AI draft when the page offers one. Nothing is added until one is chosen; the type, the space, the
|
|
293
|
+
* mounting face and the tags are not asked for first.
|
|
294
|
+
*/
|
|
295
|
+
private renderStart;
|
|
296
|
+
/**
|
|
297
|
+
* Where the panel is, held at its top: the model by its name -- the way back to its settings -- and the part chosen,
|
|
298
|
+
* with the part's three lines: its kind, how its size answers, what holds it (designer 2026-09-27, B and B.1). The
|
|
299
|
+
* model name is the same move as the parts list's root; there is no separate "whole figure" link.
|
|
300
|
+
*/
|
|
206
301
|
private renderSubject;
|
|
302
|
+
/**
|
|
303
|
+
* The model's settings: the same move from the parts list's root and from the panel's path (B.1). It shows them on
|
|
304
|
+
* the properties tab whatever tab was open; it changes no data, no camera, no history and no AI conversation.
|
|
305
|
+
*/
|
|
306
|
+
private showModelSettings;
|
|
207
307
|
render(): import("lit-html").TemplateResult<1>;
|
|
208
308
|
/** Position, rotation and size as rows of three cells; the shape's own measures one to a line, under their title. */
|
|
209
309
|
private renderGroups;
|
|
310
|
+
/**
|
|
311
|
+
* A box-like part's size row stands when a rule sets every side -- each following the figure's size -- with the
|
|
312
|
+
* numbers the rule gives, as its position and turn rows do (designer 2026-09-27: not a blank, not gone).
|
|
313
|
+
*/
|
|
314
|
+
private withSizeRow;
|
|
210
315
|
private renderVector;
|
|
316
|
+
/**
|
|
317
|
+
* A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
|
|
318
|
+
* typed into -- they move nothing. Declaring the room again is offered; nothing is deleted or widened on the way.
|
|
319
|
+
*/
|
|
320
|
+
private renderRoomOnly;
|
|
321
|
+
/** The room as declared and as proposed, and what lies outside each as measured, before the author confirms. */
|
|
322
|
+
private askRoomRefit;
|
|
211
323
|
private renderSingle;
|
|
212
324
|
}
|