@things-factory/figure-ui 10.1.5 → 10.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/graphql/index.ts +35 -3
- package/client/modeller/figure-ask.ts +5 -5
- package/client/modeller/figure-canvas.ts +1 -1
- package/client/modeller/figure-capabilities.ts +372 -0
- package/client/modeller/figure-history-panel.ts +171 -18
- package/client/modeller/figure-inspector.ts +392 -76
- package/client/modeller/figure-parts.ts +15 -15
- package/client/modeller/figure-preview.ts +2 -2
- package/client/modeller/figure-report.ts +102 -37
- package/client/modeller/figure-settings.ts +20 -20
- package/client/modeller/figure-side.ts +37 -9
- package/client/modeller/figure-source.test.ts +15 -1
- package/client/modeller/figure-source.ts +31 -15
- package/client/modeller/figure-thumbnail.ts +49 -4
- package/client/modeller/maturity.ts +10 -10
- package/client/pages/figure-list-page.ts +152 -66
- package/client/pages/figure-modeller-page.ts +260 -43
- package/client/types.ts +25 -0
- package/client/viewparts/figure-thumb.ts +172 -0
- package/dist-client/graphql/index.d.ts +8 -1
- package/dist-client/graphql/index.js +32 -3
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-ask.js +5 -5
- package/dist-client/modeller/figure-ask.js.map +1 -1
- package/dist-client/modeller/figure-canvas.js +1 -1
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-capabilities.d.ts +42 -0
- package/dist-client/modeller/figure-capabilities.js +344 -0
- package/dist-client/modeller/figure-capabilities.js.map +1 -0
- package/dist-client/modeller/figure-history-panel.d.ts +15 -1
- package/dist-client/modeller/figure-history-panel.js +165 -18
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +84 -0
- package/dist-client/modeller/figure-inspector.js +379 -75
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-parts.js +15 -15
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.js +2 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +18 -1
- package/dist-client/modeller/figure-report.js +105 -37
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-settings.js +19 -19
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +1 -0
- package/dist-client/modeller/figure-side.js +36 -8
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +15 -8
- package/dist-client/modeller/figure-source.js +16 -7
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.js +40 -4
- package/dist-client/modeller/figure-thumbnail.js.map +1 -1
- package/dist-client/modeller/maturity.js +10 -10
- package/dist-client/modeller/maturity.js.map +1 -1
- package/dist-client/pages/figure-list-page.d.ts +1 -4
- package/dist-client/pages/figure-list-page.js +150 -63
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +45 -0
- package/dist-client/pages/figure-modeller-page.js +259 -43
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +23 -0
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.d.ts +29 -0
- package/dist-client/viewparts/figure-thumb.js +168 -0
- package/dist-client/viewparts/figure-thumb.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -5
- package/test/i18n-prefix-guard.test.ts +129 -0
- package/test/korean-register-guard.test.ts +83 -0
- package/translations/en.json +307 -264
- package/translations/ja.json +275 -264
- package/translations/ko.json +307 -264
- package/translations/ms.json +275 -264
- package/translations/zh.json +275 -264
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-inspector.js","sourceRoot":"","sources":["../../client/modeller/figure-inspector.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, svg } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport {\n EXTRUDE_PRIMITIVES,\n MATERIAL_PRESETS,\n PRIMITIVE_KINDS,\n SEGMENTED_PRIMITIVES,\n SEGMENT_PRESETS,\n SIZING_RULES,\n trianglesOf\n} from '@hatiolab/figure-model'\nimport type { Axis, FigurePart, MaterialPreset, PrimitiveKind } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\n\nimport { partToFigure, partFromFigure } from './figure-source.js'\nimport type { BoardModel, PartModel } from './figure-source.js'\nimport { canMirror } from './part-edits.js'\n\n/**\n * 고른 부품의 속성.\n *\n * ## 여기가 사용자 용어로 보여 주는 자리다\n *\n * 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 — 보드 위에 부품 컴포넌트가 놓인 상태.\n * 그런데 사용자에게 보여야 하는 것은 씬 속성 이름(`left`·`top`·`zPos`)이 아니라\n * **X·Y·Z** 다. 다른 3D 도구가 모두 그렇게 부르고, 위치도 부품의 **중심**으로 읽는다.\n *\n * 그래서 이 패널은 받은 부품을 FigureSource 형태로 **변환해서 보여 주고, 수정한 것을\n * 다시 씬 모델로 되돌려 알린다.** 변환은 `figure-source.ts` 가 한다 — 여기서 축을 다시 계산하지\n * 않는다.\n *\n * ## 있는 것만 보여 준다\n *\n * 분할 수는 곡면이 있는 도형에만, 모서리 굴림은 단면을 뽑는 도형에만 뜻이 있다.\n * 뜻 없는 칸을 회색으로 늘어놓으면 저작자가 「왜 안 먹지」를 고민하게 된다 —\n * 아예 내지 않는다.\n *\n * ## 이름은 저장되는 식별자다\n *\n * `FigurePart.name` 은 바인딩·애니메이션·슬롯이 가리키는 이름이다. 고치면 배치된\n * 인스턴스의 바인딩이 **조용히** 끊긴다. 그래서 이름 칸에만 경고를 붙인다.\n *\n * ## 「상태 색이 붙는 자리」를 걷었다\n *\n * `FigurePart.materialSlot`(당시 이름은 `slot`) 을 고르는 칸이 있었는데 **아무 일도 하지 않았다.** things-scene 의\n * figure 런타임이 `slot` 을 읽지 않는다 — 선언 한 줄이 전부다. 되는 줄 알고 만들었고,\n * 확인하지 않은 것이 잘못이다.\n *\n * 그리고 되게 만들더라도 이 모양이 아니다. 상태 색은 특별한 것이 아니라 **속성 하나**이고,\n * 툴팁·라벨·표시 여부와 같은 자리에서 다뤄져야 한다. 그 설계가 정해지면(figure-model#2)\n * 이 자리에 **속성 선언과 부품 바인딩** 편집기가 들어온다.\n *\n * 그때까지 빈 약속을 두지 않는다. 아무 일도 안 하는 칸은 없느니만 못하다.\n *\n * ## 고치지 않는다\n *\n * 정본 한 벌은 저작면이 갖는다. 여기서는 바뀐 부품을 알릴 뿐이다.\n */\n@customElement('figure-inspector')\nexport class FigureInspector 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-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\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 h3 {\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\n label {\n display: block;\n margin-bottom: 2px;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n input,\n select {\n width: 100%;\n box-sizing: border-box;\n padding: 4px 7px;\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input:focus,\n select:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n\n /*\n 숫자 칸 — 라벨을 칸 **안** 왼쪽에 둔다.\n\n 벡터 저작도구(Sketch·Figma)가 X·Y·W·H 를 이렇게 둔다. 라벨을 위에 얹으면 줄마다\n 두 줄을 먹어서, 자리·크기·회전 아홉 칸이 화면 절반을 차지한다. 안에 두면 한 줄에\n 셋이 들어가고 무엇이 무엇인지도 붙어 읽힌다.\n */\n div[cell] {\n display: flex;\n align-items: center;\n gap: 2px;\n padding-left: 6px;\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n div[cell]:focus-within {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n border-color: transparent;\n }\n div[cell] span[axis] {\n flex: none;\n font-size: 0.64rem;\n letter-spacing: 0.02em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[cell] input {\n min-width: 0;\n padding: 4px 6px 4px 2px;\n font-family: var(--mono-font, monospace);\n text-align: right;\n background: none;\n border: none;\n border-radius: 0;\n }\n div[cell] input:focus {\n outline: none;\n }\n\n /* 축 셋을 한 줄에 — 좌표는 셋이 같이 읽혀야 한다 */\n div[triple] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n div[field] + div[field],\n div[triple] + div[field],\n div[field] + div[triple] {\n margin-top: 7px;\n }\n\n /*\n 정밀 제작 도구.\n\n 보드 편집기는 자산을 배치하는 곳이라 대충 놓아도 되지만, 여기는 자산을 만드는\n 곳이다. 「몸통 한가운데」·「바닥에 붙이기」·「반대쪽에도 하나」를 손으로 계산하면\n 늘 조금씩 어긋나고, 기준 치수를 바꾸면 전부 다시 해야 한다.\n */\n div[tools] {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n margin-top: 6px;\n }\n div[tools] button {\n padding: 3px 8px;\n font: var(--label-font, inherit);\n font-size: 0.7rem;\n color: 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 div[tools] button[disabled] {\n opacity: 0.35;\n cursor: default;\n }\n div[tools] button: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 div[tools] span[sep] {\n width: 1px;\n margin: 2px 3px;\n background-color: var(--md-sys-color-outline-variant);\n }\n\n /* 지금 어느 축이 늘어나나 — 크기를 고치면 바뀔 수 있어서 값으로 적는다 */\n p[axis-now] {\n margin: 4px 0 0 0;\n font-size: 0.7rem;\n color: var(--md-sys-color-primary);\n }\n /* 이 선택이 무엇을 무는지 — 성숙도의 묶음 수에 그대로 잡힌다 */\n p[cost] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 이름을 바꾸면 무엇이 끊기는지 말한다 */\n p[warn] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-tertiary);\n }\n\n /* 분할 수 프리셋 — 자유 입력보다 이 셋을 권한다 */\n div[presets] {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n div[presets] button {\n flex: 1;\n padding: 3px 0;\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: 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 div[presets] button[on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 분할 수 선택 — 모양과 삼각형 수를 **함께** 보여 준다.\n\n 숫자만 늘어놓으면 8 과 16 이 무엇이 다른지 알 수 없다. 로우폴리에서 이 선택이\n 곧 「몇 개까지 편하게 놓을 수 있나」를 정하므로, 각지는 정도와 그 값이 무는\n 삼각형 수를 한자리에서 보아야 한다.\n\n 실루엣은 지어낸 그림이 아니다 — 곡면 프리미티브를 위에서 본 윤곽이 정확히\n 정n각형이다. 삼각형 수는 화면이 셈하지 않고 figure-model 의 trianglesOf 가 낸다.\n */\n div[segments] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n /* 원 유지 · 끄기 — 고르는 값이라 분할 수와 같은 모양으로 둔다 */\n div[choices] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 4px;\n }\n button[choice] {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 1px;\n padding: 6px 8px;\n text-align: left;\n font: var(--label-font, inherit);\n color: 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[choice]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[choice][on] {\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[choice] small {\n opacity: 0.75;\n }\n button[seg] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n padding: 6px 2px 4px 2px;\n font: var(--label-font, inherit);\n color: 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[seg]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[seg][on] {\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[seg] svg {\n display: block;\n /* 작으면 12 와 16 이 둘 다 동그라미로 보인다 — 각이 읽힐 만큼은 키운다 */\n width: 40px;\n height: 40px;\n }\n button[seg] svg polygon {\n fill: var(--md-sys-color-surface-container-highest);\n stroke: currentColor;\n stroke-width: 1.1;\n stroke-linejoin: miter;\n }\n button[seg][on] svg polygon {\n fill: var(--md-sys-color-surface-container-lowest);\n }\n button[seg] b {\n font-size: 0.8rem;\n font-weight: 700;\n line-height: 1.1;\n }\n button[seg] small {\n font-size: 0.62rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[seg][on] small {\n color: inherit;\n }\n /* 프리셋 밖의 값도 쓸 수 있다. 다만 권하는 쪽을 크게 둔다 */\n details[free] {\n margin-top: 6px;\n }\n details[free] summary {\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n cursor: pointer;\n }\n details[free] div[cell] {\n margin-top: 4px;\n }\n\n /*\n 색 견본.\n\n 토큰을 글자로 받지 않는다. things-scene 의 팔레트 선언이 「여기 든 이름이 저작에서\n 고를 수 있는 전부이고, 자유색을 허용하지 않는 것이 감각적인 로우폴리의 첫 규칙」\n 이라고 못박고 있다. 글자 칸으로 두면 그 규칙이 화면에서 사라지고, 오타 하나로\n resolveToken 이 던진다.\n */\n div[swatches] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n /*\n 두 무리의 경계. 「칠한 색」과 「켜진 색」이 한 줄에 섞이면 accent(주황)와\n status-warning(주황)이 나란히 놓여 무엇이 무엇인지 알 수 없다.\n */\n label[group] {\n margin-top: 8px;\n padding-top: 8px;\n font-size: var(--ui-t-micro, 11px);\n letter-spacing: 0.03em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 값이 옆에 붙어야 끌면서 얼마인지 안다 */\n div[slider] {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n div[slider] input[type='range'] {\n flex: 1;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n div[slider] output {\n flex: none;\n width: 2.6em;\n text-align: right;\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[swatch] {\n width: 26px;\n height: 26px;\n padding: 0;\n background-color: var(--swatch);\n border: 1px solid rgba(0, 0, 0, 0.16);\n border-radius: 50%;\n cursor: pointer;\n }\n button[swatch][on] {\n /* 고른 것은 테두리가 아니라 고리로 표시한다 — 색 자체를 가리지 않으려고 */\n box-shadow:\n 0 0 0 2px var(--md-sys-color-surface-container-lowest),\n 0 0 0 4px var(--md-sys-color-primary);\n }\n span[token] {\n display: block;\n margin-top: 6px;\n font-family: var(--mono-font, monospace);\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n label[toggle] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 7px 0 0 0;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n label[toggle] input {\n width: auto;\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\n 전에는 그냥 글자였고, 옆의 이름 칸과 나란히 있으니 눌러도 되는 것처럼 보였다. 자물쇠와\n 칸 없는 바탕이 「읽는 값」이라고 먼저 말한다.\n */\n code[fixed] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 6px;\n background-color: var(--md-sys-color-surface-container-high, rgba(0, 0, 0, 0.05));\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n font-family: var(--mono-font, monospace);\n font-size: 0.76rem;\n }\n\n code[fixed] md-icon {\n --md-icon-size: 13px;\n opacity: 0.7;\n }\n `\n ]\n\n /** 보드. 위치를 보드 중심 기준으로 바꾸려면 이것이 있어야 한다. */\n /** 도형의 표시 이름. 정본은 저작면이 들고, 여기서는 보여 주고 바꾼다(`figure-rename` 으로 알린다). */\n @property({ type: String }) figureName = ''\n\n @property({ type: Object }) board?: BoardModel\n /** 보드 위의 부품들. 씬 모델 그대로다. */\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Number }) selected = -1\n\n /** `part` 는 HTMLElement 의 속성(DOMTokenList)이다 — 가리면 형이 어긋난다. */\n private get chosen(): PartModel | undefined {\n return this.parts?.[this.selected]\n }\n\n render() {\n const part = this.chosen\n if (!part || !this.board) {\n return html`\n ${this.renderFigure()}\n <section>\n <h3>${i18next.t('label.properties')}</h3>\n <p quiet>${i18next.t('text.select-a-part-to-edit')}</p>\n </section>\n `\n }\n\n // 여기부터는 사용자 용어다 — X·Y·Z, 위치는 부품 중심, 회전은 도(degree).\n const shown = partToFigure(part, this.board)\n\n return html`\n ${this.renderFigure()} ${this.renderIdentity(shown)} ${this.renderPlacement(shown)}\n ${this.renderShape(shown)} ${this.renderMaterial(shown)} ${this.renderBehaviour(shown)}\n `\n }\n\n /**\n * 도형 자체 — 이름과 타입.\n *\n * ## 왜 머리줄이 아니라 여기인가\n *\n * 저작면 위에 줄을 하나 더 두고 이름 칸을 놓았었다. 앱에서는 셸이 페이지 제목으로 그 이름을\n * 이미 그리므로 같은 글자가 두 번 나오고, 캔버스가 주인공인 화면에서 그 줄만큼 높이를 잃었다.\n * 속성 패널은 「이 도형이 무엇인가」를 다루는 자리이므로 이름도 여기 산다.\n *\n * ## 타입은 읽기만 한다\n *\n * 저장되는 식별자다. 배치된 보드가 이 이름으로 타입을 찾으므로 만든 뒤에는 못 바꾼다.\n */\n private renderFigure() {\n return html`\n <section>\n <h3>${i18next.t('label.figure')}</h3>\n <div field>\n <label>${i18next.t('label.name')}</label>\n <input\n .value=${this.figureName}\n placeholder=${i18next.t('text.figure-display-name')}\n @change=${(e: Event) =>\n this.dispatchEvent(\n new CustomEvent('figure-rename', {\n detail: { name: (e.target as HTMLInputElement).value },\n bubbles: true,\n composed: true\n })\n )}\n />\n </div>\n <div field>\n <label>${i18next.t('label.type')}</label>\n <code fixed title=${i18next.t('text.type-name-cannot-change')}>\n <md-icon>lock</md-icon>${this.board?.figureType ?? ''}\n </code>\n </div>\n <!--\n 왜 못 고치는지 **적어 둔다.** 전에는 tooltip 에만 있었고, 그러면 눌러 보고 안 되는 것을\n 알게 된다 — 화면이 안 되는 이유를 말하지 않으면 사용자가 자기 실수라고 생각한다.\n -->\n <p quiet>${i18next.t('text.type-name-cannot-change')}</p>\n </section>\n `\n }\n\n private renderIdentity(part: FigurePart) {\n return html`\n <section>\n <h3>${i18next.t('label.part')}</h3>\n <div field>\n <label>${i18next.t('label.name')}</label>\n <input\n .value=${part.name}\n @change=${(e: Event) => this.put({ name: (e.target as HTMLInputElement).value })}\n />\n <p warn>${i18next.t('text.renaming-a-part-breaks-bindings')}</p>\n </div>\n <div field>\n <label>${i18next.t('label.shape')}</label>\n <select @change=${(e: Event) => this.changePrimitive((e.target as HTMLSelectElement).value as PrimitiveKind)}>\n ${PRIMITIVE_KINDS.map(\n kind => html`\n <option value=${kind} ?selected=${kind === part.primitive}>\n ${i18next.t('label.primitive-' + kind)}\n </option>\n `\n )}\n </select>\n </div>\n </section>\n `\n }\n\n private renderPlacement(part: FigurePart) {\n const t = part.transform\n const rotation = t.rotation ?? {}\n\n return html`\n <section>\n <h3>${i18next.t('label.placement')}</h3>\n\n <label>${i18next.t('label.position')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), t.position[axis], value =>\n this.putTransform({ position: { ...t.position, [axis]: value } })\n )\n )}\n </div>\n\n <div field>\n <label>${i18next.t('label.size')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(\n axis.toUpperCase(),\n t.size[axis],\n value => this.putTransform({ size: { ...t.size, [axis]: value } }),\n 1\n )\n )}\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('label.tools')}</label>\n <div tools>\n <button title=${i18next.t('text.center-on-x')} @click=${() => this.ask('center', 'x')}>\n ${i18next.t('button.center-x')}\n </button>\n <button title=${i18next.t('text.center-on-z')} @click=${() => this.ask('center', 'z')}>\n ${i18next.t('button.center-z')}\n </button>\n <button title=${i18next.t('text.put-on-floor')} @click=${() => this.ask('floor', 'y')}>\n ${i18next.t('button.to-floor')}\n </button>\n <span sep></span>\n ${(['x', 'z'] as const).map(axis => {\n // 이미 가운데면 대칭이 제자리다 — 겹친 부품이 조용히 생기지 않게 막는다\n const able = this.board ? canMirror(this.board, this.parts, this.selected, axis) : false\n return html`\n <button\n ?disabled=${!able}\n title=${i18next.t(able ? 'text.mirror-explained' : 'text.mirror-would-overlap')}\n @click=${() => this.ask('mirror', axis)}\n >\n ${i18next.t('button.mirror-' + axis)}\n </button>\n `\n })}\n <span sep></span>\n <button title=${i18next.t('text.snap-explained')} @click=${() => this.ask('snap', 'x')}>\n ${i18next.t('button.snap')}\n </button>\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('label.rotation-degrees')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), rotation[axis] ?? 0, value =>\n this.putTransform({ rotation: { ...rotation, [axis]: value } })\n )\n )}\n </div>\n </div>\n </section>\n `\n }\n\n /** 분할 수와 모서리 굴림 — 해당하는 도형에만 낸다. */\n private renderShape(part: FigurePart) {\n const segmented = SEGMENTED_PRIMITIVES.includes(part.primitive)\n const extruded = EXTRUDE_PRIMITIVES.includes(part.primitive)\n if (!segmented && !extruded) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('label.shape-detail')}</h3>\n ${segmented ? this.renderSegments(part) : nothing}\n ${segmented ? this.renderKeepRound(part) : nothing}\n ${extruded ? this.renderExtruded(part) : nothing}\n </section>\n `\n }\n\n /**\n * 민 단면 — 모서리와 **속을 파는 두께.**\n *\n * 벽 두께를 0 으로 두면 통짜다. 0 보다 크면 그만큼 안으로 들어온 테가 생기고, 그것이\n * 트레이 · 토트 · 통 · 케이스가 되는 자리다. 바닥은 비워 두면 벽과 같고, 0 으로 두면\n * 위아래가 다 뚫린 테두리가 된다.\n */\n private renderExtruded(part: FigurePart) {\n const hollow = part.shape?.hollow\n const wall = hollow?.wall ?? 0\n\n const reshape = (over: Record<string, unknown>) =>\n this.put({ shape: { ...(part.shape ?? {}), ...over } })\n\n return html`\n <div field>\n <label>${i18next.t('label.corner-round')}</label>\n ${this.number('', part.shape?.round ?? 0, value => reshape({ round: value }))}\n </div>\n <div field>\n <label>${i18next.t('label.hollow-wall')}</label>\n ${this.number('', wall, value =>\n reshape({ hollow: value > 0 ? { ...(hollow ?? {}), wall: value } : undefined })\n )}\n </div>\n ${wall > 0\n ? html`\n <div field>\n <label>${i18next.t('label.hollow-floor')}</label>\n ${this.number('', hollow?.floor ?? wall, value => reshape({ hollow: { ...hollow, wall, floor: value } }))}\n </div>\n `\n : nothing}\n `\n }\n\n /**\n * 분할 수 — 모양과 무게를 함께 고른다.\n *\n * 삼각형 수는 화면이 셈하지 않는다. `trianglesOf` 에 그 분할 수를 가진 부품을 주고\n * 물어본다 — 화면이 제 나름의 공식을 들면 figure-model 이 규칙을 바꿔도 화면만\n * 옛 답을 계속 낸다.\n */\n /**\n * 원통을 원통으로 둘까 — **고르는 값이고, 값을 치른다.**\n *\n * 인스턴스가 축마다 다른 배율을 받으면 원기둥이 타원 기둥이 된다. 지키려면 제 변환이\n * 있어야 하고, 합친 형상은 변환을 하나만 받으므로 지키는 곡면 부품 하나가 draw call\n * 하나다. 저작자가 그 대가를 모르고 고르면 안 되므로 그 자리에 적는다.\n */\n private renderKeepRound(part: FigurePart) {\n const on = part.keepRound !== false\n\n return html`\n <div field>\n <label>${i18next.t('label.keep-round')}</label>\n <div choices>\n <button choice ?on=${on} @click=${() => this.put({ keepRound: undefined })}>\n <b>${i18next.t('text.keep-round-on')}</b>\n <small>${i18next.t('text.keep-round-cost')}</small>\n </button>\n <button choice ?on=${!on} @click=${() => this.put({ keepRound: false })}>\n <b>${i18next.t('text.keep-round-off')}</b>\n <small>${i18next.t('text.keep-round-merged')}</small>\n </button>\n </div>\n </div>\n `\n }\n\n private renderSegments(part: FigurePart) {\n const current = part.segments ?? SEGMENT_PRESETS[1]\n\n return html`\n <div field>\n <label>${i18next.t('label.segments')}</label>\n <div segments>\n ${SEGMENT_PRESETS.map(preset => {\n const triangles = trianglesOf({ ...part, segments: preset })\n return html`\n <button seg ?on=${current === preset} @click=${() => this.put({ segments: preset })}>\n ${this.silhouette(preset)}\n <b>${preset}</b>\n <small>${i18next.t('text.n-triangles', { count: triangles })}</small>\n </button>\n `\n })}\n </div>\n\n <details free>\n <summary>${i18next.t('text.other-segment-count')}</summary>\n ${this.number('', current, value => this.put({ segments: value }), 3)}\n </details>\n </div>\n `\n }\n\n /**\n * 분할 수의 실루엣.\n *\n * 곡면 프리미티브를 위에서 본 윤곽이 정확히 정n각형이다 — 원기둥의 단면도, 구의\n * 적도도. 그림을 지어내는 것이 아니라 실제로 만들어지는 모양이다.\n */\n private silhouette(segments: number) {\n const points: string[] = []\n for (let i = 0; i < segments; i++) {\n // 꼭짓점 하나가 위로 오게 -90도에서 시작한다\n const angle = -Math.PI / 2 + (i * 2 * Math.PI) / segments\n points.push(`${(16 + 13 * Math.cos(angle)).toFixed(2)},${(16 + 13 * Math.sin(angle)).toFixed(2)}`)\n }\n\n return svg`\n <svg viewBox=\"0 0 32 32\" aria-hidden=\"true\">\n <polygon points=${points.join(' ')}></polygon>\n </svg>\n `\n }\n\n /** 견본 한 줄. 무리마다 부른다. */\n private swatches(entries: [string, string][], chosen?: string) {\n return entries.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === chosen}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ token })}\n ></button>\n `\n )\n }\n\n private renderMaterial(part: FigurePart) {\n const material = part.material ?? {}\n const palette = activePalette() as Record<string, string>\n\n /*\n 두 무리로 나눠 보인다 — **칠한 색**과 **켜진 색**.\n\n `palette.status-*` 는 램프 · 표시등에 쓰라고 있는 색이다. 한 줄에 섞어 두면\n `accent`(주황)와 `status-warning`(주황)이 나란히 놓여 무엇이 무엇인지 알 수\n 없다. 값을 비틀어 벌리는 대신 **고르는 자리에서 경계를 긋는다.**\n */\n const entries = Object.entries(palette)\n const painted = entries.filter(([token]) => !token.startsWith('palette.status-'))\n const lit = entries.filter(([token]) => token.startsWith('palette.status-'))\n\n /** 자체발광. 없으면 안 빛나는 부품이다. */\n const glow = material.emissive\n\n return html`\n <section>\n <h3>${i18next.t('label.material')}</h3>\n\n <div field>\n <label>${i18next.t('label.palette-token')}</label>\n <div swatches>\n ${this.swatches(painted, material.token)}\n </div>\n ${lit.length\n ? html`\n <label group>${i18next.t('label.status-colors')}</label>\n <div swatches>\n ${this.swatches(lit, material.token)}\n </div>\n `\n : ''}\n <span token>${material.token ?? i18next.t('label.no-color-chosen')}</span>\n </div>\n\n <div field>\n <label>${i18next.t('label.preset')}</label>\n <select\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.putMaterial({ preset: (value || undefined) as MaterialPreset | undefined })\n }}\n >\n <option value=\"\" ?selected=${!material.preset}>${i18next.t('label.none')}</option>\n ${MATERIAL_PRESETS.map(\n preset => html`\n <option value=${preset} ?selected=${preset === material.preset}>\n ${i18next.t('label.preset-' + preset)}\n </option>\n `\n )}\n </select>\n </div>\n\n <!-- 값이 없으면 각지게 그린다 — 형식이 그렇게 못 박았다. 체크상자도 그대로 보인다 -->\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${material.flatShading ?? true}\n @change=${(e: Event) => this.putMaterial({ flatShading: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('label.flat-shading')}\n </label>\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!material.transparent}\n @change=${(e: Event) => this.putMaterial({ transparent: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('label.transparent')}\n </label>\n\n <div field>\n <label>${i18next.t('label.emissive')}</label>\n <p cost>${i18next.t('text.emissive-explained')}</p>\n\n <div slider>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n .value=${String(glow?.intensity ?? 0)}\n @input=${(e: Event) => this.putEmissive(Number((e.target as HTMLInputElement).value))}\n />\n <output>${(glow?.intensity ?? 0).toFixed(2)}</output>\n </div>\n </div>\n\n ${glow\n ? html`\n <div field>\n <label>${i18next.t('label.emissive-color')}</label>\n <div swatches>\n <button\n swatch\n same\n ?on=${!glow.token}\n title=${i18next.t('label.same-as-off')}\n style=\"--swatch:${(material.token && palette[material.token]) || palette['palette.neutral']}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token: undefined } })}\n ></button>\n ${lit.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === glow.token}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token } })}\n ></button>\n `\n )}\n </div>\n <span token>${glow.token ?? i18next.t('label.same-as-off')}</span>\n </div>\n\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!glow.on}\n @change=${(e: Event) =>\n this.putMaterial({ emissive: { ...glow, on: (e.target as HTMLInputElement).checked } })}\n />\n ${i18next.t('label.on-by-default')}\n </label>\n <p cost>${i18next.t('text.on-by-default-explained')}</p>\n `\n : ''}\n\n </section>\n `\n }\n\n /**\n * 인스턴스 크기가 바뀔 때 이 부품이 어떻게 변하나.\n *\n * ## 늘어나는 축은 아직 못 보여 준다\n *\n * `stretch` 는 형식에 축을 적는 자리가 없어서 그리는 쪽이 **가장 길게 뻗은 축**으로\n * 정한다. 그 답을 화면에 적어 주려면 규칙을 가진 쪽에 물어야 하는데, 그 함수\n * (`stretchAxisOf` · `stretchAxis`)가 아직 배포된 판에 없다.\n *\n * 화면이 제 나름대로 계산하지 않는다. 규칙이 두 벌이 되면 보이는 축과 실제로 늘어나는\n * 축이 갈리고, 그 어긋남은 화면을 믿게 만든 다음에 드러난다. 배포되면 잇는다.\n *\n * ## 값이 있는 것을 밝힌다\n *\n * `scale` 이 아닌 부품은 합쳐 그릴 수 없어 draw call 을 하나 더 쓴다. 그 비용이\n * 성숙도의 묶음 수에 그대로 잡힌다. 고르는 자리에서 미리 말해 준다.\n */\n private renderBehaviour(part: FigurePart) {\n const sizing = part.sizing ?? 'scale'\n const repeat = part.repeat ?? { axis: 'x' as Axis, pitch: 0 }\n\n return html`\n <section>\n <h3>${i18next.t('label.sizing-rule')}</h3>\n <select @change=${(e: Event) => this.put({ sizing: (e.target as HTMLSelectElement).value as never })}>\n ${SIZING_RULES.map(\n rule => html`\n <option value=${rule} ?selected=${rule === sizing}>${i18next.t('label.sizing-' + rule)}</option>\n `\n )}\n </select>\n <p warn>${i18next.t('text.sizing-' + sizing + '-explained')}</p>\n\n ${sizing === 'scale'\n ? nothing\n : html`<p cost>${i18next.t('text.sizing-costs-one-draw-call')}</p>`}\n ${sizing === 'repeat'\n ? html`<p cost>${i18next.t('text.repeat-edits-the-unit')}</p>`\n : nothing}\n\n ${sizing === 'repeat'\n ? html`\n <div field>\n <label>${i18next.t('label.repeat-axis')}</label>\n <select\n @change=${(e: Event) =>\n this.put({ repeat: { ...repeat, axis: (e.target as HTMLSelectElement).value as Axis } })}\n >\n ${(['x', 'y', 'z'] as const).map(\n axis => html`<option value=${axis} ?selected=${axis === repeat.axis}>${axis}</option>`\n )}\n </select>\n </div>\n <div field>\n <label>${i18next.t('label.repeat-pitch')}</label>\n ${this.number('', repeat.pitch, value => this.put({ repeat: { ...repeat, pitch: value } }), 1)}\n <p cost>${i18next.t('text.repeat-pitch-explained')}</p>\n </div>\n `\n : nothing}\n </section>\n `\n }\n\n /**\n * 숫자 칸.\n *\n * 빈 칸이나 글자를 넣었을 때 0 으로 바꿔치지 않는다 — 저작자가 지우는 중일 수 있고,\n * 조용히 0 이 되면 부품이 사라진 것처럼 보인다. 숫자가 아니면 그냥 안 받는다.\n */\n private number(label: string, value: number, set: (value: number) => void, min?: number) {\n return html`\n <div cell>\n ${label ? html`<span axis>${label}</span>` : nothing}\n <input\n type=\"number\"\n .value=${String(value)}\n min=${min ?? nothing}\n @change=${(e: Event) => {\n const raw = (e.target as HTMLInputElement).value\n const parsed = Number(raw)\n if (raw === '' || !Number.isFinite(parsed)) {\n // 되돌려 놓는다. 안 그러면 화면과 정본이 갈린다.\n ;(e.target as HTMLInputElement).value = String(value)\n return\n }\n set(parsed)\n }}\n />\n </div>\n `\n }\n\n /**\n * 도형을 바꾼다.\n *\n * 도형이 바뀌면 그 도형에 뜻 없는 값이 남는다. 남겨 두면 검증이 「무시됨」 위반을\n * 내므로 여기서 걷는다.\n */\n private changePrimitive(primitive: PrimitiveKind) {\n this.change(part => {\n const next: FigurePart = { ...part, primitive }\n if (!SEGMENTED_PRIMITIVES.includes(primitive)) delete next.segments\n if (!EXTRUDE_PRIMITIVES.includes(primitive)) delete next.shape\n if (SEGMENTED_PRIMITIVES.includes(primitive) && next.segments === undefined) {\n next.segments = SEGMENT_PRESETS[1]\n }\n return next\n })\n }\n\n private put(patch: Partial<FigurePart>) {\n this.change(part => ({ ...part, ...patch }))\n }\n\n private putTransform(patch: Record<string, unknown>) {\n this.change(part => ({ ...part, transform: { ...part.transform, ...patch } as FigurePart['transform'] }))\n }\n\n /**\n * 발광 세기를 고친다. **0 이면 블록을 통째로 지운다.**\n *\n * 세기 0 인 발광을 남겨 두면 「안 빛나는데 램프인 것」이 되어, 재질 키가 갈려 묶음이\n * 헛되이 하나 는다. 0 은 「램프가 아니다」로 읽는 것이 맞다.\n *\n * `Number(value) || undefined` 를 쓰면 안 된다 — 0 이 falsy 라 변경으로 안 잡혀 옛\n * 값이 그대로 남는다. 실제로 램프가 안 꺼졌다.\n */\n private putEmissive(intensity: number) {\n // `chosen` 은 씬 모델이다. 형식으로 옮긴 값은 `partToFigure` 이 만든 것이라 여기서 다시 꺼낸다.\n const glow = this.board && this.chosen ? partToFigure(this.chosen, this.board).material?.emissive : undefined\n\n this.putMaterial({ emissive: intensity > 0 ? { ...glow, intensity } : undefined })\n }\n\n private putMaterial(patch: Record<string, unknown>) {\n this.change(part => ({\n ...part,\n material: { ...(part.material ?? {}), ...patch } as FigurePart['material']\n }))\n }\n\n /**\n * 수정한 것을 씬 모델로 되돌려 알린다.\n *\n * 사용자는 X·Y·Z 로 수정하고, 모델러 페이지는 씬 모델을 갖는다. 그 사이를 여기서\n * 잇는다 — 변환해서 수정하고 다시 되돌린다. **왕복 변환이 무손실이라야** 위치 하나\n * 고쳤을 뿐인데 회전값이 조금씩 밀리는 일이 없다(`fold.test.ts` 가 이를 확인한다).\n */\n private change(edit: (part: FigurePart) => FigurePart) {\n const part = this.chosen\n const board = this.board\n if (!part || !board) return\n\n this.emit(partFromFigure(edit(partToFigure(part, board)), board))\n }\n\n /** 정밀 도구를 시킨다. 정본은 저작면이 갖는다 — 여기서는 알리기만 한다. */\n private ask(what: string, axis: string) {\n this.dispatchEvent(\n new CustomEvent('part-tool', {\n detail: { index: this.selected, tool: what, axis },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private emit(part: PartModel) {\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index: this.selected, part }, bubbles: true, composed: true })\n )\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-inspector.js","sourceRoot":"","sources":["../../client/modeller/figure-inspector.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, svg } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport {\n EXTRUDE_PRIMITIVES,\n MATERIAL_PRESETS,\n PRIMITIVE_KINDS,\n SEGMENTED_PRIMITIVES,\n SEGMENT_PRESETS,\n SIZING_RULES,\n trianglesOf\n} from '@hatiolab/figure-model'\nimport { ANCHOR_RULES, PART_ROLES, REPEAT_LIMIT, anchorOfPart, repeatPlanOfPart } from '@hatiolab/figure-model'\nimport type { AnchorRule, Axis, FigurePart, MaterialPreset, PartRole, PrimitiveKind } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\n\nimport { partToFigure, partFromFigure } from './figure-source.js'\nimport type { BoardModel, PartModel } from './figure-source.js'\nimport { canMirror } from './part-edits.js'\n\n/**\n * 고른 부품의 속성.\n *\n * ## 여기가 사용자 용어로 보여 주는 자리다\n *\n * 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 — 보드 위에 부품 컴포넌트가 놓인 상태.\n * 그런데 사용자에게 보여야 하는 것은 씬 속성 이름(`left`·`top`·`zPos`)이 아니라\n * **X·Y·Z** 다. 다른 3D 도구가 모두 그렇게 부르고, 위치도 부품의 **중심**으로 읽는다.\n *\n * 그래서 이 패널은 받은 부품을 FigureSource 형태로 **변환해서 보여 주고, 수정한 것을\n * 다시 씬 모델로 되돌려 알린다.** 변환은 `figure-source.ts` 가 한다 — 여기서 축을 다시 계산하지\n * 않는다.\n *\n * ## 있는 것만 보여 준다\n *\n * 분할 수는 곡면이 있는 도형에만, 모서리 굴림은 단면을 뽑는 도형에만 뜻이 있다.\n * 뜻 없는 칸을 회색으로 늘어놓으면 저작자가 「왜 안 먹지」를 고민하게 된다 —\n * 아예 내지 않는다.\n *\n * ## 이름은 저장되는 식별자다\n *\n * `FigurePart.name` 은 바인딩·애니메이션·슬롯이 가리키는 이름이다. 고치면 배치된\n * 인스턴스의 바인딩이 **조용히** 끊긴다. 그래서 이름 칸에만 경고를 붙인다.\n *\n * ## 「상태 색이 붙는 자리」를 걷었다\n *\n * `FigurePart.materialSlot`(당시 이름은 `slot`) 을 고르는 칸이 있었는데 **아무 일도 하지 않았다.** things-scene 의\n * figure 런타임이 `slot` 을 읽지 않는다 — 선언 한 줄이 전부다. 되는 줄 알고 만들었고,\n * 확인하지 않은 것이 잘못이다.\n *\n * 그리고 되게 만들더라도 이 모양이 아니다. 상태 색은 특별한 것이 아니라 **속성 하나**이고,\n * 툴팁·라벨·표시 여부와 같은 자리에서 다뤄져야 한다. 그 설계가 정해지면(figure-model#2)\n * 이 자리에 **속성 선언과 부품 바인딩** 편집기가 들어온다.\n *\n * 그때까지 빈 약속을 두지 않는다. 아무 일도 안 하는 칸은 없느니만 못하다.\n *\n * ## 고치지 않는다\n *\n * 정본 한 벌은 저작면이 갖는다. 여기서는 바뀐 부품을 알릴 뿐이다.\n */\n@customElement('figure-inspector')\nexport class FigureInspector 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-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\n\n /*\n ── 자리를 아끼는 판 ──────────────────────────────────────────────────────\n 속성은 앞으로 더 늘어난다(능력 · 포트 · 애니메이션 · 기준정보). 지금 여백을 그대로 두면\n 그것들이 들어올 자리가 없다. 절 여백을 6/10 으로, 제목 아래를 4px 로 줄였다 — 글자 크기와\n 칸 높이는 건드리지 않는다(작게 만드는 것이 아니라 **빈 곳을 줄이는 것**이다).\n */\n section {\n padding: 6px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n h3 {\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 0 0 4px 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\n label {\n display: flex;\n align-items: center;\n gap: 3px;\n margin-bottom: 2px;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /*\n 도움말 물음표. **글자 줄을 하나 먹지 않는다** — 라벨·제목 줄의 남는 폭에 얹힌다.\n 아이콘만 두고 테두리를 안 두는 이유는 이것이 누를 것이라기보다 「더 있다」는 표시라서다.\n */\n button[help] {\n display: inline-flex;\n align-items: center;\n padding: 0;\n margin: 0;\n color: var(--md-sys-color-outline);\n background: none;\n border: none;\n cursor: pointer;\n }\n button[help] md-icon {\n --md-icon-size: 13px;\n font-size: 13px;\n }\n button[help]:hover,\n button[help][aria-expanded='true'] {\n color: var(--md-sys-color-primary);\n }\n\n /* 편 도움말. 라벨 아래 한 칸 들여 붙인다 — 무엇에 딸린 말인지 보이게. */\n p[hint] {\n margin: 2px 0 4px 0;\n padding-left: 6px;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border-left: 2px solid var(--md-sys-color-outline-variant);\n }\n\n input,\n select {\n width: 100%;\n box-sizing: border-box;\n padding: 4px 7px;\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input:focus,\n select:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n\n /*\n 숫자 칸 — 라벨을 칸 **안** 왼쪽에 둔다.\n\n 벡터 저작도구(Sketch·Figma)가 X·Y·W·H 를 이렇게 둔다. 라벨을 위에 얹으면 줄마다\n 두 줄을 먹어서, 자리·크기·회전 아홉 칸이 화면 절반을 차지한다. 안에 두면 한 줄에\n 셋이 들어가고 무엇이 무엇인지도 붙어 읽힌다.\n */\n div[cell] {\n display: flex;\n align-items: center;\n gap: 2px;\n padding-left: 6px;\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n div[cell]:focus-within {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n border-color: transparent;\n }\n div[cell] span[axis] {\n flex: none;\n font-size: 0.64rem;\n letter-spacing: 0.02em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[cell] input {\n min-width: 0;\n padding: 4px 6px 4px 2px;\n font-family: var(--mono-font, monospace);\n text-align: right;\n background: none;\n border: none;\n border-radius: 0;\n }\n div[cell] input:focus {\n outline: none;\n }\n\n /* 축 셋을 한 줄에 — 좌표는 셋이 같이 읽혀야 한다 */\n div[triple] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n div[field] + div[field],\n div[triple] + div[field],\n div[field] + div[triple] {\n /* 칸 사이 7 → 5. 칸 자체는 그대로 두고 사이만 좁힌다. */\n margin-top: 5px;\n }\n\n /*\n 정밀 제작 도구.\n\n 보드 편집기는 자산을 배치하는 곳이라 대충 놓아도 되지만, 여기는 자산을 만드는\n 곳이다. 「몸통 한가운데」·「바닥에 붙이기」·「반대쪽에도 하나」를 손으로 계산하면\n 늘 조금씩 어긋나고, 기준 치수를 바꾸면 전부 다시 해야 한다.\n */\n div[tools] {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n margin-top: 6px;\n }\n div[tools] button {\n padding: 3px 8px;\n font: var(--label-font, inherit);\n font-size: 0.7rem;\n color: 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 div[tools] button[disabled] {\n opacity: 0.35;\n cursor: default;\n }\n div[tools] button: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 div[tools] span[sep] {\n width: 1px;\n margin: 2px 3px;\n background-color: var(--md-sys-color-outline-variant);\n }\n\n /* 지금 어느 축이 늘어나나 — 크기를 고치면 바뀔 수 있어서 값으로 적는다 */\n p[axis-now] {\n margin: 4px 0 0 0;\n font-size: 0.7rem;\n color: var(--md-sys-color-primary);\n }\n /* 이 선택이 무엇을 무는지 — 성숙도의 묶음 수에 그대로 잡힌다 */\n p[cost] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 이름을 바꾸면 무엇이 끊기는지 말한다 */\n p[warn] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-tertiary);\n }\n\n /* 분할 수 프리셋 — 자유 입력보다 이 셋을 권한다 */\n div[presets] {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n div[presets] button {\n flex: 1;\n padding: 3px 0;\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: 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 div[presets] button[on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 분할 수 선택 — 모양과 삼각형 수를 **함께** 보여 준다.\n\n 숫자만 늘어놓으면 8 과 16 이 무엇이 다른지 알 수 없다. 로우폴리에서 이 선택이\n 곧 「몇 개까지 편하게 놓을 수 있나」를 정하므로, 각지는 정도와 그 값이 무는\n 삼각형 수를 한자리에서 보아야 한다.\n\n 실루엣은 지어낸 그림이 아니다 — 곡면 프리미티브를 위에서 본 윤곽이 정확히\n 정n각형이다. 삼각형 수는 화면이 셈하지 않고 figure-model 의 trianglesOf 가 낸다.\n */\n div[segments] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n /* 원 유지 · 끄기 — 고르는 값이라 분할 수와 같은 모양으로 둔다 */\n div[choices] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 4px;\n }\n button[choice] {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 1px;\n padding: 6px 8px;\n text-align: left;\n font: var(--label-font, inherit);\n color: 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[choice]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[choice][on] {\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[choice] small {\n opacity: 0.75;\n }\n button[seg] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n padding: 6px 2px 4px 2px;\n font: var(--label-font, inherit);\n color: 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[seg]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[seg][on] {\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[seg] svg {\n display: block;\n /* 작으면 12 와 16 이 둘 다 동그라미로 보인다 — 각이 읽힐 만큼은 키운다 */\n width: 40px;\n height: 40px;\n }\n button[seg] svg polygon {\n fill: var(--md-sys-color-surface-container-highest);\n stroke: currentColor;\n stroke-width: 1.1;\n stroke-linejoin: miter;\n }\n button[seg][on] svg polygon {\n fill: var(--md-sys-color-surface-container-lowest);\n }\n button[seg] b {\n font-size: 0.8rem;\n font-weight: 700;\n line-height: 1.1;\n }\n button[seg] small {\n font-size: 0.62rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[seg][on] small {\n color: inherit;\n }\n /* 프리셋 밖의 값도 쓸 수 있다. 다만 권하는 쪽을 크게 둔다 */\n details[free] {\n margin-top: 6px;\n }\n details[free] summary {\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n cursor: pointer;\n }\n details[free] div[cell] {\n margin-top: 4px;\n }\n\n /*\n 색 견본.\n\n 토큰을 글자로 받지 않는다. things-scene 의 팔레트 선언이 「여기 든 이름이 저작에서\n 고를 수 있는 전부이고, 자유색을 허용하지 않는 것이 감각적인 로우폴리의 첫 규칙」\n 이라고 못박고 있다. 글자 칸으로 두면 그 규칙이 화면에서 사라지고, 오타 하나로\n resolveToken 이 던진다.\n */\n div[swatches] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n /*\n 두 무리의 경계. 「칠한 색」과 「켜진 색」이 한 줄에 섞이면 accent(주황)와\n status-warning(주황)이 나란히 놓여 무엇이 무엇인지 알 수 없다.\n */\n label[group] {\n margin-top: 8px;\n padding-top: 8px;\n font-size: var(--ui-t-micro, 11px);\n letter-spacing: 0.03em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 값이 옆에 붙어야 끌면서 얼마인지 안다 */\n div[slider] {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n div[slider] input[type='range'] {\n flex: 1;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n div[slider] output {\n flex: none;\n width: 2.6em;\n text-align: right;\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[swatch] {\n width: 26px;\n height: 26px;\n padding: 0;\n background-color: var(--swatch);\n border: 1px solid rgba(0, 0, 0, 0.16);\n border-radius: 50%;\n cursor: pointer;\n }\n button[swatch][on] {\n /* 고른 것은 테두리가 아니라 고리로 표시한다 — 색 자체를 가리지 않으려고 */\n box-shadow:\n 0 0 0 2px var(--md-sys-color-surface-container-lowest),\n 0 0 0 4px var(--md-sys-color-primary);\n }\n span[token] {\n display: block;\n margin-top: 6px;\n font-family: var(--mono-font, monospace);\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n label[toggle] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 7px 0 0 0;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n label[toggle] input {\n width: auto;\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\n 전에는 그냥 글자였고, 옆의 이름 칸과 나란히 있으니 눌러도 되는 것처럼 보였다. 자물쇠와\n 칸 없는 바탕이 「읽는 값」이라고 먼저 말한다.\n */\n code[fixed] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 6px;\n background-color: var(--md-sys-color-surface-container-high, rgba(0, 0, 0, 0.05));\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n font-family: var(--mono-font, monospace);\n font-size: 0.76rem;\n }\n\n code[fixed] md-icon {\n --md-icon-size: 13px;\n opacity: 0.7;\n }\n `\n ]\n\n /** 보드. 위치를 보드 중심 기준으로 바꾸려면 이것이 있어야 한다. */\n /** 도형의 표시 이름. 정본은 저작면이 들고, 여기서는 보여 주고 바꾼다(`figure-rename` 으로 알린다). */\n @property({ type: String }) figureName = ''\n\n /** 펴 둔 도움말. 부품을 바꿔도 유지된다 — 매번 다시 펴게 하지 않는다. */\n @state() private opened = new Set<string>()\n\n @property({ type: Object }) board?: BoardModel\n /** 보드 위의 부품들. 씬 모델 그대로다. */\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Number }) selected = -1\n\n /** `part` 는 HTMLElement 의 속성(DOMTokenList)이다 — 가리면 형이 어긋난다. */\n private get chosen(): PartModel | undefined {\n return this.parts?.[this.selected]\n }\n\n render() {\n const part = this.chosen\n if (!part || !this.board) {\n return html`\n ${this.renderFigure()}\n <section>\n <h3>${i18next.t('figure.label.properties')}</h3>\n <p quiet>${i18next.t('figure.text.select-a-part-to-edit')}</p>\n </section>\n `\n }\n\n // 여기부터는 사용자 용어다 — X·Y·Z, 위치는 부품 중심, 회전은 도(degree).\n const shown = partToFigure(part, this.board)\n\n /*\n 역할을 먼저 묻는다.\n\n 자리(slot)로 지정한 부품은 **그려지지 않는다.** 그 사실을 모른 채 재질부터 고르면 안 쓰일\n 색을 고르게 된다. 그리고 자리는 도형이 능력을 질 때만 뜻이 있으므로, 능력이 없으면 이\n 묶음은 아예 나오지 않는다 — 해 둬도 아무 일도 안 일어나는 칸을 보여 주지 않는다.\n */\n return html`\n ${this.renderFigure()} ${this.renderIdentity(shown)} ${this.renderRole(shown)}\n ${this.renderPlacement(shown)} ${this.renderShape(shown)} ${this.renderMaterial(shown)}\n ${this.renderBehaviour(shown)}\n `\n }\n\n /**\n * ── 이 부품이 능력에서 맡는 자리 ────────────────────────────────────────────\n *\n * 「능력」 탭이 도형 전체가 무엇을 하는지 정하고, 어느 부품이 그것을 받쳐 주는지는 여기서\n * 정한다. **부품을 고치는 곳이 한 군데여야** 하기 때문이다 — 목록을 따로 두면 같은 부품을\n * 두 화면에서 고치게 되고, 저작자는 어느 쪽이 이기는지 매번 생각해야 한다.\n *\n * 칸 수는 `repeat` 가 있으면 받지 않는다. 반복하는 자리의 칸 수는 **인스턴스 크기**가 정한다 —\n * 컨베이어가 길어지면 실을 자리가 늘어나는 것이 그것이고, 여기 적은 값은 안 쓰인다.\n */\n private renderRole(part: FigurePart) {\n if (!this.board?.capabilities?.length) return nothing\n\n const roles = part.capability?.roles ?? []\n const repeats = part.sizing === 'repeat' && !!part.repeat\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.role-in-capability')}${this.help('figure.text.anchors-are-not-drawn')}</h3>\n\n ${(PART_ROLES as readonly PartRole[]).map(\n one => html`\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${live(roles.includes(one))}\n @change=${(e: Event) => this.toggleRole(one, (e.target as HTMLInputElement).checked)}\n />\n ${i18next.t('figure.label.role-' + one)}\n </label>\n `\n )}\n ${roles.length === 0 ? html`<p quiet>${i18next.t('figure.text.role-none-means-drawn')}</p>` : nothing}\n\n ${roles.includes('slot')\n ? html`\n <div field>\n <label>${i18next.t('figure.label.accepts')}${this.help('figure.text.accepts-explained')}</label>\n <input\n .value=${(part.capability?.accepts ?? []).join(', ')}\n placeholder=${i18next.t('figure.text.accepts-anything')}\n @change=${(e: Event) => this.setAccepts((e.target as HTMLInputElement).value)}\n />\n </div>\n <div field>\n <label>${i18next.t('figure.label.slot-capacity')}</label>\n ${repeats\n ? html`<p quiet>${i18next.t('figure.text.capacity-from-repeat')}</p>`\n : this.number('', part.capability?.capacity ?? 1, value => this.setCapacity(value), 1)}\n </div>\n `\n : nothing}\n </section>\n `\n }\n\n /**\n * 역할 하나를 켜고 끈다.\n *\n * **여럿을 함께 맡을 수 있다.** AGV 의 상판은 물건을 놓는 자리이면서 그 자리로 물건이\n * 들어오고 나간다. 그래서 하나를 고르는 선택 상자가 아니라 확인 상자다.\n *\n * 마지막 역할을 끄면 `capability` 를 통째로 지운다. 빈 배열을 남기면 형식이 오류로 보고,\n * 저작자가 보기에는 아무 역할도 없는 평범한 부품인데 발행이 막힌다.\n */\n private toggleRole(role: PartRole, on: boolean) {\n this.change(part => {\n const roles = (part.capability?.roles ?? []).filter(one => one !== role)\n if (on) roles.push(role)\n\n if (roles.length === 0) {\n const { capability, ...rest } = part\n return rest as FigurePart\n }\n\n // 형식이 적은 순서를 지킨다 — 켠 순서대로 두면 같은 뜻이 다른 값으로 저장된다.\n const ordered = (PART_ROLES as readonly PartRole[]).filter(one => roles.includes(one))\n return { ...part, capability: { ...part.capability, roles: ordered } }\n })\n }\n\n private setAccepts(raw: string) {\n const accepts = raw\n .split(',')\n .map(one => one.trim())\n .filter(one => one.length > 0)\n\n this.change(part => {\n const spec = { ...part.capability, roles: part.capability?.roles ?? ['slot' as PartRole] }\n // 빈 목록은 제한하지 않는다는 뜻이다. 빈 배열을 저장하면 아무것도 못 받는 것으로 읽힌다.\n if (accepts.length > 0) spec.accepts = accepts\n else delete spec.accepts\n return { ...part, capability: spec as NonNullable<FigurePart['capability']> }\n })\n }\n\n private setCapacity(value: number) {\n this.change(part => {\n const spec = { ...part.capability, roles: part.capability?.roles ?? ['slot' as PartRole] }\n if (Number.isFinite(value) && value >= 1) spec.capacity = Math.floor(value)\n else delete spec.capacity\n return { ...part, capability: spec as NonNullable<FigurePart['capability']> }\n })\n }\n\n /**\n * 도형 자체 — 이름과 타입.\n *\n * ## 왜 머리줄이 아니라 여기인가\n *\n * 저작면 위에 줄을 하나 더 두고 이름 칸을 놓았었다. 앱에서는 셸이 페이지 제목으로 그 이름을\n * 이미 그리므로 같은 글자가 두 번 나오고, 캔버스가 주인공인 화면에서 그 줄만큼 높이를 잃었다.\n * 속성 패널은 「이 도형이 무엇인가」를 다루는 자리이므로 이름도 여기 산다.\n *\n * ## 타입은 읽기만 한다\n *\n * 저장되는 식별자다. 배치된 보드가 이 이름으로 타입을 찾으므로 만든 뒤에는 못 바꾼다.\n */\n private renderFigure() {\n return html`\n <section>\n <h3>${i18next.t('figure.label.figure')}</h3>\n <div field>\n <label>${i18next.t('figure.label.name')}</label>\n <input\n .value=${this.figureName}\n placeholder=${i18next.t('figure.text.figure-display-name')}\n @change=${(e: Event) =>\n this.dispatchEvent(\n new CustomEvent('figure-rename', {\n detail: { name: (e.target as HTMLInputElement).value },\n bubbles: true,\n composed: true\n })\n )}\n />\n </div>\n <div field>\n <label>${i18next.t('figure.label.figure-type')}${this.help('figure.text.type-name-cannot-change')}</label>\n <code fixed title=${i18next.t('figure.text.type-name-cannot-change')}>\n <md-icon>lock</md-icon>${this.board?.figureType ?? ''}\n </code>\n </div>\n <!--\n 왜 못 고치는지 **적어 둔다.** 전에는 tooltip 에만 있었고, 그러면 눌러 보고 안 되는 것을\n 알게 된다 — 화면이 안 되는 이유를 말하지 않으면 사용자가 자기 실수라고 생각한다.\n -->\n </section>\n `\n }\n\n private renderIdentity(part: FigurePart) {\n return html`\n <section>\n <h3>${i18next.t('figure.label.part')}</h3>\n <div field>\n <label>${i18next.t('figure.label.name')}${this.help('figure.text.renaming-a-part-breaks-bindings')}</label>\n <input\n .value=${part.name}\n @change=${(e: Event) => this.put({ name: (e.target as HTMLInputElement).value })}\n />\n </div>\n <div field>\n <label>${i18next.t('figure.label.shape')}</label>\n <select @change=${(e: Event) => this.changePrimitive((e.target as HTMLSelectElement).value as PrimitiveKind)}>\n ${PRIMITIVE_KINDS.map(\n kind => html`\n <option value=${kind} ?selected=${kind === part.primitive}>\n ${i18next.t('figure.label.primitive-' + kind)}\n </option>\n `\n )}\n </select>\n </div>\n </section>\n `\n }\n\n private renderPlacement(part: FigurePart) {\n const t = part.transform\n const rotation = t.rotation ?? {}\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.placement')}</h3>\n\n <label>${i18next.t('figure.label.part-position')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), t.position[axis], value =>\n this.putTransform({ position: { ...t.position, [axis]: value } })\n )\n )}\n </div>\n\n <div field>\n <label>${i18next.t('figure.label.part-size')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(\n axis.toUpperCase(),\n t.size[axis],\n value => this.putTransform({ size: { ...t.size, [axis]: value } }),\n 1\n )\n )}\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('figure.label.tools')}</label>\n <div tools>\n <button title=${i18next.t('figure.text.center-on-x')} @click=${() => this.ask('center', 'x')}>\n ${i18next.t('figure.button.center-x')}\n </button>\n <button title=${i18next.t('figure.text.center-on-z')} @click=${() => this.ask('center', 'z')}>\n ${i18next.t('figure.button.center-z')}\n </button>\n <button title=${i18next.t('figure.text.put-on-floor')} @click=${() => this.ask('floor', 'y')}>\n ${i18next.t('figure.button.to-floor')}\n </button>\n <span sep></span>\n ${(['x', 'z'] as const).map(axis => {\n // 이미 가운데면 대칭이 제자리다 — 겹친 부품이 조용히 생기지 않게 막는다\n const able = this.board ? canMirror(this.board, this.parts, this.selected, axis) : false\n return html`\n <button\n ?disabled=${!able}\n title=${i18next.t(able ? 'figure.text.mirror-explained' : 'figure.text.mirror-would-overlap')}\n @click=${() => this.ask('mirror', axis)}\n >\n ${i18next.t('figure.button.mirror-' + axis)}\n </button>\n `\n })}\n <span sep></span>\n <button title=${i18next.t('figure.text.snap-explained')} @click=${() => this.ask('snap', 'x')}>\n ${i18next.t('figure.button.snap')}\n </button>\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('figure.label.rotation-degrees')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), rotation[axis] ?? 0, value =>\n this.putTransform({ rotation: { ...rotation, [axis]: value } })\n )\n )}\n </div>\n </div>\n </section>\n `\n }\n\n /** 분할 수와 모서리 굴림 — 해당하는 도형에만 낸다. */\n private renderShape(part: FigurePart) {\n const segmented = SEGMENTED_PRIMITIVES.includes(part.primitive)\n const extruded = EXTRUDE_PRIMITIVES.includes(part.primitive)\n if (!segmented && !extruded) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.shape-detail')}</h3>\n ${segmented ? this.renderSegments(part) : nothing}\n ${segmented ? this.renderKeepRound(part) : nothing}\n ${extruded ? this.renderExtruded(part) : nothing}\n </section>\n `\n }\n\n /**\n * 민 단면 — 모서리와 **속을 파는 두께.**\n *\n * 벽 두께를 0 으로 두면 통짜다. 0 보다 크면 그만큼 안으로 들어온 테가 생기고, 그것이\n * 트레이 · 토트 · 통 · 케이스가 되는 자리다. 바닥은 비워 두면 벽과 같고, 0 으로 두면\n * 위아래가 다 뚫린 테두리가 된다.\n */\n private renderExtruded(part: FigurePart) {\n const hollow = part.shape?.hollow\n const wall = hollow?.wall ?? 0\n\n const reshape = (over: Record<string, unknown>) =>\n this.put({ shape: { ...(part.shape ?? {}), ...over } })\n\n return html`\n <div field>\n <label>${i18next.t('figure.label.corner-round')}</label>\n ${this.number('', part.shape?.round ?? 0, value => reshape({ round: value }))}\n </div>\n <div field>\n <label>${i18next.t('figure.label.hollow-wall')}</label>\n ${this.number('', wall, value =>\n reshape({ hollow: value > 0 ? { ...(hollow ?? {}), wall: value } : undefined })\n )}\n </div>\n ${wall > 0\n ? html`\n <div field>\n <label>${i18next.t('figure.label.hollow-floor')}</label>\n ${this.number('', hollow?.floor ?? wall, value => reshape({ hollow: { ...hollow, wall, floor: value } }))}\n </div>\n `\n : nothing}\n `\n }\n\n /**\n * 분할 수 — 모양과 무게를 함께 고른다.\n *\n * 삼각형 수는 화면이 셈하지 않는다. `trianglesOf` 에 그 분할 수를 가진 부품을 주고\n * 물어본다 — 화면이 제 나름의 공식을 들면 figure-model 이 규칙을 바꿔도 화면만\n * 옛 답을 계속 낸다.\n */\n /**\n * 원통을 원통으로 둘까 — **고르는 값이고, 값을 치른다.**\n *\n * 인스턴스가 축마다 다른 배율을 받으면 원기둥이 타원 기둥이 된다. 지키려면 제 변환이\n * 있어야 하고, 합친 형상은 변환을 하나만 받으므로 지키는 곡면 부품 하나가 draw call\n * 하나다. 저작자가 그 대가를 모르고 고르면 안 되므로 그 자리에 적는다.\n */\n private renderKeepRound(part: FigurePart) {\n const on = part.keepRound !== false\n\n return html`\n <div field>\n <label>${i18next.t('figure.label.keep-round')}</label>\n <div choices>\n <button choice ?on=${on} @click=${() => this.put({ keepRound: undefined })}>\n <b>${i18next.t('figure.text.keep-round-on')}</b>\n <small>${i18next.t('figure.text.keep-round-cost')}</small>\n </button>\n <button choice ?on=${!on} @click=${() => this.put({ keepRound: false })}>\n <b>${i18next.t('figure.text.keep-round-off')}</b>\n <small>${i18next.t('figure.text.keep-round-merged')}</small>\n </button>\n </div>\n </div>\n `\n }\n\n private renderSegments(part: FigurePart) {\n const current = part.segments ?? SEGMENT_PRESETS[1]\n\n return html`\n <div field>\n <label>${i18next.t('figure.label.segments')}</label>\n <div segments>\n ${SEGMENT_PRESETS.map(preset => {\n const triangles = trianglesOf({ ...part, segments: preset })\n return html`\n <button seg ?on=${current === preset} @click=${() => this.put({ segments: preset })}>\n ${this.silhouette(preset)}\n <b>${preset}</b>\n <small>${i18next.t('figure.text.n-triangles', { count: triangles })}</small>\n </button>\n `\n })}\n </div>\n\n <details free>\n <summary>${i18next.t('figure.text.other-segment-count')}</summary>\n ${this.number('', current, value => this.put({ segments: value }), 3)}\n </details>\n </div>\n `\n }\n\n /**\n * 분할 수의 실루엣.\n *\n * 곡면 프리미티브를 위에서 본 윤곽이 정확히 정n각형이다 — 원기둥의 단면도, 구의\n * 적도도. 그림을 지어내는 것이 아니라 실제로 만들어지는 모양이다.\n */\n private silhouette(segments: number) {\n const points: string[] = []\n for (let i = 0; i < segments; i++) {\n // 꼭짓점 하나가 위로 오게 -90도에서 시작한다\n const angle = -Math.PI / 2 + (i * 2 * Math.PI) / segments\n points.push(`${(16 + 13 * Math.cos(angle)).toFixed(2)},${(16 + 13 * Math.sin(angle)).toFixed(2)}`)\n }\n\n return svg`\n <svg viewBox=\"0 0 32 32\" aria-hidden=\"true\">\n <polygon points=${points.join(' ')}></polygon>\n </svg>\n `\n }\n\n /** 견본 한 줄. 무리마다 부른다. */\n private swatches(entries: [string, string][], chosen?: string) {\n return entries.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === chosen}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ token })}\n ></button>\n `\n )\n }\n\n private renderMaterial(part: FigurePart) {\n const material = part.material ?? {}\n const palette = activePalette() as Record<string, string>\n\n /*\n 두 무리로 나눠 보인다 — **칠한 색**과 **켜진 색**.\n\n `palette.status-*` 는 램프 · 표시등에 쓰라고 있는 색이다. 한 줄에 섞어 두면\n `accent`(주황)와 `status-warning`(주황)이 나란히 놓여 무엇이 무엇인지 알 수\n 없다. 값을 비틀어 벌리는 대신 **고르는 자리에서 경계를 긋는다.**\n */\n const entries = Object.entries(palette)\n const painted = entries.filter(([token]) => !token.startsWith('palette.status-'))\n const lit = entries.filter(([token]) => token.startsWith('palette.status-'))\n\n /** 자체발광. 없으면 안 빛나는 부품이다. */\n const glow = material.emissive\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.part-material')}</h3>\n\n <div field>\n <label>${i18next.t('figure.label.palette-token')}</label>\n <div swatches>\n ${this.swatches(painted, material.token)}\n </div>\n ${lit.length\n ? html`\n <label group>${i18next.t('figure.label.status-colors')}</label>\n <div swatches>\n ${this.swatches(lit, material.token)}\n </div>\n `\n : ''}\n <span token>${material.token ?? i18next.t('figure.label.no-color-chosen')}</span>\n </div>\n\n <div field>\n <label>${i18next.t('figure.label.preset')}</label>\n <select\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.putMaterial({ preset: (value || undefined) as MaterialPreset | undefined })\n }}\n >\n <option value=\"\" ?selected=${!material.preset}>${i18next.t('figure.label.none')}</option>\n ${MATERIAL_PRESETS.map(\n preset => html`\n <option value=${preset} ?selected=${preset === material.preset}>\n ${i18next.t('figure.label.preset-' + preset)}\n </option>\n `\n )}\n </select>\n </div>\n\n <!-- 값이 없으면 각지게 그린다 — 형식이 그렇게 못 박았다. 체크상자도 그대로 보인다 -->\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${material.flatShading ?? true}\n @change=${(e: Event) => this.putMaterial({ flatShading: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('figure.label.flat-shading')}\n </label>\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!material.transparent}\n @change=${(e: Event) => this.putMaterial({ transparent: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('figure.label.transparent')}\n </label>\n\n <div field>\n <label>${i18next.t('figure.label.emissive')}${this.help('figure.text.emissive-explained')}</label>\n\n <div slider>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n .value=${String(glow?.intensity ?? 0)}\n @input=${(e: Event) => this.putEmissive(Number((e.target as HTMLInputElement).value))}\n />\n <output>${(glow?.intensity ?? 0).toFixed(2)}</output>\n </div>\n </div>\n\n ${glow\n ? html`\n <div field>\n <label>${i18next.t('figure.label.emissive-color')}</label>\n <div swatches>\n <button\n swatch\n same\n ?on=${!glow.token}\n title=${i18next.t('figure.label.same-as-off')}\n style=\"--swatch:${(material.token && palette[material.token]) || palette['palette.neutral']}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token: undefined } })}\n ></button>\n ${lit.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === glow.token}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token } })}\n ></button>\n `\n )}\n </div>\n <span token>${glow.token ?? i18next.t('figure.label.same-as-off')}</span>\n </div>\n\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!glow.on}\n @change=${(e: Event) =>\n this.putMaterial({ emissive: { ...glow, on: (e.target as HTMLInputElement).checked } })}\n />\n ${i18next.t('figure.label.on-by-default')}\n </label>\n ${this.help('figure.text.on-by-default-explained')}\n `\n : ''}\n\n </section>\n `\n }\n\n /**\n * 인스턴스 크기가 바뀔 때 이 부품이 어떻게 변하나.\n *\n * ## 늘어나는 축은 아직 못 보여 준다\n *\n * `stretch` 는 형식에 축을 적는 자리가 없어서 그리는 쪽이 **가장 길게 뻗은 축**으로\n * 정한다. 그 답을 화면에 적어 주려면 규칙을 가진 쪽에 물어야 하는데, 그 함수\n * (`stretchAxisOf` · `stretchAxis`)가 아직 배포된 판에 없다.\n *\n * 화면이 제 나름대로 계산하지 않는다. 규칙이 두 벌이 되면 보이는 축과 실제로 늘어나는\n * 축이 갈리고, 그 어긋남은 화면을 믿게 만든 다음에 드러난다. 배포되면 잇는다.\n *\n * ## 값이 있는 것을 밝힌다\n *\n * `scale` 이 아닌 부품은 합쳐 그릴 수 없어 draw call 을 하나 더 쓴다. 그 비용이\n * 성숙도의 묶음 수에 그대로 잡힌다. 고르는 자리에서 미리 말해 준다.\n */\n private renderBehaviour(part: FigurePart) {\n const sizing = part.sizing ?? 'scale'\n const repeat = part.repeat ?? { axis: 'x' as Axis, pitch: 0 }\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.sizing-rule')}${this.help('figure.text.sizing-' + sizing + '-explained')}</h3>\n <select @change=${(e: Event) => this.put({ sizing: (e.target as HTMLSelectElement).value as never })}>\n ${SIZING_RULES.map(\n rule => html`\n <option value=${rule} ?selected=${rule === sizing}>${i18next.t('figure.label.sizing-' + rule)}</option>\n `\n )}\n </select>\n ${sizing === 'scale' ? nothing : this.help('figure.text.sizing-costs-one-draw-call')}\n ${sizing === 'repeat' ? this.help('figure.text.repeat-edits-the-unit') : nothing}\n\n ${this.renderAnchors(part)}\n\n ${sizing === 'repeat'\n ? html`\n <div field>\n <label>${i18next.t('figure.label.repeat-axis')}</label>\n <select\n @change=${(e: Event) =>\n this.put({ repeat: { ...repeat, axis: (e.target as HTMLSelectElement).value as Axis } })}\n >\n ${(['x', 'y', 'z'] as const).map(\n axis => html`<option value=${axis} ?selected=${axis === repeat.axis}>${axis}</option>`\n )}\n </select>\n </div>\n <div field>\n <label>${i18next.t('figure.label.repeat-pitch')}${this.help('figure.text.repeat-pitch-explained')}</label>\n ${this.number('', repeat.pitch, value => this.put({ repeat: { ...repeat, pitch: value } }), 1)}\n </div>\n ${this.renderRepeatCount(part)}\n `\n : nothing}\n </section>\n `\n }\n\n /**\n * ── 몇 개가 되나 ──────────────────────────────────────────────────────────\n *\n * 저작 화면은 축과 피치만 받고 있었다. 형식이 가진 반복 설정이 그 둘뿐이므로 편집할 것은\n * 다 있었는데, **결과인 개수를 어디에도 표시하지 않았다.** 피치 300 을 적은 저작자가 몇 개가\n * 되는지 알려면 미리보기를 세어야 했다.\n *\n * 개수는 그리는 쪽이 쓰는 `repeatPlan` 을 그대로 불러 얻는다. 여기서 다시 계산하면 화면이\n * 말하는 개수와 실제로 그려지는 개수가 달라진다.\n *\n * ## 두 가지 반복이 있다\n *\n * 컨베이어의 롤러는 길어지면 **개수가 늘고** 한 칸 간격은 그대로다. 셀트레이의 셀은 커지면\n * **셀이 커지고** 개수는 그대로다 — 몇 셀짜리 트레이냐는 화면 크기와 무관하다. 그 갈림을\n * 이 축의 anchor 가 정하는데 화면에는 그 말이 없었다. 그래서 어느 쪽인지 함께 적는다.\n */\n private renderRepeatCount(part: FigurePart) {\n const board = this.board\n if (!board) return nothing\n\n const base = { x: board.width, y: board.depth, z: board.height }\n const plan = repeatPlanOfPart(part, { x: 1, y: 1, z: 1 }, base)\n if (!plan) return nothing\n\n const grows = anchorOfPart(part, base, plan.axis) !== 'scale'\n\n return html`\n <div field>\n <label>${i18next.t('figure.label.repeat-count')}</label>\n <p quiet>\n ${i18next.t('figure.text.repeat-count-now', { count: plan.count })}\n ${plan.count >= REPEAT_LIMIT\n ? html`<br />${i18next.t('figure.text.repeat-count-capped', { limit: REPEAT_LIMIT })}`\n : ''}\n <br />${i18next.t(grows ? 'figure.text.repeat-grows-with-instance' : 'figure.text.repeat-keeps-count')}\n </p>\n </div>\n `\n }\n\n /**\n * ── 어느 면을 붙잡나 ──────────────────────────────────────────────────────\n *\n * **이 자리가 없었다.** 형식은 축마다 `anchor` 를 지고 다니고(`figure-source.ts` 가 왕복시킨다),\n * 발행 게이트는 「valve-wheel 의 anchor.z 를 max 로 두라」고 사유까지 말하는데, 저작면에는 그\n * 값을 바꿀 자리가 없었다 — 고치라는 말만 있고 고칠 문이 없었다.\n *\n * ## 「자동」이 기본이다\n *\n * 형식에서 `anchor` 는 적지 않아도 된다. 안 적으면 **가까운 면**을 붙잡고, 표본 열넷 중 어느\n * 것도 이 값을 적지 않고 제대로 선다. 그래서 축마다 첫 항목이 「자동」이고, 지금 자동이 무엇으로\n * 읽히는지를 **함께 보여 준다** — 그것을 모르면 무엇을 바꾸는지 모른 채 고르게 된다.\n *\n * 자동이 무엇인지는 형식에게 묻는다(`anchorOfPart`). 여기서 「위 절반이면 max」를 다시 세면\n * 화면이 말하는 면과 실제로 그려지는 면이 갈린다.\n *\n * ## `scale` 인 부품에도 보여 준다\n *\n * 처음에는 감췄다 — 형식 주석에 「`sizing` 이 `scale` 인 부품에는 뜻이 없다」고 적혀 있어서다.\n * **그 문장은 기본값에 대한 것이다.** `anchorOf` 는 적힌 값을 가장 먼저 보고(`if (said) return\n * said`), 그 뒤에야 `sizing` 을 본다. 즉 `scale` 부품에도 면을 적으면 그것이 이긴다.\n *\n * 감춰 두면 어긋남이 난다: 발행 게이트가 막는 MIXER 의 `valve-wheel` 이 `scale` 부품이고,\n * 게이트의 사유는 「그 부품의 anchor.z 를 max 로 두라」다. 고치라는 자리에 문을 닫아 둔 셈이었다.\n *\n * 대신 `scale` 일 때는 자동이 무엇인지가 「함께 커진다」로 보이므로, 저작자가 그것을 바꾸는\n * 것임을 알 수 있다.\n */\n /**\n * ── 도움말은 자리를 먹지 않는다 ─────────────────────────────────────────────\n *\n * 설명 문단이 열 줄 있었고 그 줄들이 패널 높이의 상당 부분이었다. 속성은 앞으로 더 늘어난다\n * (능력 · 포트 · 애니메이션 · 기준정보) — 늘 보이는 설명은 그 자리를 먼저 먹는다.\n *\n * 그래서 물음표로 접는다. **지우지 않는다** — 그 문장들은 「이걸 고르면 무엇이 달라지나」를\n * 말하는 것이고, 없으면 닫힌 목록을 고를 수 없다. 한 번 읽으면 되는 것이라 접어 두는 것이 맞다.\n *\n * 편 것은 **부품을 바꿔도 그대로 둔다**(`opened` 는 부품에 딸린 상태가 아니다). 같은 것을\n * 계속 고치는 사람이 매번 다시 펴야 하면 접은 것이 되레 방해가 된다.\n */\n private help(key: string) {\n const shown = this.opened.has(key)\n\n return html`\n <button\n help\n aria-label=${i18next.t('figure.label.help')}\n aria-expanded=${shown ? 'true' : 'false'}\n @click=${() => {\n shown ? this.opened.delete(key) : this.opened.add(key)\n this.requestUpdate()\n }}\n >\n <md-icon>${shown ? 'help' : 'help_outline'}</md-icon>\n </button>\n ${shown ? html`<p hint>${i18next.t(key)}</p>` : nothing}\n `\n }\n\n private renderAnchors(part: FigurePart) {\n const base = this.board\n ? { x: this.board.width, y: this.board.height, z: this.board.depth }\n : { x: 0, y: 0, z: 0 }\n\n return html`\n <div field>\n <!-- 「anchor.x」로 적힌 사유를 화면에서 찾을 수 있게 칸 이름에 그 낱말을 남긴다 -->\n <label>${i18next.t('figure.label.anchor')} (anchor)${this.help('figure.text.anchor-explained')}</label>\n ${(['x', 'y', 'z'] as const).map(axis => {\n const said = part.anchor?.[axis]\n const auto = anchorOfPart(part, base, axis)\n\n /*\n **면 이름은 축마다 다르다.** 처음에는 min 을 「아래·왼쪽·뒤 면」으로 한 줄에 다 적었는데,\n x 줄에서 「아래」와 「뒤」는 관계가 없어 읽는 사람이 무엇을 고르는지 몰랐다.\n\n 그리고 이름씨가 아니라 **동작**으로 적는다 — 「오른쪽 면」이 아니라 「오른쪽 면에 붙어\n 있는다」다. 「anchor.x 를 max 로 둔다」가 무슨 뜻인지 모르겠다는 말이 그 자리에서 나왔다.\n */\n /*\n **값 이름을 함께 보인다.** 동작만 적었더니(「오른쪽 면에 붙어 있는다») 발행 게이트가\n 「anchor.x 를 max 로 두라」고 말할 때 화면에서 그 낱말을 찾을 수 없었다 — 사용자가\n 그 자리에서 지적했다.\n\n `max` 는 정본에 저장되는 이름이고 서버 오류·형식 문서·게이트 사유가 다 그 이름을 쓴다.\n 숨기면 안 되는 종류다. 동작이 먼저고 이름이 괄호로 따라온다.\n */\n const nameOf = (rule: string) =>\n `${i18next.t(rule === 'min' || rule === 'max' ? `figure.label.anchor-${rule}-${axis}` : `figure.label.anchor-${rule}`)} (${rule})`\n\n return html`\n <div cell>\n <span axis>${axis}</span>\n <select\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n const anchor = { ...(part.anchor ?? {}) }\n /* 「자동」은 칸을 지우는 것이다 — 빈 값을 넣어 두면 형식이 그것을 값으로 읽는다. */\n if (value) {\n anchor[axis] = value as AnchorRule\n } else {\n delete anchor[axis]\n }\n this.put({ anchor: Object.keys(anchor).length ? anchor : undefined })\n }}\n >\n <option value=\"\" ?selected=${!said}>\n ${i18next.t('figure.label.anchor-auto', { rule: nameOf(auto) })}\n </option>\n ${ANCHOR_RULES.map(\n rule => html`<option value=${rule} ?selected=${rule === said}>${nameOf(rule)}</option>`\n )}\n </select>\n </div>\n `\n })}\n </div>\n `\n }\n\n /**\n * 숫자 칸.\n *\n * 빈 칸이나 글자를 넣었을 때 0 으로 바꿔치지 않는다 — 저작자가 지우는 중일 수 있고,\n * 조용히 0 이 되면 부품이 사라진 것처럼 보인다. 숫자가 아니면 그냥 안 받는다.\n */\n private number(label: string, value: number, set: (value: number) => void, min?: number) {\n return html`\n <div cell>\n ${label ? html`<span axis>${label}</span>` : nothing}\n <input\n type=\"number\"\n .value=${String(value)}\n min=${min ?? nothing}\n @change=${(e: Event) => {\n const raw = (e.target as HTMLInputElement).value\n const parsed = Number(raw)\n if (raw === '' || !Number.isFinite(parsed)) {\n // 되돌려 놓는다. 안 그러면 화면과 정본이 갈린다.\n ;(e.target as HTMLInputElement).value = String(value)\n return\n }\n set(parsed)\n }}\n />\n </div>\n `\n }\n\n /**\n * 도형을 바꾼다.\n *\n * 도형이 바뀌면 그 도형에 뜻 없는 값이 남는다. 남겨 두면 검증이 「무시됨」 위반을\n * 내므로 여기서 걷는다.\n */\n private changePrimitive(primitive: PrimitiveKind) {\n this.change(part => {\n const next: FigurePart = { ...part, primitive }\n if (!SEGMENTED_PRIMITIVES.includes(primitive)) delete next.segments\n if (!EXTRUDE_PRIMITIVES.includes(primitive)) delete next.shape\n if (SEGMENTED_PRIMITIVES.includes(primitive) && next.segments === undefined) {\n next.segments = SEGMENT_PRESETS[1]\n }\n return next\n })\n }\n\n private put(patch: Partial<FigurePart>) {\n this.change(part => ({ ...part, ...patch }))\n }\n\n private putTransform(patch: Record<string, unknown>) {\n this.change(part => ({ ...part, transform: { ...part.transform, ...patch } as FigurePart['transform'] }))\n }\n\n /**\n * 발광 세기를 고친다. **0 이면 블록을 통째로 지운다.**\n *\n * 세기 0 인 발광을 남겨 두면 「안 빛나는데 램프인 것」이 되어, 재질 키가 갈려 묶음이\n * 헛되이 하나 는다. 0 은 「램프가 아니다」로 읽는 것이 맞다.\n *\n * `Number(value) || undefined` 를 쓰면 안 된다 — 0 이 falsy 라 변경으로 안 잡혀 옛\n * 값이 그대로 남는다. 실제로 램프가 안 꺼졌다.\n */\n private putEmissive(intensity: number) {\n // `chosen` 은 씬 모델이다. 형식으로 옮긴 값은 `partToFigure` 이 만든 것이라 여기서 다시 꺼낸다.\n const glow = this.board && this.chosen ? partToFigure(this.chosen, this.board).material?.emissive : undefined\n\n this.putMaterial({ emissive: intensity > 0 ? { ...glow, intensity } : undefined })\n }\n\n private putMaterial(patch: Record<string, unknown>) {\n this.change(part => ({\n ...part,\n material: { ...(part.material ?? {}), ...patch } as FigurePart['material']\n }))\n }\n\n /**\n * 수정한 것을 씬 모델로 되돌려 알린다.\n *\n * 사용자는 X·Y·Z 로 수정하고, 모델러 페이지는 씬 모델을 갖는다. 그 사이를 여기서\n * 잇는다 — 변환해서 수정하고 다시 되돌린다. **왕복 변환이 무손실이라야** 위치 하나\n * 고쳤을 뿐인데 회전값이 조금씩 밀리는 일이 없다(`fold.test.ts` 가 이를 확인한다).\n */\n private change(edit: (part: FigurePart) => FigurePart) {\n const part = this.chosen\n const board = this.board\n if (!part || !board) return\n\n this.emit(partFromFigure(edit(partToFigure(part, board)), board))\n }\n\n /** 정밀 도구를 시킨다. 정본은 저작면이 갖는다 — 여기서는 알리기만 한다. */\n private ask(what: string, axis: string) {\n this.dispatchEvent(\n new CustomEvent('part-tool', {\n detail: { index: this.selected, tool: what, axis },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private emit(part: PartModel) {\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index: this.selected, part }, bubbles: true, composed: true })\n )\n }\n}\n"]}
|
|
@@ -317,52 +317,52 @@ let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
|
|
|
317
317
|
const full = parts.length >= limit;
|
|
318
318
|
return html `
|
|
319
319
|
<section>
|
|
320
|
-
<h3>${i18next.t('label.shapes')}</h3>
|
|
320
|
+
<h3>${i18next.t('figure.label.shapes')}</h3>
|
|
321
321
|
<div palette>
|
|
322
322
|
${PRIMITIVE_KINDS.map(kind => html `
|
|
323
323
|
<button
|
|
324
324
|
shape
|
|
325
325
|
?disabled=${full}
|
|
326
|
-
title=${full ? i18next.t('text.part-limit-reached') : i18next.t('text.add-a-shape')}
|
|
326
|
+
title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.add-a-shape')}
|
|
327
327
|
@click=${() => this.ask('add-part', { primitive: kind })}
|
|
328
328
|
>
|
|
329
329
|
<md-icon>${SHAPE_ICON[kind]}</md-icon>
|
|
330
|
-
${i18next.t('label.primitive-' + kind)}
|
|
330
|
+
${i18next.t('figure.label.primitive-' + kind)}
|
|
331
331
|
</button>
|
|
332
332
|
`)}
|
|
333
333
|
</div>
|
|
334
334
|
${full
|
|
335
|
-
? html `<p full>${i18next.t('text.part-limit-reached-short', { level, limit })}</p>`
|
|
335
|
+
? html `<p full>${i18next.t('figure.text.part-limit-reached-short', { level, limit })}</p>`
|
|
336
336
|
: ''}
|
|
337
337
|
</section>
|
|
338
338
|
|
|
339
339
|
<section list>
|
|
340
340
|
<h3>
|
|
341
|
-
${i18next.t('label.parts')}
|
|
342
|
-
<span room ?full=${full} title=${i18next.t('text.limit-comes-from-level')}>
|
|
343
|
-
${i18next.t('text.n-of-limit', { used: parts.length, limit })}
|
|
341
|
+
${i18next.t('figure.label.parts')}
|
|
342
|
+
<span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}>
|
|
343
|
+
${i18next.t('figure.text.n-of-limit', { used: parts.length, limit })}
|
|
344
344
|
</span>
|
|
345
345
|
${this.renderShowAll(parts)}
|
|
346
346
|
</h3>
|
|
347
347
|
|
|
348
348
|
<div level>
|
|
349
|
-
<label for="detail-level">${i18next.t('label.detail-level')}</label>
|
|
349
|
+
<label for="detail-level">${i18next.t('figure.label.detail-level')}</label>
|
|
350
350
|
<select
|
|
351
351
|
id="detail-level"
|
|
352
|
-
title=${i18next.t('text.detail-level-explained')}
|
|
352
|
+
title=${i18next.t('figure.text.detail-level-explained')}
|
|
353
353
|
@change=${(e) => this.ask('change-detail-level', { level: e.target.value })}
|
|
354
354
|
>
|
|
355
355
|
${DETAIL_LEVELS.map(one => html `
|
|
356
356
|
<option value=${one} ?selected=${one === level}>
|
|
357
|
-
${one} ${i18next.t('label.detail-' + one)} ·
|
|
358
|
-
${i18next.t('text.n-parts-at-most', { limit: PART_LIMIT[one] })}
|
|
357
|
+
${one} ${i18next.t('figure.label.detail-' + one)} ·
|
|
358
|
+
${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}
|
|
359
359
|
</option>
|
|
360
360
|
`)}
|
|
361
361
|
</select>
|
|
362
362
|
</div>
|
|
363
363
|
|
|
364
364
|
${parts.length === 0
|
|
365
|
-
? html `<p quiet>${i18next.t('text.pick-a-shape-above-to-start')}</p>`
|
|
365
|
+
? html `<p quiet>${i18next.t('figure.text.pick-a-shape-above-to-start')}</p>`
|
|
366
366
|
: html `
|
|
367
367
|
<ol>
|
|
368
368
|
${parts.map((part, index) => this.renderRow(part, index, parts.length))}
|
|
@@ -389,7 +389,7 @@ let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
|
|
|
389
389
|
eye
|
|
390
390
|
all
|
|
391
391
|
?off=${!anyHidden}
|
|
392
|
-
title=${i18next.t(anyHidden ? 'text.show-all-parts' : 'text.hide-all-parts')}
|
|
392
|
+
title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}
|
|
393
393
|
@click=${(e) => {
|
|
394
394
|
e.stopPropagation();
|
|
395
395
|
this.ask('set-all-parts-hidden', { hidden: !anyHidden });
|
|
@@ -407,7 +407,7 @@ let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
|
|
|
407
407
|
<button
|
|
408
408
|
eye
|
|
409
409
|
?off=${off}
|
|
410
|
-
title=${i18next.t(off ? 'text.show-this-part' : 'text.hide-this-part')}
|
|
410
|
+
title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}
|
|
411
411
|
@click=${(e) => {
|
|
412
412
|
// 눌렀을 때 줄 고르기가 함께 일어나지 않게 막는다 — 가리려던 것이지 고르려던 것이 아니다
|
|
413
413
|
e.stopPropagation();
|
|
@@ -436,7 +436,7 @@ let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
|
|
|
436
436
|
<button
|
|
437
437
|
op=${name}
|
|
438
438
|
?disabled=${!enabled}
|
|
439
|
-
title=${i18next.t('button.' + name)}
|
|
439
|
+
title=${i18next.t('figure.button.' + name)}
|
|
440
440
|
@click=${(e) => {
|
|
441
441
|
e.stopPropagation();
|
|
442
442
|
if (enabled)
|
|
@@ -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'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { DETAIL_LEVELS, PART_LIMIT, PRIMITIVE_KINDS } from '@hatiolab/figure-model'\nimport type { DetailLevel, PrimitiveKind } from '@hatiolab/figure-model'\n\nimport type { PartModel } from './figure-source.js'\n\n/** 도형마다의 아이콘. Material Symbols 이름이다. */\nconst SHAPE_ICON: Record<PrimitiveKind, string> = {\n cube: 'deployed_code',\n wall: 'rectangle',\n cylinder: 'database',\n sphere: 'circle',\n rect: 'crop_square',\n polygon: 'pentagon'\n}\n\n/**\n * 도형 팔레트와 부품 목록.\n *\n * ## 팔레트를 목록 위에 둔다\n *\n * 처음 온 사람은 「무엇을 놓을 수 있나」를 먼저 알아야 한다. 빈 목록 아래에 팔레트를\n * 두면 아무것도 없는 화면에서 다음 동작이 안 보인다.\n *\n * ## 한도를 미리 보여 준다\n *\n * 부품 수 한도는 디테일 등급이 정한다(`PART_LIMIT`). 넘긴 뒤에 판정 패널이\n * 나무라는 것보다, **넣기 전에 몇 개 남았는지** 보이는 것이 낫다.\n *\n * ## 고치지 않는다\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — 정본 한 벌을\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 border-right: 1px solid var(--md-sys-color-outline-variant);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\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 /*\n 부품 수와 한도.\n\n 전에는 이 숫자가 「도형」 절 제목에 붙어 있었다. 부품 수인데 도형 옆에 있으니\n 「도형 8종 중 2종」으로 읽힌다 — 실제로 그렇게 읽혔다. 세는 대상이 있는 절로\n 옮기고, 한도가 어디서 나온 값인지(디테일 등급)를 함께 적는다.\n */\n span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n }\n\n /* ── 팔레트 ─────────────────────────────────────────────────────── */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding: 7px 2px 5px 2px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\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[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: 20px;\n }\n\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\n /*\n 숨김 단추.\n\n 고른 줄에서만 보이는 조작(div[ops])과 달리 **늘 자리를 차지한다.** 무엇이 숨겨져\n 있는지는 목록을 훑을 때 한눈에 보여야 한다 — 눌러 봐야 알면 숨겨 둔 것을 잊는다.\n 다만 켜지지 않은 눈은 줄에 마우스를 올렸을 때만 보인다. 여덟 개 줄에 회색 눈이\n 여덟 개 떠 있으면 목록이 시끄럽다.\n */\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\n /* 숨긴 줄은 목록에서도 옅게 — 미리보기에서 faint 로 보이는 것과 같은 뜻이어야 한다 */\n li[off] span[label],\n li[off] > 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\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 /*\n 디테일 등급 선택.\n\n 전에는 등급이 \"16 / 20 · L 등급\" 안의 글자 하나로만 있었다. S·M·L 이 무엇인지\n 어디에도 없었고, 한도를 다 쓰면 \"등급을 올리세요\" 라고 해 놓고 올릴 칸이 없었다.\n\n 부품 목록 위에 둔다. 등급이 정하는 것이 바로 아래 목록의 한도이고, 팔레트가\n 잠기는 것도 이 값 때문이다. 결과가 보이는 자리에서 고치게 한다.\n */\n div[level] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0 0 7px 0;\n }\n div[level] label {\n flex: none;\n margin: 0;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[level] select {\n flex: 1;\n min-width: 0;\n padding: 3px 5px;\n font: var(--input-field-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n\n /* 팔레트가 잠긴 이유를 말한다. 흐린 단추만 두면 왜 안 눌리는지 모른다 */\n p[full] {\n margin: 6px 0 0 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\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 /**\n * 보드 위의 부품들. **씬 모델 그대로 받는다.**\n *\n * 이 패널은 이름과 도형만 읽으므로 어느 쪽 형태로 받아도 화면은 같다. 그래도 씬\n * 모델로 받는 것은 모델러 페이지가 데이터를 **한 벌만** 갖게 하기 위해서다. 여기만\n * FigureSource 로 받으면 페이지가 이 패널을 위해 변환본을 따로 들고 있어야 하고,\n * 그 변환본이 언제 낡는지를 또 관리해야 한다.\n */\n @property({ type: Array }) parts: PartModel[] = []\n /** 디테일 등급. 부품 수 한도가 여기서 결정된다. */\n @property({ type: String }) level: DetailLevel = 'M'\n /** 고른 부품의 자리. 고르지 않았으면 -1. */\n @property({ type: Number }) selected = -1\n\n render() {\n const parts = this.parts ?? []\n const level = this.level\n const limit = PART_LIMIT[level]\n const full = parts.length >= limit\n\n return html`\n <section>\n <h3>${i18next.t('label.shapes')}</h3>\n <div palette>\n ${PRIMITIVE_KINDS.map(\n kind => html`\n <button\n shape\n ?disabled=${full}\n title=${full ? i18next.t('text.part-limit-reached') : i18next.t('text.add-a-shape')}\n @click=${() => this.ask('add-part', { primitive: kind })}\n >\n <md-icon>${SHAPE_ICON[kind]}</md-icon>\n ${i18next.t('label.primitive-' + kind)}\n </button>\n `\n )}\n </div>\n ${full\n ? html`<p full>${i18next.t('text.part-limit-reached-short', { level, limit })}</p>`\n : ''}\n </section>\n\n <section list>\n <h3>\n ${i18next.t('label.parts')}\n <span room ?full=${full} title=${i18next.t('text.limit-comes-from-level')}>\n ${i18next.t('text.n-of-limit', { used: parts.length, limit })}\n </span>\n ${this.renderShowAll(parts)}\n </h3>\n\n <div level>\n <label for=\"detail-level\">${i18next.t('label.detail-level')}</label>\n <select\n id=\"detail-level\"\n title=${i18next.t('text.detail-level-explained')}\n @change=${(e: Event) =>\n this.ask('change-detail-level', { level: (e.target as HTMLSelectElement).value })}\n >\n ${DETAIL_LEVELS.map(\n one => html`\n <option value=${one} ?selected=${one === level}>\n ${one} ${i18next.t('label.detail-' + one)} ·\n ${i18next.t('text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>\n `\n )}\n </select>\n </div>\n\n ${parts.length === 0\n ? html`<p quiet>${i18next.t('text.pick-a-shape-above-to-start')}</p>`\n : html`\n <ol>\n ${parts.map((part, index) => this.renderRow(part, index, parts.length))}\n </ol>\n `}\n </section>\n `\n }\n\n /**\n * 한꺼번에 감추고 한꺼번에 되돌린다.\n *\n * 부품 스물짜리를 들여다볼 때 하나만 남기려면 열아홉을 눌러야 했다. 안쪽에 있는 것을\n * 보려고 겉을 걷어내는 일은 저작 중에 늘 하는 일이다.\n *\n * 무엇을 할지는 **지금 상태가 정한다** — 하나라도 감춰져 있으면 다 되돌리고, 다\n * 보이고 있으면 다 감춘다. 단추 하나로 오가는 것이 두 개보다 낫다.\n */\n private renderShowAll(parts: PartModel[]) {\n if (parts.length === 0) return nothing\n\n const anyHidden = parts.some(part => part.hidden)\n\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'text.show-all-parts' : '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: PartModel, index: number, count: number) {\n const selected = index === this.selected\n const off = !!part.hidden\n\n return html`\n <li ?selected=${selected} ?off=${off} @click=${() => this.ask('select-part', { index })}>\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'text.show-this-part' : 'text.hide-this-part')}\n @click=${(e: Event) => {\n // 눌렀을 때 줄 고르기가 함께 일어나지 않게 막는다 — 가리려던 것이지 고르려던 것이 아니다\n e.stopPropagation()\n this.ask('toggle-part-hidden', { index })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${SHAPE_ICON[part.primitive]}</md-icon>\n <span label title=${part.name}>${part.name}</span>\n ${selected ? this.renderOps(index, count) : nothing}\n </li>\n `\n }\n\n /**\n * 고른 줄의 조작.\n *\n * 못 하는 것을 **빼지 않고 흐리게 둔다.** 빼면 남은 단추가 그 자리로 밀려서, 첫\n * 줄과 마지막 줄에서 「지우기」가 다른 자리에 온다 — 연달아 지우다 엉뚱한 것을\n * 누르게 된다.\n *\n * 눌렀을 때 줄 고르기가 다시 일어나지 않게 전파를 멈춘다.\n */\n private renderOps(index: number, count: number) {\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled}\n title=${i18next.t('button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { index, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { index, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { index })} ${op('remove-part', 'delete', { index })}\n </div>\n `\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'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { DETAIL_LEVELS, PART_LIMIT, PRIMITIVE_KINDS } from '@hatiolab/figure-model'\nimport type { DetailLevel, PrimitiveKind } from '@hatiolab/figure-model'\n\nimport type { PartModel } from './figure-source.js'\n\n/** 도형마다의 아이콘. Material Symbols 이름이다. */\nconst SHAPE_ICON: Record<PrimitiveKind, string> = {\n cube: 'deployed_code',\n wall: 'rectangle',\n cylinder: 'database',\n sphere: 'circle',\n rect: 'crop_square',\n polygon: 'pentagon'\n}\n\n/**\n * 도형 팔레트와 부품 목록.\n *\n * ## 팔레트를 목록 위에 둔다\n *\n * 처음 온 사람은 「무엇을 놓을 수 있나」를 먼저 알아야 한다. 빈 목록 아래에 팔레트를\n * 두면 아무것도 없는 화면에서 다음 동작이 안 보인다.\n *\n * ## 한도를 미리 보여 준다\n *\n * 부품 수 한도는 디테일 등급이 정한다(`PART_LIMIT`). 넘긴 뒤에 판정 패널이\n * 나무라는 것보다, **넣기 전에 몇 개 남았는지** 보이는 것이 낫다.\n *\n * ## 고치지 않는다\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — 정본 한 벌을\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 border-right: 1px solid var(--md-sys-color-outline-variant);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\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 /*\n 부품 수와 한도.\n\n 전에는 이 숫자가 「도형」 절 제목에 붙어 있었다. 부품 수인데 도형 옆에 있으니\n 「도형 8종 중 2종」으로 읽힌다 — 실제로 그렇게 읽혔다. 세는 대상이 있는 절로\n 옮기고, 한도가 어디서 나온 값인지(디테일 등급)를 함께 적는다.\n */\n span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n }\n\n /* ── 팔레트 ─────────────────────────────────────────────────────── */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding: 7px 2px 5px 2px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\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[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: 20px;\n }\n\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\n /*\n 숨김 단추.\n\n 고른 줄에서만 보이는 조작(div[ops])과 달리 **늘 자리를 차지한다.** 무엇이 숨겨져\n 있는지는 목록을 훑을 때 한눈에 보여야 한다 — 눌러 봐야 알면 숨겨 둔 것을 잊는다.\n 다만 켜지지 않은 눈은 줄에 마우스를 올렸을 때만 보인다. 여덟 개 줄에 회색 눈이\n 여덟 개 떠 있으면 목록이 시끄럽다.\n */\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\n /* 숨긴 줄은 목록에서도 옅게 — 미리보기에서 faint 로 보이는 것과 같은 뜻이어야 한다 */\n li[off] span[label],\n li[off] > 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\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 /*\n 디테일 등급 선택.\n\n 전에는 등급이 \"16 / 20 · L 등급\" 안의 글자 하나로만 있었다. S·M·L 이 무엇인지\n 어디에도 없었고, 한도를 다 쓰면 \"등급을 올리세요\" 라고 해 놓고 올릴 칸이 없었다.\n\n 부품 목록 위에 둔다. 등급이 정하는 것이 바로 아래 목록의 한도이고, 팔레트가\n 잠기는 것도 이 값 때문이다. 결과가 보이는 자리에서 고치게 한다.\n */\n div[level] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0 0 7px 0;\n }\n div[level] label {\n flex: none;\n margin: 0;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[level] select {\n flex: 1;\n min-width: 0;\n padding: 3px 5px;\n font: var(--input-field-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n\n /* 팔레트가 잠긴 이유를 말한다. 흐린 단추만 두면 왜 안 눌리는지 모른다 */\n p[full] {\n margin: 6px 0 0 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\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 /**\n * 보드 위의 부품들. **씬 모델 그대로 받는다.**\n *\n * 이 패널은 이름과 도형만 읽으므로 어느 쪽 형태로 받아도 화면은 같다. 그래도 씬\n * 모델로 받는 것은 모델러 페이지가 데이터를 **한 벌만** 갖게 하기 위해서다. 여기만\n * FigureSource 로 받으면 페이지가 이 패널을 위해 변환본을 따로 들고 있어야 하고,\n * 그 변환본이 언제 낡는지를 또 관리해야 한다.\n */\n @property({ type: Array }) parts: PartModel[] = []\n /** 디테일 등급. 부품 수 한도가 여기서 결정된다. */\n @property({ type: String }) level: DetailLevel = 'M'\n /** 고른 부품의 자리. 고르지 않았으면 -1. */\n @property({ type: Number }) selected = -1\n\n render() {\n const parts = this.parts ?? []\n const level = this.level\n const limit = PART_LIMIT[level]\n const full = parts.length >= limit\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${PRIMITIVE_KINDS.map(\n kind => html`\n <button\n shape\n ?disabled=${full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.add-a-shape')}\n @click=${() => this.ask('add-part', { primitive: kind })}\n >\n <md-icon>${SHAPE_ICON[kind]}</md-icon>\n ${i18next.t('figure.label.primitive-' + kind)}\n </button>\n `\n )}\n </div>\n ${full\n ? html`<p full>${i18next.t('figure.text.part-limit-reached-short', { level, limit })}</p>`\n : ''}\n </section>\n\n <section list>\n <h3>\n ${i18next.t('figure.label.parts')}\n <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}>\n ${i18next.t('figure.text.n-of-limit', { used: parts.length, limit })}\n </span>\n ${this.renderShowAll(parts)}\n </h3>\n\n <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 @change=${(e: Event) =>\n this.ask('change-detail-level', { level: (e.target as HTMLSelectElement).value })}\n >\n ${DETAIL_LEVELS.map(\n one => html`\n <option value=${one} ?selected=${one === level}>\n ${one} ${i18next.t('figure.label.detail-' + one)} ·\n ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>\n `\n )}\n </select>\n </div>\n\n ${parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.pick-a-shape-above-to-start')}</p>`\n : html`\n <ol>\n ${parts.map((part, index) => this.renderRow(part, index, parts.length))}\n </ol>\n `}\n </section>\n `\n }\n\n /**\n * 한꺼번에 감추고 한꺼번에 되돌린다.\n *\n * 부품 스물짜리를 들여다볼 때 하나만 남기려면 열아홉을 눌러야 했다. 안쪽에 있는 것을\n * 보려고 겉을 걷어내는 일은 저작 중에 늘 하는 일이다.\n *\n * 무엇을 할지는 **지금 상태가 정한다** — 하나라도 감춰져 있으면 다 되돌리고, 다\n * 보이고 있으면 다 감춘다. 단추 하나로 오가는 것이 두 개보다 낫다.\n */\n private renderShowAll(parts: PartModel[]) {\n if (parts.length === 0) return nothing\n\n const anyHidden = parts.some(part => part.hidden)\n\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: PartModel, index: number, count: number) {\n const selected = index === this.selected\n const off = !!part.hidden\n\n return html`\n <li ?selected=${selected} ?off=${off} @click=${() => this.ask('select-part', { index })}>\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 // 눌렀을 때 줄 고르기가 함께 일어나지 않게 막는다 — 가리려던 것이지 고르려던 것이 아니다\n e.stopPropagation()\n this.ask('toggle-part-hidden', { index })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${SHAPE_ICON[part.primitive]}</md-icon>\n <span label title=${part.name}>${part.name}</span>\n ${selected ? this.renderOps(index, count) : nothing}\n </li>\n `\n }\n\n /**\n * 고른 줄의 조작.\n *\n * 못 하는 것을 **빼지 않고 흐리게 둔다.** 빼면 남은 단추가 그 자리로 밀려서, 첫\n * 줄과 마지막 줄에서 「지우기」가 다른 자리에 온다 — 연달아 지우다 엉뚱한 것을\n * 누르게 된다.\n *\n * 눌렀을 때 줄 고르기가 다시 일어나지 않게 전파를 멈춘다.\n */\n private renderOps(index: number, count: number) {\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { index, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { index, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { index })} ${op('remove-part', 'delete', { index })}\n </div>\n `\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -227,7 +227,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
227
227
|
standUp() {
|
|
228
228
|
const source = this.source;
|
|
229
229
|
if (!source || source.parts.length === 0) {
|
|
230
|
-
this.cannot = i18next.t('text.preview-needs-a-part');
|
|
230
|
+
this.cannot = i18next.t('figure.text.preview-needs-a-part');
|
|
231
231
|
this.teardown();
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
@@ -236,7 +236,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
236
236
|
return;
|
|
237
237
|
const { errors } = validate(source);
|
|
238
238
|
if (errors.length > 0) {
|
|
239
|
-
this.cannot = i18next.t('text.preview-blocked-by-errors');
|
|
239
|
+
this.cannot = i18next.t('figure.text.preview-blocked-by-errors');
|
|
240
240
|
this.teardown();
|
|
241
241
|
return;
|
|
242
242
|
}
|