@things-factory/figure-ui 10.1.39 → 10.1.40

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 (33) hide show
  1. package/client/modeller/figure-v3-properties.ts +254 -0
  2. package/client/modeller/v3-canvas.ts +27 -3
  3. package/client/modeller/v3-editor.ts +39 -11
  4. package/client/modeller/v3-figure-facts.ts +53 -0
  5. package/client/modeller/v3-occupancy.ts +6 -2
  6. package/client/pages/figure-v3-modeller-page.ts +68 -13
  7. package/dist-client/modeller/figure-v3-properties.d.ts +46 -0
  8. package/dist-client/modeller/figure-v3-properties.js +259 -0
  9. package/dist-client/modeller/figure-v3-properties.js.map +1 -0
  10. package/dist-client/modeller/v3-canvas.d.ts +6 -0
  11. package/dist-client/modeller/v3-canvas.js +27 -3
  12. package/dist-client/modeller/v3-canvas.js.map +1 -1
  13. package/dist-client/modeller/v3-editor.js +39 -11
  14. package/dist-client/modeller/v3-editor.js.map +1 -1
  15. package/dist-client/modeller/v3-figure-facts.d.ts +26 -0
  16. package/dist-client/modeller/v3-figure-facts.js +34 -0
  17. package/dist-client/modeller/v3-figure-facts.js.map +1 -0
  18. package/dist-client/modeller/v3-occupancy.d.ts +5 -1
  19. package/dist-client/modeller/v3-occupancy.js +4 -2
  20. package/dist-client/modeller/v3-occupancy.js.map +1 -1
  21. package/dist-client/pages/figure-v3-modeller-page.d.ts +16 -2
  22. package/dist-client/pages/figure-v3-modeller-page.js +78 -22
  23. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  24. package/dist-client/route.d.ts +1 -1
  25. package/dist-client/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +4 -4
  27. package/test/v3-properties.test.ts +93 -0
  28. package/test/v3-theme.test.ts +67 -0
  29. package/translations/en.json +6 -0
  30. package/translations/ja.json +6 -0
  31. package/translations/ko.json +6 -0
  32. package/translations/ms.json +6 -0
  33. package/translations/zh.json +6 -0
@@ -1 +1 @@
1
- {"version":3,"file":"v3-editor.js","sourceRoot":"","sources":["../../client/modeller/v3-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, type PropertyValues } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport type { V3InputChange, V3PrimitiveCreation } from '@hatiolab/figure-model'\nimport type { V3ProductSession } from './v3-product-session.js'\nimport { captureV3Thumbnail } from './v3-thumbnail.js'\nimport { drawableV3Parts } from './v3-renderer.js'\nimport './v3-canvas.js'\nimport type { FigureV3Canvas } from './v3-canvas.js'\n\nconst names: Record<string, string> = {\n position: '위치',\n rotation: '회전',\n size: '크기',\n dimension: '치수',\n width: '너비',\n height: '높이',\n depth: '깊이',\n radius: '반지름',\n length: '길이',\n round: '모서리 반경',\n left: '왼쪽',\n right: '오른쪽',\n bottom: '아랫면',\n top: '윗면',\n openingLeft: '개구부 왼쪽',\n openingRight: '개구부 오른쪽',\n openingTop: '개구부 윗면'\n}\nfunction fieldLabel(id: string) {\n return id\n .split('.')\n .map(p => names[p] ?? p.toUpperCase())\n .join(' · ')\n}\n\n/** Product editor surface. The injected session is shared by page persistence and AI commands. */\n@customElement('figure-v3-editor')\nexport class FigureV3Editor extends LitElement {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n font: 13px sans-serif;\n color: var(--md-sys-color-on-surface, #22343f);\n }\n header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px;\n border-bottom: 1px solid #d4dde2;\n flex-wrap: wrap;\n }\n main {\n flex: 1;\n min-height: 0;\n display: grid;\n grid-template-columns: 180px minmax(180px, 1fr) 300px;\n }\n aside {\n padding: 12px;\n overflow: auto;\n background: var(--md-sys-color-surface, #fff);\n }\n aside:first-child {\n border-right: 1px solid #d4dde2;\n }\n aside:last-child {\n border-left: 1px solid #d4dde2;\n }\n h3 {\n font-size: var(--ui-t-body, 13px);\n margin: 22px 0 10px;\n }\n h3:first-child {\n margin-top: 0;\n }\n button,\n input,\n select {\n font: inherit;\n box-sizing: border-box;\n height: 34px;\n border: 1px solid #cbd5dc;\n border-radius: 6px;\n background: var(--md-sys-color-surface, #fff);\n color: inherit;\n padding: 0 8px;\n min-width: 0;\n }\n button {\n cursor: pointer;\n }\n button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n button[aria-pressed='true'] {\n background: #d6edf0;\n border-color: #337783;\n }\n .parts {\n display: grid;\n gap: 6px;\n }\n .recipes {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 6px;\n }\n label {\n display: grid;\n grid-template-columns: 112px minmax(0, 1fr);\n align-items: center;\n gap: 8px;\n margin: 8px 0;\n }\n label span {\n overflow-wrap: anywhere;\n }\n .error {\n padding: 10px;\n background: #fff0eb;\n color: #922716;\n white-space: pre-wrap;\n }\n .status {\n margin-left: auto;\n }\n .hint {\n color: #637783;\n line-height: 1.5;\n }\n @media (max-width: 800px) {\n main {\n grid-template-columns: 140px minmax(160px, 1fr);\n }\n aside:last-child {\n grid-column: 1/-1;\n max-height: 260px;\n border-top: 1px solid #d4dde2;\n }\n }\n `\n @property({ attribute: false }) session?: V3ProductSession\n @property({ type: Number }) revision = 0\n @state() private selected = ''\n @state() private error = ''\n private lastContext: string | null = null\n protected willUpdate(_changed: PropertyValues) {\n const view = this.session?.read()\n if (view?.context !== this.lastContext) {\n this.lastContext = view?.context ?? null\n this.selected = ''\n this.error = ''\n }\n if (view && !view.geometry.some(g => g.id === this.selected)) this.selected = ''\n }\n private run(action: () => unknown) {\n try {\n action()\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n private changed() {\n this.requestUpdate()\n this.dispatchEvent(new CustomEvent('v3-changed', { bubbles: true, composed: true }))\n }\n private async save() {\n if (!this.session) return\n try {\n /*\n The card's picture is taken before the save starts, from a canvas of its own at the card's size —\n not from the one on screen, which stands wherever the author left it turned. A picture that cannot\n be taken is not an error: the save goes on without it (see `v3-thumbnail.ts`).\n */\n const view = this.session.read()\n const thumbnail = await captureV3Thumbnail(drawableV3Parts(view.geometry ?? []), view.palette ?? {})\n const promise = this.session.save({ thumbnail })\n this.changed()\n await promise\n this.error = ''\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n } finally {\n this.changed()\n }\n }\n private add(kind: V3PrimitiveCreation['kind']) {\n if (!this.session) return\n this.run(() => {\n const ids = new Set(this.session!.read().geometry.map(g => g.id))\n let n = 1\n while (ids.has(`${kind}-${n}`)) n++\n const common = { id: `${kind}-${n}`, position: { x: 0, y: 50, z: 0 } }\n const part: V3PrimitiveCreation =\n kind === 'box'\n ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100 } }\n : kind === 'cylinder'\n ? { ...common, kind, dimensions: { radius: 50, length: 100 } }\n : kind === 'rounded-box'\n ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } }\n : {\n ...common,\n position: { x: 0, y: 0, z: 0 },\n kind: 'portal',\n dimensions: {\n left: -100,\n right: 100,\n bottom: 0,\n top: 200,\n openingLeft: -70,\n openingRight: 70,\n openingTop: 170,\n depth: 30\n }\n }\n this.session!.add(part)\n this.selected = part.id\n })\n }\n private changeField(field: { id: string; unit: V3InputChange['unit']; role?: string }, event: Event) {\n const input = event.target as HTMLInputElement,\n value = input.valueAsNumber\n this.run(() =>\n this.session!.apply([\n { kind: field.role === 'state' ? 'set-state' : 'set-design', input: field.id, unit: field.unit, value }\n ])\n )\n // Rejected edits must also restore the displayed numeric value.\n input.value = String(this.session!.read().fields.find(i => i.id === field.id)!.value)\n }\n render() {\n const view = this.session?.read()\n if (!view) return html`<p>모델을 여는 중입니다.</p>`\n const unavailable = view.loading || !!view.error || view.saving || !view.context\n const part = view.geometry.find(g => g.id === this.selected)\n // Shared/root parameters remain available even when a part is selected.\n const own = view.fields.filter(i => this.selected && i.id.startsWith(this.selected + '.'))\n const common = view.fields.filter(i => !view.geometry.some(g => i.id.startsWith(g.id + '.')))\n const fields = this.selected ? [...own, ...common] : view.fields\n return html` <header>\n <strong>V3 모델링</strong>\n <button ?disabled=${unavailable || !view.canUndo} @click=${() => this.run(() => this.session!.undo())}>\n 실행 취소\n </button>\n <button ?disabled=${unavailable || !view.canRedo} @click=${() => this.run(() => this.session!.redo())}>\n 다시 실행\n </button>\n <button @click=${() => this.renderRoot.querySelector<FigureV3Canvas>('figure-v3-canvas')?.fit()}>\n 전체 보기\n </button>\n <span class=\"status\" role=\"status\"\n >${view.loading ? '불러오는 중' : view.saving ? '저장 중' : view.dirty || !view.id ? '저장 안 됨' : '저장됨'}</span\n >\n <button ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>저장</button>\n </header>\n ${this.error || view.error ? html`<div role=\"alert\" class=\"error\">${this.error || view.error}</div>` : null}\n <main>\n <aside>\n <h3>도형 추가</h3>\n <div class=\"recipes\">\n ${(['box', 'cylinder', 'rounded-box', 'portal'] as const).map((kind, i) => html`<button ?disabled=${unavailable} @click=${() => this.add(kind)}>${['상자', '원기둥', '둥근 상자', '문틀'][i]}</button>`)}\n </div>\n <h3>부품 ${view.geometry.length}</h3>\n <div class=\"parts\">\n ${view.geometry.map(\n g =>\n html`<button\n aria-pressed=${g.id === this.selected}\n @click=${() => {\n this.selected = g.id\n }}\n >\n ${g.id}\n </button>`\n )}\n </div>\n </aside>\n <figure-v3-canvas\n .parts=${drawableV3Parts(view.geometry ?? [])}\n .palette=${view.palette ?? {}}\n .context=${view.context ?? ''}\n @part-selected=${(e: CustomEvent<string>) => {\n this.selected = e.detail\n }}\n @render-error=${(e: CustomEvent<Error>) => {\n this.error = e.detail.message\n }}\n ></figure-v3-canvas>\n <aside>\n <h3>${this.selected || '모델 입력'}</h3>\n ${fields.map(field => html`<label><span>${fieldLabel(field.id)} (${field.unit})${field.role === 'state' ? ' · 동작' : ''}</span><input aria-label=${field.id} type=\"number\" step=\"any\" .value=${String(field.value)} ?disabled=${unavailable} @change=${(e: Event) => this.changeField(field, e)} /></label>`)}\n ${\n part\n ? html`<h3>재질</h3>\n <label\n ><span>색 역할</span\n ><select\n .value=${part.material.token}\n ?disabled=${unavailable}\n @change=${(e: Event) => this.run(() => this.session!.edit({ kind: 'set-material', target: this.selected, material: { ...part.material, token: (e.target as HTMLSelectElement).value } }))}\n >\n ${Object.keys(view.palette ?? {}).map(token => html`<option value=${token}>${token === 'palette.primary' ? '기본 몸체' : token}</option>`)}\n </select></label\n >\n <p class=\"hint\">\n 도형과 위치를 결정하는 입력을 편집합니다. 다른 부품과 공유하는 입력은 함께 영향을 줄 수 있습니다.\n </p>`\n : null\n }\n ${\n view.bindings.length\n ? html`<h3>배치 연결점</h3>\n ${view.bindings.map(b => html`<p>${b.label} · ${b.role}</p>`)}`\n : null\n }\n </aside>\n </main>`\n }\n}\n"]}
1
+ {"version":3,"file":"v3-editor.js","sourceRoot":"","sources":["../../client/modeller/v3-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, type PropertyValues } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport type { V3InputChange, V3PrimitiveCreation } from '@hatiolab/figure-model'\nimport type { V3ProductSession } from './v3-product-session.js'\nimport { captureV3Thumbnail } from './v3-thumbnail.js'\nimport { drawableV3Parts } from './v3-renderer.js'\nimport './v3-canvas.js'\nimport type { FigureV3Canvas } from './v3-canvas.js'\n\nconst names: Record<string, string> = {\n position: '위치',\n rotation: '회전',\n size: '크기',\n dimension: '치수',\n width: '너비',\n height: '높이',\n depth: '깊이',\n radius: '반지름',\n length: '길이',\n round: '모서리 반경',\n left: '왼쪽',\n right: '오른쪽',\n bottom: '아랫면',\n top: '윗면',\n openingLeft: '개구부 왼쪽',\n openingRight: '개구부 오른쪽',\n openingTop: '개구부 윗면'\n}\nfunction fieldLabel(id: string) {\n return id\n .split('.')\n .map(p => names[p] ?? p.toUpperCase())\n .join(' · ')\n}\n\n/** Product editor surface. The injected session is shared by page persistence and AI commands. */\n@customElement('figure-v3-editor')\nexport class FigureV3Editor extends LitElement {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n /*\n The editor folds by the width it is given, not the window's. The rail (208 open, 56 closed) and the page's\n 320 side take their share first, so a window rule was wrong whenever either was there: at 945 with the rail\n open the editor had 417 and still drew its 660 of columns, over the side (UX ruling, 2026-09-24).\n */\n container-type: inline-size;\n font: 13px sans-serif;\n color: var(--md-sys-color-on-surface, #22343f);\n }\n header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n flex-wrap: wrap;\n }\n main {\n flex: 1;\n min-height: 0;\n display: grid;\n grid-template-columns: 180px minmax(180px, 1fr) 300px;\n }\n aside {\n padding: 12px;\n overflow: auto;\n background: var(--md-sys-color-surface, #fff);\n }\n aside:first-child {\n border-right: 1px solid var(--md-sys-color-outline-variant);\n }\n aside:last-child {\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n h3 {\n font-size: var(--ui-t-body, 13px);\n margin: 22px 0 10px;\n }\n h3:first-child {\n margin-top: 0;\n }\n button,\n input,\n select {\n font: inherit;\n box-sizing: border-box;\n height: 34px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface, #fff);\n color: inherit;\n padding: 0 8px;\n min-width: 0;\n }\n button {\n cursor: pointer;\n }\n button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n button[aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n border-color: var(--md-sys-color-primary);\n }\n .parts {\n display: grid;\n gap: 6px;\n }\n .recipes {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 6px;\n }\n label {\n display: grid;\n grid-template-columns: 112px minmax(0, 1fr);\n align-items: center;\n gap: 8px;\n margin: 8px 0;\n }\n label span {\n overflow-wrap: anywhere;\n }\n .error {\n padding: 10px;\n background: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n white-space: pre-wrap;\n }\n .status {\n margin-left: auto;\n }\n .hint {\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n /* 660 and over: parts | canvas | inputs. Below that the sum of the columns' least widths no longer fits. */\n @container (max-width: 659px) {\n main {\n grid-template-columns: 140px minmax(160px, 1fr);\n }\n aside:last-child {\n grid-column: 1/-1;\n max-height: 260px;\n border-left: none;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n }\n /* Under 420: one column. The canvas first — seeing what is being made comes before the lists — and main scrolls. */\n @container (max-width: 419px) {\n main {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: minmax(240px, 45%);\n grid-auto-rows: auto;\n overflow-y: auto;\n }\n figure-v3-canvas {\n order: -1;\n }\n aside:first-child {\n border-right: none;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n aside:last-child {\n max-height: none;\n }\n }\n `\n @property({ attribute: false }) session?: V3ProductSession\n @property({ type: Number }) revision = 0\n @state() private selected = ''\n @state() private error = ''\n private lastContext: string | null = null\n protected willUpdate(_changed: PropertyValues) {\n const view = this.session?.read()\n if (view?.context !== this.lastContext) {\n this.lastContext = view?.context ?? null\n this.selected = ''\n this.error = ''\n }\n if (view && !view.geometry.some(g => g.id === this.selected)) this.selected = ''\n }\n private run(action: () => unknown) {\n try {\n action()\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n private changed() {\n this.requestUpdate()\n this.dispatchEvent(new CustomEvent('v3-changed', { bubbles: true, composed: true }))\n }\n private async save() {\n if (!this.session) return\n try {\n /*\n The card's picture is taken before the save starts, from a canvas of its own at the card's size —\n not from the one on screen, which stands wherever the author left it turned. A picture that cannot\n be taken is not an error: the save goes on without it (see `v3-thumbnail.ts`).\n */\n const view = this.session.read()\n const thumbnail = await captureV3Thumbnail(drawableV3Parts(view.geometry ?? []), view.palette ?? {})\n const promise = this.session.save({ thumbnail })\n this.changed()\n await promise\n this.error = ''\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n } finally {\n this.changed()\n }\n }\n private add(kind: V3PrimitiveCreation['kind']) {\n if (!this.session) return\n this.run(() => {\n const ids = new Set(this.session!.read().geometry.map(g => g.id))\n let n = 1\n while (ids.has(`${kind}-${n}`)) n++\n const common = { id: `${kind}-${n}`, position: { x: 0, y: 50, z: 0 } }\n const part: V3PrimitiveCreation =\n kind === 'box'\n ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100 } }\n : kind === 'cylinder'\n ? { ...common, kind, dimensions: { radius: 50, length: 100 } }\n : kind === 'rounded-box'\n ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } }\n : {\n ...common,\n position: { x: 0, y: 0, z: 0 },\n kind: 'portal',\n dimensions: {\n left: -100,\n right: 100,\n bottom: 0,\n top: 200,\n openingLeft: -70,\n openingRight: 70,\n openingTop: 170,\n depth: 30\n }\n }\n this.session!.add(part)\n this.selected = part.id\n })\n }\n private changeField(field: { id: string; unit: V3InputChange['unit']; role?: string }, event: Event) {\n const input = event.target as HTMLInputElement,\n value = input.valueAsNumber\n this.run(() =>\n this.session!.apply([\n { kind: field.role === 'state' ? 'set-state' : 'set-design', input: field.id, unit: field.unit, value }\n ])\n )\n // Rejected edits must also restore the displayed numeric value.\n input.value = String(this.session!.read().fields.find(i => i.id === field.id)!.value)\n }\n render() {\n const view = this.session?.read()\n if (!view) return html`<p>모델을 여는 중입니다.</p>`\n const unavailable = view.loading || !!view.error || view.saving || !view.context\n const part = view.geometry.find(g => g.id === this.selected)\n // Shared/root parameters remain available even when a part is selected.\n const own = view.fields.filter(i => this.selected && i.id.startsWith(this.selected + '.'))\n const common = view.fields.filter(i => !view.geometry.some(g => i.id.startsWith(g.id + '.')))\n const fields = this.selected ? [...own, ...common] : view.fields\n return html` <header>\n <strong>V3 모델링</strong>\n <button ?disabled=${unavailable || !view.canUndo} @click=${() => this.run(() => this.session!.undo())}>\n 실행 취소\n </button>\n <button ?disabled=${unavailable || !view.canRedo} @click=${() => this.run(() => this.session!.redo())}>\n 다시 실행\n </button>\n <button @click=${() => this.renderRoot.querySelector<FigureV3Canvas>('figure-v3-canvas')?.fit()}>\n 전체 보기\n </button>\n <span class=\"status\" role=\"status\"\n >${view.loading ? '불러오는 중' : view.saving ? '저장 중' : view.dirty || !view.id ? '저장 안 됨' : '저장됨'}</span\n >\n <button ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>저장</button>\n </header>\n ${this.error || view.error ? html`<div role=\"alert\" class=\"error\">${this.error || view.error}</div>` : null}\n <main>\n <aside>\n <h3>도형 추가</h3>\n <div class=\"recipes\">\n ${(['box', 'cylinder', 'rounded-box', 'portal'] as const).map((kind, i) => html`<button ?disabled=${unavailable} @click=${() => this.add(kind)}>${['상자', '원기둥', '둥근 상자', '문틀'][i]}</button>`)}\n </div>\n <h3>부품 ${view.geometry.length}</h3>\n <div class=\"parts\">\n ${view.geometry.map(\n g =>\n html`<button\n aria-pressed=${g.id === this.selected}\n @click=${() => {\n this.selected = g.id\n }}\n >\n ${g.id}\n </button>`\n )}\n </div>\n </aside>\n <figure-v3-canvas\n .parts=${drawableV3Parts(view.geometry ?? [])}\n .palette=${view.palette ?? {}}\n .context=${view.context ?? ''}\n @part-selected=${(e: CustomEvent<string>) => {\n this.selected = e.detail\n }}\n @render-error=${(e: CustomEvent<Error>) => {\n this.error = e.detail.message\n }}\n ></figure-v3-canvas>\n <aside>\n <h3>${this.selected || '모델 입력'}</h3>\n ${fields.map(field => html`<label><span>${fieldLabel(field.id)} (${field.unit})${field.role === 'state' ? ' · 동작' : ''}</span><input aria-label=${field.id} type=\"number\" step=\"any\" .value=${String(field.value)} ?disabled=${unavailable} @change=${(e: Event) => this.changeField(field, e)} /></label>`)}\n ${\n part\n ? html`<h3>재질</h3>\n <label\n ><span>색 역할</span\n ><select\n .value=${part.material.token}\n ?disabled=${unavailable}\n @change=${(e: Event) => this.run(() => this.session!.edit({ kind: 'set-material', target: this.selected, material: { ...part.material, token: (e.target as HTMLSelectElement).value } }))}\n >\n ${Object.keys(view.palette ?? {}).map(token => html`<option value=${token}>${token === 'palette.primary' ? '기본 몸체' : token}</option>`)}\n </select></label\n >\n <p class=\"hint\">\n 도형과 위치를 결정하는 입력을 편집합니다. 다른 부품과 공유하는 입력은 함께 영향을 줄 수 있습니다.\n </p>`\n : null\n }\n ${\n view.bindings.length\n ? html`<h3>배치 연결점</h3>\n ${view.bindings.map(b => html`<p>${b.label} · ${b.role}</p>`)}`\n : null\n }\n </aside>\n </main>`\n }\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import type { V3Face } from '@hatiolab/figure-model';
2
+ export type Placement = 'floor' | 'ceiling' | 'center';
3
+ /** What the properties tab reads off the asset. Absent parts of the asset read as absent, never as a default. */
4
+ export interface V3FigureFacts {
5
+ name: string;
6
+ parts: string[];
7
+ declared: boolean;
8
+ placement?: Placement;
9
+ mounting?: {
10
+ part: string;
11
+ face: V3Face;
12
+ };
13
+ }
14
+ /**
15
+ * The asset's own facts, as the properties tab shows them.
16
+ *
17
+ * The mounting face is read back from the feature `set-mounting-face` writes (`<part>.mount.<face>`): the asset
18
+ * records the face only as that node, so this is where the screen learns which one is chosen. Parts are the placed
19
+ * bodies (`place@1`), in the order the model lists them.
20
+ */
21
+ export declare function v3FigureFacts(source: string): V3FigureFacts;
22
+ /**
23
+ * A tag line as a list — the V2 inspector's rule: split on commas, drop a leading `#`, keep each once. An empty
24
+ * line is an empty list, so an author can clear every tag.
25
+ */
26
+ export declare function tagsOf(raw: string): string[];
@@ -0,0 +1,34 @@
1
+ import { parseV3Asset } from '@hatiolab/figure-model';
2
+ /**
3
+ * The asset's own facts, as the properties tab shows them.
4
+ *
5
+ * The mounting face is read back from the feature `set-mounting-face` writes (`<part>.mount.<face>`): the asset
6
+ * records the face only as that node, so this is where the screen learns which one is chosen. Parts are the placed
7
+ * bodies (`place@1`), in the order the model lists them.
8
+ */
9
+ export function v3FigureFacts(source) {
10
+ const asset = parseV3Asset(source);
11
+ const parts = asset.document.model.nodes.filter(node => node.op === 'place@1').map(node => node.id);
12
+ const feature = asset.occupancy?.contact?.surface?.feature;
13
+ const found = feature?.match(/^(.+)\.mount\.(left|right|bottom|top|back|front)\.value$/);
14
+ return {
15
+ name: asset.name,
16
+ parts,
17
+ declared: !!asset.occupancy,
18
+ placement: asset.occupancy?.placement,
19
+ mounting: found ? { part: found[1], face: found[2] } : undefined
20
+ };
21
+ }
22
+ /**
23
+ * A tag line as a list — the V2 inspector's rule: split on commas, drop a leading `#`, keep each once. An empty
24
+ * line is an empty list, so an author can clear every tag.
25
+ */
26
+ export function tagsOf(raw) {
27
+ return [
28
+ ...new Set(raw
29
+ .split(',')
30
+ .map(one => one.trim().replace(/^#+/, '').trim())
31
+ .filter(one => one.length > 0))
32
+ ];
33
+ }
34
+ //# sourceMappingURL=v3-figure-facts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3-figure-facts.js","sourceRoot":"","sources":["../../client/modeller/v3-figure-facts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAkBrD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAoF,CAAA;IACrH,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACnG,MAAM,OAAO,GAAI,KAAK,CAAC,SAAS,EAAE,OAA0D,EAAE,OAAO,EAAE,OAAO,CAAA;IAC9G,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,CAAC,0DAA0D,CAAC,CAAA;IACxF,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK;QACL,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS;QAC3B,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,SAAkC;QAC9D,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAW,EAAE,CAAC,CAAC,CAAC,SAAS;KAC3E,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,OAAO;QACL,GAAG,IAAI,GAAG,CACR,GAAG;aACA,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;aAChD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CACjC;KACF,CAAA;AACH,CAAC","sourcesContent":["import { parseV3Asset } from '@hatiolab/figure-model'\nimport type { V3Asset, V3Face } from '@hatiolab/figure-model'\n\n/*\n * What the V3 properties tab reads, kept apart from the element so it can be tested without a browser.\n */\n\nexport type Placement = 'floor' | 'ceiling' | 'center'\n\n/** What the properties tab reads off the asset. Absent parts of the asset read as absent, never as a default. */\nexport interface V3FigureFacts {\n name: string\n parts: string[]\n declared: boolean\n placement?: Placement\n mounting?: { part: string; face: V3Face }\n}\n\n/**\n * The asset's own facts, as the properties tab shows them.\n *\n * The mounting face is read back from the feature `set-mounting-face` writes (`<part>.mount.<face>`): the asset\n * records the face only as that node, so this is where the screen learns which one is chosen. Parts are the placed\n * bodies (`place@1`), in the order the model lists them.\n */\nexport function v3FigureFacts(source: string): V3FigureFacts {\n const asset = parseV3Asset(source) as V3Asset & { document: { model: { nodes: Array<{ id: string; op: string }> } } }\n const parts = asset.document.model.nodes.filter(node => node.op === 'place@1').map(node => node.id)\n const feature = (asset.occupancy?.contact as { surface?: { feature?: string } } | undefined)?.surface?.feature\n const found = feature?.match(/^(.+)\\.mount\\.(left|right|bottom|top|back|front)\\.value$/)\n return {\n name: asset.name,\n parts,\n declared: !!asset.occupancy,\n placement: asset.occupancy?.placement as Placement | undefined,\n mounting: found ? { part: found[1], face: found[2] as V3Face } : undefined\n }\n}\n\n/**\n * A tag line as a list — the V2 inspector's rule: split on commas, drop a leading `#`, keep each once. An empty\n * line is an empty list, so an author can clear every tag.\n */\nexport function tagsOf(raw: string): string[] {\n return [\n ...new Set(\n raw\n .split(',')\n .map(one => one.trim().replace(/^#+/, '').trim())\n .filter(one => one.length > 0)\n )\n ]\n}\n"]}
@@ -11,4 +11,8 @@ import { type V3AuthoringAction } from '@hatiolab/figure-model';
11
11
  * 막는다. 배치 기준은 이미 선언된 것을 지키고, 처음 선언하는 도형은 바닥에 선다고 둔다 — 새로 만든 도형의
12
12
  * 모델러 기본이 바닥이다.
13
13
  */
14
- export declare function occupancyFromParts(source: string): V3AuthoringAction;
14
+ export declare function occupancyFromParts(source: string,
15
+ /** A different face to place it by — the properties tab's choice. Absent keeps the declared one. */
16
+ placement?: 'floor' | 'ceiling' | 'center'): Extract<V3AuthoringAction, {
17
+ kind: 'declare-occupancy';
18
+ }>;
@@ -11,12 +11,14 @@ import { parseV3Asset, proposeV3Occupancy } from '@hatiolab/figure-model';
11
11
  * 막는다. 배치 기준은 이미 선언된 것을 지키고, 처음 선언하는 도형은 바닥에 선다고 둔다 — 새로 만든 도형의
12
12
  * 모델러 기본이 바닥이다.
13
13
  */
14
- export function occupancyFromParts(source) {
14
+ export function occupancyFromParts(source,
15
+ /** A different face to place it by — the properties tab's choice. Absent keeps the declared one. */
16
+ placement) {
15
17
  const asset = parseV3Asset(source);
16
18
  return {
17
19
  kind: 'declare-occupancy',
18
20
  proposal: proposeV3Occupancy(asset, { over: 'range' }),
19
- placement: asset.occupancy?.placement ?? 'floor'
21
+ placement: placement ?? asset.occupancy?.placement ?? 'floor'
20
22
  };
21
23
  }
22
24
  //# sourceMappingURL=v3-occupancy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v3-occupancy.js","sourceRoot":"","sources":["../../client/modeller/v3-occupancy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAA0B,MAAM,wBAAwB,CAAA;AAEjG;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;IAClC,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE,kBAAkB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACtD,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,IAAI,OAAO;KACjD,CAAA;AACH,CAAC","sourcesContent":["import { parseV3Asset, proposeV3Occupancy, type V3AuthoringAction } from '@hatiolab/figure-model'\n\n/**\n * 「부품에 맞추기」의 V3 짝 — 공간을 지금 부품에서 다시 선언하는 한 걸음.\n *\n * figure-model 은 이것을 둘로 나눠 두었다: 제안(`proposeV3Occupancy`)은 무엇이 선언될지를 보여 주고, 확인\n * (`declare-occupancy`)은 그 제안이 읽은 모양 그대로일 때만 받는다. 저작자가 누른 단추가 그 확인이다. 제안과 확인\n * 사이에 모델이 바뀌면 figure-model 이 `OCCUPANCY_STALE` 로 거절하므로, 여기서는 누른 순간의 원본으로 둘을 한\n * 번에 만든다.\n *\n * 범위는 움직임 전체다(`over: 'range'`). 쉬는 자세만 담으면 움직이는 부품이 선언한 공간을 넘어 발행 판정이 다시\n * 막는다. 배치 기준은 이미 선언된 것을 지키고, 처음 선언하는 도형은 바닥에 선다고 둔다 — 새로 만든 도형의\n * 모델러 기본이 바닥이다.\n */\nexport function occupancyFromParts(source: string): V3AuthoringAction {\n const asset = parseV3Asset(source)\n return {\n kind: 'declare-occupancy',\n proposal: proposeV3Occupancy(asset, { over: 'range' }),\n placement: asset.occupancy?.placement ?? 'floor'\n }\n}\n"]}
1
+ {"version":3,"file":"v3-occupancy.js","sourceRoot":"","sources":["../../client/modeller/v3-occupancy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAA0B,MAAM,wBAAwB,CAAA;AAEjG;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc;AACd,oGAAoG;AACpG,SAA0C;IAE1C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;IAClC,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE,kBAAkB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACtD,SAAS,EAAE,SAAS,IAAI,KAAK,CAAC,SAAS,EAAE,SAAS,IAAI,OAAO;KAC9D,CAAA;AACH,CAAC","sourcesContent":["import { parseV3Asset, proposeV3Occupancy, type V3AuthoringAction } from '@hatiolab/figure-model'\n\n/**\n * 「부품에 맞추기」의 V3 짝 — 공간을 지금 부품에서 다시 선언하는 한 걸음.\n *\n * figure-model 은 이것을 둘로 나눠 두었다: 제안(`proposeV3Occupancy`)은 무엇이 선언될지를 보여 주고, 확인\n * (`declare-occupancy`)은 그 제안이 읽은 모양 그대로일 때만 받는다. 저작자가 누른 단추가 그 확인이다. 제안과 확인\n * 사이에 모델이 바뀌면 figure-model 이 `OCCUPANCY_STALE` 로 거절하므로, 여기서는 누른 순간의 원본으로 둘을 한\n * 번에 만든다.\n *\n * 범위는 움직임 전체다(`over: 'range'`). 쉬는 자세만 담으면 움직이는 부품이 선언한 공간을 넘어 발행 판정이 다시\n * 막는다. 배치 기준은 이미 선언된 것을 지키고, 처음 선언하는 도형은 바닥에 선다고 둔다 — 새로 만든 도형의\n * 모델러 기본이 바닥이다.\n */\nexport function occupancyFromParts(\n source: string,\n /** A different face to place it by — the properties tab's choice. Absent keeps the declared one. */\n placement?: 'floor' | 'ceiling' | 'center'\n): Extract<V3AuthoringAction, { kind: 'declare-occupancy' }> {\n const asset = parseV3Asset(source)\n return {\n kind: 'declare-occupancy',\n proposal: proposeV3Occupancy(asset, { over: 'range' }),\n placement: placement ?? asset.occupancy?.placement ?? 'floor'\n }\n}\n"]}
@@ -3,6 +3,7 @@ import '../modeller/v3-editor.js';
3
3
  import '@material/web/icon/icon.js';
4
4
  import '../modeller/figure-history-panel.js';
5
5
  import '../modeller/figure-v3-verdict.js';
6
+ import '../modeller/figure-v3-properties.js';
6
7
  /**
7
8
  * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
8
9
  *
@@ -29,8 +30,8 @@ export declare class FigureV3ModellerPage extends PageView {
29
30
  */
30
31
  private row;
31
32
  private typeError;
32
- /** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
33
- private fitError;
33
+ /** Why a command from the side did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
34
+ private commandError;
34
35
  /**
35
36
  * Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on
36
37
  * its tab stays in sight while the history is open — as on the V2 page.
@@ -44,6 +45,7 @@ export declare class FigureV3ModellerPage extends PageView {
44
45
  private draft?;
45
46
  private draftSource;
46
47
  private wasDirty;
48
+ private titled?;
47
49
  private announce;
48
50
  private changed;
49
51
  private readRow;
@@ -57,6 +59,18 @@ export declare class FigureV3ModellerPage extends PageView {
57
59
  private renameType;
58
60
  /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */
59
61
  private fitToParts;
62
+ /** A command from the properties tab — rename, declare the room, the mounting face. */
63
+ private onCommand;
64
+ /**
65
+ * One step of the session's history, whichever part of the side asked for it — so undo takes it back like any
66
+ * other edit, and it is saved with the figure.
67
+ */
68
+ private runCommand;
69
+ /**
70
+ * Category and tags — the catalogue's, kept on the row, not in the asset. Written at once, as the V2 page does;
71
+ * the server's refusal is shown as it gave it.
72
+ */
73
+ private onCatalog;
60
74
  private released;
61
75
  /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */
62
76
  private reverted;
@@ -11,6 +11,7 @@ import '../modeller/v3-editor.js';
11
11
  import '@material/web/icon/icon.js';
12
12
  import '../modeller/figure-history-panel.js';
13
13
  import '../modeller/figure-v3-verdict.js';
14
+ import '../modeller/figure-v3-properties.js';
14
15
  /**
15
16
  * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
16
17
  *
@@ -35,10 +36,12 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
35
36
  * 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로
36
37
  * V2 화면의 패널을 그대로 건다.
37
38
  */
38
- this.row = { version: 0 };
39
+ this.row = {
40
+ version: 0
41
+ };
39
42
  this.typeError = '';
40
- /** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
41
- this.fitError = '';
43
+ /** Why a command from the side did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
44
+ this.commandError = '';
42
45
  /**
43
46
  * Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on
44
47
  * its tab stays in sight while the history is open — as on the V2 page.
@@ -61,9 +64,14 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
61
64
  };
62
65
  this.changed = () => {
63
66
  this.revision++;
64
- /* Any edit after a failed fit makes the message stale — the author has done something about it. */
65
- this.fitError = '';
67
+ /* Any edit after a failed command makes the message stale — the author has done something about it. */
68
+ this.commandError = '';
66
69
  const view = this.session.read();
70
+ /* The title bar reads the name through `context`, which the shell asks for only when told — a rename left it stale. */
71
+ if (view.name !== this.titled) {
72
+ this.titled = view.name;
73
+ this.updateContext();
74
+ }
67
75
  if (!view.saving)
68
76
  this.announce();
69
77
  /* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */
@@ -97,28 +105,37 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
97
105
  };
98
106
  /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */
99
107
  this.fitToParts = () => {
108
+ this.runCommand(() => occupancyFromParts(this.session.source()));
109
+ };
110
+ /** A command from the properties tab — rename, declare the room, the mounting face. */
111
+ this.onCommand = (event) => {
112
+ const action = event.detail?.action;
113
+ if (action)
114
+ this.runCommand(() => action);
115
+ };
116
+ /**
117
+ * Category and tags — the catalogue's, kept on the row, not in the asset. Written at once, as the V2 page does;
118
+ * the server's refusal is shown as it gave it.
119
+ */
120
+ this.onCatalog = async (event) => {
121
+ const patch = event.detail;
122
+ const id = this.session.read().id;
123
+ if (!id || !patch)
124
+ return;
100
125
  try {
101
- this.session.command(occupancyFromParts(this.session.source()));
102
- this.fitError = '';
103
- this.changed();
126
+ const result = await updateFigure(id, patch);
127
+ this.commandError = '';
128
+ this.row = { ...this.row, category: result.figure.category ?? undefined, tags: result.figure.tags ?? undefined };
104
129
  }
105
130
  catch (error) {
106
- /*
107
- This used to set the page error, which replaces the whole editor with one raw line — for a figure with no
108
- parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).
109
- */
110
- this.fitError =
111
- error?.code === 'OCCUPANCY_NO_PARTS'
112
- ? i18next.t('figure.text.fit-needs-parts')
113
- : error instanceof Error
114
- ? error.message
115
- : String(error);
131
+ this.commandError = error instanceof Error ? error.message : String(error);
116
132
  }
117
133
  };
118
134
  this.released = (event) => {
119
135
  const figure = event.detail?.figure;
136
+ /* Keep the rest of the row: rebuilding it from these two dropped the type, and the locked type field went blank. */
120
137
  if (figure?.id && figure.id === this.session.read().id)
121
- this.row = { state: figure.state, version: figure.version ?? 0 };
138
+ this.row = { ...this.row, state: figure.state, version: figure.version ?? 0 };
122
139
  };
123
140
  /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */
124
141
  this.reverted = async () => {
@@ -267,12 +284,41 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
267
284
  try {
268
285
  const figure = await fetchFigure(id);
269
286
  if (this.session.read().id === id)
270
- this.row = { state: figure.state, version: figure.version ?? 0, type: figure.type };
287
+ this.row = {
288
+ state: figure.state,
289
+ version: figure.version ?? 0,
290
+ type: figure.type,
291
+ category: figure.category ?? undefined,
292
+ tags: figure.tags ?? undefined
293
+ };
271
294
  }
272
295
  catch {
273
296
  /* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */
274
297
  }
275
298
  }
299
+ /**
300
+ * One step of the session's history, whichever part of the side asked for it — so undo takes it back like any
301
+ * other edit, and it is saved with the figure.
302
+ */
303
+ runCommand(actionOf) {
304
+ try {
305
+ this.session.command(actionOf());
306
+ this.commandError = '';
307
+ this.changed();
308
+ }
309
+ catch (error) {
310
+ /*
311
+ This used to set the page error, which replaces the whole editor with one raw line — for a figure with no
312
+ parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).
313
+ */
314
+ this.commandError =
315
+ error?.code === 'OCCUPANCY_NO_PARTS'
316
+ ? i18next.t('figure.text.fit-needs-parts')
317
+ : error instanceof Error
318
+ ? error.message
319
+ : String(error);
320
+ }
321
+ }
276
322
  connectedCallback() {
277
323
  super.connectedCallback();
278
324
  window.addEventListener('figure-ai-context-request', this.announce);
@@ -352,11 +398,21 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
352
398
  />`}
353
399
  ${this.typeError ? html `<small role="alert">${this.typeError}</small>` : ''}
354
400
  </label>
355
- ${this.fitError ? html `<p fit-error role="alert">${this.fitError}</p>` : ''}
401
+ ${this.commandError ? html `<p fit-error role="alert">${this.commandError}</p>` : ''}
356
402
  <div tabs>
403
+ ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
357
404
  ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
358
405
  ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
359
406
  </div>
407
+ <figure-v3-properties
408
+ ?off=${this.tab !== 'properties'}
409
+ .source=${this.draftSource()}
410
+ .figureId=${view.id}
411
+ .figureCategory=${this.row.category}
412
+ .figureTags=${this.row.tags}
413
+ @v3-command=${this.onCommand}
414
+ @v3-catalog=${this.onCatalog}
415
+ ></figure-v3-properties>
360
416
  <figure-v3-verdict
361
417
  ?off=${this.tab !== 'verdict'}
362
418
  .source=${this.draftSource()}
@@ -406,7 +462,7 @@ __decorate([
406
462
  __decorate([
407
463
  state(),
408
464
  __metadata("design:type", Object)
409
- ], FigureV3ModellerPage.prototype, "fitError", void 0);
465
+ ], FigureV3ModellerPage.prototype, "commandError", void 0);
410
466
  __decorate([
411
467
  state(),
412
468
  __metadata("design:type", String)
@@ -1 +1 @@
1
- {"version":3,"file":"figure-v3-modeller-page.js","sourceRoot":"","sources":["../../client/pages/figure-v3-modeller-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { createV3ProductSession } from '../modeller/v3-product-session.js'\nimport { v3ProductStore } from '../graphql/v3-store.js'\nimport { i18next } from '@operato/i18n'\nimport { fetchFigure, updateFigure } from '../graphql/index.js'\nimport { occupancyFromParts } from '../modeller/v3-occupancy.js'\nimport '../modeller/v3-editor.js'\nimport '@material/web/icon/icon.js'\nimport '../modeller/figure-history-panel.js'\nimport '../modeller/figure-v3-verdict.js'\n\n/**\n * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.\n *\n * It answers to `figure-v3-modeller` while the catalogue's assets are still V2. The designer's order switches\n * the production data and the consuming paths together (ADR-0087, step 5): until the assets are converted, a\n * `figure-modeller` that opened this page would fail on every figure in the catalogue. Having its own route\n * is what makes steps 3 and 4 — the conversion on a recoverable copy, and the canvas check — possible at all.\n * At step 5 the V2 page goes, and this one takes the `figure-modeller` name and route.\n */\n@customElement('figure-v3-modeller-page')\nexport class FigureV3ModellerPage extends PageView {\n static styles = css`\n :host {\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n /*\n The page must fit the width it is given. Without this the shell sizes it to its content — the editor's\n 660 plus the 320 release panel — and the panel runs off the right edge (seen at 945 px, 2026-09-24). The\n V2 page does the same.\n */\n overflow: hidden;\n }\n figure-v3-editor {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n aside {\n width: 320px;\n flex: none;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] {\n display: grid;\n gap: 4px;\n padding: 12px 12px 8px;\n font-size: 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] input,\n label[type-field] code {\n font: inherit;\n font-family: monospace;\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n background: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n label[type-field] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n p[fit-error] {\n margin: 0;\n padding: 8px 12px;\n font-size: 12px;\n color: var(--md-sys-color-error);\n }\n label[type-field] small[role='alert'] {\n color: var(--md-sys-color-error);\n }\n /* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n /* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */\n [off] {\n display: none;\n }\n .error {\n padding: 16px;\n color: #922716;\n }\n `\n private readonly session = createV3ProductSession(v3ProductStore)\n private routeId: string | undefined\n private discardApproved = false\n @state() private revision = 0\n @state() private error = ''\n /**\n * 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.\n *\n * 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서\n * `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 V3 도형은 공간을 선언하기 전에는 전부\n * 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로\n * V2 화면의 패널을 그대로 건다.\n */\n @state() private row: { state?: 'draft' | 'released'; version: number; type?: string } = { version: 0 }\n @state() private typeError = ''\n /** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */\n @state() private fitError = ''\n /**\n * Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on\n * its tab stays in sight while the history is open — as on the V2 page.\n */\n @state() private tab: 'verdict' | 'history' = 'verdict'\n @state() private instances?: number\n /**\n * The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is\n * kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.\n */\n private draft?: string\n private draftSource(): string | undefined {\n try {\n this.draft = this.session.source()\n } catch {\n /* loading or saving — keep the last source */\n }\n return this.draft\n }\n private wasDirty = false\n private announce = () => {\n const view = this.session.read()\n if (!this.active || view.loading || view.saving || view.error || !view.context) return\n window.dispatchEvent(\n new CustomEvent('figure-ai-context', {\n detail: {\n figureId: view.id,\n source: JSON.parse(this.session.source()),\n context: view.context,\n revision: view.revision,\n format: 'v3'\n }\n })\n )\n }\n private changed = () => {\n this.revision++\n /* Any edit after a failed fit makes the message stale — the author has done something about it. */\n this.fitError = ''\n const view = this.session.read()\n if (!view.saving) this.announce()\n /* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */\n if (this.wasDirty && !view.dirty && !view.saving && view.id) this.readRow(view.id)\n this.wasDirty = !!view.dirty\n }\n private async readRow(id: string) {\n try {\n const figure = await fetchFigure(id)\n if (this.session.read().id === id)\n this.row = { state: figure.state as 'draft' | 'released', version: figure.version ?? 0, type: figure.type }\n } catch {\n /* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */\n }\n }\n /**\n * 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).\n *\n * 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.\n * 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.\n * 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.\n */\n private renameType = async (event: Event) => {\n const input = event.target as HTMLInputElement\n const id = this.session.read().id\n const wanted = input.value.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()\n input.value = wanted\n if (!id || !wanted || wanted === this.row.type) return\n try {\n const result = await updateFigure(id, { type: wanted })\n this.typeError = ''\n this.row = { ...this.row, type: result.figure.type }\n } catch (error) {\n this.typeError = error instanceof Error ? error.message : String(error)\n input.value = this.row.type ?? ''\n }\n }\n /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */\n private fitToParts = () => {\n try {\n this.session.command(occupancyFromParts(this.session.source()))\n this.fitError = ''\n this.changed()\n } catch (error) {\n /*\n This used to set the page error, which replaces the whole editor with one raw line — for a figure with no\n parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).\n */\n this.fitError =\n (error as { code?: string })?.code === 'OCCUPANCY_NO_PARTS'\n ? i18next.t('figure.text.fit-needs-parts')\n : error instanceof Error\n ? error.message\n : String(error)\n }\n }\n private released = (event: Event) => {\n const figure = (event as CustomEvent).detail?.figure\n if (figure?.id && figure.id === this.session.read().id) this.row = { state: figure.state, version: figure.version ?? 0 }\n }\n /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */\n private reverted = async () => {\n const id = this.session.read().id\n if (!id) return\n await this.session.open(id, { discard: true })\n this.changed()\n this.readRow(id)\n }\n /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */\n private applyAI = (event: Event) => {\n const detail = (event as CustomEvent).detail\n if (!this.active || !detail) return\n try {\n this.session.apply(detail.changes, 'ai', detail.revision, detail.context)\n detail.applied = true\n this.changed()\n } catch (error) {\n detail.applied = false\n detail.error = error instanceof Error ? error.message : String(error)\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('figure-ai-context-request', this.announce)\n window.addEventListener('figure-v3-apply', this.applyAI)\n }\n disconnectedCallback() {\n window.removeEventListener('figure-ai-context-request', this.announce)\n window.removeEventListener('figure-v3-apply', this.applyAI)\n super.disconnectedCallback()\n }\n async canDeactivate() {\n const view = this.session.read()\n if (view.saving) return false\n if (!view.dirty) return true\n // The shell invokes this while the edited model is still visible, before navigating away.\n this.discardApproved = window.confirm('저장하지 않은 변경을 버리고 이동하시겠습니까?')\n return this.discardApproved\n }\n async pageUpdated(changes: Record<string, unknown>, lifecycle: { resourceId?: string }) {\n if (!this.active) {\n if (this.session.read().loading) this.session.close()\n window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }))\n return\n }\n const id = lifecycle?.resourceId\n if (this.session.read().context && id === this.routeId && !changes.active) return\n try {\n const discard = this.discardApproved\n this.discardApproved = false\n if (id) {\n this.draft = undefined\n const opening = this.session.open(id, { discard })\n this.changed()\n await opening\n this.readRow(id)\n } else {\n this.session.startNew({ discard })\n this.draft = undefined\n this.row = { version: 0 }\n this.typeError = ''\n }\n this.routeId = id\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n get context() {\n return { title: this.session.read().name ?? 'Figure', actions: [] }\n }\n render() {\n if (this.error) return html`<p role=\"alert\" class=\"error\">${this.error}</p>`\n const view = this.session.read()\n return html`<figure-v3-editor\n .session=${this.session}\n .revision=${this.revision}\n @v3-changed=${this.changed}\n ></figure-v3-editor>\n <aside>\n <label type-field>\n <span>${i18next.t('figure.label.figure-type')}</span>\n ${this.row.version > 0\n ? html`<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`\n : html`<input\n .value=${this.row.type ?? ''}\n placeholder=${i18next.t('figure.text.figure-type-placeholder')}\n ?disabled=${!view.id}\n @change=${this.renameType}\n />`}\n ${this.typeError ? html`<small role=\"alert\">${this.typeError}</small>` : ''}\n </label>\n ${this.fitError ? html`<p fit-error role=\"alert\">${this.fitError}</p>` : ''}\n <div tabs>\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n <figure-v3-verdict\n ?off=${this.tab !== 'verdict'}\n .source=${this.draftSource()}\n .figureId=${view.id}\n .figureType=${this.row.type}\n @capacity-changed=${(event: CustomEvent) => (this.instances = event.detail.instances)}\n @fit-base-to-parts=${this.fitToParts}\n ></figure-v3-verdict>\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n format=\"v3\"\n .figureId=${view.id}\n .figureState=${this.row.state}\n .figureVersion=${this.row.version}\n .figureType=${this.row.type}\n .dirty=${!!view.dirty}\n @fit-base-to-parts=${this.fitToParts}\n @figure-released=${this.released}\n @figure-reverted=${this.reverted}\n ></figure-history-panel>\n </aside>`\n }\n private renderTab(tab: 'verdict' | 'history', icon: string, label: string, badge?: number) {\n return html`\n <button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
1
+ {"version":3,"file":"figure-v3-modeller-page.js","sourceRoot":"","sources":["../../client/pages/figure-v3-modeller-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { createV3ProductSession } from '../modeller/v3-product-session.js'\nimport { v3ProductStore } from '../graphql/v3-store.js'\nimport { i18next } from '@operato/i18n'\nimport { fetchFigure, updateFigure } from '../graphql/index.js'\nimport { occupancyFromParts } from '../modeller/v3-occupancy.js'\nimport type { V3AuthoringAction } from '@hatiolab/figure-model'\nimport '../modeller/v3-editor.js'\nimport '@material/web/icon/icon.js'\nimport '../modeller/figure-history-panel.js'\nimport '../modeller/figure-v3-verdict.js'\nimport '../modeller/figure-v3-properties.js'\n\n/**\n * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.\n *\n * It answers to `figure-v3-modeller` while the catalogue's assets are still V2. The designer's order switches\n * the production data and the consuming paths together (ADR-0087, step 5): until the assets are converted, a\n * `figure-modeller` that opened this page would fail on every figure in the catalogue. Having its own route\n * is what makes steps 3 and 4 — the conversion on a recoverable copy, and the canvas check — possible at all.\n * At step 5 the V2 page goes, and this one takes the `figure-modeller` name and route.\n */\n@customElement('figure-v3-modeller-page')\nexport class FigureV3ModellerPage extends PageView {\n static styles = css`\n :host {\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n /*\n The page must fit the width it is given. Without this the shell sizes it to its content — the editor's\n 660 plus the 320 release panel — and the panel runs off the right edge (seen at 945 px, 2026-09-24). The\n V2 page does the same.\n */\n overflow: hidden;\n }\n figure-v3-editor {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n aside {\n width: 320px;\n flex: none;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] {\n display: grid;\n gap: 4px;\n padding: 12px 12px 8px;\n font-size: 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] input,\n label[type-field] code {\n font: inherit;\n font-family: monospace;\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n background: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n label[type-field] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n p[fit-error] {\n margin: 0;\n padding: 8px 12px;\n font-size: 12px;\n color: var(--md-sys-color-error);\n }\n label[type-field] small[role='alert'] {\n color: var(--md-sys-color-error);\n }\n /* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n /* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */\n [off] {\n display: none;\n }\n .error {\n padding: 16px;\n color: #922716;\n }\n `\n private readonly session = createV3ProductSession(v3ProductStore)\n private routeId: string | undefined\n private discardApproved = false\n @state() private revision = 0\n @state() private error = ''\n /**\n * 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.\n *\n * 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서\n * `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 V3 도형은 공간을 선언하기 전에는 전부\n * 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로\n * V2 화면의 패널을 그대로 건다.\n */\n @state() private row: { state?: 'draft' | 'released'; version: number; type?: string; category?: string; tags?: string[] } = {\n version: 0\n }\n @state() private typeError = ''\n /** Why a command from the side did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */\n @state() private commandError = ''\n /**\n * Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on\n * its tab stays in sight while the history is open — as on the V2 page.\n */\n @state() private tab: 'properties' | 'verdict' | 'history' = 'verdict'\n @state() private instances?: number\n /**\n * The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is\n * kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.\n */\n private draft?: string\n private draftSource(): string | undefined {\n try {\n this.draft = this.session.source()\n } catch {\n /* loading or saving — keep the last source */\n }\n return this.draft\n }\n private wasDirty = false\n private titled?: string | null\n private announce = () => {\n const view = this.session.read()\n if (!this.active || view.loading || view.saving || view.error || !view.context) return\n window.dispatchEvent(\n new CustomEvent('figure-ai-context', {\n detail: {\n figureId: view.id,\n source: JSON.parse(this.session.source()),\n context: view.context,\n revision: view.revision,\n format: 'v3'\n }\n })\n )\n }\n private changed = () => {\n this.revision++\n /* Any edit after a failed command makes the message stale — the author has done something about it. */\n this.commandError = ''\n const view = this.session.read()\n /* The title bar reads the name through `context`, which the shell asks for only when told — a rename left it stale. */\n if (view.name !== this.titled) {\n this.titled = view.name\n this.updateContext()\n }\n if (!view.saving) this.announce()\n /* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */\n if (this.wasDirty && !view.dirty && !view.saving && view.id) this.readRow(view.id)\n this.wasDirty = !!view.dirty\n }\n private async readRow(id: string) {\n try {\n const figure = await fetchFigure(id)\n if (this.session.read().id === id)\n this.row = {\n state: figure.state as 'draft' | 'released',\n version: figure.version ?? 0,\n type: figure.type,\n category: figure.category ?? undefined,\n tags: figure.tags ?? undefined\n }\n } catch {\n /* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */\n }\n }\n /**\n * 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).\n *\n * 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.\n * 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.\n * 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.\n */\n private renameType = async (event: Event) => {\n const input = event.target as HTMLInputElement\n const id = this.session.read().id\n const wanted = input.value.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()\n input.value = wanted\n if (!id || !wanted || wanted === this.row.type) return\n try {\n const result = await updateFigure(id, { type: wanted })\n this.typeError = ''\n this.row = { ...this.row, type: result.figure.type }\n } catch (error) {\n this.typeError = error instanceof Error ? error.message : String(error)\n input.value = this.row.type ?? ''\n }\n }\n /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */\n private fitToParts = () => {\n this.runCommand(() => occupancyFromParts(this.session.source()))\n }\n /** A command from the properties tab — rename, declare the room, the mounting face. */\n private onCommand = (event: Event) => {\n const action = (event as CustomEvent).detail?.action as V3AuthoringAction | undefined\n if (action) this.runCommand(() => action)\n }\n /**\n * One step of the session's history, whichever part of the side asked for it — so undo takes it back like any\n * other edit, and it is saved with the figure.\n */\n private runCommand(actionOf: () => V3AuthoringAction) {\n try {\n this.session.command(actionOf())\n this.commandError = ''\n this.changed()\n } catch (error) {\n /*\n This used to set the page error, which replaces the whole editor with one raw line — for a figure with no\n parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).\n */\n this.commandError =\n (error as { code?: string })?.code === 'OCCUPANCY_NO_PARTS'\n ? i18next.t('figure.text.fit-needs-parts')\n : error instanceof Error\n ? error.message\n : String(error)\n }\n }\n /**\n * Category and tags — the catalogue's, kept on the row, not in the asset. Written at once, as the V2 page does;\n * the server's refusal is shown as it gave it.\n */\n private onCatalog = async (event: Event) => {\n const patch = (event as CustomEvent).detail as { category?: string; tags?: string[] }\n const id = this.session.read().id\n if (!id || !patch) return\n try {\n const result = await updateFigure(id, patch)\n this.commandError = ''\n this.row = { ...this.row, category: result.figure.category ?? undefined, tags: result.figure.tags ?? undefined }\n } catch (error) {\n this.commandError = error instanceof Error ? error.message : String(error)\n }\n }\n private released = (event: Event) => {\n const figure = (event as CustomEvent).detail?.figure\n /* Keep the rest of the row: rebuilding it from these two dropped the type, and the locked type field went blank. */\n if (figure?.id && figure.id === this.session.read().id) this.row = { ...this.row, state: figure.state, version: figure.version ?? 0 }\n }\n /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */\n private reverted = async () => {\n const id = this.session.read().id\n if (!id) return\n await this.session.open(id, { discard: true })\n this.changed()\n this.readRow(id)\n }\n /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */\n private applyAI = (event: Event) => {\n const detail = (event as CustomEvent).detail\n if (!this.active || !detail) return\n try {\n this.session.apply(detail.changes, 'ai', detail.revision, detail.context)\n detail.applied = true\n this.changed()\n } catch (error) {\n detail.applied = false\n detail.error = error instanceof Error ? error.message : String(error)\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('figure-ai-context-request', this.announce)\n window.addEventListener('figure-v3-apply', this.applyAI)\n }\n disconnectedCallback() {\n window.removeEventListener('figure-ai-context-request', this.announce)\n window.removeEventListener('figure-v3-apply', this.applyAI)\n super.disconnectedCallback()\n }\n async canDeactivate() {\n const view = this.session.read()\n if (view.saving) return false\n if (!view.dirty) return true\n // The shell invokes this while the edited model is still visible, before navigating away.\n this.discardApproved = window.confirm('저장하지 않은 변경을 버리고 이동하시겠습니까?')\n return this.discardApproved\n }\n async pageUpdated(changes: Record<string, unknown>, lifecycle: { resourceId?: string }) {\n if (!this.active) {\n if (this.session.read().loading) this.session.close()\n window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }))\n return\n }\n const id = lifecycle?.resourceId\n if (this.session.read().context && id === this.routeId && !changes.active) return\n try {\n const discard = this.discardApproved\n this.discardApproved = false\n if (id) {\n this.draft = undefined\n const opening = this.session.open(id, { discard })\n this.changed()\n await opening\n this.readRow(id)\n } else {\n this.session.startNew({ discard })\n this.draft = undefined\n this.row = { version: 0 }\n this.typeError = ''\n }\n this.routeId = id\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n get context() {\n return { title: this.session.read().name ?? 'Figure', actions: [] }\n }\n render() {\n if (this.error) return html`<p role=\"alert\" class=\"error\">${this.error}</p>`\n const view = this.session.read()\n return html`<figure-v3-editor\n .session=${this.session}\n .revision=${this.revision}\n @v3-changed=${this.changed}\n ></figure-v3-editor>\n <aside>\n <label type-field>\n <span>${i18next.t('figure.label.figure-type')}</span>\n ${this.row.version > 0\n ? html`<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`\n : html`<input\n .value=${this.row.type ?? ''}\n placeholder=${i18next.t('figure.text.figure-type-placeholder')}\n ?disabled=${!view.id}\n @change=${this.renameType}\n />`}\n ${this.typeError ? html`<small role=\"alert\">${this.typeError}</small>` : ''}\n </label>\n ${this.commandError ? html`<p fit-error role=\"alert\">${this.commandError}</p>` : ''}\n <div tabs>\n ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n <figure-v3-properties\n ?off=${this.tab !== 'properties'}\n .source=${this.draftSource()}\n .figureId=${view.id}\n .figureCategory=${this.row.category}\n .figureTags=${this.row.tags}\n @v3-command=${this.onCommand}\n @v3-catalog=${this.onCatalog}\n ></figure-v3-properties>\n <figure-v3-verdict\n ?off=${this.tab !== 'verdict'}\n .source=${this.draftSource()}\n .figureId=${view.id}\n .figureType=${this.row.type}\n @capacity-changed=${(event: CustomEvent) => (this.instances = event.detail.instances)}\n @fit-base-to-parts=${this.fitToParts}\n ></figure-v3-verdict>\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n format=\"v3\"\n .figureId=${view.id}\n .figureState=${this.row.state}\n .figureVersion=${this.row.version}\n .figureType=${this.row.type}\n .dirty=${!!view.dirty}\n @fit-base-to-parts=${this.fitToParts}\n @figure-released=${this.released}\n @figure-reverted=${this.reverted}\n ></figure-history-panel>\n </aside>`\n }\n private renderTab(tab: 'properties' | 'verdict' | 'history', icon: string, label: string, badge?: number) {\n return html`\n <button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
@@ -1 +1 @@
1
- export default function route(page: string): "figure-list" | "figure-modeller" | "figure-v3-modeller" | undefined;
1
+ export default function route(page: string): "figure-modeller" | "figure-list" | "figure-v3-modeller" | undefined;