@things-factory/figure-ui 10.1.169 → 10.1.171

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.
Files changed (105) hide show
  1. package/client/modeller/affected-parts.ts +11 -0
  2. package/client/modeller/canvas.ts +15 -1
  3. package/client/modeller/editor-styles.ts +98 -6
  4. package/client/modeller/editor.ts +212 -53
  5. package/client/modeller/fasten-geometry.ts +19 -0
  6. package/client/modeller/figure-fasten-drawing.ts +4 -3
  7. package/client/modeller/figure-links.ts +80 -37
  8. package/client/modeller/figure-material-panel.ts +20 -1
  9. package/client/modeller/figure-preview.ts +121 -8
  10. package/client/modeller/figure-properties.ts +1 -2
  11. package/client/modeller/figure-section-panel.ts +5 -0
  12. package/client/modeller/figure-verdict.ts +9 -2
  13. package/client/modeller/gate-findings.ts +12 -4
  14. package/client/modeller/group-menu.ts +57 -0
  15. package/client/modeller/links.ts +22 -1
  16. package/client/modeller/modeller-keys.ts +6 -0
  17. package/client/modeller/preview-size.ts +32 -0
  18. package/client/modeller/stage-model.ts +11 -0
  19. package/client/pages/figure-modeller-page.ts +8 -2
  20. package/dist-client/modeller/affected-parts.d.ts +6 -0
  21. package/dist-client/modeller/affected-parts.js +12 -0
  22. package/dist-client/modeller/affected-parts.js.map +1 -0
  23. package/dist-client/modeller/canvas.d.ts +3 -0
  24. package/dist-client/modeller/canvas.js +12 -1
  25. package/dist-client/modeller/canvas.js.map +1 -1
  26. package/dist-client/modeller/clips.d.ts +1 -1
  27. package/dist-client/modeller/editor-styles.js +98 -6
  28. package/dist-client/modeller/editor-styles.js.map +1 -1
  29. package/dist-client/modeller/editor.d.ts +42 -1
  30. package/dist-client/modeller/editor.js +220 -51
  31. package/dist-client/modeller/editor.js.map +1 -1
  32. package/dist-client/modeller/fasten-geometry.d.ts +27 -0
  33. package/dist-client/modeller/fasten-geometry.js +14 -0
  34. package/dist-client/modeller/fasten-geometry.js.map +1 -1
  35. package/dist-client/modeller/figure-fasten-drawing.js +4 -3
  36. package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
  37. package/dist-client/modeller/figure-links.d.ts +2 -0
  38. package/dist-client/modeller/figure-links.js +84 -38
  39. package/dist-client/modeller/figure-links.js.map +1 -1
  40. package/dist-client/modeller/figure-material-panel.d.ts +7 -0
  41. package/dist-client/modeller/figure-material-panel.js +24 -1
  42. package/dist-client/modeller/figure-material-panel.js.map +1 -1
  43. package/dist-client/modeller/figure-preview.d.ts +12 -0
  44. package/dist-client/modeller/figure-preview.js +120 -5
  45. package/dist-client/modeller/figure-preview.js.map +1 -1
  46. package/dist-client/modeller/figure-properties.js +1 -2
  47. package/dist-client/modeller/figure-properties.js.map +1 -1
  48. package/dist-client/modeller/figure-section-panel.js +5 -0
  49. package/dist-client/modeller/figure-section-panel.js.map +1 -1
  50. package/dist-client/modeller/figure-verdict.d.ts +1 -0
  51. package/dist-client/modeller/figure-verdict.js +8 -2
  52. package/dist-client/modeller/figure-verdict.js.map +1 -1
  53. package/dist-client/modeller/gate-findings.d.ts +3 -1
  54. package/dist-client/modeller/gate-findings.js +8 -4
  55. package/dist-client/modeller/gate-findings.js.map +1 -1
  56. package/dist-client/modeller/group-menu.d.ts +18 -0
  57. package/dist-client/modeller/group-menu.js +52 -0
  58. package/dist-client/modeller/group-menu.js.map +1 -0
  59. package/dist-client/modeller/links.d.ts +5 -0
  60. package/dist-client/modeller/links.js +21 -1
  61. package/dist-client/modeller/links.js.map +1 -1
  62. package/dist-client/modeller/modeller-keys.js +7 -0
  63. package/dist-client/modeller/modeller-keys.js.map +1 -1
  64. package/dist-client/modeller/preview-size.d.ts +23 -0
  65. package/dist-client/modeller/preview-size.js +32 -0
  66. package/dist-client/modeller/preview-size.js.map +1 -0
  67. package/dist-client/modeller/stage-model.d.ts +11 -0
  68. package/dist-client/modeller/stage-model.js +11 -0
  69. package/dist-client/modeller/stage-model.js.map +1 -1
  70. package/dist-client/pages/figure-modeller-page.d.ts +3 -0
  71. package/dist-client/pages/figure-modeller-page.js +11 -2
  72. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  73. package/dist-client/route.d.ts +1 -1
  74. package/dist-client/tsconfig.tsbuildinfo +1 -1
  75. package/package.json +8 -8
  76. package/test/browser/editing-page.js +3 -1
  77. package/test/browser/editing.check.mjs +2 -1
  78. package/test/browser/links-page.js +1 -0
  79. package/test/browser/links.check.mjs +20 -5
  80. package/test/browser/modeller.check.mjs +9 -1
  81. package/test/browser/more-open.js +11 -0
  82. package/test/browser/parts.check.mjs +11 -1
  83. package/test/browser/places-mesh.check.mjs +9 -3
  84. package/test/browser/product-page.check.mjs +9 -4
  85. package/test/browser/props.check.mjs +224 -0
  86. package/test/browser/screen-page.js +18 -0
  87. package/test/browser/screen.check.mjs +172 -29
  88. package/test/browser/section-page.js +1 -0
  89. package/test/browser/section.check.mjs +6 -0
  90. package/test/command-preview.test.ts +2 -2
  91. package/test/fasten-geometry.test.ts +15 -0
  92. package/test/fields.test.ts +4 -2
  93. package/test/first-minute.test.ts +15 -2
  94. package/test/layout.test.ts +1 -1
  95. package/test/preview-size.test.ts +33 -0
  96. package/test/refusals.test.ts +1 -1
  97. package/test/resize-with-placement.test.ts +1 -1
  98. package/test/stage-model.test.ts +11 -1
  99. package/test/verdict.test.ts +7 -0
  100. package/translations/en.json +37 -27
  101. package/translations/es.json +18 -8
  102. package/translations/ja.json +19 -9
  103. package/translations/ko.json +50 -40
  104. package/translations/ms.json +19 -9
  105. package/translations/zh.json +19 -9
@@ -0,0 +1,57 @@
1
+ import { css, html, nothing } from 'lit'
2
+ import '@material/web/icon/icon.js'
3
+ import { i18next } from '@operato/i18n'
4
+ import { OxPopupMenu } from '@operato/popup'
5
+ import '@operato/popup/ox-popup-menuitem.js'
6
+
7
+ /** One thing a group's 「⋯」 offers: done now and then, so kept off the group's face (chief architect 2026-10-16). */
8
+ export type GroupMenuItem = { label: string; icon: string; disabled?: boolean; run: () => void }
9
+
10
+ /**
11
+ * A group title's 「⋯」: the group's rarer actions (taking a fastening off, removing a row) in a menu, so the group
12
+ * shows its fields and one action, not a row of boxes (user 2026-10-16). The menu takes the arrow keys, Enter and Esc
13
+ * (`ox-popup-menu`) and gives the focus back to the button when it closes. Nothing to offer, no button.
14
+ */
15
+ export function groupMenu(items: GroupMenuItem[]) {
16
+ const offered = items.filter(item => !item.disabled)
17
+ if (!offered.length) return nothing
18
+ const open = (e: Event) => {
19
+ const button = e.currentTarget as HTMLElement
20
+ const at = button.getBoundingClientRect()
21
+ OxPopupMenu.open({
22
+ template: html`${offered.map(
23
+ item => html`<ox-popup-menuitem label=${item.label} @select=${() => item.run()}><md-icon slot="icon">${item.icon}</md-icon></ox-popup-menuitem>`
24
+ )}`,
25
+ top: at.bottom + 2,
26
+ right: Math.max(0, window.innerWidth - at.right)
27
+ })
28
+ }
29
+ return html`<button group-menu aria-haspopup="menu" aria-label=${i18next.t('figure.label.group-menu')} title=${i18next.t('figure.label.group-menu')} @click=${open}>
30
+ <md-icon>more_horiz</md-icon>
31
+ </button>`
32
+ }
33
+
34
+ /** The 「⋯」 as a quiet mark at the end of the title, not a box: it is there for the rare action, and looks it. */
35
+ export const GroupMenuStyles = css`
36
+ button[group-menu] {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ width: 24px;
41
+ height: 24px;
42
+ margin-left: auto;
43
+ padding: 0;
44
+ color: var(--md-sys-color-on-surface-variant);
45
+ background: none;
46
+ border: 0;
47
+ border-radius: 4px;
48
+ cursor: pointer;
49
+ }
50
+ button[group-menu]:hover,
51
+ button[group-menu]:focus-visible {
52
+ background: var(--md-sys-color-surface-container);
53
+ }
54
+ button[group-menu] md-icon {
55
+ --md-icon-size: 18px;
56
+ }
57
+ `
@@ -134,7 +134,9 @@ export function partSummaryOf(
134
134
  if (f.of === 'own') continue
135
135
  if (f.of === 'instance') {
136
136
  key = f.times === 1 ? 'figure.text.summary-size-follows' : 'figure.text.summary-size-share'
137
- params = { pct: pct(f.times), whole: say(`figure.label.measure-${f.axis}`) }
137
+ /* A measure following the figure's own size on its own axis says no measure word, so the axes that do so are one
138
+ sentence -- 「너비 · 높이 · 깊이는 도형 크기를 따라 바뀝니다」, not three (user 2026-10-16). */
139
+ params = f.times === 1 && f.axis === axis && !f.plus ? {} : { pct: pct(f.times), whole: say(`figure.label.measure-${f.axis}`) }
138
140
  if (f.plus) (key = key + '-plus'), (params.plus = String(f.plus))
139
141
  } else if (f.of === 'part') {
140
142
  const other = say(`figure.label.measure-${f.axis}`)
@@ -675,6 +677,25 @@ export function motionFormRefusalOf(m: MotionForm, follow: string): 'multiplier'
675
677
  }
676
678
 
677
679
  /** The select's value for what a dimension follows now. */
680
+ /**
681
+ * What a measure that is not its own follows, in the words of the choice that set it -- 「도형 X 크기에 비례」, 「bed의 X」 --
682
+ * where the row once said only 「다른 규칙에 연결됨」 (user 2026-10-16: which rule?).
683
+ */
684
+ export function followWordsOf(follow: Follow, say: (key: string, params?: Record<string, string>) => string): string {
685
+ switch (follow.of) {
686
+ case 'own':
687
+ return say('figure.label.follows-nothing')
688
+ case 'instance':
689
+ return say(follow.times === 1 ? 'figure.label.follows-instance' : 'figure.label.follows-instance-share', { axis: follow.axis.toUpperCase() })
690
+ case 'part':
691
+ return say('figure.label.follows-part-axis', { part: follow.part, axis: follow.axis.toUpperCase() })
692
+ case 'input':
693
+ return follow.input
694
+ case 'expression':
695
+ return say('figure.label.follows-expression')
696
+ }
697
+ }
698
+
678
699
  export function choiceOf(follow: Follow): string {
679
700
  switch (follow.of) {
680
701
  case 'own':
@@ -35,6 +35,12 @@ export class ModellerKeys implements ReactiveController {
35
35
  if (!this.of.taking()) return
36
36
  const canvas = this.of.scene()
37
37
  const path = e.composedPath()
38
+ /*
39
+ A key for something open over the work -- a group's 「⋯」 menu, the view or the history, a question -- is that
40
+ thing's: heard here first, Esc deselected the part and was kept from the menu, which then could not be closed by
41
+ the keyboard (2026-10-16).
42
+ */
43
+ if (path.some(node => node instanceof Element && (node.localName.startsWith('ox-popup') || node.getAttribute('role') === 'dialog'))) return
38
44
  const action = keyActionOf(
39
45
  {
40
46
  key: e.key,
@@ -0,0 +1,32 @@
1
+ import { figureRowOf, FIGURE_INSTANCE_SIZE_INPUTS, type FigureAsset } from '@hatiolab/figure-model'
2
+
3
+ type Say = (key: string, params?: Record<string, string>) => string
4
+
5
+ /**
6
+ * What the preview's one figure is at the size being tried, in a line the author reads beside it -- its size in
7
+ * millimetres and how many copies each row holds there (first 15 minutes, 2026-10-16: the author stretched the conveyor
8
+ * and could not tell that the rollers grew in number). The counts are figure-model's reading of what the figure draws at
9
+ * that size (`figureRowOf`), never worked out again here.
10
+ */
11
+ export function previewSizeWordsOf(asset: FigureAsset, size: { x: number; y: number; z: number }, say: Say): string {
12
+ const mm = (v: number) => Math.round(v).toLocaleString()
13
+ const words = [`${mm(size.x)} × ${mm(size.y)} × ${mm(size.z)} mm`]
14
+ for (const row of rowCountsAt(asset, size)) words.push(say('figure.text.preview-copies', { part: row.part, n: String(row.count) }))
15
+ return words.join(' · ')
16
+ }
17
+
18
+ /** Each row's count at the size given, in the order the parts are listed. */
19
+ export function rowCountsAt(asset: FigureAsset, size: { x: number; y: number; z: number }): Array<{ part: string; count: number }> {
20
+ const values = { [FIGURE_INSTANCE_SIZE_INPUTS.x]: size.x, [FIGURE_INSTANCE_SIZE_INPUTS.y]: size.y, [FIGURE_INSTANCE_SIZE_INPUTS.z]: size.z }
21
+ const rows: Array<{ part: string; count: number }> = []
22
+ for (const look of asset.appearance) {
23
+ let reading: ReturnType<typeof figureRowOf>
24
+ try {
25
+ reading = figureRowOf(asset, look.target, values)
26
+ } catch {
27
+ reading = undefined
28
+ }
29
+ if (reading) rows.push({ part: reading.part, count: reading.count })
30
+ }
31
+ return rows
32
+ }
@@ -191,6 +191,17 @@ export function boardFor(parts: readonly FigureRenderPart[], at: Board & { depth
191
191
  }
192
192
  }
193
193
 
194
+ /**
195
+ * Whether the figure's reach has changed so far since the view was last fitted that the camera should fit it again:
196
+ * more than twice, or less than half, on any axis (first 15 minutes, 2026-10-16: a 100 mm box typed to 3000 × 800 × 600
197
+ * put the camera inside it, and the author saw grey until pressing 「화면에 맞추기」). A drag that nudges a part does not
198
+ * move the camera.
199
+ */
200
+ export function needsRefit(fitted: (Board & { depth: number }) | undefined, now: Board & { depth: number }): boolean {
201
+ if (!fitted) return false
202
+ return (['width', 'height', 'depth'] as const).some(key => now[key] > fitted[key] * 2 || now[key] < fitted[key] / 2)
203
+ }
204
+
194
205
  /**
195
206
  * A body's placement as its fields would read it: its origin in mm and its turn in degrees (rigid@1's X·Y·Z order, as
196
207
  * `componentOf` reads it). For a field a rule sets -- a fastened part's height, a turn its joint fixes -- the screen shows
@@ -12,6 +12,7 @@ import { availableRecipes, recipeGuide } from '../modeller/recipe-source.js'
12
12
  import type { RecipeTemplate } from '../modeller/recipes.js'
13
13
  import { occupancyFromParts } from '../modeller/occupancy.js'
14
14
  import type { FigureAuthoringAction } from '@hatiolab/figure-model'
15
+ import type { FigureGateFinding } from '../types.js'
15
16
  import '../modeller/editor.js'
16
17
  import '@material/web/icon/icon.js'
17
18
  import '../modeller/figure-history-panel.js'
@@ -119,6 +120,8 @@ export class FigureModellerPage extends PageView {
119
120
  @state() private typeError = ''
120
121
  /** Why a command from the side did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
121
122
  @state() private commandError = ''
123
+ /** What the verdict says blocks release now, handed to the editor's bar. */
124
+ @state() private releaseBlockers: FigureGateFinding[] = []
122
125
  /** The verdict's count, handed to the editor's verdict tab. */
123
126
  @state() private instances?: number
124
127
  @state() private budget?: FigureBudgetLine
@@ -474,6 +477,8 @@ export class FigureModellerPage extends PageView {
474
477
  the rail matches the page name and marks "new figure" while an existing one is being edited.
475
478
  */
476
479
  menu: view.id ? 'figure-list' : undefined,
480
+ /* The canvas needs the width: the rail folds while the modeller is open and comes back as it was on leaving. */
481
+ rail: 'collapsed',
477
482
  actions: [],
478
483
  /*
479
484
  No heading line: the canvas is what this page is for, and the name is already in the properties' first section
@@ -487,7 +492,7 @@ export class FigureModellerPage extends PageView {
487
492
  if (this.error) return html`<p role="alert" class="error">${this.error}</p>`
488
493
  const view = this.session.read()
489
494
  /* A new figure's third way to start: the app's assistant dock, with a request put in its input for the author to finish and send (B, U04). */
490
- return html`<figure-editor .session=${this.session} .releaseState=${this.row} .templates=${this.templates} .guideOf=${this.guideOf} .thumbnailOf=${this.thumbnailOf} .firstSave=${this.askFirstSave} .revision=${this.revision} .instances=${this.instances} .budget=${this.budget} aiDraft @ask-ai-draft=${() => askAssistant({ draft: i18next.t('figure.text.ai-draft-request') })} @changed=${this.changed}>
495
+ return html`<figure-editor .session=${this.session} .releaseState=${this.row} .releaseBlockers=${this.releaseBlockers} .templates=${this.templates} .guideOf=${this.guideOf} .thumbnailOf=${this.thumbnailOf} .firstSave=${this.askFirstSave} .revision=${this.revision} .instances=${this.instances} .budget=${this.budget} aiDraft @ask-ai-draft=${() => askAssistant({ draft: i18next.t('figure.text.ai-draft-request') })} @changed=${this.changed}>
491
496
  ${this.commandError ? html`<p slot="notice" fit-error role="alert">${this.commandError}</p>` : ''}
492
497
  <figure-properties
493
498
  slot="figure"
@@ -499,7 +504,7 @@ export class FigureModellerPage extends PageView {
499
504
  @catalog=${this.onCatalog}
500
505
  >
501
506
  <label slot="type" type-field>
502
- <span>${i18next.t('figure.label.figure-type')}</span>
507
+ <span>${i18next.t('figure.label.figure-identifier')}</span>
503
508
  ${this.row.version > 0
504
509
  ? html`<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`
505
510
  : html`<input
@@ -520,6 +525,7 @@ export class FigureModellerPage extends PageView {
520
525
  this.budget = event.detail.budget
521
526
  }}
522
527
  @fit-base-to-parts=${this.fitToParts}
528
+ @release-verdict=${(event: CustomEvent<{ blocking: FigureGateFinding[] }>) => (this.releaseBlockers = event.detail.blocking)}
523
529
  ></figure-verdict>
524
530
  <figure-history-panel
525
531
  slot="history"
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The parts a release finding points at, by the author's names. The server's `at` lists the first word of each path it
3
+ * could not prove -- a part's name, or a section of the format such as 「occupancy」 -- and the screen showed it as it came
4
+ * (「occupancy · roller-row-1-roller」, user 2026-10-16). Only the figure's own part names are kept.
5
+ */
6
+ export declare function affectedPartsOf(at: string, parts: readonly string[]): string[];
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The parts a release finding points at, by the author's names. The server's `at` lists the first word of each path it
3
+ * could not prove -- a part's name, or a section of the format such as 「occupancy」 -- and the screen showed it as it came
4
+ * (「occupancy · roller-row-1-roller」, user 2026-10-16). Only the figure's own part names are kept.
5
+ */
6
+ export function affectedPartsOf(at, parts) {
7
+ return at
8
+ .split('·')
9
+ .map(word => word.trim())
10
+ .filter(word => parts.includes(word));
11
+ }
12
+ //# sourceMappingURL=affected-parts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"affected-parts.js","sourceRoot":"","sources":["../../client/modeller/affected-parts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAU,EAAE,KAAwB;IAClE,OAAO,EAAE;SACN,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SACxB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;AACzC,CAAC","sourcesContent":["/**\n * The parts a release finding points at, by the author's names. The server's `at` lists the first word of each path it\n * could not prove -- a part's name, or a section of the format such as 「occupancy」 -- and the screen showed it as it came\n * (「occupancy · roller-row-1-roller」, user 2026-10-16). Only the figure's own part names are kept.\n */\nexport function affectedPartsOf(at: string, parts: readonly string[]): string[] {\n return at\n .split('·')\n .map(word => word.trim())\n .filter(word => parts.includes(word))\n}\n"]}
@@ -34,6 +34,9 @@ export declare class FigureCanvas extends LitElement {
34
34
  private stage;
35
35
  private scene?;
36
36
  private board?;
37
+ /** The figure's reach when the view was last fitted to it. */
38
+ private fittedFor?;
39
+ private refitTimer?;
37
40
  private standingFor?;
38
41
  private watcher?;
39
42
  /** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */
@@ -3,11 +3,13 @@ import { LitElement, html, css, unsafeCSS } from 'lit';
3
3
  import { customElement, property, query } from 'lit/decorators.js';
4
4
  import { create, Model, niceGridStep } from '@hatiolab/things-scene';
5
5
  import { DEFAULT_VIEW, sceneLook, WORKBENCH } from '@things-factory/figure-viewer/dist-client/figure-view.js';
6
- import { boardFor, changedKeys, componentOf, gizmoResultOf } from './stage-model.js';
6
+ import { boardFor, changedKeys, componentOf, gizmoResultOf, needsRefit } from './stage-model.js';
7
7
  import { JOINT_FAN } from './joint-fan.js';
8
8
  import { SLOT_MARK } from './slot-layout-host.js';
9
9
  import { PREVIEW, TARGET_FACE } from './command-preview.js';
10
10
  import { ownerOf } from './fields.js';
11
+ /* How long the figure stands still before the camera judges whether to follow it. */
12
+ const REFIT_SETTLE_MS = 400;
11
13
  /* The fan's colour when the figure's palette names no accent. */
12
14
  const FAN_COLOUR = '#ff8a00';
13
15
  /* The 2D side of the board, which the author never sees on this page but the scene paints. */
@@ -101,6 +103,7 @@ let FigureCanvas = class FigureCanvas extends LitElement {
101
103
  }
102
104
  disconnectedCallback() {
103
105
  super.disconnectedCallback();
106
+ clearTimeout(this.refitTimer);
104
107
  this.watcher?.disconnect();
105
108
  this.watcher = undefined;
106
109
  this.teardown();
@@ -130,6 +133,7 @@ let FigureCanvas = class FigureCanvas extends LitElement {
130
133
  }
131
134
  /** The whole figure in view: the board's fit, then the 3D camera's own. */
132
135
  fit() {
136
+ this.fittedFor = boardFor(this.parts);
133
137
  this.scene?.fit?.('ratio');
134
138
  this.scene?.root?.fitCameraToModel?.();
135
139
  }
@@ -154,6 +158,13 @@ let FigureCanvas = class FigureCanvas extends LitElement {
154
158
  if (outgrown)
155
159
  this.grow(needed);
156
160
  this.sync();
161
+ /*
162
+ Grown or shrunk far past the view (a size typed in): the camera follows, once the figure has settled. A value is
163
+ drawn as it is typed -- 「3」, 「30」, 「300」 on the way to 3000 -- so the reach is judged after the last change.
164
+ */
165
+ clearTimeout(this.refitTimer);
166
+ if (this.parts.length)
167
+ this.refitTimer = setTimeout(() => needsRefit(this.fittedFor, boardFor(this.parts)) && this.fit(), REFIT_SETTLE_MS);
157
168
  return;
158
169
  }
159
170
  this.teardown();
@@ -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, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\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 '@things-factory/figure-viewer/dist-client/figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, type Board, type ScaleAnchor } from './stage-model.js'\nimport { JOINT_FAN } from './joint-fan.js'\nimport { SLOT_MARK } from './slot-layout-host.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 `\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 /** 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 /** The slots a slot layout unfolds to (ADR-0118), as boxes of what each allows: shown, never picked, never saved. */\n @property({ attribute: false }) marks: FigureRenderPart[] = []\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 /*\n * No line of its own over an empty stage: a new figure's start card says how to begin, and an empty stage otherwise\n * is one loading, or with every part hidden -- where 「도형을 추가해…」 flashed and said the wrong thing (2026-10-02).\n */\n return html`<div stage></div>`\n }\n\n firstUpdated() {\n this.standUp()\n /* Out of sight it has no size to draw at: it resizes when it is shown again, not to nothing. */\n this.watcher = new ResizeObserver(() => {\n if (this.checkVisibility?.() !== false) this.scene?.resize?.()\n })\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', 'marks'].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 /* Kept standing while the preview is in front, it is not drawn: no picture, as when there was no canvas. */\n if (this.checkVisibility?.() === false) return 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 slots = this.marks.map(part => componentOf(part, { ...this.palette, [SLOT_MARK]: this.palette['palette.secondary'] ?? accent }, this.board!))\n const shown = [...(fan ? [fan] : []), ...slots, ...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 /*\n A canvas taken off the screen disposes its scene, and the scene empties its selection as it goes. Told out,\n that emptied the editor's choice whenever the middle turned to the preview (U03): the listener lit set on this\n element hears it even after it left the page. A canvas not on the page has nothing to say about the choice.\n */\n if (!this.isConnected) return\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, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\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 '@things-factory/figure-viewer/dist-client/figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, needsRefit, type Board, type ScaleAnchor } from './stage-model.js'\nimport { JOINT_FAN } from './joint-fan.js'\nimport { SLOT_MARK } from './slot-layout-host.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/* How long the figure stands still before the camera judges whether to follow it. */\nconst REFIT_SETTLE_MS = 400\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 `\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 /** 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 /** The slots a slot layout unfolds to (ADR-0118), as boxes of what each allows: shown, never picked, never saved. */\n @property({ attribute: false }) marks: FigureRenderPart[] = []\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n private scene?: SceneHandle\n private board?: Board & { depth: number }\n /** The figure's reach when the view was last fitted to it. */\n private fittedFor?: Board & { depth: number }\n private refitTimer?: ReturnType<typeof setTimeout>\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 /*\n * No line of its own over an empty stage: a new figure's start card says how to begin, and an empty stage otherwise\n * is one loading, or with every part hidden -- where 「도형을 추가해…」 flashed and said the wrong thing (2026-10-02).\n */\n return html`<div stage></div>`\n }\n\n firstUpdated() {\n this.standUp()\n /* Out of sight it has no size to draw at: it resizes when it is shown again, not to nothing. */\n this.watcher = new ResizeObserver(() => {\n if (this.checkVisibility?.() !== false) this.scene?.resize?.()\n })\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', 'marks'].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 clearTimeout(this.refitTimer)\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 /* Kept standing while the preview is in front, it is not drawn: no picture, as when there was no canvas. */\n if (this.checkVisibility?.() === false) return 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.fittedFor = boardFor(this.parts)\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 /*\n Grown or shrunk far past the view (a size typed in): the camera follows, once the figure has settled. A value is\n drawn as it is typed -- 「3」, 「30」, 「300」 on the way to 3000 -- so the reach is judged after the last change.\n */\n clearTimeout(this.refitTimer)\n if (this.parts.length) this.refitTimer = setTimeout(() => needsRefit(this.fittedFor, boardFor(this.parts)) && this.fit(), REFIT_SETTLE_MS)\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 slots = this.marks.map(part => componentOf(part, { ...this.palette, [SLOT_MARK]: this.palette['palette.secondary'] ?? accent }, this.board!))\n const shown = [...(fan ? [fan] : []), ...slots, ...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 /*\n A canvas taken off the screen disposes its scene, and the scene empties its selection as it goes. Told out,\n that emptied the editor's choice whenever the middle turned to the preview (U03): the listener lit set on this\n element hears it even after it left the page. A canvas not on the page has nothing to say about the choice.\n */\n if (!this.isConnected) return\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"]}
@@ -65,5 +65,5 @@ export declare const trackEdit: (clip: Clip, track: ClipTrack, patch?: Partial<P
65
65
  clip: string;
66
66
  keys: ClipKey[];
67
67
  loop: "wrap" | "hold";
68
- interpolation: "linear" | "step";
68
+ interpolation: "step" | "linear";
69
69
  };
@@ -2,6 +2,7 @@ import { css } from 'lit';
2
2
  /** The modeller editor's own styles: the bar's tools, the panes, the start card, the side's tabs and panels. */
3
3
  export const EditorStyles = css `
4
4
  :host {
5
+ position: relative;
5
6
  display: flex;
6
7
  flex-direction: column;
7
8
  min-height: 0;
@@ -15,6 +16,7 @@ export const EditorStyles = css `
15
16
  fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */
16
17
  ox-canvas-bar button[mode] {
17
18
  height: 28px;
19
+ border: 1px solid transparent;
18
20
  padding: 0 10px;
19
21
  border-radius: 999em;
20
22
  border-color: transparent;
@@ -249,6 +251,63 @@ export const EditorStyles = css `
249
251
  [off] {
250
252
  display: none !important;
251
253
  }
254
+ /* The part's own numbers inside a group the links draw (size, place): one column, the panel's gap. */
255
+ div[group-fields] {
256
+ display: grid;
257
+ gap: 8px;
258
+ }
259
+ /* A folded group (capability, 「더 보기」): its title is the fold's handle, at the title's size. */
260
+ section[more] > details > summary,
261
+ section[capability] > details > summary {
262
+ padding: 0;
263
+ }
264
+ section[more] > details > summary > h3,
265
+ section[capability] > details > summary > h3 {
266
+ margin: 0;
267
+ }
268
+ div[more-body] {
269
+ display: grid;
270
+ gap: 16px;
271
+ margin-top: 12px;
272
+ }
273
+ /*
274
+ The view settings and the release history, opened from the bar over the right of the work: neither is about the
275
+ part chosen, so neither takes a tab beside the part's (chief architect 2026-10-16).
276
+ */
277
+ :host {
278
+ position: relative;
279
+ }
280
+ div[bar-overlay-panel] {
281
+ position: absolute;
282
+ top: var(--figure-bar-height, 44px);
283
+ right: 8px;
284
+ z-index: 5;
285
+ display: flex;
286
+ flex-direction: column;
287
+ width: min(360px, calc(100% - 16px));
288
+ max-height: calc(100% - var(--figure-bar-height, 44px) - 16px);
289
+ background: var(--md-sys-color-surface-container-lowest);
290
+ border: 1px solid var(--md-sys-color-outline-variant);
291
+ border-radius: 8px;
292
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);
293
+ }
294
+ div[overlay-head] {
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: space-between;
298
+ padding: 8px 8px 8px 12px;
299
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
300
+ }
301
+ div[overlay-head] h3 {
302
+ margin: 0;
303
+ }
304
+ div[overlay-head] button[quiet] {
305
+ margin-left: 0;
306
+ }
307
+ div[overlay-body] {
308
+ min-height: 0;
309
+ overflow: auto;
310
+ }
252
311
  /* Which part the panel speaks of. */
253
312
  div[subject] {
254
313
  position: sticky;
@@ -276,19 +335,52 @@ export const EditorStyles = css `
276
335
  div[subject-summary] small {
277
336
  color: var(--md-sys-color-on-surface-variant);
278
337
  }
338
+ /*
339
+ No rule here draws every button as a box: the side's buttons take the shared panel rules (SidePanelStyles), one
340
+ height with the fields beside them, and only the bar's publish keeps an outline (user 2026-10-16: 「버튼 박스도
341
+ 과하게 사용되어서」 -- a bare 「button」 rule had put a 32px box round every button the editor drew).
342
+ */
279
343
  button {
280
344
  font: inherit;
281
345
  box-sizing: border-box;
346
+ color: inherit;
347
+ min-width: 0;
348
+ cursor: pointer;
349
+ }
350
+ /* What blocks release, beside 「발행」: one red word button, and its list under the bar. */
351
+ ox-canvas-bar button[release-blocked] {
352
+ display: inline-flex;
353
+ align-items: center;
354
+ gap: 4px;
282
355
  height: 32px;
283
- border: 1px solid var(--md-sys-color-outline-variant);
356
+ padding: 0 10px;
357
+ border: none;
284
358
  border-radius: 6px;
359
+ color: var(--md-sys-color-on-error-container);
360
+ background: var(--md-sys-color-error-container);
361
+ }
362
+ ox-canvas-bar button[release-blocked] md-icon {
363
+ --md-icon-size: 18px;
364
+ }
365
+ div[release-blockers] {
366
+ position: absolute;
367
+ top: 46px;
368
+ right: 8px;
369
+ z-index: 6;
370
+ width: min(340px, calc(100% - 16px));
371
+ padding: 8px 10px 10px;
372
+ box-sizing: border-box;
285
373
  background: var(--md-sys-color-surface);
286
- color: inherit;
287
- padding: 0 8px;
288
- min-width: 0;
374
+ border: 1px solid var(--md-sys-color-outline-variant);
375
+ border-radius: 8px;
376
+ box-shadow: var(--md-sys-elevation-2, 0 2px 8px #0003);
289
377
  }
290
- button {
291
- cursor: pointer;
378
+ ox-canvas-bar button[publish] {
379
+ height: 32px;
380
+ padding: 0 8px;
381
+ border: 1px solid var(--md-sys-color-outline-variant);
382
+ border-radius: 6px;
383
+ background: var(--md-sys-color-surface);
292
384
  }
293
385
  button:disabled {
294
386
  opacity: 0.45;