@things-factory/figure-ui 10.1.84 → 10.1.85

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.
@@ -20,14 +20,12 @@ const KIND_ICON = {
20
20
  const RECIPES = [
21
21
  { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },
22
22
  { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },
23
- { kind: 'rounded-box', icon: 'crop_square', label: 'figure.label.primitive-rounded-box' },
24
23
  { kind: 'sphere', icon: 'circle', label: 'figure.label.primitive-sphere' },
25
24
  { kind: 'frustum', icon: 'change_history', label: 'figure.label.primitive-frustum' },
26
25
  { kind: 'polygon', icon: 'hexagon', label: 'figure.label.primitive-polygon' },
27
26
  { kind: 'plate', icon: 'check_box_outline_blank', label: 'figure.label.primitive-plate' },
28
27
  { kind: 'wall', icon: 'crop_portrait', label: 'figure.label.primitive-wall' },
29
- { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' },
30
- { kind: 'hollow-box', icon: 'inventory_2', label: 'figure.label.primitive-hollow-box' }
28
+ { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }
31
29
  ];
32
30
  let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
33
31
  constructor() {
@@ -1 +1 @@
1
- {"version":3,"file":"figure-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { PART_LIMIT } from '@hatiolab/figure-model'\nimport type { FigurePrimitiveCreation } from '@hatiolab/figure-model'\nimport type { FigureBudgetLine } from './maturity.js'\nimport { partLabelOf } from './fields.js'\n\n/**\n * 도형 팔레트와 부품 목록 — 자산의 것.\n *\n * 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.\n *\n * 부품의 도형은 평가가 준 형상에서 읽은 시작 모양 이름(`kind`)으로 보인다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라\n * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다.\n */\n\n/** 한 줄 — 평가에서 이 패널이 읽는 것만. */\nexport interface FigurePartRow {\n id: string\n /** 시작 모양 이름(`startingShapeKindOfBody`): 상자 · 원기둥 … 아이콘을 고른다. */\n kind: string\n /** 작업대에서 잠깐 가린 것. 저장되지 않는다. */\n hidden?: boolean\n /** 자산이 지금 상태에서 보이지 않는다고 한 것. 저장되는 것이고, 여기서는 표시만 한다. */\n invisible?: boolean\n /** 되풀이한 부품이면 지금 몇 개로 그려지나. 되풀이는 한 부품이다 — 한 줄로 보인다. */\n copies?: number\n /**\n * 바로 붙어 있는 곳: `대상 부품 · 대상 면`. 목록은 평평한 채로 두고 보조 문구로만 적는다 — 순서도 들여쓰기도\n * 붙임 때문에 바뀌지 않는다(ADR-0097 §5.5, 설계자 2026-09-27).\n */\n fastenedTo?: string\n}\n\n/** 시작 모양마다의 아이콘. Material Symbols 이름이다. */\nconst KIND_ICON: Record<string, string> = {\n cube: 'deployed_code',\n cylinder: 'database',\n frustum: 'change_history',\n sphere: 'circle',\n 'rounded-box': 'crop_square',\n 'hollow-box': 'check_box_outline_blank',\n polygon: 'pentagon',\n portal: 'door_front'\n}\n\n/** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */\n/*\n The palette. A plate and a wall are boxes of a shape a person reaches for, so they are starting sizes, not kinds of their own.\n*/\nexport type FigurePaletteKind = FigurePrimitiveCreation['kind'] | 'plate' | 'wall'\nconst RECIPES: Array<{ kind: FigurePaletteKind; icon: string; label: string }> = [\n { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },\n { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },\n { kind: 'rounded-box', icon: 'crop_square', label: 'figure.label.primitive-rounded-box' },\n { kind: 'sphere', icon: 'circle', label: 'figure.label.primitive-sphere' },\n { kind: 'frustum', icon: 'change_history', label: 'figure.label.primitive-frustum' },\n { kind: 'polygon', icon: 'hexagon', label: 'figure.label.primitive-polygon' },\n { kind: 'plate', icon: 'check_box_outline_blank', label: 'figure.label.primitive-plate' },\n { kind: 'wall', icon: 'crop_portrait', label: 'figure.label.primitive-wall' },\n { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' },\n { kind: 'hollow-box', icon: 'inventory_2', label: 'figure.label.primitive-hollow-box' }\n]\n\n@customElement('figure-parts')\nexport class FigureParts extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n /* The divider beside it is the editor's frame's (ox-three-pane). */\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n /* Folds by its own width: the editor gives this column 140 px when it has two columns (UX, 2026-09-24). */\n container-type: inline-size;\n }\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section[list] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n h3 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 6px;\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n /* The cost line: the grade's letter in its colour, then what the figure costs. */\n button[model-root] {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 32px;\n padding: 4px 8px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: none;\n font: inherit;\n font-weight: 600;\n text-align: start;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n button[model-root] span {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* Chosen: a frame and a tint, not colour alone. */\n button[model-root][on] {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary-container);\n }\n button[model-root]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n button[budget] {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n width: 100%;\n margin: 0 0 6px;\n padding: 4px 6px;\n font: inherit;\n font-size: 0.68rem;\n line-height: 1.5;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[budget]:hover {\n background: var(--md-sys-color-surface-container);\n }\n button[budget] b {\n flex: none;\n min-width: 16px;\n text-align: center;\n border-radius: 4px;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n }\n button[budget][grade='C'] b {\n color: var(--md-sys-color-on-tertiary-container);\n background: var(--md-sys-color-tertiary-container);\n }\n button[budget][grade='D'] {\n border-color: var(--md-sys-color-error);\n }\n button[budget][grade='D'] b {\n color: var(--md-sys-color-on-error);\n background: var(--md-sys-color-error);\n }\n button[budget] em {\n display: block;\n font-style: normal;\n color: var(--md-sys-color-error);\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n font-weight: 600;\n }\n p[full] {\n margin: 4px 0 0;\n font-size: 0.68rem;\n color: var(--md-sys-color-error);\n }\n div[level] {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: 6px;\n margin: 0 0 6px;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[level] select {\n min-width: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n small[copies] {\n color: var(--md-sys-color-on-surface-variant);\n }\n span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n /* Three to a row, small (user, 2026-09-24): the palette is reached for, not read -- the list below is what grows. */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 3px;\n }\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n min-width: 0;\n padding: 5px 1px 4px;\n font: var(--label-font, inherit);\n font-size: 0.6rem;\n line-height: 1.2;\n text-align: center;\n overflow-wrap: anywhere;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[recipe-catalog] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font: inherit;\n font-size: 0.75rem;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 999em;\n cursor: pointer;\n }\n button[shape]:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[shape][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n button[shape] md-icon {\n --md-icon-size: 18px;\n }\n ol {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n }\n li:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n li[selected] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n li md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[eye][all] {\n margin-left: auto;\n opacity: 0.75;\n }\n button[eye][all]:hover {\n opacity: 1;\n }\n button[eye] {\n display: flex;\n flex: none;\n padding: 1px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n }\n li:hover button[eye],\n li[selected] button[eye],\n button[eye][off] {\n opacity: 1;\n }\n button[eye][off] {\n color: var(--md-sys-color-tertiary);\n }\n button[eye] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n li[off] span[label],\n li[off] > md-icon,\n li[invisible] span[label],\n li[invisible] > md-icon {\n opacity: 0.45;\n }\n li[selected] md-icon {\n color: inherit;\n }\n span[label] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.78rem;\n }\n span[label] small {\n margin-left: 4px;\n font-size: 0.66rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[ops] {\n display: none;\n gap: 1px;\n }\n li[selected] div[ops] {\n display: flex;\n }\n div[ops] button {\n display: flex;\n padding: 2px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n div[ops] button:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[ops] button[disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[ops] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n /*\n Under 200 px the selected row's four buttons pushed its name out and the last one past the edge (seen at 140,\n 2026-09-24). They go to a line of their own under the name.\n */\n @container (max-width: 199px) {\n li[selected] {\n flex-wrap: wrap;\n }\n li[selected] div[ops] {\n flex-basis: 100%;\n justify-content: flex-end;\n }\n }\n p[quiet] {\n margin: 0;\n font-size: 0.76rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n @property({ type: Array }) parts: FigurePartRow[] = []\n /** 고른 부품의 id. 자리(index)가 아니다 — 부품은 id 로 안정하게 가리킨다. */\n @property({ type: String }) selected = ''\n /** The model's own name, the list's root: choosing it shows the model's settings (B.1). */\n @property({ type: String }) modelName = ''\n /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */\n @property({ type: Boolean }) locked = false\n /**\n * 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의\n * 기준이 저작을 이끈다. 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.\n */\n @property({ attribute: false }) budget?: FigureBudgetLine\n /**\n * 디테일 등급(S/M/L)의 부품 한도(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가\n * 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.\n */\n @property({ attribute: false }) partBudget?: { level: 'S' | 'M' | 'L' | null; used: number; limit: number | null }\n /** How many recipe templates the catalog holds; none, and the section is not drawn. */\n @property({ type: Number }) recipeCount = 0\n\n render() {\n const b = this.partBudget\n const full = !!b && b.limit !== null && b.used >= b.limit\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${RECIPES.map(\n recipe => html`\n <button\n shape\n ?disabled=${this.locked || full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t(recipe.label)}\n @click=${() => this.ask('add-part', { kind: recipe.kind })}\n >\n <md-icon>${recipe.icon}</md-icon>\n <span>${i18next.t(recipe.label)}</span>\n </button>\n `\n )}\n </div>\n ${full ? html`<p full>${i18next.t('figure.text.part-limit-reached-short', { level: b!.level, limit: b!.limit })}</p>` : nothing}\n </section>\n ${this.renderRecipes(full)}\n\n <section list>\n <!--\n The list's root: the model by its name. Choosing it shows the model's settings -- not \"select all\", not \"fit the\n view\" -- the same move as the model name in the properties panel's path (designer 2026-09-27, B.1).\n -->\n <button\n model-root\n aria-pressed=${String(!this.selected)}\n ?on=${!this.selected}\n title=${i18next.t('figure.button.show-model-settings')}\n aria-label=${`${i18next.t('figure.button.show-model-settings')}: ${this.modelName || i18next.t('figure.text.new-figure-name')}`}\n @click=${() => this.dispatchEvent(new CustomEvent('show-model-settings', { bubbles: true, composed: true }))}\n >\n <md-icon>category</md-icon><span>${this.modelName || i18next.t('figure.text.new-figure-name')}</span>\n </button>\n <h3>\n <span>${i18next.t('figure.label.parts')}</span>\n <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}\n >${b && b.limit !== null ? i18next.t('figure.text.n-of-limit', { used: b.used, limit: b.limit }) : (b?.used ?? this.parts.length)}</span\n >\n ${this.renderShowAll()}\n </h3>\n ${this.renderLevel()}\n ${this.renderBudget()}\n ${this.parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`\n : html`<ol>\n ${this.parts.map((part, index) => this.renderRow(part, index))}\n </ol>`}\n </section>\n `\n }\n\n /** 디테일 등급 — 고르면 한도가 바뀐다. 목록 머리 아래. */\n private renderLevel() {\n const b = this.partBudget\n /* Not before the first part: a level with nothing to limit was the first choice a new figure met (U09). Once set, it shows. */\n if (!b || (!this.parts.length && !b.level)) return nothing\n return html`<div level>\n <label for=\"detail-level\">${i18next.t('figure.label.detail-level')}</label>\n <select\n id=\"detail-level\"\n title=${i18next.t('figure.text.detail-level-explained')}\n .value=${b.level ?? ''}\n ?disabled=${this.locked}\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.dispatchEvent(new CustomEvent('set-detail-level', { detail: { level: value || null }, bubbles: true, composed: true }))\n }}\n >\n <option value=\"\" ?selected=${!b.level}>${i18next.t('figure.label.detail-none')}</option>\n ${(['S', 'M', 'L'] as const).map(\n one => html`<option value=${one} ?selected=${one === b.level}>\n ${one} ${i18next.t('figure.label.detail-' + one)} · ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>`\n )}\n </select>\n </div>`\n }\n\n private renderBudget() {\n const b = this.budget\n if (!b || !this.parts.length) return nothing\n const over = b.grade === 'D'\n return html`<button\n budget\n grade=${b.grade}\n title=${i18next.t('figure.text.parts-budget-open')}\n @click=${() => this.dispatchEvent(new CustomEvent('show-maturity', { bubbles: true, composed: true }))}\n >\n <b>${b.grade}</b>\n <span>\n <strong performance-cost>${i18next.t('figure.label.performance-cost')}</strong>\n ${i18next.t('figure.text.parts-budget', {\n triangles: b.triangles.toLocaleString(),\n groups: b.groups.toLocaleString(),\n instances: b.instances.toLocaleString()\n })}\n ${over\n ? html`<em\n >${i18next.t('figure.text.parts-budget-over', {\n budget: i18next.t('figure.label.budget-' + b.tightest.key),\n used: b.tightest.used.toLocaleString(),\n limit: b.tightest.limit.toLocaleString()\n })}</em\n >`\n : nothing}\n </span>\n </button>`\n }\n\n /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */\n private renderShowAll() {\n if (this.parts.length === 0) return nothing\n const anyHidden = this.parts.some(part => part.hidden)\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('set-all-parts-hidden', { hidden: !anyHidden })\n }}\n >\n <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n `\n }\n\n private renderRow(part: FigurePartRow, index: number) {\n const selected = part.id === this.selected\n const off = !!part.hidden\n return html`\n <li\n ?selected=${selected}\n ?off=${off}\n ?invisible=${!!part.invisible}\n @click=${() => this.ask('select-part', { id: part.id })}\n >\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('toggle-part-hidden', { id: part.id })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${KIND_ICON[part.kind] ?? 'deployed_code'}</md-icon>\n <span label title=${part.id}\n >${partLabelOf(part.id)}${part.copies ? html` <small copies>× ${part.copies}</small>` : nothing}${part.invisible ? html`<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}${part.fastenedTo\n ? html`<small fastened-to title=${i18next.t('figure.label.fastened-to')}>${part.fastenedTo}</small>`\n : nothing}</span\n >\n ${selected ? this.renderOps(part.id, index) : nothing}\n </li>\n `\n }\n\n /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */\n private renderOps(id: string, index: number) {\n const count = this.parts.length\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled || this.locked}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled && !this.locked) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}\n </div>\n `\n }\n\n /**\n * Recipes (ADR-0095): the catalog of templates -- a door, a roller row -- opened over the modeller, put onto the chosen\n * part in one step.\n */\n private renderRecipes(full: boolean) {\n if (!this.recipeCount) return nothing\n return html`<section recipes>\n <h3>${i18next.t('figure.label.recipes')}</h3>\n <button recipe-catalog ?disabled=${this.locked || full} @click=${() => this.ask('open-recipe-catalog', {})}>\n <md-icon>widgets</md-icon>${i18next.t('figure.button.open-recipe-catalog', { count: this.recipeCount })}\n </button>\n </section>`\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
1
+ {"version":3,"file":"figure-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { PART_LIMIT } from '@hatiolab/figure-model'\nimport type { FigurePrimitiveCreation } from '@hatiolab/figure-model'\nimport type { FigureBudgetLine } from './maturity.js'\nimport { partLabelOf } from './fields.js'\n\n/**\n * 도형 팔레트와 부품 목록 — 자산의 것.\n *\n * 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.\n *\n * 부품의 도형은 평가가 준 형상에서 읽은 시작 모양 이름(`kind`)으로 보인다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라\n * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다.\n */\n\n/** 한 줄 — 평가에서 이 패널이 읽는 것만. */\nexport interface FigurePartRow {\n id: string\n /** 시작 모양 이름(`startingShapeKindOfBody`): 상자 · 원기둥 … 아이콘을 고른다. */\n kind: string\n /** 작업대에서 잠깐 가린 것. 저장되지 않는다. */\n hidden?: boolean\n /** 자산이 지금 상태에서 보이지 않는다고 한 것. 저장되는 것이고, 여기서는 표시만 한다. */\n invisible?: boolean\n /** 되풀이한 부품이면 지금 몇 개로 그려지나. 되풀이는 한 부품이다 — 한 줄로 보인다. */\n copies?: number\n /**\n * 바로 붙어 있는 곳: `대상 부품 · 대상 면`. 목록은 평평한 채로 두고 보조 문구로만 적는다 — 순서도 들여쓰기도\n * 붙임 때문에 바뀌지 않는다(ADR-0097 §5.5, 설계자 2026-09-27).\n */\n fastenedTo?: string\n}\n\n/** 시작 모양마다의 아이콘. Material Symbols 이름이다. */\nconst KIND_ICON: Record<string, string> = {\n cube: 'deployed_code',\n cylinder: 'database',\n frustum: 'change_history',\n sphere: 'circle',\n 'rounded-box': 'crop_square',\n 'hollow-box': 'check_box_outline_blank',\n polygon: 'pentagon',\n portal: 'door_front'\n}\n\n/** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */\n/*\n The palette: starting shapes (ADR-0100 §2.1). A plate and a wall are boxes of a shape a person reaches for, so they are\n starting sizes, not kinds of their own. A rounded box and a box open at the top are not here: they are a box with its\n corners rounded or an inside made, from the part's own 단면 section (§3.3), so the palette does not offer a second way.\n*/\nexport type FigurePaletteKind = FigurePrimitiveCreation['kind'] | 'plate' | 'wall'\nconst RECIPES: Array<{ kind: FigurePaletteKind; icon: string; label: string }> = [\n { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },\n { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },\n { kind: 'sphere', icon: 'circle', label: 'figure.label.primitive-sphere' },\n { kind: 'frustum', icon: 'change_history', label: 'figure.label.primitive-frustum' },\n { kind: 'polygon', icon: 'hexagon', label: 'figure.label.primitive-polygon' },\n { kind: 'plate', icon: 'check_box_outline_blank', label: 'figure.label.primitive-plate' },\n { kind: 'wall', icon: 'crop_portrait', label: 'figure.label.primitive-wall' },\n { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }\n]\n\n@customElement('figure-parts')\nexport class FigureParts extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n /* The divider beside it is the editor's frame's (ox-three-pane). */\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n /* Folds by its own width: the editor gives this column 140 px when it has two columns (UX, 2026-09-24). */\n container-type: inline-size;\n }\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section[list] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n h3 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 6px;\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n /* The cost line: the grade's letter in its colour, then what the figure costs. */\n button[model-root] {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 32px;\n padding: 4px 8px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: none;\n font: inherit;\n font-weight: 600;\n text-align: start;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n button[model-root] span {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* Chosen: a frame and a tint, not colour alone. */\n button[model-root][on] {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary-container);\n }\n button[model-root]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n button[budget] {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n width: 100%;\n margin: 0 0 6px;\n padding: 4px 6px;\n font: inherit;\n font-size: 0.68rem;\n line-height: 1.5;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[budget]:hover {\n background: var(--md-sys-color-surface-container);\n }\n button[budget] b {\n flex: none;\n min-width: 16px;\n text-align: center;\n border-radius: 4px;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n }\n button[budget][grade='C'] b {\n color: var(--md-sys-color-on-tertiary-container);\n background: var(--md-sys-color-tertiary-container);\n }\n button[budget][grade='D'] {\n border-color: var(--md-sys-color-error);\n }\n button[budget][grade='D'] b {\n color: var(--md-sys-color-on-error);\n background: var(--md-sys-color-error);\n }\n button[budget] em {\n display: block;\n font-style: normal;\n color: var(--md-sys-color-error);\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n font-weight: 600;\n }\n p[full] {\n margin: 4px 0 0;\n font-size: 0.68rem;\n color: var(--md-sys-color-error);\n }\n div[level] {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: 6px;\n margin: 0 0 6px;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[level] select {\n min-width: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n small[copies] {\n color: var(--md-sys-color-on-surface-variant);\n }\n span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n /* Three to a row, small (user, 2026-09-24): the palette is reached for, not read -- the list below is what grows. */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 3px;\n }\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n min-width: 0;\n padding: 5px 1px 4px;\n font: var(--label-font, inherit);\n font-size: 0.6rem;\n line-height: 1.2;\n text-align: center;\n overflow-wrap: anywhere;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[recipe-catalog] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font: inherit;\n font-size: 0.75rem;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 999em;\n cursor: pointer;\n }\n button[shape]:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[shape][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n button[shape] md-icon {\n --md-icon-size: 18px;\n }\n ol {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n }\n li:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n li[selected] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n li md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[eye][all] {\n margin-left: auto;\n opacity: 0.75;\n }\n button[eye][all]:hover {\n opacity: 1;\n }\n button[eye] {\n display: flex;\n flex: none;\n padding: 1px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n }\n li:hover button[eye],\n li[selected] button[eye],\n button[eye][off] {\n opacity: 1;\n }\n button[eye][off] {\n color: var(--md-sys-color-tertiary);\n }\n button[eye] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n li[off] span[label],\n li[off] > md-icon,\n li[invisible] span[label],\n li[invisible] > md-icon {\n opacity: 0.45;\n }\n li[selected] md-icon {\n color: inherit;\n }\n span[label] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.78rem;\n }\n span[label] small {\n margin-left: 4px;\n font-size: 0.66rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[ops] {\n display: none;\n gap: 1px;\n }\n li[selected] div[ops] {\n display: flex;\n }\n div[ops] button {\n display: flex;\n padding: 2px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n div[ops] button:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[ops] button[disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[ops] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n /*\n Under 200 px the selected row's four buttons pushed its name out and the last one past the edge (seen at 140,\n 2026-09-24). They go to a line of their own under the name.\n */\n @container (max-width: 199px) {\n li[selected] {\n flex-wrap: wrap;\n }\n li[selected] div[ops] {\n flex-basis: 100%;\n justify-content: flex-end;\n }\n }\n p[quiet] {\n margin: 0;\n font-size: 0.76rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n @property({ type: Array }) parts: FigurePartRow[] = []\n /** 고른 부품의 id. 자리(index)가 아니다 — 부품은 id 로 안정하게 가리킨다. */\n @property({ type: String }) selected = ''\n /** The model's own name, the list's root: choosing it shows the model's settings (B.1). */\n @property({ type: String }) modelName = ''\n /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */\n @property({ type: Boolean }) locked = false\n /**\n * 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의\n * 기준이 저작을 이끈다. 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.\n */\n @property({ attribute: false }) budget?: FigureBudgetLine\n /**\n * 디테일 등급(S/M/L)의 부품 한도(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가\n * 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.\n */\n @property({ attribute: false }) partBudget?: { level: 'S' | 'M' | 'L' | null; used: number; limit: number | null }\n /** How many recipe templates the catalog holds; none, and the section is not drawn. */\n @property({ type: Number }) recipeCount = 0\n\n render() {\n const b = this.partBudget\n const full = !!b && b.limit !== null && b.used >= b.limit\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${RECIPES.map(\n recipe => html`\n <button\n shape\n ?disabled=${this.locked || full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t(recipe.label)}\n @click=${() => this.ask('add-part', { kind: recipe.kind })}\n >\n <md-icon>${recipe.icon}</md-icon>\n <span>${i18next.t(recipe.label)}</span>\n </button>\n `\n )}\n </div>\n ${full ? html`<p full>${i18next.t('figure.text.part-limit-reached-short', { level: b!.level, limit: b!.limit })}</p>` : nothing}\n </section>\n ${this.renderRecipes(full)}\n\n <section list>\n <!--\n The list's root: the model by its name. Choosing it shows the model's settings -- not \"select all\", not \"fit the\n view\" -- the same move as the model name in the properties panel's path (designer 2026-09-27, B.1).\n -->\n <button\n model-root\n aria-pressed=${String(!this.selected)}\n ?on=${!this.selected}\n title=${i18next.t('figure.button.show-model-settings')}\n aria-label=${`${i18next.t('figure.button.show-model-settings')}: ${this.modelName || i18next.t('figure.text.new-figure-name')}`}\n @click=${() => this.dispatchEvent(new CustomEvent('show-model-settings', { bubbles: true, composed: true }))}\n >\n <md-icon>category</md-icon><span>${this.modelName || i18next.t('figure.text.new-figure-name')}</span>\n </button>\n <h3>\n <span>${i18next.t('figure.label.parts')}</span>\n <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}\n >${b && b.limit !== null ? i18next.t('figure.text.n-of-limit', { used: b.used, limit: b.limit }) : (b?.used ?? this.parts.length)}</span\n >\n ${this.renderShowAll()}\n </h3>\n ${this.renderLevel()}\n ${this.renderBudget()}\n ${this.parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`\n : html`<ol>\n ${this.parts.map((part, index) => this.renderRow(part, index))}\n </ol>`}\n </section>\n `\n }\n\n /** 디테일 등급 — 고르면 한도가 바뀐다. 목록 머리 아래. */\n private renderLevel() {\n const b = this.partBudget\n /* Not before the first part: a level with nothing to limit was the first choice a new figure met (U09). Once set, it shows. */\n if (!b || (!this.parts.length && !b.level)) return nothing\n return html`<div level>\n <label for=\"detail-level\">${i18next.t('figure.label.detail-level')}</label>\n <select\n id=\"detail-level\"\n title=${i18next.t('figure.text.detail-level-explained')}\n .value=${b.level ?? ''}\n ?disabled=${this.locked}\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.dispatchEvent(new CustomEvent('set-detail-level', { detail: { level: value || null }, bubbles: true, composed: true }))\n }}\n >\n <option value=\"\" ?selected=${!b.level}>${i18next.t('figure.label.detail-none')}</option>\n ${(['S', 'M', 'L'] as const).map(\n one => html`<option value=${one} ?selected=${one === b.level}>\n ${one} ${i18next.t('figure.label.detail-' + one)} · ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>`\n )}\n </select>\n </div>`\n }\n\n private renderBudget() {\n const b = this.budget\n if (!b || !this.parts.length) return nothing\n const over = b.grade === 'D'\n return html`<button\n budget\n grade=${b.grade}\n title=${i18next.t('figure.text.parts-budget-open')}\n @click=${() => this.dispatchEvent(new CustomEvent('show-maturity', { bubbles: true, composed: true }))}\n >\n <b>${b.grade}</b>\n <span>\n <strong performance-cost>${i18next.t('figure.label.performance-cost')}</strong>\n ${i18next.t('figure.text.parts-budget', {\n triangles: b.triangles.toLocaleString(),\n groups: b.groups.toLocaleString(),\n instances: b.instances.toLocaleString()\n })}\n ${over\n ? html`<em\n >${i18next.t('figure.text.parts-budget-over', {\n budget: i18next.t('figure.label.budget-' + b.tightest.key),\n used: b.tightest.used.toLocaleString(),\n limit: b.tightest.limit.toLocaleString()\n })}</em\n >`\n : nothing}\n </span>\n </button>`\n }\n\n /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */\n private renderShowAll() {\n if (this.parts.length === 0) return nothing\n const anyHidden = this.parts.some(part => part.hidden)\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('set-all-parts-hidden', { hidden: !anyHidden })\n }}\n >\n <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n `\n }\n\n private renderRow(part: FigurePartRow, index: number) {\n const selected = part.id === this.selected\n const off = !!part.hidden\n return html`\n <li\n ?selected=${selected}\n ?off=${off}\n ?invisible=${!!part.invisible}\n @click=${() => this.ask('select-part', { id: part.id })}\n >\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('toggle-part-hidden', { id: part.id })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${KIND_ICON[part.kind] ?? 'deployed_code'}</md-icon>\n <span label title=${part.id}\n >${partLabelOf(part.id)}${part.copies ? html` <small copies>× ${part.copies}</small>` : nothing}${part.invisible ? html`<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}${part.fastenedTo\n ? html`<small fastened-to title=${i18next.t('figure.label.fastened-to')}>${part.fastenedTo}</small>`\n : nothing}</span\n >\n ${selected ? this.renderOps(part.id, index) : nothing}\n </li>\n `\n }\n\n /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */\n private renderOps(id: string, index: number) {\n const count = this.parts.length\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled || this.locked}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled && !this.locked) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}\n </div>\n `\n }\n\n /**\n * Recipes (ADR-0095): the catalog of templates -- a door, a roller row -- opened over the modeller, put onto the chosen\n * part in one step.\n */\n private renderRecipes(full: boolean) {\n if (!this.recipeCount) return nothing\n return html`<section recipes>\n <h3>${i18next.t('figure.label.recipes')}</h3>\n <button recipe-catalog ?disabled=${this.locked || full} @click=${() => this.ask('open-recipe-catalog', {})}>\n <md-icon>widgets</md-icon>${i18next.t('figure.button.open-recipe-catalog', { count: this.recipeCount })}\n </button>\n </section>`\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
@@ -1,28 +1,42 @@
1
- import { LitElement, nothing } from 'lit';
1
+ import { LitElement, nothing, type PropertyValues } from 'lit';
2
2
  import type { FigureEvaluatedShape } from '@hatiolab/figure-model';
3
+ type Body = {
4
+ id: string;
5
+ shape: FigureEvaluatedShape;
6
+ };
3
7
  declare const FigureSectionPanel_base: (new (...args: any[]) => LitElement) & typeof LitElement;
4
8
  /**
5
- * 단면 — a pushed part's rounded corners and its inside (ADR-0100 stage 1).
9
+ * 단면 — a pushed part's rounded corners and its inside (ADR-0100 §3.3).
6
10
  *
7
- * Only what the part's section has: corners for a rectangle, an inside for a rectangle or an ellipse. A drawn section
8
- * and a turned body show nothing here. A radius or a wall entered sets every corner or every wall to it; the kernel
9
- * holds per-axis values, and when they differ the field says so and shows none of them.
11
+ * Corners for a rectangle; an inside for a rectangle or an ellipse, with its walls (one thickness, or one per side when
12
+ * asked for), its two ends open or closed, and what that makes of the part and how deep. A drawn section says beside
13
+ * the inside why it has none. A turned body shows nothing here.
10
14
  *
11
- * Every change leaves as one `command`; the editor runs it through the session, one step of the undo history.
15
+ * A value being typed is drawn at once and recorded nowhere (`command-live`); a value the kernel would refuse is said
16
+ * under its field with the most it can be, and is not sent. Enter or leaving the field sends the one `command`, one step
17
+ * of the undo history; Esc puts the value back.
12
18
  */
13
19
  export declare class FigureSectionPanel extends FigureSectionPanel_base {
14
20
  static styles: import("lit").CSSResult[];
15
- body?: {
16
- id: string;
17
- shape: FigureEvaluatedShape;
18
- };
21
+ body?: Body;
19
22
  locked: boolean;
23
+ /** What each field would not take, by field, said under it. */
24
+ private refusals;
25
+ /** What is typed in a field and not yet sent, by field: the figure drawn with it must not type over it. */
26
+ private typed;
27
+ /** Walls one per side, asked for or because they already differ. */
28
+ private perSide;
29
+ protected willUpdate(changed: PropertyValues<this>): void;
20
30
  render(): import("lit-html").TemplateResult<1> | typeof nothing;
21
31
  private renderCorner;
22
32
  private renderCavity;
23
33
  private renderEnd;
24
- /** A thickness field: a positive number, committed on change; anything else puts the old value back. */
25
- private thickness;
34
+ /**
35
+ * A millimetre field. Typing draws the value (or, if the kernel would refuse it, says why under the field and draws the
36
+ * figure as it stands); Enter or leaving sends it once; Esc puts the value back.
37
+ */
38
+ private number;
39
+ private preview;
26
40
  private command;
27
41
  }
28
42
  export {};
@@ -1,29 +1,31 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import { css, html, LitElement, nothing } from 'lit';
3
- import { customElement, property } from 'lit/decorators.js';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
4
  import { live } from 'lit/directives/live.js';
5
5
  import { i18next, localize } from '@operato/i18n';
6
6
  import { SidePanelStyles } from './side-panel-styles.js';
7
- import { cavityRequestOf, defaultWallOf, mm, sameOf } from './section-controls.js';
8
- /** The face each end of a push is, by the axis it runs along: its start is the low side. */
9
- const END_FACE = {
10
- x: { start: 'left', end: 'right' },
11
- y: { start: 'bottom', end: 'top' },
12
- z: { start: 'back', end: 'front' }
13
- };
7
+ import { END_FACE, SIDE_FACE, cavityDepthOf, cavityRefusalOf, cavityRequestOf, cavityResultOf, cornerLimitOf, cornerRefusalOf, defaultWallOf, mm, sameOf, sidesOf, sideWallLimitOf, wallLimitOf } from './section-controls.js';
14
8
  /**
15
- * 단면 — a pushed part's rounded corners and its inside (ADR-0100 stage 1).
9
+ * 단면 — a pushed part's rounded corners and its inside (ADR-0100 §3.3).
16
10
  *
17
- * Only what the part's section has: corners for a rectangle, an inside for a rectangle or an ellipse. A drawn section
18
- * and a turned body show nothing here. A radius or a wall entered sets every corner or every wall to it; the kernel
19
- * holds per-axis values, and when they differ the field says so and shows none of them.
11
+ * Corners for a rectangle; an inside for a rectangle or an ellipse, with its walls (one thickness, or one per side when
12
+ * asked for), its two ends open or closed, and what that makes of the part and how deep. A drawn section says beside
13
+ * the inside why it has none. A turned body shows nothing here.
20
14
  *
21
- * Every change leaves as one `command`; the editor runs it through the session, one step of the undo history.
15
+ * A value being typed is drawn at once and recorded nowhere (`command-live`); a value the kernel would refuse is said
16
+ * under its field with the most it can be, and is not sent. Enter or leaving the field sends the one `command`, one step
17
+ * of the undo history; Esc puts the value back.
22
18
  */
23
19
  let FigureSectionPanel = class FigureSectionPanel extends localize(i18next)(LitElement) {
24
20
  constructor() {
25
21
  super(...arguments);
26
22
  this.locked = false;
23
+ /** What each field would not take, by field, said under it. */
24
+ this.refusals = {};
25
+ /** What is typed in a field and not yet sent, by field: the figure drawn with it must not type over it. */
26
+ this.typed = {};
27
+ /** Walls one per side, asked for or because they already differ. */
28
+ this.perSide = false;
27
29
  }
28
30
  static { this.styles = [SidePanelStyles, css `
29
31
  :host {
@@ -38,21 +40,48 @@ let FigureSectionPanel = class FigureSectionPanel extends localize(i18next)(LitE
38
40
  flex: 1;
39
41
  min-width: 0;
40
42
  }
41
- div[row] > label {
42
- flex: 0 0 auto;
43
+ div[sides] {
44
+ display: grid;
45
+ grid-template-columns: repeat(2, minmax(0, 1fr));
46
+ gap: 6px;
47
+ /* A side's refusal under its field lengthens its cell only: the field beside it stays level. */
48
+ align-items: start;
49
+ }
50
+ div[sides] > label {
51
+ display: grid;
52
+ gap: 2px;
53
+ }
54
+ small[refusal] {
55
+ color: var(--md-sys-color-error);
56
+ }
57
+ input[aria-invalid='true'] {
58
+ outline: 2px solid var(--md-sys-color-error);
43
59
  }
44
60
  `]; }
61
+ willUpdate(changed) {
62
+ const before = changed.get('body');
63
+ if (changed.has('body') && before?.id !== this.body?.id) {
64
+ this.refusals = {};
65
+ this.typed = {};
66
+ /* A part whose walls already differ opens with them one per side. */
67
+ const walls = this.body?.shape.cavity?.wall;
68
+ this.perSide = !!walls && sameOf(Object.values(walls)) === undefined;
69
+ }
70
+ }
45
71
  render() {
46
72
  const body = this.body;
47
- const extrusion = body?.shape?.provider === 'extrude' ? body.shape.extrusion : undefined;
48
- if (!body || !extrusion)
49
- return nothing;
50
- const kind = extrusion.section.kind;
51
- if (kind !== 'rectangle' && kind !== 'ellipse')
73
+ const e = body?.shape?.provider === 'extrude' ? body.shape.extrusion : undefined;
74
+ if (!body || !e)
52
75
  return nothing;
76
+ const kind = e.section.kind;
53
77
  return html `<section>
54
78
  <h3>${i18next.t('figure.label.group-section')}</h3>
55
- ${kind === 'rectangle' ? this.renderCorner(body) : nothing} ${this.renderCavity(body, extrusion.axis)}
79
+ ${kind === 'rectangle' ? this.renderCorner(body) : nothing}
80
+ ${kind === 'rectangle' || kind === 'ellipse'
81
+ ? this.renderCavity(body, e.axis)
82
+ : html `<div field>
83
+ <small>${i18next.t('figure.text.section-cavity-unavailable')}</small>
84
+ </div>`}
56
85
  </section>`;
57
86
  }
58
87
  renderCorner(body) {
@@ -60,98 +89,174 @@ let FigureSectionPanel = class FigureSectionPanel extends localize(i18next)(LitE
60
89
  const reaches = Object.keys(d)
61
90
  .filter(k => k.startsWith('corner.'))
62
91
  .map(k => d[k]);
63
- const one = reaches.length ? sameOf(reaches) : 0;
92
+ const now = reaches.length ? sameOf(reaches) : 0;
93
+ const max = cornerLimitOf(body.shape);
64
94
  return html `<div field>
65
- <label>${i18next.t('figure.label.section-corner')}</label>
66
- <input
67
- type="number"
68
- min="0"
69
- step="any"
70
- .value=${live(one === undefined ? '' : String(mm(one)))}
71
- placeholder=${one === undefined ? i18next.t('figure.text.section-corner-differs') : ''}
72
- ?disabled=${this.locked}
73
- @change=${(e) => {
74
- const input = e.target;
75
- const value = Number(input.value);
76
- if (input.value === '' || !Number.isFinite(value) || value < 0) {
77
- input.value = one === undefined ? '' : String(mm(one));
78
- return;
79
- }
80
- /* 0 is a sharp corner: the rounding is taken away rather than kept at nothing. */
81
- this.command({ kind: 'set-section-corner', part: body.id, corner: value > 0 ? value : null });
82
- }}
83
- />
95
+ <label for="corner">${i18next.t('figure.label.section-corner')}</label>
96
+ ${this.number('corner', now, {
97
+ min: 0,
98
+ ask: value => {
99
+ const r = cornerRefusalOf(body.shape, value);
100
+ return r ? i18next.t('figure.text.section-corner-over', { max: String(mm(r.max)) }) : '';
101
+ },
102
+ action: value => ({ kind: 'set-section-corner', part: body.id, corner: value > 0 ? value : null }),
103
+ placeholder: now === undefined ? i18next.t('figure.text.section-corner-differs') : ''
104
+ })}
105
+ ${this.refusals.corner ? nothing : max !== undefined ? html `<small>${i18next.t('figure.text.section-at-most', { max: String(mm(max)) })}</small>` : nothing}
84
106
  </div>`;
85
107
  }
86
108
  renderCavity(body, axis) {
87
109
  const cavity = body.shape.cavity;
88
110
  const request = (patch) => cavityRequestOf(body.shape, patch);
111
+ const said = (ask) => !ask
112
+ ? ''
113
+ : ask.code === 'not-positive'
114
+ ? i18next.t('figure.text.section-not-positive')
115
+ : i18next.t(`figure.text.section-${ask.code === 'wall-too-thick' ? 'wall' : 'ends'}-over`, { max: String(ask.max) });
116
+ const cavityField = (key, now, patch) => this.number(key, now, {
117
+ min: 0,
118
+ ask: value => {
119
+ if (!(value > 0))
120
+ return i18next.t('figure.text.section-not-positive');
121
+ const ask = cavityRefusalOf(body.shape, request(patch(value)));
122
+ /* One side too thick: the most it can be is the width across less the wall opposite, not the even limit. */
123
+ const side = key.startsWith('wall.') ? key.slice(5) : undefined;
124
+ if (ask?.code === 'wall-too-thick' && side && cavity)
125
+ return said({ code: 'wall-too-thick', max: mm(sideWallLimitOf(body.shape, cavity.wall, side)) });
126
+ return said(ask);
127
+ },
128
+ action: value => ({ kind: 'set-cavity', part: body.id, cavity: request(patch(value)) })
129
+ });
89
130
  return html `<div field>
90
131
  <label>
91
132
  <input
92
133
  type="checkbox"
93
134
  .checked=${live(!!cavity)}
94
135
  ?disabled=${this.locked}
95
- @change=${(e) => {
96
- const on = e.target.checked;
97
- e.target.checked = !!cavity;
136
+ @change=${(ev) => {
137
+ const on = ev.target.checked;
138
+ ev.target.checked = !!cavity;
98
139
  this.command({ kind: 'set-cavity', part: body.id, cavity: on ? request({}) : null });
99
140
  }}
100
141
  />
101
142
  ${i18next.t('figure.label.section-cavity')}
102
143
  </label>
144
+ ${cavity
145
+ ? html `<small result>
146
+ ${i18next.t('figure.text.section-depth', {
147
+ result: i18next.t(`figure.label.section-result-${cavityResultOf(cavity)}`),
148
+ depth: String(cavityDepthOf(body.shape))
149
+ })}
150
+ </small>`
151
+ : nothing}
103
152
  </div>
104
153
  ${cavity
105
154
  ? html `<div field>
106
155
  <label>${i18next.t('figure.label.section-wall')}</label>
107
- ${this.thickness(sameOf(Object.values(cavity.wall)), value => this.command({ kind: 'set-cavity', part: body.id, cavity: request({ wall: value }) }), 'figure.text.section-wall-differs')}
156
+ ${this.perSide
157
+ ? html `<div sides>
158
+ ${[...sidesOf(axis)].sort((a, b) => a[0].localeCompare(b[0]) || (a[1] === '-' ? -1 : 1)).map(side => html `<label>
159
+ <span>${i18next.t('figure.label.face-' + SIDE_FACE[side])}</span>
160
+ ${cavityField(`wall.${side}`, cavity.wall[side], value => ({ wall: { ...cavity.wall, [side]: value } }))}
161
+ </label>`)}
162
+ </div>`
163
+ : cavityField('wall', sameOf(Object.values(cavity.wall)), value => ({ wall: value }))}
164
+ ${this.perSide || this.refusals.wall
165
+ ? nothing
166
+ : html `<small>${i18next.t('figure.text.section-wall-at-most', { max: String(mm(wallLimitOf(body.shape))) })}</small>`}
167
+ <label>
168
+ <input
169
+ type="checkbox"
170
+ .checked=${live(this.perSide)}
171
+ ?disabled=${this.locked}
172
+ @change=${(ev) => (this.perSide = ev.target.checked)}
173
+ />
174
+ ${i18next.t('figure.label.section-wall-per-side')}
175
+ </label>
108
176
  </div>
109
- ${['start', 'end'].map(end => this.renderEnd(body.id, axis, end, cavity[end], request))}`
177
+ ${['start', 'end'].map(end => this.renderEnd(body, axis, end, cavity[end], request, cavityField))}`
110
178
  : nothing}`;
111
179
  }
112
- renderEnd(part, axis, end, now, request) {
180
+ renderEnd(body, axis, end, now, request, cavityField) {
113
181
  const closed = now !== 'open';
114
- const set = (value) => this.command({ kind: 'set-cavity', part, cavity: request({ [end]: value }) });
115
182
  return html `<div field>
116
- <label>${i18next.t('figure.label.face-' + END_FACE[axis][end])}</label>
183
+ <label>${i18next.t('figure.label.face-' + END_FACE(axis, end))}</label>
117
184
  <div row>
118
185
  <select
119
186
  .value=${live(closed ? 'closed' : 'open')}
120
187
  ?disabled=${this.locked}
121
- @change=${(e) => {
122
- const select = e.target;
188
+ @change=${(ev) => {
189
+ const select = ev.target;
123
190
  const want = select.value;
124
191
  select.value = closed ? 'closed' : 'open';
125
- set(want === 'open' ? 'open' : { closed: defaultWallOf(this.body.shape) });
192
+ this.command({ kind: 'set-cavity', part: body.id, cavity: request({ [end]: want === 'open' ? 'open' : { closed: defaultWallOf(body.shape) } }) });
126
193
  }}
127
194
  >
128
195
  <option value="closed">${i18next.t('figure.label.section-end-closed')}</option>
129
196
  <option value="open">${i18next.t('figure.label.section-end-open')}</option>
130
197
  </select>
131
- ${closed ? this.thickness(now.closed, value => set({ closed: value })) : html `<span></span>`}
198
+ ${closed ? cavityField(`closed.${end}`, now.closed, value => ({ [end]: { closed: value } })) : html `<span></span>`}
132
199
  </div>
133
200
  </div>`;
134
201
  }
135
- /** A thickness field: a positive number, committed on change; anything else puts the old value back. */
136
- thickness(now, commit, differs) {
202
+ /**
203
+ * A millimetre field. Typing draws the value (or, if the kernel would refuse it, says why under the field and draws the
204
+ * figure as it stands); Enter or leaving sends it once; Esc puts the value back.
205
+ */
206
+ number(key, now, o) {
137
207
  const shown = now === undefined ? '' : String(mm(now));
208
+ const done = () => {
209
+ const { [key]: _, ...rest } = this.typed;
210
+ this.typed = rest;
211
+ };
212
+ const read = (input) => (input.value === '' ? NaN : Number(input.value));
213
+ const judge = (input) => {
214
+ const value = read(input);
215
+ const refusal = !Number.isFinite(value) || value < o.min ? '' : o.ask(value);
216
+ this.refusals = { ...this.refusals, [key]: refusal };
217
+ return Number.isFinite(value) && value >= o.min && !refusal ? value : undefined;
218
+ };
138
219
  return html `<input
139
- type="number"
140
- min="0"
141
- step="any"
142
- .value=${live(shown)}
143
- placeholder=${now === undefined && differs ? i18next.t(differs) : ''}
144
- ?disabled=${this.locked}
145
- @change=${(e) => {
146
- const input = e.target;
147
- const value = Number(input.value);
148
- if (input.value === '' || !Number.isFinite(value) || !(value > 0)) {
149
- input.value = shown;
220
+ id=${key}
221
+ type="number"
222
+ min=${o.min}
223
+ step="any"
224
+ inputmode="decimal"
225
+ .value=${live(this.typed[key] ?? shown)}
226
+ placeholder=${o.placeholder ?? ''}
227
+ aria-invalid=${this.refusals[key] ? 'true' : 'false'}
228
+ aria-describedby=${`${key}-refusal`}
229
+ ?disabled=${this.locked}
230
+ @input=${(ev) => {
231
+ this.typed = { ...this.typed, [key]: ev.target.value };
232
+ const value = judge(ev.target);
233
+ this.preview(value === undefined ? null : o.action(value));
234
+ }}
235
+ @change=${(ev) => {
236
+ const input = ev.target;
237
+ if (input.value === shown)
238
+ return done(), this.preview(null);
239
+ const value = judge(input);
240
+ /* A refused value stays typed with its reason under it, to be corrected; nothing is sent. */
241
+ if (value === undefined)
242
+ return this.preview(null);
243
+ done();
244
+ this.command(o.action(value));
245
+ }}
246
+ @keydown=${(ev) => {
247
+ if (ev.key !== 'Escape')
150
248
  return;
151
- }
152
- commit(value);
249
+ const input = ev.target;
250
+ input.value = shown;
251
+ done();
252
+ this.refusals = { ...this.refusals, [key]: '' };
253
+ this.preview(null);
153
254
  }}
154
- />`;
255
+ />
256
+ ${this.refusals[key] ? html `<small refusal id=${`${key}-refusal`} role="alert">${this.refusals[key]}</small>` : nothing}`;
257
+ }
258
+ preview(action) {
259
+ this.dispatchEvent(new CustomEvent('command-live', { detail: { action }, bubbles: true, composed: true }));
155
260
  }
156
261
  command(action) {
157
262
  this.dispatchEvent(new CustomEvent('command', { detail: { action }, bubbles: true, composed: true }));
@@ -165,6 +270,18 @@ __decorate([
165
270
  property({ type: Boolean }),
166
271
  __metadata("design:type", Object)
167
272
  ], FigureSectionPanel.prototype, "locked", void 0);
273
+ __decorate([
274
+ state(),
275
+ __metadata("design:type", Object)
276
+ ], FigureSectionPanel.prototype, "refusals", void 0);
277
+ __decorate([
278
+ state(),
279
+ __metadata("design:type", Object)
280
+ ], FigureSectionPanel.prototype, "typed", void 0);
281
+ __decorate([
282
+ state(),
283
+ __metadata("design:type", Object)
284
+ ], FigureSectionPanel.prototype, "perSide", void 0);
168
285
  FigureSectionPanel = __decorate([
169
286
  customElement('figure-section-panel')
170
287
  ], FigureSectionPanel);