@things-factory/figure-ui 10.1.181 → 10.1.185

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 (67) hide show
  1. package/client/modeller/editor-styles.ts +28 -9
  2. package/client/modeller/editor.ts +105 -12
  3. package/client/modeller/fasten-form.ts +207 -0
  4. package/client/modeller/fasten-geometry.ts +5 -1
  5. package/client/modeller/figure-facts.ts +4 -35
  6. package/client/modeller/figure-fasten-dialog.ts +457 -0
  7. package/client/modeller/figure-fasten-drawing.ts +32 -11
  8. package/client/modeller/figure-links.ts +56 -300
  9. package/client/modeller/figure-properties.ts +23 -70
  10. package/client/modeller/modeller-keys.ts +4 -4
  11. package/dist-client/modeller/clips.d.ts +2 -2
  12. package/dist-client/modeller/editor-styles.js +28 -9
  13. package/dist-client/modeller/editor-styles.js.map +1 -1
  14. package/dist-client/modeller/editor.d.ts +23 -2
  15. package/dist-client/modeller/editor.js +121 -12
  16. package/dist-client/modeller/editor.js.map +1 -1
  17. package/dist-client/modeller/fasten-form.d.ts +69 -0
  18. package/dist-client/modeller/fasten-form.js +131 -0
  19. package/dist-client/modeller/fasten-form.js.map +1 -0
  20. package/dist-client/modeller/fasten-geometry.d.ts +3 -1
  21. package/dist-client/modeller/fasten-geometry.js +6 -1
  22. package/dist-client/modeller/fasten-geometry.js.map +1 -1
  23. package/dist-client/modeller/figure-facts.d.ts +2 -13
  24. package/dist-client/modeller/figure-facts.js +2 -29
  25. package/dist-client/modeller/figure-facts.js.map +1 -1
  26. package/dist-client/modeller/figure-fasten-dialog.d.ts +69 -0
  27. package/dist-client/modeller/figure-fasten-dialog.js +433 -0
  28. package/dist-client/modeller/figure-fasten-dialog.js.map +1 -0
  29. package/dist-client/modeller/figure-fasten-drawing.d.ts +6 -3
  30. package/dist-client/modeller/figure-fasten-drawing.js +36 -10
  31. package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
  32. package/dist-client/modeller/figure-links.d.ts +14 -38
  33. package/dist-client/modeller/figure-links.js +58 -301
  34. package/dist-client/modeller/figure-links.js.map +1 -1
  35. package/dist-client/modeller/figure-properties.d.ts +12 -12
  36. package/dist-client/modeller/figure-properties.js +23 -72
  37. package/dist-client/modeller/figure-properties.js.map +1 -1
  38. package/dist-client/modeller/modeller-keys.js +4 -4
  39. package/dist-client/modeller/modeller-keys.js.map +1 -1
  40. package/dist-client/route.d.ts +1 -1
  41. package/dist-client/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +8 -8
  43. package/test/browser/links-page.js +4 -4
  44. package/test/browser/links.check.mjs +196 -46
  45. package/test/browser/modeller-page.js +23 -0
  46. package/test/browser/modeller.check.mjs +19 -20
  47. package/test/browser/namespace-page.js +2 -29
  48. package/test/browser/popup-stand-in.js +48 -0
  49. package/test/browser/product-page.check.mjs +60 -9
  50. package/test/browser/product-page.js +30 -33
  51. package/test/browser/property-section.check.mjs +3 -3
  52. package/test/browser/props.check.mjs +28 -7
  53. package/test/browser/screen-page.js +7 -2
  54. package/test/browser/screen.check.mjs +28 -23
  55. package/test/browser/section-page.js +13 -41
  56. package/test/browser/section.check.mjs +39 -30
  57. package/test/fasten-form.test.ts +77 -0
  58. package/test/fasten-geometry.test.ts +2 -0
  59. package/test/first-minute.test.ts +7 -4
  60. package/test/properties.test.ts +11 -19
  61. package/translations/en.json +5 -12
  62. package/translations/es.json +5 -12
  63. package/translations/ja.json +5 -12
  64. package/translations/ko.json +5 -12
  65. package/translations/ms.json +5 -12
  66. package/translations/zh.json +5 -12
  67. package/client/modeller/mounting-part.ts +0 -15
@@ -1,32 +1,29 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import '@material/web/icon/icon.js';
3
3
  import { css, html, LitElement, nothing } from 'lit';
4
- import { customElement, property, state } from 'lit/decorators.js';
4
+ import { customElement, property } from 'lit/decorators.js';
5
5
  import { live } from 'lit/directives/live.js';
6
6
  import { i18next, localize } from '@operato/i18n';
7
- import { FIGURE_FACES } from '@hatiolab/figure-model';
8
- import { occupancyFromParts } from './occupancy.js';
9
7
  import { tagsOf, figureFigureFacts } from './figure-facts.js';
10
- import { offeredMountingOf } from './mounting-part.js';
11
8
  import { figurePropertySections } from './property-sections.js';
12
9
  import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
13
10
  import { ScrollbarStyles } from '@operato/styles';
14
11
  const PLACEMENTS = ['floor', 'ceiling', 'center', 'wall'];
15
- const FACES = Object.keys(FIGURE_FACES);
16
12
  /**
17
13
  * 속성 — the modeller's figure properties.
18
14
  *
19
- * The tab opens with what the figure is: its name, the face it is placed by, the room it takes up, where the
20
- * catalogue files it.
15
+ * The tab opens with what the figure is: its name, where it stands, where the catalogue files it.
16
+ *
17
+ * Nothing here is set up before work can go on (user 2026-10-18, after a walk without it). The room the figure
18
+ * takes up is declared where release asks for it, from the parts -- the bar's blockers and the verdict offer it;
19
+ * whether the figure meets its plane is said by the release check from the parts, with no face to name. A mounting
20
+ * face named already (by the assistant, or before) is shown with the way to take it off, and nothing more.
21
21
  *
22
22
  * Two kinds of change leave this tab, and it does neither itself:
23
- * - `command` carries an authoring command (rename, declare the room, the mounting face). The page runs it
24
- * through the session, so it is one step of the same undo history as every other edit, and is saved with it.
23
+ * - `command` carries an authoring command (rename, where it stands, taking the mounting face off). The page runs
24
+ * it through the session, so it is one step of the same undo history as every other edit, and is saved with it.
25
25
  * - `catalog` carries a change to the row (category, tags). Those are not in the asset; the page writes them
26
26
  * to the saved row at once, so they need a saved figure.
27
- *
28
- * Not here yet, because has no place for them: editing the declared room by hand (declares it from the
29
- * parts, as graph expressions), and the detail level. Both wait on a ruling.
30
27
  */
31
28
  let FigureProperties = class FigureProperties extends localize(i18next)(LitElement) {
32
29
  constructor() {
@@ -45,16 +42,12 @@ let FigureProperties = class FigureProperties extends localize(i18next)(LitEleme
45
42
  gap: 6px;
46
43
  align-items: center;
47
44
  }
48
- div[row] > select {
45
+ /* What is named, then the way to take it off at the line's end, as wide as its words. */
46
+ div[row] > span {
49
47
  flex: 1;
50
48
  }
51
- /* Words as wide as themselves (side-panel-styles' action): stretched over the row, their label floated in the middle. */
52
- div[row] > button[act] {
49
+ div[row] > button[quiet] {
53
50
  flex: none;
54
- width: auto;
55
- }
56
- div[row] > span {
57
- flex: 1;
58
51
  }
59
52
  `]; }
60
53
  facts() {
@@ -96,11 +89,7 @@ let FigureProperties = class FigureProperties extends localize(i18next)(LitEleme
96
89
  return html `<section>
97
90
  <h3>${i18next.t('figure.label.group-figure')}</h3>
98
91
  <slot name="type"></slot>
99
- ${this.renderName(facts)} ${this.renderPlacement(facts)}
100
- </section>
101
- <section>
102
- <h3>${i18next.t('figure.label.group-standing')}</h3>
103
- ${this.renderSpace(facts)} ${this.renderMounting(facts)}
92
+ ${this.renderName(facts)} ${this.renderPlacement(facts)} ${this.renderMounting(facts)}
104
93
  </section>
105
94
  <section>
106
95
  <h3>${i18next.t('figure.label.group-catalog')}</h3>
@@ -153,49 +142,24 @@ let FigureProperties = class FigureProperties extends localize(i18next)(LitEleme
153
142
  </div>
154
143
  `;
155
144
  }
156
- renderSpace(facts) {
157
- return html `
158
- <div field>
159
- <label>${i18next.t('figure.label.declared-space')}</label>
160
- <div row>
161
- <span>${i18next.t(facts.declared ? 'figure.text.declared-space-declared' : 'figure.text.declared-space-none')}</span>
162
- <button act ?disabled=${!facts.parts.length} @click=${() => this.declare(facts.placement ?? 'floor')}>
163
- <md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}
164
- </button>
165
- </div>
166
- </div>
167
- `;
168
- }
145
+ /**
146
+ * A mounting face named already: which, and the way to take it off. A centred figure has no plane, so the gate does
147
+ * not check it there; the line says so rather than show a promise that is not kept.
148
+ */
169
149
  renderMounting(facts) {
170
150
  const mounting = facts.mounting;
171
- /* Chosen, or what is set, or where the figure meets its plane -- not the first part listed (walk 1). */
172
- const { part, face } = offeredMountingOf(facts, { part: this.pickedPart, face: this.pickedFace });
151
+ if (!mounting)
152
+ return nothing;
153
+ const said = `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`;
173
154
  return html `
174
- <div field>
155
+ <div field mounting-face>
175
156
  <label>${i18next.t('figure.label.mounting-face')}</label>
176
157
  <div row>
177
- <select ?disabled=${!facts.declared} @change=${(e) => (this.pickedPart = e.target.value)}>
178
- ${facts.parts.map(one => html `<option value=${one} ?selected=${one === part}>${one}</option>`)}
179
- </select>
180
- <select ?disabled=${!facts.declared} @change=${(e) => (this.pickedFace = e.target.value)}>
181
- ${FACES.map(one => html `<option value=${one} ?selected=${one === face}>${i18next.t('figure.label.face-' + one)}</option>`)}
182
- </select>
183
- </div>
184
- <div row>
185
- <button act ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part, face })}>
186
- <md-icon>check</md-icon>${i18next.t('figure.button.set-mounting-face')}
187
- </button>
188
- <button quiet ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>
158
+ <span>${facts.placement === 'center' ? `${said} · ${i18next.t('figure.text.mounting-face-unchecked-at-center')}` : said}</span>
159
+ <button quiet clear-mounting-face @click=${() => this.command({ kind: 'clear-mounting-face' })}>
189
160
  <md-icon>close</md-icon>${i18next.t('figure.button.clear-mounting-face')}
190
161
  </button>
191
162
  </div>
192
- <small>
193
- ${facts.declared
194
- ? mounting
195
- ? `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`
196
- : i18next.t('figure.label.mounting-face-none')
197
- : i18next.t('figure.text.mounting-face-needs-space')}
198
- </small>
199
163
  </div>
200
164
  `;
201
165
  }
@@ -255,11 +219,6 @@ let FigureProperties = class FigureProperties extends localize(i18next)(LitEleme
255
219
  </div>
256
220
  `;
257
221
  }
258
- declare(placement) {
259
- if (!this.revision)
260
- return;
261
- this.command(occupancyFromParts(this.revision.asset, placement));
262
- }
263
222
  command(action) {
264
223
  this.dispatchEvent(new CustomEvent('command', { detail: { action }, bubbles: true, composed: true }));
265
224
  }
@@ -287,14 +246,6 @@ __decorate([
287
246
  property({ type: Array }),
288
247
  __metadata("design:type", Array)
289
248
  ], FigureProperties.prototype, "figureTags", void 0);
290
- __decorate([
291
- state(),
292
- __metadata("design:type", String)
293
- ], FigureProperties.prototype, "pickedPart", void 0);
294
- __decorate([
295
- state(),
296
- __metadata("design:type", String)
297
- ], FigureProperties.prototype, "pickedFace", void 0);
298
249
  FigureProperties = __decorate([
299
250
  customElement('figure-properties')
300
251
  ], FigureProperties);
@@ -1 +1 @@
1
- {"version":3,"file":"figure-properties.js","sourceRoot":"","sources":["../../client/modeller/figure-properties.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { FIGURE_FACES } from '@hatiolab/figure-model'\nimport type { FigureAuthoringAction, FigureFace } from '@hatiolab/figure-model'\n\nimport { occupancyFromParts } from './occupancy.js'\nimport { tagsOf, figureFigureFacts, type Placement, type FigureFigureFacts } from './figure-facts.js'\nimport { offeredMountingOf } from './mounting-part.js'\nimport type { FigureRevision } from './revision.js'\nimport { figurePropertySections } from './property-sections.js'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\nimport { ScrollbarStyles } from '@operato/styles'\n\nconst PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center', 'wall']\nconst FACES = Object.keys(FIGURE_FACES) as FigureFace[]\n\n/**\n * 속성 — the modeller's figure properties.\n *\n * The tab opens with what the figure is: its name, the face it is placed by, the room it takes up, where the\n * catalogue files it.\n *\n * Two kinds of change leave this tab, and it does neither itself:\n * - `command` carries an authoring command (rename, declare the room, the mounting face). The page runs it\n * through the session, so it is one step of the same undo history as every other edit, and is saved with it.\n * - `catalog` carries a change to the row (category, tags). Those are not in the asset; the page writes them\n * to the saved row at once, so they need a saved figure.\n *\n * Not here yet, because has no place for them: editing the declared room by hand (declares it from the\n * parts, as graph expressions), and the detail level. Both wait on a ruling.\n */\n@customElement('figure-properties')\nexport class FigureProperties extends localize(i18next)(LitElement) {\n static styles = [ScrollbarStyles, SidePanelStyles, css`\n :host {\n display: block;\n /* It stands inside the properties tab's panel, which gives the margin; a second one indented it past the type. */\n padding: 0;\n overflow: auto;\n }\n div[row] {\n display: flex;\n gap: 6px;\n align-items: center;\n }\n div[row] > select {\n flex: 1;\n }\n /* Words as wide as themselves (side-panel-styles' action): stretched over the row, their label floated in the middle. */\n div[row] > button[act] {\n flex: none;\n width: auto;\n }\n div[row] > span {\n flex: 1;\n }\n `]\n\n /** The figure at the version on screen. */\n @property({ attribute: false }) revision?: FigureRevision\n /** The saved row, if any. Category and tags live on it. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureCategory?: string\n /** The categories the server allows (LV figure-server design G5): chosen from, and fixed once set. Null: free text. */\n @property({ attribute: false }) figureCategories?: string[] | null\n @property({ type: Array }) figureTags?: string[]\n /** The part and face picked for the mounting face, before 「이 면으로」 is pressed. */\n @state() private pickedPart?: string\n @state() private pickedFace?: FigureFace\n\n private facts(): FigureFigureFacts | undefined {\n try {\n return this.revision ? figureFigureFacts(this.revision) : undefined\n } catch {\n /* A source the format refuses has no facts to show; the verdict tab says why. */\n return undefined\n }\n }\n\n /*\n The figure's own settings are shown while no part is picked; with one picked this is out of sight, and working out its\n facts (the figure parsed and compiled) on every edit is for nobody. Out of sight it keeps what it last drew, and it\n draws again when it comes back into view.\n */\n private lastDrawn?: unknown\n private outOfSight = false\n private watch?: ResizeObserver\n connectedCallback() {\n super.connectedCallback()\n this.watch = new ResizeObserver(() => {\n if (this.outOfSight && this.checkVisibility?.() !== false) {\n this.outOfSight = false\n this.requestUpdate()\n }\n })\n this.watch.observe(this)\n }\n disconnectedCallback() {\n this.watch?.disconnect()\n super.disconnectedCallback()\n }\n\n render() {\n if (this.hasUpdated && this.checkVisibility?.() === false) {\n this.outOfSight = true\n return this.lastDrawn ?? nothing\n }\n this.lastDrawn = this.draw()\n return this.lastDrawn\n }\n\n private draw() {\n const facts = this.facts()\n if (!facts) return nothing\n /* The figure's type is the page's (it lives on the row); it is slotted into the first section. */\n return html`<section>\n <h3>${i18next.t('figure.label.group-figure')}</h3>\n <slot name=\"type\"></slot>\n ${this.renderName(facts)} ${this.renderPlacement(facts)}\n </section>\n <section>\n <h3>${i18next.t('figure.label.group-standing')}</h3>\n ${this.renderSpace(facts)} ${this.renderMounting(facts)}\n </section>\n <section>\n <h3>${i18next.t('figure.label.group-catalog')}</h3>\n ${this.renderCatalog()}\n </section>\n ${this.renderAppSections()}`\n }\n\n private renderName(facts: FigureFigureFacts) {\n return html`\n <div field>\n <label>${i18next.t('figure.label.name')}</label>\n <input\n .value=${facts.name}\n placeholder=${i18next.t('figure.text.figure-display-name')}\n @change=${(event: Event) => {\n const input = event.target as HTMLInputElement\n if (!input.value.trim()) {\n /* The format refuses a blank name; put the old one back rather than send a command that fails. */\n input.value = facts.name\n return\n }\n if (input.value.trim() !== facts.name) this.command({ kind: 'rename', name: input.value })\n }}\n />\n </div>\n `\n }\n\n /**\n * The face the figure is placed by. In it is part of the room declaration, so choosing one declares the room\n * again from the parts — the only way declares it — with the new face. The hint says so.\n */\n private renderPlacement(facts: FigureFigureFacts) {\n const current = facts.placement ?? 'floor'\n return html`\n <div field>\n <label>${i18next.t('figure.label.figure-placement')}</label>\n <select\n .value=${live(current)}\n @change=${(event: Event) => {\n const select = event.target as HTMLSelectElement\n /* The figure's own setting (ADR-0097): chosen with no part too, and the room is left as it is declared. */\n this.command({ kind: 'set-placement', placement: select.value as Placement })\n /* Refused, the figure is placed as it was, and the list says so; taken, the figure drawn again says the new one. */\n select.value = current\n }}\n >\n ${PLACEMENTS.map(\n one => html`<option value=${one} ?selected=${one === current}>${i18next.t('figure.label.placement-' + one)}</option>`\n )}\n </select>\n </div>\n `\n }\n\n private renderSpace(facts: FigureFigureFacts) {\n return html`\n <div field>\n <label>${i18next.t('figure.label.declared-space')}</label>\n <div row>\n <span>${i18next.t(facts.declared ? 'figure.text.declared-space-declared' : 'figure.text.declared-space-none')}</span>\n <button act ?disabled=${!facts.parts.length} @click=${() => this.declare(facts.placement ?? 'floor')}>\n <md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}\n </button>\n </div>\n </div>\n `\n }\n\n private renderMounting(facts: FigureFigureFacts) {\n const mounting = facts.mounting\n /* Chosen, or what is set, or where the figure meets its plane -- not the first part listed (walk 1). */\n const { part, face } = offeredMountingOf(facts, { part: this.pickedPart, face: this.pickedFace })\n return html`\n <div field>\n <label>${i18next.t('figure.label.mounting-face')}</label>\n <div row>\n <select ?disabled=${!facts.declared} @change=${(e: Event) => (this.pickedPart = (e.target as HTMLSelectElement).value)}>\n ${facts.parts.map(one => html`<option value=${one} ?selected=${one === part}>${one}</option>`)}\n </select>\n <select ?disabled=${!facts.declared} @change=${(e: Event) => (this.pickedFace = (e.target as HTMLSelectElement).value as FigureFace)}>\n ${FACES.map(one => html`<option value=${one} ?selected=${one === face}>${i18next.t('figure.label.face-' + one)}</option>`)}\n </select>\n </div>\n <div row>\n <button act ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part!, face })}>\n <md-icon>check</md-icon>${i18next.t('figure.button.set-mounting-face')}\n </button>\n <button quiet ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>\n <md-icon>close</md-icon>${i18next.t('figure.button.clear-mounting-face')}\n </button>\n </div>\n <small>\n ${facts.declared\n ? mounting\n ? `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`\n : i18next.t('figure.label.mounting-face-none')\n : i18next.t('figure.text.mounting-face-needs-space')}\n </small>\n </div>\n `\n }\n\n /** The sections an app added (G11), after the panel's own, for the figures they are for. A section that throws says so in its place. */\n private renderAppSections() {\n const asset = this.revision?.asset\n if (!asset) return nothing\n const figure = { id: this.figureId, category: this.figureCategory }\n return figurePropertySections().map(section => {\n try {\n if (!section.when({ asset, figure })) return nothing\n return html`<section data-section=${section.id}>\n <h3>${section.title()}</h3>\n ${section.render({ asset, figure, command: action => this.command(action) })}\n </section>`\n } catch (error) {\n console.error(`[figure-ui] the property section '${section.id}' failed`, error)\n return html`<section data-section=${section.id}>\n <p role=\"alert\">${i18next.t('figure.text.property-section-failed', { section: section.id, reason: (error as Error)?.message ?? String(error) })}</p>\n </section>`\n }\n })\n }\n\n private renderCatalog() {\n const saved = !!this.figureId\n return html`\n <div field>\n <label>${i18next.t('figure.label.category')}</label>\n ${this.figureCategories?.length\n ? html`<select\n category\n ?disabled=${!saved || !!this.figureCategory}\n @change=${(e: Event) => this.catalog({ category: (e.target as HTMLSelectElement).value })}\n >\n ${this.figureCategory ? nothing : html`<option value=\"\" selected disabled>${i18next.t('figure.text.category-choose')}</option>`}\n ${this.figureCategories.map(code => html`<option value=${code} ?selected=${code === this.figureCategory}>${code}</option>`)}\n </select>`\n : html`<input\n .value=${this.figureCategory ?? ''}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.category-placeholder')}\n @change=${(e: Event) => this.catalog({ category: (e.target as HTMLInputElement).value.trim() })}\n />`}\n </div>\n <div field>\n <label>${i18next.t('figure.label.tags')}</label>\n <input\n .value=${(this.figureTags ?? []).join(', ')}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.tags-placeholder')}\n @change=${(e: Event) => this.catalog({ tags: tagsOf((e.target as HTMLInputElement).value) })}\n />\n ${saved ? nothing : html`<small>${i18next.t('figure.text.catalog-needs-a-save')}</small>`}\n </div>\n `\n }\n\n private declare(placement: Placement) {\n if (!this.revision) return\n this.command(occupancyFromParts(this.revision.asset, placement))\n }\n\n private command(action: FigureAuthoringAction) {\n this.dispatchEvent(new CustomEvent('command', { detail: { action }, bubbles: true, composed: true }))\n }\n\n private catalog(patch: { category?: string; tags?: string[] }) {\n this.dispatchEvent(new CustomEvent('catalog', { detail: patch, bubbles: true, composed: true }))\n }\n}\n"]}
1
+ {"version":3,"file":"figure-properties.js","sourceRoot":"","sources":["../../client/modeller/figure-properties.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureAuthoringAction } from '@hatiolab/figure-model'\n\nimport { tagsOf, figureFigureFacts, type Placement, type FigureFigureFacts } from './figure-facts.js'\nimport type { FigureRevision } from './revision.js'\nimport { figurePropertySections } from './property-sections.js'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\nimport { ScrollbarStyles } from '@operato/styles'\n\nconst PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center', 'wall']\n\n/**\n * 속성 — the modeller's figure properties.\n *\n * The tab opens with what the figure is: its name, where it stands, where the catalogue files it.\n *\n * Nothing here is set up before work can go on (user 2026-10-18, after a walk without it). The room the figure\n * takes up is declared where release asks for it, from the parts -- the bar's blockers and the verdict offer it;\n * whether the figure meets its plane is said by the release check from the parts, with no face to name. A mounting\n * face named already (by the assistant, or before) is shown with the way to take it off, and nothing more.\n *\n * Two kinds of change leave this tab, and it does neither itself:\n * - `command` carries an authoring command (rename, where it stands, taking the mounting face off). The page runs\n * it through the session, so it is one step of the same undo history as every other edit, and is saved with it.\n * - `catalog` carries a change to the row (category, tags). Those are not in the asset; the page writes them\n * to the saved row at once, so they need a saved figure.\n */\n@customElement('figure-properties')\nexport class FigureProperties extends localize(i18next)(LitElement) {\n static styles = [ScrollbarStyles, SidePanelStyles, css`\n :host {\n display: block;\n /* It stands inside the properties tab's panel, which gives the margin; a second one indented it past the type. */\n padding: 0;\n overflow: auto;\n }\n div[row] {\n display: flex;\n gap: 6px;\n align-items: center;\n }\n /* What is named, then the way to take it off at the line's end, as wide as its words. */\n div[row] > span {\n flex: 1;\n }\n div[row] > button[quiet] {\n flex: none;\n }\n `]\n\n /** The figure at the version on screen. */\n @property({ attribute: false }) revision?: FigureRevision\n /** The saved row, if any. Category and tags live on it. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureCategory?: string\n /** The categories the server allows (LV figure-server design G5): chosen from, and fixed once set. Null: free text. */\n @property({ attribute: false }) figureCategories?: string[] | null\n @property({ type: Array }) figureTags?: string[]\n private facts(): FigureFigureFacts | undefined {\n try {\n return this.revision ? figureFigureFacts(this.revision) : undefined\n } catch {\n /* A source the format refuses has no facts to show; the verdict tab says why. */\n return undefined\n }\n }\n\n /*\n The figure's own settings are shown while no part is picked; with one picked this is out of sight, and working out its\n facts (the figure parsed and compiled) on every edit is for nobody. Out of sight it keeps what it last drew, and it\n draws again when it comes back into view.\n */\n private lastDrawn?: unknown\n private outOfSight = false\n private watch?: ResizeObserver\n connectedCallback() {\n super.connectedCallback()\n this.watch = new ResizeObserver(() => {\n if (this.outOfSight && this.checkVisibility?.() !== false) {\n this.outOfSight = false\n this.requestUpdate()\n }\n })\n this.watch.observe(this)\n }\n disconnectedCallback() {\n this.watch?.disconnect()\n super.disconnectedCallback()\n }\n\n render() {\n if (this.hasUpdated && this.checkVisibility?.() === false) {\n this.outOfSight = true\n return this.lastDrawn ?? nothing\n }\n this.lastDrawn = this.draw()\n return this.lastDrawn\n }\n\n private draw() {\n const facts = this.facts()\n if (!facts) return nothing\n /* The figure's type is the page's (it lives on the row); it is slotted into the first section. */\n return html`<section>\n <h3>${i18next.t('figure.label.group-figure')}</h3>\n <slot name=\"type\"></slot>\n ${this.renderName(facts)} ${this.renderPlacement(facts)} ${this.renderMounting(facts)}\n </section>\n <section>\n <h3>${i18next.t('figure.label.group-catalog')}</h3>\n ${this.renderCatalog()}\n </section>\n ${this.renderAppSections()}`\n }\n\n private renderName(facts: FigureFigureFacts) {\n return html`\n <div field>\n <label>${i18next.t('figure.label.name')}</label>\n <input\n .value=${facts.name}\n placeholder=${i18next.t('figure.text.figure-display-name')}\n @change=${(event: Event) => {\n const input = event.target as HTMLInputElement\n if (!input.value.trim()) {\n /* The format refuses a blank name; put the old one back rather than send a command that fails. */\n input.value = facts.name\n return\n }\n if (input.value.trim() !== facts.name) this.command({ kind: 'rename', name: input.value })\n }}\n />\n </div>\n `\n }\n\n /**\n * The face the figure is placed by. In it is part of the room declaration, so choosing one declares the room\n * again from the parts — the only way declares it — with the new face. The hint says so.\n */\n private renderPlacement(facts: FigureFigureFacts) {\n const current = facts.placement ?? 'floor'\n return html`\n <div field>\n <label>${i18next.t('figure.label.figure-placement')}</label>\n <select\n .value=${live(current)}\n @change=${(event: Event) => {\n const select = event.target as HTMLSelectElement\n /* The figure's own setting (ADR-0097): chosen with no part too, and the room is left as it is declared. */\n this.command({ kind: 'set-placement', placement: select.value as Placement })\n /* Refused, the figure is placed as it was, and the list says so; taken, the figure drawn again says the new one. */\n select.value = current\n }}\n >\n ${PLACEMENTS.map(\n one => html`<option value=${one} ?selected=${one === current}>${i18next.t('figure.label.placement-' + one)}</option>`\n )}\n </select>\n </div>\n `\n }\n\n /**\n * A mounting face named already: which, and the way to take it off. A centred figure has no plane, so the gate does\n * not check it there; the line says so rather than show a promise that is not kept.\n */\n private renderMounting(facts: FigureFigureFacts) {\n const mounting = facts.mounting\n if (!mounting) return nothing\n const said = `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`\n return html`\n <div field mounting-face>\n <label>${i18next.t('figure.label.mounting-face')}</label>\n <div row>\n <span>${facts.placement === 'center' ? `${said} · ${i18next.t('figure.text.mounting-face-unchecked-at-center')}` : said}</span>\n <button quiet clear-mounting-face @click=${() => this.command({ kind: 'clear-mounting-face' })}>\n <md-icon>close</md-icon>${i18next.t('figure.button.clear-mounting-face')}\n </button>\n </div>\n </div>\n `\n }\n\n /** The sections an app added (G11), after the panel's own, for the figures they are for. A section that throws says so in its place. */\n private renderAppSections() {\n const asset = this.revision?.asset\n if (!asset) return nothing\n const figure = { id: this.figureId, category: this.figureCategory }\n return figurePropertySections().map(section => {\n try {\n if (!section.when({ asset, figure })) return nothing\n return html`<section data-section=${section.id}>\n <h3>${section.title()}</h3>\n ${section.render({ asset, figure, command: action => this.command(action) })}\n </section>`\n } catch (error) {\n console.error(`[figure-ui] the property section '${section.id}' failed`, error)\n return html`<section data-section=${section.id}>\n <p role=\"alert\">${i18next.t('figure.text.property-section-failed', { section: section.id, reason: (error as Error)?.message ?? String(error) })}</p>\n </section>`\n }\n })\n }\n\n private renderCatalog() {\n const saved = !!this.figureId\n return html`\n <div field>\n <label>${i18next.t('figure.label.category')}</label>\n ${this.figureCategories?.length\n ? html`<select\n category\n ?disabled=${!saved || !!this.figureCategory}\n @change=${(e: Event) => this.catalog({ category: (e.target as HTMLSelectElement).value })}\n >\n ${this.figureCategory ? nothing : html`<option value=\"\" selected disabled>${i18next.t('figure.text.category-choose')}</option>`}\n ${this.figureCategories.map(code => html`<option value=${code} ?selected=${code === this.figureCategory}>${code}</option>`)}\n </select>`\n : html`<input\n .value=${this.figureCategory ?? ''}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.category-placeholder')}\n @change=${(e: Event) => this.catalog({ category: (e.target as HTMLInputElement).value.trim() })}\n />`}\n </div>\n <div field>\n <label>${i18next.t('figure.label.tags')}</label>\n <input\n .value=${(this.figureTags ?? []).join(', ')}\n ?disabled=${!saved}\n placeholder=${i18next.t('figure.text.tags-placeholder')}\n @change=${(e: Event) => this.catalog({ tags: tagsOf((e.target as HTMLInputElement).value) })}\n />\n ${saved ? nothing : html`<small>${i18next.t('figure.text.catalog-needs-a-save')}</small>`}\n </div>\n `\n }\n\n private command(action: FigureAuthoringAction) {\n this.dispatchEvent(new CustomEvent('command', { detail: { action }, bubbles: true, composed: true }))\n }\n\n private catalog(patch: { category?: string; tags?: string[] }) {\n this.dispatchEvent(new CustomEvent('catalog', { detail: patch, bubbles: true, composed: true }))\n }\n}\n"]}
@@ -12,11 +12,11 @@ export class ModellerKeys {
12
12
  const canvas = this.of.scene();
13
13
  const path = e.composedPath();
14
14
  /*
15
- A key for something open over the work -- a group's 「⋯」 menu, the view or the history, a question -- is that
16
- thing's: heard here first, Esc deselected the part and was kept from the menu, which then could not be closed by
17
- the keyboard (2026-10-16).
15
+ A key for something open over the work -- a group's 「⋯」 menu, the view or the history, a question, a popup -- is
16
+ that thing's: heard here first, Esc deselected the part and was kept from the menu, which then could not be closed
17
+ by the keyboard (2026-10-16).
18
18
  */
19
- if (path.some(node => node instanceof Element && (node.localName.startsWith('ox-popup') || node.getAttribute('role') === 'dialog')))
19
+ if (path.some(node => node instanceof Element && (node.localName.startsWith('ox-popup') || node.localName === 'ox-floating-overlay' || node.getAttribute('role') === 'dialog')))
20
20
  return;
21
21
  const action = keyActionOf({
22
22
  key: e.key,
@@ -1 +1 @@
1
- {"version":3,"file":"modeller-keys.js","sourceRoot":"","sources":["../../client/modeller/modeller-keys.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAkB,MAAM,WAAW,CAAA;AAYvD;;;GAGG;AACH,MAAM,OAAO,YAAY;IACvB,YACE,IAA4B,EACX,EAAoB;QAApB,OAAE,GAAF,EAAE,CAAkB;QAa/B,UAAK,GAAG,CAAC,CAAgB,EAAE,EAAE;YACnC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;gBAAE,OAAM;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;YAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,CAAA;YAC7B;;;;cAIE;YACF,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,YAAY,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC;gBAAE,OAAM;YAC3I,MAAM,MAAM,GAAG,WAAW,CACxB;gBACE,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE,IAAI,CAAC,CAAC,CAAgB;gBAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;aAC3C,EACD,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CACnB,CAAA;YACD,IAAI,CAAC,MAAM;gBAAE,OAAM;YACnB,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB;;;;cAIE;YACF,CAAC,CAAC,eAAe,EAAE,CAAA;YACnB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC,CAAA;QA1CC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED,aAAa;QACX,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACtD,CAAC;IAED,gBAAgB;QACd,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzD,CAAC;CAkCF","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit'\nimport { keyActionOf, type KeyAction } from './keys.js'\n\n/** What the keys ask of the editor. */\nexport interface ModellerKeysHost {\n /** Whether the modeller takes keys now: a figure open, not loading or saving, and no section editor open. */\n taking(): boolean\n /** The scene, whose keys the modeller's are too. */\n scene(): Element | null\n selected(): string\n act(action: KeyAction): void\n}\n\n/**\n * The modeller's keys (`keys.ts`): undo, redo, duplicate, remove, back to the whole figure, the gizmo's modes. Heard on\n * the window, first (capture), while the editor is on the page.\n */\nexport class ModellerKeys implements ReactiveController {\n constructor(\n host: ReactiveControllerHost,\n private readonly of: ModellerKeysHost\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n window.addEventListener('keydown', this.onKey, true)\n }\n\n hostDisconnected() {\n window.removeEventListener('keydown', this.onKey, true)\n }\n\n private onKey = (e: KeyboardEvent) => {\n if (!this.of.taking()) return\n const canvas = this.of.scene()\n const path = e.composedPath()\n /*\n A key for something open over the work -- a group's 「⋯」 menu, the view or the history, a question -- is that\n thing's: heard here first, Esc deselected the part and was kept from the menu, which then could not be closed by\n the keyboard (2026-10-16).\n */\n if (path.some(node => node instanceof Element && (node.localName.startsWith('ox-popup') || node.getAttribute('role') === 'dialog'))) return\n const action = keyActionOf(\n {\n key: e.key,\n ctrlKey: e.ctrlKey,\n metaKey: e.metaKey,\n shiftKey: e.shiftKey,\n altKey: e.altKey,\n target: path[0] as HTMLElement,\n inScene: !!canvas && path.includes(canvas)\n },\n this.of.selected()\n )\n if (!action) return\n e.preventDefault()\n /*\n The session's history is the only one. With the canvas focused the scene took Ctrl+Z as well, undoing on its own\n board the last value the canvas had written -- which the canvas then told as a drag: an undo of a drag on a\n part placed by a rule wrote the drag again (2026-09-25). Heard first (capture) and kept from the scene.\n */\n e.stopPropagation()\n this.of.act(action)\n }\n}\n"]}
1
+ {"version":3,"file":"modeller-keys.js","sourceRoot":"","sources":["../../client/modeller/modeller-keys.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAkB,MAAM,WAAW,CAAA;AAYvD;;;GAGG;AACH,MAAM,OAAO,YAAY;IACvB,YACE,IAA4B,EACX,EAAoB;QAApB,OAAE,GAAF,EAAE,CAAkB;QAa/B,UAAK,GAAG,CAAC,CAAgB,EAAE,EAAE;YACnC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;gBAAE,OAAM;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;YAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,CAAA;YAC7B;;;;cAIE;YACF,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,YAAY,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,qBAAqB,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC;gBAAE,OAAM;YACvL,MAAM,MAAM,GAAG,WAAW,CACxB;gBACE,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE,IAAI,CAAC,CAAC,CAAgB;gBAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;aAC3C,EACD,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CACnB,CAAA;YACD,IAAI,CAAC,MAAM;gBAAE,OAAM;YACnB,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB;;;;cAIE;YACF,CAAC,CAAC,eAAe,EAAE,CAAA;YACnB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC,CAAA;QA1CC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED,aAAa;QACX,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACtD,CAAC;IAED,gBAAgB;QACd,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzD,CAAC;CAkCF","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit'\nimport { keyActionOf, type KeyAction } from './keys.js'\n\n/** What the keys ask of the editor. */\nexport interface ModellerKeysHost {\n /** Whether the modeller takes keys now: a figure open, not loading or saving, and no section editor open. */\n taking(): boolean\n /** The scene, whose keys the modeller's are too. */\n scene(): Element | null\n selected(): string\n act(action: KeyAction): void\n}\n\n/**\n * The modeller's keys (`keys.ts`): undo, redo, duplicate, remove, back to the whole figure, the gizmo's modes. Heard on\n * the window, first (capture), while the editor is on the page.\n */\nexport class ModellerKeys implements ReactiveController {\n constructor(\n host: ReactiveControllerHost,\n private readonly of: ModellerKeysHost\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n window.addEventListener('keydown', this.onKey, true)\n }\n\n hostDisconnected() {\n window.removeEventListener('keydown', this.onKey, true)\n }\n\n private onKey = (e: KeyboardEvent) => {\n if (!this.of.taking()) return\n const canvas = this.of.scene()\n const path = e.composedPath()\n /*\n A key for something open over the work -- a group's 「⋯」 menu, the view or the history, a question, a popup -- is\n that thing's: heard here first, Esc deselected the part and was kept from the menu, which then could not be closed\n by the keyboard (2026-10-16).\n */\n if (path.some(node => node instanceof Element && (node.localName.startsWith('ox-popup') || node.localName === 'ox-floating-overlay' || node.getAttribute('role') === 'dialog'))) return\n const action = keyActionOf(\n {\n key: e.key,\n ctrlKey: e.ctrlKey,\n metaKey: e.metaKey,\n shiftKey: e.shiftKey,\n altKey: e.altKey,\n target: path[0] as HTMLElement,\n inScene: !!canvas && path.includes(canvas)\n },\n this.of.selected()\n )\n if (!action) return\n e.preventDefault()\n /*\n The session's history is the only one. With the canvas focused the scene took Ctrl+Z as well, undoing on its own\n board the last value the canvas had written -- which the canvas then told as a drag: an undo of a drag on a\n part placed by a rule wrote the drag again (2026-09-25). Heard first (capture) and kept from the scene.\n */\n e.stopPropagation()\n this.of.act(action)\n }\n}\n"]}
@@ -1 +1 @@
1
- export default function route(page: string): "figure-modeller" | "figure-list" | undefined;
1
+ export default function route(page: string): "figure-list" | "figure-modeller" | undefined;