@things-factory/figure-ui 10.1.15 → 10.1.17
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/modeller/figure-animations.ts +12 -12
- package/client/modeller/figure-canvas.ts +32 -48
- package/client/modeller/figure-capabilities.ts +10 -10
- package/client/modeller/figure-inspector.ts +39 -28
- package/client/modeller/figure-parts.ts +1 -1
- package/client/modeller/figure-preview.ts +122 -25
- package/client/modeller/figure-report.ts +24 -6
- package/client/modeller/figure-settings.ts +45 -2
- package/client/modeller/figure-side.ts +6 -6
- package/client/modeller/figure-source.ts +84 -38
- package/client/modeller/figure-thumbnail.ts +5 -5
- package/client/modeller/figure-view.ts +53 -0
- package/client/modeller/part-edits.ts +36 -36
- package/client/modeller/proposal.ts +25 -4
- package/client/pages/figure-modeller-page.ts +142 -98
- package/dist-client/modeller/figure-animations.d.ts +4 -4
- package/dist-client/modeller/figure-animations.js +10 -10
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +7 -7
- package/dist-client/modeller/figure-canvas.js +27 -40
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-capabilities.d.ts +2 -2
- package/dist-client/modeller/figure-capabilities.js +9 -9
- package/dist-client/modeller/figure-capabilities.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +16 -5
- package/dist-client/modeller/figure-inspector.js +38 -27
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +1 -1
- package/dist-client/modeller/figure-parts.js +1 -1
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +39 -0
- package/dist-client/modeller/figure-preview.js +112 -23
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +9 -0
- package/dist-client/modeller/figure-report.js +23 -5
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-settings.js +42 -2
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +2 -2
- package/dist-client/modeller/figure-side.js +5 -5
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +38 -23
- package/dist-client/modeller/figure-source.js +40 -21
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +2 -2
- package/dist-client/modeller/figure-thumbnail.js +3 -3
- package/dist-client/modeller/figure-thumbnail.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +45 -0
- package/dist-client/modeller/figure-view.js +27 -0
- package/dist-client/modeller/figure-view.js.map +1 -1
- package/dist-client/modeller/part-edits.d.ts +16 -16
- package/dist-client/modeller/part-edits.js +35 -35
- package/dist-client/modeller/part-edits.js.map +1 -1
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +15 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +44 -9
- package/dist-client/pages/figure-modeller-page.js +134 -92
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/ai-proposal-contract.test.ts +393 -12
- package/test/expect.ts +55 -0
- package/test/figure-source.test.ts +562 -0
- package/test/i18n-prefix-guard.test.ts +1 -1
- package/{client/modeller → test}/part-edits.test.ts +50 -45
- package/translations/en.json +31 -28
- package/translations/ja.json +16 -21
- package/translations/ko.json +41 -38
- package/translations/ms.json +16 -21
- package/translations/zh.json +16 -21
- package/client/modeller/figure-ask.ts +0 -265
- package/client/modeller/figure-source.test.ts +0 -352
- package/dist-client/modeller/figure-ask.d.ts +0 -40
- package/dist-client/modeller/figure-ask.js +0 -282
- package/dist-client/modeller/figure-ask.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-report.js","sourceRoot":"","sources":["../../client/modeller/figure-report.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 { unsafeSVG } from 'lit/directives/unsafe-svg.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { compile, costCurveSvg, costOf, scoreOf, sizingReport, validate } from '@hatiolab/figure-model'\nimport type { FigureCost, FigureScore, FigureSource } from '@hatiolab/figure-model'\n\nimport { capacityOf } from './scene-budget.js'\nimport { maturityOf } from './maturity.js'\nimport type { MaturityAxis } from './maturity.js'\nimport type { FigureCapacity } from './scene-budget.js'\n\n/**\n * 판단 패널. 모델러 화면에 **늘 떠 있다.**\n *\n * 저장을 선택해야 아는 것이 아니다. 부품을 하나 놓을 때마다 무게와 점수가 움직이고,\n * 그것이 보여야 저작자가 고칠 수 있다. 「무엇 때문에 점수가 낮은가」를 말해 주지\n * 않으면 점수는 잔소리일 뿐이다.\n *\n * ## 셈을 여기서 하지 않는다\n *\n * 검증·원가·점수는 전부 `@hatiolab/figure-model` 이 한다. 화면은 **부르고 보여 줄\n * 뿐**이다. 한도나 등급 경계를 화면이 다시 정하면 서버가 거절할 것을 화면이\n * 통과시키게 된다.\n */\n@customElement('figure-report')\nexport class FigureReport extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n border-left: 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 > 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 /* ── 몇 개까지 편한가 ───────────────────────────────────────────\n 로우폴리의 값은 「얼마나 많이 편하게 놓을 수 있나」다. 그 답이 머리에 온다.\n 등급은 형상 자체가 한도 안인지를 말할 뿐이라 옆의 작은 badge 로 둔다. */\n div[verdict] {\n display: flex;\n align-items: baseline;\n gap: 6px;\n }\n span[capacity] {\n font-size: 2.1rem;\n font-weight: 800;\n line-height: 1;\n letter-spacing: -0.01em;\n color: var(--md-sys-color-primary);\n }\n span[unit] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--md-sys-color-primary);\n }\n /*\n 등급 badge.\n\n 전에는 \"C 0\" 만 있었다. 글자 하나와 숫자 하나인데 어디에도 뜻이 없어서, 크게 뜬\n 개수 옆에 빨간 badge 가 붙은 것으로만 읽혔다. 0 을 100점 만점의 0 점으로 읽는 것이\n 자연스럽다. 실제 뜻은 \"선언한 한도에 남은 여유가 0\" 이다.\n\n 그래서 무엇을 측정하는지를 badge 안에 적고, 눈금은 바로 아래 줄에 늘 보이게 둔다.\n */\n span[grade] {\n display: flex;\n align-items: baseline;\n gap: 5px;\n margin-left: auto;\n padding: 3px 10px;\n font-size: 0.76rem;\n font-weight: 700;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n }\n span[grade] small {\n font-size: 0.68rem;\n font-weight: 500;\n }\n /*\n C 는 한도를 지킨 것이다. 빨강은 넘긴 것(D)에만 쓴다. 지킨 것에 빨강을 쓰면\n 고칠 것이 없는데도 고쳐야 할 것처럼 보인다.\n */\n span[grade][tight] {\n color: var(--md-sys-color-on-tertiary-container, #3d2f00);\n background-color: var(--md-sys-color-tertiary-container, #ffdf9a);\n }\n /*\n Pair the container with its on-container.\n\n This used to fill with --md-sys-color-error and write with\n --md-sys-color-on-error-container. Once the palette made containers\n neutral and moved the meaning into the text colour, those two became the\n same red and the badge went blank -- a filled pill with no readable text.\n */\n span[grade][over] {\n color: var(--md-sys-color-on-error-container);\n background-color: var(--md-sys-color-error-container);\n }\n\n /* 등급 눈금. 접어 두지 않는다 — 한 번 보고 마는 값이 아니라 볼 때마다 필요하다 */\n p[scale] {\n display: flex;\n flex-wrap: wrap;\n gap: 2px 10px;\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n p[scale] b {\n font-weight: 700;\n }\n p[scale] span[now] b {\n color: var(--md-sys-color-primary);\n }\n p[scale] span[now] {\n color: var(--md-sys-color-on-surface);\n }\n /* 이 큰 숫자가 어느 축인지 먼저 말한다 — 성숙도의 한 축이지 전부가 아니다 */\n p[lead] {\n margin: 0 0 4px 0;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 근거를 함께 낸다 — 숫자만 크게 내걸면 재 본 값처럼 읽힌다 */\n p[basis] {\n margin: 6px 0 0 0;\n font-size: 0.72rem;\n line-height: 1.55;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n p[basis] em {\n font-style: normal;\n color: var(--md-sys-color-tertiary);\n }\n span[score] {\n font-size: 0.8rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 무엇 때문에 이 등급인가 — 한도에 가장 가까운 항목 */\n p[because] {\n margin: 8px 0 0 0;\n /* 본문 크기를 물려받으면 이 줄만 제목처럼 커진다 — 화면을 띄워 보고 알았다 */\n font-size: 0.78rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n p[because] strong {\n color: var(--md-sys-color-on-surface);\n }\n\n /* ── 성숙도 radar ────────────────────────────────────────────────\n 여섯 축을 한 그림으로. 목록만 두면 「어느 쪽이 모자란가」가 안 보인다.\n 못 잰 축은 점을 찍지 않고 살만 흐리게 남긴다 — 0 으로 찍으면 「나쁘다」가 된다. */\n div[radar] {\n display: flex;\n justify-content: center;\n padding: 2px 0 6px 0;\n color: var(--md-sys-color-primary);\n }\n div[radar] svg {\n width: 100%;\n max-width: 240px;\n height: auto;\n }\n div[radar] polygon[web] {\n fill: none;\n stroke: var(--md-sys-color-outline-variant);\n stroke-width: 0.6;\n }\n div[radar] line[spoke] {\n stroke: var(--md-sys-color-outline-variant);\n stroke-width: 0.6;\n }\n div[radar] polygon[shape] {\n fill: color-mix(in srgb, var(--md-sys-color-primary) 22%, transparent);\n stroke: var(--md-sys-color-primary);\n stroke-width: 1.4;\n stroke-linejoin: round;\n }\n div[radar] circle[dot] {\n fill: var(--md-sys-color-primary);\n }\n div[radar] text {\n font-size: 5.2px;\n fill: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 그림에 자리를 주지 않은 축을 글로 밝힌다 */\n p[unmeasured] {\n margin: 0 0 8px 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-tertiary);\n }\n\n /* ── 성숙도 축 ──────────────────────────────────────────────────\n 한 숫자로 합치지 않는다. 합치면 「무엇을 고쳐야 하나」가 사라진다. */\n ul[axes] {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 7px;\n }\n ul[axes] li {\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 2px 8px;\n font-size: 0.75rem;\n }\n ul[axes] li[blocked] {\n opacity: 0.7;\n }\n ul[axes] em {\n grid-column: 1 / -1;\n font-style: normal;\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 /* 못 잰 축은 0 으로 두지 않는다 — 0 은 「나쁘다」는 뜻이 되어 버린다 */\n ul[axes] li[blocked] em {\n color: var(--md-sys-color-tertiary);\n }\n span[reading] {\n font-family: var(--mono-font, monospace);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* ── 예산 막대 ────────────────────────────────────────────────── */\n ul[budgets] {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n ul[budgets] li {\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 2px 8px;\n font-size: 0.75rem;\n }\n div[bar] {\n grid-column: 1 / -1;\n height: 4px;\n background-color: var(--md-sys-color-surface-container-highest);\n border-radius: 2px;\n overflow: hidden;\n }\n div[bar] > i {\n display: block;\n height: 100%;\n background-color: var(--md-sys-color-primary);\n }\n div[bar] > i[tight] {\n background-color: var(--md-sys-color-error);\n }\n\n /* ── 무게 ─────────────────────────────────────────────────────── */\n dl[metrics] {\n margin: 0;\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 4px 8px;\n font-size: 0.78rem;\n }\n dl[metrics] dt {\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n dl[metrics] dd {\n margin: 0;\n text-align: right;\n font-family: var(--mono-font, monospace);\n }\n\n /* ── 추이 ──────────────────────────────────────────────────────────\n 그림은 자연 크기가 680px 다. 300px 칸에 넣으면 글자가 4px 이 되어 아무것도\n 읽을 수 없다 — 사이드바에는 숫자를 두고, 그림은 선택해서 크게 본다. */\n button[expand] {\n margin-top: 6px;\n padding: 4px 10px;\n font: var(--label-font, inherit);\n font-size: 0.74rem;\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[expand]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n\n div[sheet] {\n position: fixed;\n inset: 0;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-large, 12px);\n background-color: rgba(0, 0, 0, 0.32);\n }\n div[sheet] > figure {\n max-width: 100%;\n max-height: 100%;\n margin: 0;\n padding: var(--spacing-large, 12px);\n overflow: auto;\n color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container-lowest);\n /* 담는 면이 팝업이면 안쪽 모서리는 알약이다 — 시트 자체는 페이지 값을 쓴다 */\n border-radius: 6px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28);\n }\n div[sheet] svg {\n display: block;\n /* 자연 크기보다 작게 줄이지 않는다 — 좁으면 넘겨 보게 둔다 */\n min-width: 680px;\n }\n div[sheet] figcaption {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n }\n div[sheet] button[close] {\n padding: 5px 14px;\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: 999em;\n cursor: pointer;\n }\n\n /* ── 알림 ─────────────────────────────────────────────────────── */\n ul[findings] {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n ul[findings] li {\n display: flex;\n gap: 6px;\n line-height: 1.5;\n }\n ul[findings] md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-error);\n }\n ul[findings] li[violation] md-icon {\n color: var(--md-sys-color-tertiary);\n }\n /* 어긴 자리. 문구에 붙어 흐르면 문장의 일부로 읽힌다 — badge 로 보이게 뗀다 */\n code[at] {\n padding: 1px 5px;\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 background-color: var(--md-sys-color-surface-container);\n border-radius: 4px;\n white-space: nowrap;\n }\n\n /*\n 발견이 없을 때의 문구.\n\n 크기를 안 적어 두었더니 본문 기본 크기를 물려받아 이 줄만 제목처럼 커졌다. 이\n 패널의 글자는 전부 0.7rem 대인데 여기만 1rem 이라 아래쪽이 무너져 보였다.\n\n 찾은 것이 있을 때의 목록(ul[findings] li)과 같은 크기로 맞춘다. 있을 때와 없을 때\n 절의 생김새가 달라질 이유가 없다.\n\n 이 파일에서 같은 실수를 두 번째로 한다. p[because] 에도 같은 주석이 있다.\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 @property({ type: Object }) source?: FigureSource\n\n @state() private cost?: FigureCost\n @state() private score?: FigureScore\n @state() private curve = ''\n /** 1만 개를 놓았을 때. 저작자가 실제로 궁금한 숫자다. */\n @state() private many?: FigureCost\n /** 몇 개까지 놓을 수 있나. 이 패널의 머리다. */\n @state() private capacity?: FigureCapacity\n /** 추이 그림을 제 크기로 펼쳤나. */\n @state() private expanded = false\n @state() private errors: { code: string; message: string; at?: string }[] = []\n @state() private violations: { code: string; message: string }[] = []\n /**\n * 크기를 바꿔 봤을 때 갈라지는가 — **발행을 막는 것**이다.\n *\n * 이력 탭이 같은 판정을 서버에서 받아 발행 문을 잠그지만, 그것은 **저장된 정본**에 대한\n * 것이라 저장하기 전에는 답하지 못한다. 여기서는 저작 중인 정본을 그대로 재므로, 부품을\n * 옮기는 그 자리에서 갈라짐이 생겼는지 사라졌는지 보인다 — 저장하고 탭을 옮겨 확인하게\n * 두면 고칠 때가 이미 지나 있다.\n */\n @state() private sizing: { code: string; message: string; at?: string; blocking: boolean }[] = []\n /**\n * 아직 부품이 하나도 없나 — **저작 전이지 형식을 어긴 것이 아니다.**\n *\n * 형식은 부품 0개를 오류로 본다(`missing-parts`). 발행 관점에서는 맞다. 그런데 새로 만들기를\n * 누른 직후가 바로 그 상태이므로, 그것을 그대로 오류로 그리면 **아무것도 하지 않았는데 붉은\n * 화면**을 보게 된다. 그래서 이 자리에서 둘을 가른다.\n */\n @state() private bare = false\n\n willUpdate(changed: Map<string, unknown>) {\n if (changed.has('source')) {\n this.measure()\n }\n }\n\n render() {\n /*\n 부품이 없으면 이 자리는 **판정이 아니라 안내**다. 이미 있던 「부품을 추가하면 나옵니다」가\n 정확히 그 말이므로 그것을 쓴다 — 같은 상태에 문장을 둘 두지 않는다.\n\n 아래 셈들(머리·축·한도·추이)은 부품이 있어야 뜻이 있고, 재지 않은 값을 들고 그리면 0 으로\n 보인다. 0 은 「나쁘다」로 읽히므로 그 자리에 두지 않는다.\n */\n if (!this.source || this.bare) {\n return html`\n <section>\n <p quiet>${i18next.t('figure.text.report-appears-when-you-add-a-part')}</p>\n </section>\n `\n }\n\n // 형식을 어긴 상태에서는 셈이 뜻을 갖지 못한다 — 무엇이 잘못됐는지만 보여 준다.\n if (this.errors.length > 0) {\n return html`${this.renderFindings()}`\n }\n\n return html`\n ${this.renderVerdict()} ${this.renderAxes()} ${this.renderBudgets()} ${this.renderMetrics()} ${this.renderCurve()}\n ${this.renderFindings()} ${this.renderSheet()}\n `\n }\n\n /**\n * 머리 — 몇 개까지 편한가.\n *\n * 로우폴리의 값은 「얼마나 많이 편하게 놓을 수 있나」에 있다. 등급 B 는 그 물음에\n * 직접 답하지 않는다 — 형상이 선언된 한도 안인지를 말할 뿐이다. 그래서 개수를\n * 머리에 올리고 등급은 옆의 badge 로 내린다.\n *\n * 근거를 함께 낸다. 이 개수의 분모는 실측이 아니라 설계 문서의 목표치이고(미결\n * D-20), 그 사실이 화면에 없으면 재 본 값처럼 읽힌다.\n */\n private renderVerdict() {\n const score = this.score\n const capacity = this.capacity\n if (!score || !capacity) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.maturity')}</h3>\n <p lead>${i18next.t('figure.label.axis-scale')} — ${i18next.t('figure.text.how-many-fit')}</p>\n <div verdict>\n <span capacity>${capacity.instances.toLocaleString()}</span>\n <span unit>${i18next.t('figure.text.instances-unit')}</span>\n <span\n grade\n ?tight=${score.grade === 'C'}\n ?over=${score.grade === 'D'}\n title=${i18next.t('figure.text.grade-explained')}\n >\n ${score.grade}\n <small>${i18next.t('figure.text.headroom-percent', { percent: Math.round(score.score) })}</small>\n </span>\n </div>\n <p because>\n ${i18next.t('figure.text.grade-decided-by')}\n <strong>${i18next.t('figure.label.budget-' + score.tightest.key)}</strong>\n — ${score.tightest.used} / ${score.tightest.limit}\n (${Math.round((score.tightest.used / score.tightest.limit) * 100)}%)\n </p>\n ${this.renderGradeScale(score.grade)}\n <p basis>\n ${i18next.t('figure.text.bound-by-' + capacity.bound, {\n per: capacity.per.toLocaleString(),\n budget: capacity.budget.toLocaleString()\n })}\n <em>${i18next.t('figure.text.budget-not-measured-yet')}</em>\n </p>\n </section>\n `\n }\n\n /**\n * 등급 눈금.\n *\n * A·B·C·D 가 무엇을 뜻하는지 화면 어디에도 없었다. badge 만 보고는 좋은지 나쁜지도,\n * 무엇을 고쳐야 오르는지도 알 수 없다. 눈금을 늘 곁에 두고 지금 어디에 있는지를\n * 표시한다.\n *\n * 접어 두지 않는다. 한 번 배우고 마는 값이 아니라 볼 때마다 대조하는 값이다.\n */\n private renderGradeScale(now: string) {\n const bounds: [string, string][] = [\n ['A', '≤ 50%'],\n ['B', '≤ 75%'],\n ['C', '≤ 100%'],\n ['D', '> 100%']\n ]\n\n return html`\n <p scale>\n <span>${i18next.t('figure.text.grade-scale-lead')}</span>\n ${bounds.map(\n ([grade, bound]) => html`<span ?now=${grade === now}><b>${grade}</b> ${bound}</span>`\n )}\n </p>\n `\n }\n\n /**\n * 성숙도 — 여러 축으로 본다.\n *\n * 「몇 개까지 편한가」 하나로는 좋은 형상과 나쁜 형상이 구분되지 않는다. 부품 하나짜리\n * 상자도 그 축에서는 만점이다.\n *\n * **못 재는 축을 0 으로 두지 않는다.** 0 은 「나쁘다」는 뜻이 되는데, 사실은 아직\n * 측정할 방법이 없는 것이다. 측정하지 못한다고 알리고 이유를 붙인다. 지어낸 점수보다 그것이\n * 이 리포트를 믿을 만하게 만든다.\n */\n private renderAxes() {\n if (!this.cost || !this.score || !this.source) return nothing\n\n /*\n 규모는 목록에서 뺀다 — 머리로 올라갔다. 같은 숫자를 두 번 내면 읽는 사람이\n 둘이 다른 것인 줄 안다.\n */\n const axes = maturityOf(this.source, this.cost, this.score).filter(axis => axis.key !== 'scale')\n\n // radar 에는 여섯을 다 그린다. 규모가 어디쯤인지 함께 보여야 뜻이 있다\n const all = maturityOf(this.source, this.cost, this.score)\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.by-axis')}</h3>\n <div radar>${this.renderRadar(all.filter(axis => axis.ratio !== undefined))}</div>\n ${this.renderUnmeasured(all.filter(axis => axis.ratio === undefined))}\n <ul axes>\n ${axes.map(axis => this.renderAxis(axis))}\n </ul>\n </section>\n `\n }\n\n /**\n * 성숙도 radar. **측정한 축만 그린다.**\n *\n * 측정하지 못하는 축도 spoke 를 그려 두고 도형만 건너뛰던 때가 있었다. 그러면 그 방향을 가로지르는\n * 직선이 생겨서 도형이 그쪽으로 움푹 들어간 것처럼 보인다. 0 으로 찍지 않으려고 한\n * 일인데 결과는 0 으로 찍은 것과 똑같이 읽혔다. 점선 살과 물음표로는 그 인상이\n * 지워지지 않았다.\n *\n * 그래서 아예 빼고, 못 재는 축은 그림 아래에 글로 적는다. 잴 수 없는 것은 그림에\n * 자리를 주지 않는 편이 정직하다.\n */\n private renderRadar(axes: MaturityAxis[]) {\n // 그림 안에서는 **짧은 이름**을 쓴다. 긴 이름을 그대로 쓰면 왼쪽이 잘린다 —\n // 화면을 띄워 보고 알았다.\n const cx = 60\n const cy = 46\n const r = 28\n\n const at = (index: number, ratio: number) => {\n const angle = -Math.PI / 2 + (index * 2 * Math.PI) / axes.length\n return [cx + r * ratio * Math.cos(angle), cy + r * ratio * Math.sin(angle)]\n }\n\n const points = axes\n .map((axis, index) => at(index, Math.max(0.04, Math.min(1, axis.ratio ?? 0))).map(n => n.toFixed(1)).join(','))\n .join(' ')\n\n return svg`\n <svg viewBox=\"0 0 120 96\" aria-hidden=\"true\">\n ${[0.33, 0.66, 1].map(\n ring => svg`<polygon web points=${axes.map((_, i) => at(i, ring).map(n => n.toFixed(1)).join(',')).join(' ')}></polygon>`\n )}\n ${axes.map((_, index) => {\n const [x, y] = at(index, 1)\n return svg`<line spoke x1=${cx} y1=${cy} x2=${x.toFixed(1)} y2=${y.toFixed(1)}></line>`\n })}\n <polygon shape points=${points}></polygon>\n ${axes.map((axis, index) => {\n const [x, y] = at(index, Math.max(0.04, Math.min(1, axis.ratio ?? 0)))\n return svg`<circle dot cx=${x.toFixed(1)} cy=${y.toFixed(1)} r=\"1.4\"></circle>`\n })}\n ${axes.map((axis, index) => {\n const [x, y] = at(index, 1.28)\n const anchor = x < cx - 3 ? 'end' : x > cx + 3 ? 'start' : 'middle'\n return svg`\n <text x=${x.toFixed(1)} y=${(y + 1.8).toFixed(1)} text-anchor=${anchor}>\n ${i18next.t('figure.label.axis-' + axis.key + '-short')}\n </text>\n `\n })}\n </svg>\n `\n }\n\n /**\n * 그림에 없는 축을 글로 밝힌다.\n *\n * 빼기만 하면 축이 여섯인지 다섯인지 알 수 없고, 없는 것을 없다고 말하지 않은 셈이\n * 된다. 무엇을 아직 못 재는지 한 줄로 적는다. 왜 못 재는지는 아래 목록의 그 항목에\n * 그대로 있다.\n */\n private renderUnmeasured(axes: MaturityAxis[]) {\n if (axes.length === 0) return nothing\n\n return html`\n <p unmeasured>\n ${i18next.t('figure.text.axes-not-measurable-yet', {\n axes: axes.map(axis => i18next.t('figure.label.axis-' + axis.key)).join(' · ')\n })}\n </p>\n `\n }\n\n private renderAxis(axis: MaturityAxis) {\n const name = i18next.t('figure.label.axis-' + axis.key)\n\n if (axis.blocked) {\n return html`\n <li blocked>\n <span>${name}</span>\n <span reading>${i18next.t('figure.text.not-measurable-yet')}</span>\n <em>${i18next.t(axis.blocked)}</em>\n </li>\n `\n }\n\n const ratio = Math.max(0, Math.min(1, axis.ratio ?? 0))\n return html`\n <li>\n <span>${name}</span>\n <span reading>${axis.readingKey ? i18next.t(axis.readingKey) : (axis.reading ?? '')}</span>\n <div bar><i style=\"width:${Math.round(ratio * 100)}%\"></i></div>\n ${axis.because ? html`<em>${i18next.t(axis.because)}</em>` : ''}\n </li>\n `\n }\n\n /**\n * 예산 막대.\n *\n * 숫자만 늘어놓으면 어느 것이 꽉 찼는지 눈에 안 들어온다. 한도에 가장 가까운 하나를 색으로\n * 구분한다. 그것이 점수를 정한 항목이다.\n *\n * ## `use.label` 을 쓰지 않는 이유\n *\n * `FigureBudgetUse` 에 `label` 과 `why` 가 있고 「저작자에게 그대로 보여 준다」고\n * 적혀 있다. 그런데 그 문구가 **한국어로 구워져** 있다. 이 제품은 5언어를 쓰므로\n * 그것을 그대로 실으면 다른 언어 사용자에게 한국어가 나온다.\n *\n * `budgets` 는 **배열**이다. `Object.entries` 로 돌면 키가 인덱스(0·1·2…)가 되어\n * `figure.label.budget-0` 같은 없는 키를 찾는다 — 화면을 띄워 보고서야 드러났다.\n * 배열을 그대로 돌고 `use.key` 를 쓴다.\n *\n * 그래서 **안정된 `key`** 로 이 화면의 i18n 을 찾는다. `key` 는 열거형이라 형식의\n * 일부이고, 문구는 화면의 몫이다. figure-model 이 문구를 언어 중립으로 바꾸면\n * 그때 `label` 을 쓸 수 있다.\n */\n private renderBudgets() {\n const score = this.score\n if (!score) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.budgets')}</h3>\n <ul budgets>\n ${score.budgets.map(use => {\n const ratio = Math.min(1, use.ratio)\n const tight = use.key === score.tightest.key\n return html`\n <li>\n <span>${i18next.t('figure.label.budget-' + use.key)}</span>\n <span>${use.used} / ${use.limit}</span>\n <div bar><i ?tight=${tight} style=\"width:${Math.round(ratio * 100)}%\"></i></div>\n </li>\n `\n })}\n </ul>\n </section>\n `\n }\n\n private renderMetrics() {\n const cost = this.cost\n if (!cost) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.weight')}</h3>\n <dl metrics>\n <dt>${i18next.t('figure.label.triangles')}</dt>\n <dd>${cost.triangles.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.triangles-at-100')}</dt>\n <dd>${cost.at.triangles.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.draw-calls-at-100')}</dt>\n <dd>${cost.at.drawCalls.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.distinct-shapes')}</dt>\n <dd>${cost.distinctShapes}</dd>\n\n <dt>${i18next.t('figure.label.distinct-materials')}</dt>\n <dd>${cost.distinctMaterials}</dd>\n </dl>\n </section>\n `\n }\n\n /**\n * 재활용 추이 — 「많이 놓으면 어떻게 되나」에 답한다.\n *\n * ## 그림을 사이드바에 밀어 넣지 않는다\n *\n * `costCurveSvg` 의 자연 크기는 680x380 이고 글자가 11px 다. 300px 칸에서\n * width:100% 로 줄이면 글자가 4px 이 되어 **아무것도 읽을 수 없다** — 화면을 띄워\n * 보고서야 드러났다. 읽을 수 없는 그림은 없는 것과 같으므로, 사이드바에는 저작자가\n * 실제로 궁금한 숫자(1만 개일 때)를 두고 그림은 선택해서 제 크기로 본다.\n */\n private renderCurve() {\n const many = this.many\n if (!many && !this.curve) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.reuse-trend')}</h3>\n ${many\n ? html`\n <dl metrics>\n <dt>${i18next.t('figure.label.triangles-at-10k')}</dt>\n <dd>${many.at.triangles.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.draw-calls-at-10k')}</dt>\n <dd>${many.at.drawCalls.toLocaleString()}</dd>\n </dl>\n `\n : nothing}\n ${this.curve\n ? html`\n <button expand @click=${() => (this.expanded = true)}>\n ${i18next.t('figure.button.see-the-trend-chart')}\n </button>\n `\n : nothing}\n </section>\n `\n }\n\n /** 그림을 제 크기로 보여 준다. 좁으면 줄이지 않고 넘겨 보게 둔다. */\n private renderSheet() {\n if (!this.expanded || !this.curve) return nothing\n\n return html`\n <div sheet @click=${() => (this.expanded = false)}>\n <figure @click=${(e: Event) => e.stopPropagation()}>\n ${unsafeSVG(this.curve)}\n <figcaption>\n <button close @click=${() => (this.expanded = false)}>${i18next.t('figure.button.close')}</button>\n </figcaption>\n </figure>\n </div>\n `\n }\n\n /**\n * 발견.\n *\n * 오류(막는 것)와 위반(알리는 것)을 **가려서** 보여 준다. 섞으면 저작자가 무엇을\n * 반드시 고쳐야 하는지 모른다. 아무것도 없으면 「없다」고 말한다 — 빈 자리로 두면\n * 검사가 돌았는지 알 수 없다.\n */\n private renderFindings() {\n if (this.errors.length === 0 && this.violations.length === 0 && this.sizing.length === 0) {\n return html`\n <section>\n <h3>${i18next.t('figure.label.findings')}</h3>\n <p quiet>${i18next.t('figure.text.no-findings')}</p>\n </section>\n `\n }\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.findings')}</h3>\n <ul findings>\n ${this.sizing.map(\n finding => html`\n <li ?violation=${!finding.blocking}>\n <md-icon>call_split</md-icon>\n <span>${finding.message} ${finding.at ? html`<code at>${finding.at}</code>` : ''}</span>\n </li>\n `\n )}\n ${this.errors.map(\n e => html`\n <li>\n <md-icon>error</md-icon>\n <span>${e.message} ${e.at ? html`<code at>${e.at}</code>` : ''}</span>\n </li>\n `\n )}\n ${this.violations.map(\n v => html`\n <li violation>\n <md-icon>info</md-icon>\n <span>${v.message}</span>\n </li>\n `\n )}\n </ul>\n </section>\n `\n }\n\n /** 원본 데이터를 측정해 결과를 상태에 담는다. 셈은 전부 figure-model 이 한다. */\n private measure() {\n const source = this.source\n if (!source) {\n this.cost = undefined\n this.score = undefined\n this.many = undefined\n this.capacity = undefined\n this.curve = ''\n this.errors = []\n this.violations = []\n this.sizing = []\n return\n }\n\n /*\n 부품이 없으면 판정을 아예 하지 않는다 — 잴 것이 없다. 「무엇을 고쳐야 하나」가 아니라\n 「무엇부터 놓나」인 상태이고, 그 안내는 부품 목록이 이미 하고 있다.\n */\n this.bare = !source.parts?.length\n if (this.bare) {\n this.cost = undefined\n this.score = undefined\n this.many = undefined\n this.capacity = undefined\n this.curve = ''\n this.errors = []\n this.violations = []\n this.sizing = []\n return\n }\n\n const result = validate(source)\n this.errors = result.errors.map(e => ({ code: e.code, message: e.message, at: e.path }))\n this.violations = result.violations.map(v => ({ code: v.code, message: v.message }))\n\n if (this.errors.length > 0) {\n // 형식을 어겼으면 컴파일이 던진다. 셈을 비우고 오류만 보여 준다.\n this.cost = undefined\n this.score = undefined\n this.many = undefined\n this.capacity = undefined\n this.curve = ''\n this.sizing = []\n this.dispatchEvent(\n new CustomEvent('verdict-changed', { detail: {}, bubbles: true, composed: true })\n )\n return\n }\n\n const blueprint = compile(source)\n\n /*\n 크기를 바꿔 보는 일곱 조합. 판정 규칙은 `figure-model` 의 `gate.ts` 가 정한 것을 그대로\n 쓴다 — 무엇이 막는 것인지 화면이 다시 정하면 서버와 다른 말을 하게 된다.\n */\n const report = sizingReport(blueprint)\n this.sizing = report.findings.map(finding => ({\n code: finding.code,\n message: finding.message,\n /* 숫자가 아니라 자리를 말한다 — 갈라져 나온 부품 이름이 고칠 자리다. */\n at: finding.clumps.map(clump => [...clump].sort().join('+')).join(' | ') || undefined,\n blocking: finding.code === 'sizing-splits'\n }))\n\n this.cost = costOf(blueprint, 100)\n // 1만 개는 figure-model 에 다시 묻는다 — 화면이 곱셈으로 짐작하지 않는다.\n this.many = costOf(blueprint, 10000)\n this.score = scoreOf(blueprint)\n this.capacity = capacityOf(this.cost)\n\n // 개수를 밖으로 알린다. 탭 이름표가 이 값을 보여 주는데, 거기서 다시 셈하면\n // 같은 형상이 두 자리에서 다르게 나올 수 있다.\n this.dispatchEvent(\n new CustomEvent('verdict-changed', {\n detail: { instances: this.capacity?.instances, grade: this.score?.grade },\n bubbles: true,\n composed: true\n })\n )\n this.curve = costCurveSvg(blueprint)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-report.js","sourceRoot":"","sources":["../../client/modeller/figure-report.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 { unsafeSVG } from 'lit/directives/unsafe-svg.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { blocksRelease, compile, costCurveSvg, costOf, scoreOf, sizingReport, validate } from '@hatiolab/figure-model'\nimport type { FigureCost, FigureScore, FigureSource } from '@hatiolab/figure-model'\n\nimport { capacityOf } from './scene-budget.js'\nimport { maturityOf } from './maturity.js'\nimport type { MaturityAxis } from './maturity.js'\nimport type { FigureCapacity } from './scene-budget.js'\n\n/**\n * 판단 패널. 모델러 화면에 **늘 떠 있다.**\n *\n * 저장을 선택해야 아는 것이 아니다. 부품을 하나 놓을 때마다 무게와 점수가 움직이고,\n * 그것이 보여야 저작자가 고칠 수 있다. 「무엇 때문에 점수가 낮은가」를 말해 주지\n * 않으면 점수는 잔소리일 뿐이다.\n *\n * ## 셈을 여기서 하지 않는다\n *\n * 검증·원가·점수는 전부 `@hatiolab/figure-model` 이 한다. 화면은 **부르고 보여 줄\n * 뿐**이다. 한도나 등급 경계를 화면이 다시 정하면 서버가 거절할 것을 화면이\n * 통과시키게 된다.\n */\n@customElement('figure-report')\nexport class FigureReport extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n border-left: 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 > 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 /* ── 몇 개까지 편한가 ───────────────────────────────────────────\n 로우폴리의 값은 「얼마나 많이 편하게 놓을 수 있나」다. 그 답이 머리에 온다.\n 등급은 형상 자체가 한도 안인지를 말할 뿐이라 옆의 작은 badge 로 둔다. */\n div[verdict] {\n display: flex;\n align-items: baseline;\n gap: 6px;\n }\n span[capacity] {\n font-size: 2.1rem;\n font-weight: 800;\n line-height: 1;\n letter-spacing: -0.01em;\n color: var(--md-sys-color-primary);\n }\n span[unit] {\n font-size: 0.9rem;\n font-weight: 600;\n color: var(--md-sys-color-primary);\n }\n /*\n 등급 badge.\n\n 전에는 \"C 0\" 만 있었다. 글자 하나와 숫자 하나인데 어디에도 뜻이 없어서, 크게 뜬\n 개수 옆에 빨간 badge 가 붙은 것으로만 읽혔다. 0 을 100점 만점의 0 점으로 읽는 것이\n 자연스럽다. 실제 뜻은 \"선언한 한도에 남은 여유가 0\" 이다.\n\n 그래서 무엇을 측정하는지를 badge 안에 적고, 눈금은 바로 아래 줄에 늘 보이게 둔다.\n */\n span[grade] {\n display: flex;\n align-items: baseline;\n gap: 5px;\n margin-left: auto;\n padding: 3px 10px;\n font-size: 0.76rem;\n font-weight: 700;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n }\n span[grade] small {\n font-size: 0.68rem;\n font-weight: 500;\n }\n /*\n C 는 한도를 지킨 것이다. 빨강은 넘긴 것(D)에만 쓴다. 지킨 것에 빨강을 쓰면\n 고칠 것이 없는데도 고쳐야 할 것처럼 보인다.\n */\n span[grade][tight] {\n color: var(--md-sys-color-on-tertiary-container, #3d2f00);\n background-color: var(--md-sys-color-tertiary-container, #ffdf9a);\n }\n /*\n Pair the container with its on-container.\n\n This used to fill with --md-sys-color-error and write with\n --md-sys-color-on-error-container. Once the palette made containers\n neutral and moved the meaning into the text colour, those two became the\n same red and the badge went blank -- a filled pill with no readable text.\n */\n span[grade][over] {\n color: var(--md-sys-color-on-error-container);\n background-color: var(--md-sys-color-error-container);\n }\n\n /* 등급 눈금. 접어 두지 않는다 — 한 번 보고 마는 값이 아니라 볼 때마다 필요하다 */\n p[scale] {\n display: flex;\n flex-wrap: wrap;\n gap: 2px 10px;\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n p[scale] b {\n font-weight: 700;\n }\n p[scale] span[now] b {\n color: var(--md-sys-color-primary);\n }\n p[scale] span[now] {\n color: var(--md-sys-color-on-surface);\n }\n /* 이 큰 숫자가 어느 축인지 먼저 말한다 — 성숙도의 한 축이지 전부가 아니다 */\n p[lead] {\n margin: 0 0 4px 0;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 근거를 함께 낸다 — 숫자만 크게 내걸면 재 본 값처럼 읽힌다 */\n p[basis] {\n margin: 6px 0 0 0;\n font-size: 0.72rem;\n line-height: 1.55;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n p[basis] em {\n font-style: normal;\n color: var(--md-sys-color-tertiary);\n }\n span[score] {\n font-size: 0.8rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 무엇 때문에 이 등급인가 — 한도에 가장 가까운 항목 */\n p[because] {\n margin: 8px 0 0 0;\n /* 본문 크기를 물려받으면 이 줄만 제목처럼 커진다 — 화면을 띄워 보고 알았다 */\n font-size: 0.78rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n p[because] strong {\n color: var(--md-sys-color-on-surface);\n }\n\n /* ── 성숙도 radar ────────────────────────────────────────────────\n 여섯 축을 한 그림으로. 목록만 두면 「어느 쪽이 모자란가」가 안 보인다.\n 못 잰 축은 점을 찍지 않고 살만 흐리게 남긴다 — 0 으로 찍으면 「나쁘다」가 된다. */\n div[radar] {\n display: flex;\n justify-content: center;\n padding: 2px 0 6px 0;\n color: var(--md-sys-color-primary);\n }\n div[radar] svg {\n width: 100%;\n max-width: 240px;\n height: auto;\n }\n div[radar] polygon[web] {\n fill: none;\n stroke: var(--md-sys-color-outline-variant);\n stroke-width: 0.6;\n }\n div[radar] line[spoke] {\n stroke: var(--md-sys-color-outline-variant);\n stroke-width: 0.6;\n }\n div[radar] polygon[shape] {\n fill: color-mix(in srgb, var(--md-sys-color-primary) 22%, transparent);\n stroke: var(--md-sys-color-primary);\n stroke-width: 1.4;\n stroke-linejoin: round;\n }\n div[radar] circle[dot] {\n fill: var(--md-sys-color-primary);\n }\n div[radar] text {\n font-size: 5.2px;\n fill: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 그림에 자리를 주지 않은 축을 글로 밝힌다 */\n p[unmeasured] {\n margin: 0 0 8px 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-tertiary);\n }\n\n /* ── 성숙도 축 ──────────────────────────────────────────────────\n 한 숫자로 합치지 않는다. 합치면 「무엇을 고쳐야 하나」가 사라진다. */\n ul[axes] {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 7px;\n }\n ul[axes] li {\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 2px 8px;\n font-size: 0.75rem;\n }\n ul[axes] li[blocked] {\n opacity: 0.7;\n }\n ul[axes] em {\n grid-column: 1 / -1;\n font-style: normal;\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 /* 못 잰 축은 0 으로 두지 않는다 — 0 은 「나쁘다」는 뜻이 되어 버린다 */\n ul[axes] li[blocked] em {\n color: var(--md-sys-color-tertiary);\n }\n span[reading] {\n font-family: var(--mono-font, monospace);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* ── 예산 막대 ────────────────────────────────────────────────── */\n ul[budgets] {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n ul[budgets] li {\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 2px 8px;\n font-size: 0.75rem;\n }\n div[bar] {\n grid-column: 1 / -1;\n height: 4px;\n background-color: var(--md-sys-color-surface-container-highest);\n border-radius: 2px;\n overflow: hidden;\n }\n div[bar] > i {\n display: block;\n height: 100%;\n background-color: var(--md-sys-color-primary);\n }\n div[bar] > i[tight] {\n background-color: var(--md-sys-color-error);\n }\n\n /* ── 무게 ─────────────────────────────────────────────────────── */\n dl[metrics] {\n margin: 0;\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 4px 8px;\n font-size: 0.78rem;\n }\n dl[metrics] dt {\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n dl[metrics] dd {\n margin: 0;\n text-align: right;\n font-family: var(--mono-font, monospace);\n }\n\n /* ── 추이 ──────────────────────────────────────────────────────────\n 그림은 자연 크기가 680px 다. 300px 칸에 넣으면 글자가 4px 이 되어 아무것도\n 읽을 수 없다 — 사이드바에는 숫자를 두고, 그림은 선택해서 크게 본다. */\n button[expand] {\n margin-top: 6px;\n padding: 4px 10px;\n font: var(--label-font, inherit);\n font-size: 0.74rem;\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[expand]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n\n div[sheet] {\n position: fixed;\n inset: 0;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-large, 12px);\n background-color: rgba(0, 0, 0, 0.32);\n }\n div[sheet] > figure {\n max-width: 100%;\n max-height: 100%;\n margin: 0;\n padding: var(--spacing-large, 12px);\n overflow: auto;\n color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container-lowest);\n /* 담는 면이 팝업이면 안쪽 모서리는 알약이다 — 시트 자체는 페이지 값을 쓴다 */\n border-radius: 6px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28);\n }\n div[sheet] svg {\n display: block;\n /* 자연 크기보다 작게 줄이지 않는다 — 좁으면 넘겨 보게 둔다 */\n min-width: 680px;\n }\n div[sheet] figcaption {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n }\n div[sheet] button[close] {\n padding: 5px 14px;\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: 999em;\n cursor: pointer;\n }\n\n /* ── 알림 ─────────────────────────────────────────────────────── */\n ul[findings] {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n ul[findings] li {\n display: flex;\n gap: 6px;\n line-height: 1.5;\n }\n ul[findings] md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-error);\n }\n ul[findings] li[violation] md-icon {\n color: var(--md-sys-color-tertiary);\n }\n /* 어긴 자리. 문구에 붙어 흐르면 문장의 일부로 읽힌다 — badge 로 보이게 뗀다 */\n code[at] {\n padding: 1px 5px;\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 background-color: var(--md-sys-color-surface-container);\n border-radius: 4px;\n white-space: nowrap;\n }\n\n /*\n 발견이 없을 때의 문구.\n\n 크기를 안 적어 두었더니 본문 기본 크기를 물려받아 이 줄만 제목처럼 커졌다. 이\n 패널의 글자는 전부 0.7rem 대인데 여기만 1rem 이라 아래쪽이 무너져 보였다.\n\n 찾은 것이 있을 때의 목록(ul[findings] li)과 같은 크기로 맞춘다. 있을 때와 없을 때\n 절의 생김새가 달라질 이유가 없다.\n\n 이 파일에서 같은 실수를 두 번째로 한다. p[because] 에도 같은 주석이 있다.\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 @property({ type: Object }) source?: FigureSource\n\n @state() private cost?: FigureCost\n @state() private score?: FigureScore\n @state() private curve = ''\n /** 1만 개를 놓았을 때. 저작자가 실제로 궁금한 숫자다. */\n @state() private many?: FigureCost\n /** 몇 개까지 놓을 수 있나. 이 패널의 머리다. */\n @state() private capacity?: FigureCapacity\n /** 추이 그림을 제 크기로 펼쳤나. */\n @state() private expanded = false\n @state() private errors: { code: string; message: string; at?: string }[] = []\n /**\n * 형식이 권하지 않는 것 — **그중 둘은 발행을 막는다.**\n *\n * 등급을 형식에서 받는다(`blocksRelease`). 화면이 제 목록을 들면 형식에 코드가 하나 늘 때\n * 조용히 「알림」으로 그려지고, 저작자는 발행이 막힌 것을 **눌러 보고 나서야** 안다.\n *\n * `actual` 은 얼마나 넘쳤나다. 「상자를 벗어난다」만으로는 2mm 인지 250mm 인지 모르고,\n * 그 둘은 고치는 품이 다르다.\n */\n @state() private violations: { code: string; message: string; blocking: boolean; actual?: number }[] = []\n /**\n * 크기를 바꿔 봤을 때 갈라지는가 — **발행을 막는 것**이다.\n *\n * 이력 탭이 같은 판정을 서버에서 받아 발행 문을 잠그지만, 그것은 **저장된 정본**에 대한\n * 것이라 저장하기 전에는 답하지 못한다. 여기서는 저작 중인 정본을 그대로 재므로, 부품을\n * 옮기는 그 자리에서 갈라짐이 생겼는지 사라졌는지 보인다 — 저장하고 탭을 옮겨 확인하게\n * 두면 고칠 때가 이미 지나 있다.\n */\n @state() private sizing: { code: string; message: string; at?: string; blocking: boolean }[] = []\n /**\n * 아직 부품이 하나도 없나 — **저작 전이지 형식을 어긴 것이 아니다.**\n *\n * 형식은 부품 0개를 오류로 본다(`missing-parts`). 발행 관점에서는 맞다. 그런데 새로 만들기를\n * 누른 직후가 바로 그 상태이므로, 그것을 그대로 오류로 그리면 **아무것도 하지 않았는데 붉은\n * 화면**을 보게 된다. 그래서 이 자리에서 둘을 가른다.\n */\n @state() private bare = false\n\n willUpdate(changed: Map<string, unknown>) {\n if (changed.has('source')) {\n this.measure()\n }\n }\n\n render() {\n /*\n 부품이 없으면 이 자리는 **판정이 아니라 안내**다. 이미 있던 「부품을 추가하면 나옵니다」가\n 정확히 그 말이므로 그것을 쓴다 — 같은 상태에 문장을 둘 두지 않는다.\n\n 아래 셈들(머리·축·한도·추이)은 부품이 있어야 뜻이 있고, 재지 않은 값을 들고 그리면 0 으로\n 보인다. 0 은 「나쁘다」로 읽히므로 그 자리에 두지 않는다.\n */\n if (!this.source || this.bare) {\n return html`\n <section>\n <p quiet>${i18next.t('figure.text.report-appears-when-you-add-a-part')}</p>\n </section>\n `\n }\n\n // 형식을 어긴 상태에서는 셈이 뜻을 갖지 못한다 — 무엇이 잘못됐는지만 보여 준다.\n if (this.errors.length > 0) {\n return html`${this.renderFindings()}`\n }\n\n return html`\n ${this.renderVerdict()} ${this.renderAxes()} ${this.renderBudgets()} ${this.renderMetrics()} ${this.renderCurve()}\n ${this.renderFindings()} ${this.renderSheet()}\n `\n }\n\n /**\n * 머리 — 몇 개까지 편한가.\n *\n * 로우폴리의 값은 「얼마나 많이 편하게 놓을 수 있나」에 있다. 등급 B 는 그 물음에\n * 직접 답하지 않는다 — 형상이 선언된 한도 안인지를 말할 뿐이다. 그래서 개수를\n * 머리에 올리고 등급은 옆의 badge 로 내린다.\n *\n * 근거를 함께 낸다. 이 개수의 분모는 실측이 아니라 설계 문서의 목표치이고(미결\n * D-20), 그 사실이 화면에 없으면 재 본 값처럼 읽힌다.\n */\n private renderVerdict() {\n const score = this.score\n const capacity = this.capacity\n if (!score || !capacity) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.maturity')}</h3>\n <p lead>${i18next.t('figure.label.axis-scale')} — ${i18next.t('figure.text.how-many-fit')}</p>\n <div verdict>\n <span capacity>${capacity.instances.toLocaleString()}</span>\n <span unit>${i18next.t('figure.text.instances-unit')}</span>\n <span\n grade\n ?tight=${score.grade === 'C'}\n ?over=${score.grade === 'D'}\n title=${i18next.t('figure.text.grade-explained')}\n >\n ${score.grade}\n <small>${i18next.t('figure.text.headroom-percent', { percent: Math.round(score.score) })}</small>\n </span>\n </div>\n <p because>\n ${i18next.t('figure.text.grade-decided-by')}\n <strong>${i18next.t('figure.label.budget-' + score.tightest.key)}</strong>\n — ${score.tightest.used} / ${score.tightest.limit}\n (${Math.round((score.tightest.used / score.tightest.limit) * 100)}%)\n </p>\n ${this.renderGradeScale(score.grade)}\n <p basis>\n ${i18next.t('figure.text.bound-by-' + capacity.bound, {\n per: capacity.per.toLocaleString(),\n budget: capacity.budget.toLocaleString()\n })}\n <em>${i18next.t('figure.text.budget-not-measured-yet')}</em>\n </p>\n </section>\n `\n }\n\n /**\n * 등급 눈금.\n *\n * A·B·C·D 가 무엇을 뜻하는지 화면 어디에도 없었다. badge 만 보고는 좋은지 나쁜지도,\n * 무엇을 고쳐야 오르는지도 알 수 없다. 눈금을 늘 곁에 두고 지금 어디에 있는지를\n * 표시한다.\n *\n * 접어 두지 않는다. 한 번 배우고 마는 값이 아니라 볼 때마다 대조하는 값이다.\n */\n private renderGradeScale(now: string) {\n const bounds: [string, string][] = [\n ['A', '≤ 50%'],\n ['B', '≤ 75%'],\n ['C', '≤ 100%'],\n ['D', '> 100%']\n ]\n\n return html`\n <p scale>\n <span>${i18next.t('figure.text.grade-scale-lead')}</span>\n ${bounds.map(\n ([grade, bound]) => html`<span ?now=${grade === now}><b>${grade}</b> ${bound}</span>`\n )}\n </p>\n `\n }\n\n /**\n * 성숙도 — 여러 축으로 본다.\n *\n * 「몇 개까지 편한가」 하나로는 좋은 형상과 나쁜 형상이 구분되지 않는다. 부품 하나짜리\n * 상자도 그 축에서는 만점이다.\n *\n * **못 재는 축을 0 으로 두지 않는다.** 0 은 「나쁘다」는 뜻이 되는데, 사실은 아직\n * 측정할 방법이 없는 것이다. 측정하지 못한다고 알리고 이유를 붙인다. 지어낸 점수보다 그것이\n * 이 리포트를 믿을 만하게 만든다.\n */\n private renderAxes() {\n if (!this.cost || !this.score || !this.source) return nothing\n\n /*\n 규모는 목록에서 뺀다 — 머리로 올라갔다. 같은 숫자를 두 번 내면 읽는 사람이\n 둘이 다른 것인 줄 안다.\n */\n const axes = maturityOf(this.source, this.cost, this.score).filter(axis => axis.key !== 'scale')\n\n // radar 에는 여섯을 다 그린다. 규모가 어디쯤인지 함께 보여야 뜻이 있다\n const all = maturityOf(this.source, this.cost, this.score)\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.by-axis')}</h3>\n <div radar>${this.renderRadar(all.filter(axis => axis.ratio !== undefined))}</div>\n ${this.renderUnmeasured(all.filter(axis => axis.ratio === undefined))}\n <ul axes>\n ${axes.map(axis => this.renderAxis(axis))}\n </ul>\n </section>\n `\n }\n\n /**\n * 성숙도 radar. **측정한 축만 그린다.**\n *\n * 측정하지 못하는 축도 spoke 를 그려 두고 도형만 건너뛰던 때가 있었다. 그러면 그 방향을 가로지르는\n * 직선이 생겨서 도형이 그쪽으로 움푹 들어간 것처럼 보인다. 0 으로 찍지 않으려고 한\n * 일인데 결과는 0 으로 찍은 것과 똑같이 읽혔다. 점선 살과 물음표로는 그 인상이\n * 지워지지 않았다.\n *\n * 그래서 아예 빼고, 못 재는 축은 그림 아래에 글로 적는다. 잴 수 없는 것은 그림에\n * 자리를 주지 않는 편이 정직하다.\n */\n private renderRadar(axes: MaturityAxis[]) {\n // 그림 안에서는 **짧은 이름**을 쓴다. 긴 이름을 그대로 쓰면 왼쪽이 잘린다 —\n // 화면을 띄워 보고 알았다.\n const cx = 60\n const cy = 46\n const r = 28\n\n const at = (index: number, ratio: number) => {\n const angle = -Math.PI / 2 + (index * 2 * Math.PI) / axes.length\n return [cx + r * ratio * Math.cos(angle), cy + r * ratio * Math.sin(angle)]\n }\n\n const points = axes\n .map((axis, index) => at(index, Math.max(0.04, Math.min(1, axis.ratio ?? 0))).map(n => n.toFixed(1)).join(','))\n .join(' ')\n\n return svg`\n <svg viewBox=\"0 0 120 96\" aria-hidden=\"true\">\n ${[0.33, 0.66, 1].map(\n ring => svg`<polygon web points=${axes.map((_, i) => at(i, ring).map(n => n.toFixed(1)).join(',')).join(' ')}></polygon>`\n )}\n ${axes.map((_, index) => {\n const [x, y] = at(index, 1)\n return svg`<line spoke x1=${cx} y1=${cy} x2=${x.toFixed(1)} y2=${y.toFixed(1)}></line>`\n })}\n <polygon shape points=${points}></polygon>\n ${axes.map((axis, index) => {\n const [x, y] = at(index, Math.max(0.04, Math.min(1, axis.ratio ?? 0)))\n return svg`<circle dot cx=${x.toFixed(1)} cy=${y.toFixed(1)} r=\"1.4\"></circle>`\n })}\n ${axes.map((axis, index) => {\n const [x, y] = at(index, 1.28)\n const anchor = x < cx - 3 ? 'end' : x > cx + 3 ? 'start' : 'middle'\n return svg`\n <text x=${x.toFixed(1)} y=${(y + 1.8).toFixed(1)} text-anchor=${anchor}>\n ${i18next.t('figure.label.axis-' + axis.key + '-short')}\n </text>\n `\n })}\n </svg>\n `\n }\n\n /**\n * 그림에 없는 축을 글로 밝힌다.\n *\n * 빼기만 하면 축이 여섯인지 다섯인지 알 수 없고, 없는 것을 없다고 말하지 않은 셈이\n * 된다. 무엇을 아직 못 재는지 한 줄로 적는다. 왜 못 재는지는 아래 목록의 그 항목에\n * 그대로 있다.\n */\n private renderUnmeasured(axes: MaturityAxis[]) {\n if (axes.length === 0) return nothing\n\n return html`\n <p unmeasured>\n ${i18next.t('figure.text.axes-not-measurable-yet', {\n axes: axes.map(axis => i18next.t('figure.label.axis-' + axis.key)).join(' · ')\n })}\n </p>\n `\n }\n\n private renderAxis(axis: MaturityAxis) {\n const name = i18next.t('figure.label.axis-' + axis.key)\n\n if (axis.blocked) {\n return html`\n <li blocked>\n <span>${name}</span>\n <span reading>${i18next.t('figure.text.not-measurable-yet')}</span>\n <em>${i18next.t(axis.blocked)}</em>\n </li>\n `\n }\n\n const ratio = Math.max(0, Math.min(1, axis.ratio ?? 0))\n return html`\n <li>\n <span>${name}</span>\n <span reading>${axis.readingKey ? i18next.t(axis.readingKey) : (axis.reading ?? '')}</span>\n <div bar><i style=\"width:${Math.round(ratio * 100)}%\"></i></div>\n ${axis.because ? html`<em>${i18next.t(axis.because)}</em>` : ''}\n </li>\n `\n }\n\n /**\n * 예산 막대.\n *\n * 숫자만 늘어놓으면 어느 것이 꽉 찼는지 눈에 안 들어온다. 한도에 가장 가까운 하나를 색으로\n * 구분한다. 그것이 점수를 정한 항목이다.\n *\n * ## `use.label` 을 쓰지 않는 이유\n *\n * `FigureBudgetUse` 에 `label` 과 `why` 가 있고 「저작자에게 그대로 보여 준다」고\n * 적혀 있다. 그런데 그 문구가 **한국어로 구워져** 있다. 이 제품은 5언어를 쓰므로\n * 그것을 그대로 실으면 다른 언어 사용자에게 한국어가 나온다.\n *\n * `budgets` 는 **배열**이다. `Object.entries` 로 돌면 키가 인덱스(0·1·2…)가 되어\n * `figure.label.budget-0` 같은 없는 키를 찾는다 — 화면을 띄워 보고서야 드러났다.\n * 배열을 그대로 돌고 `use.key` 를 쓴다.\n *\n * 그래서 **안정된 `key`** 로 이 화면의 i18n 을 찾는다. `key` 는 열거형이라 형식의\n * 일부이고, 문구는 화면의 몫이다. figure-model 이 문구를 언어 중립으로 바꾸면\n * 그때 `label` 을 쓸 수 있다.\n */\n private renderBudgets() {\n const score = this.score\n if (!score) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.budgets')}</h3>\n <ul budgets>\n ${score.budgets.map(use => {\n const ratio = Math.min(1, use.ratio)\n const tight = use.key === score.tightest.key\n return html`\n <li>\n <span>${i18next.t('figure.label.budget-' + use.key)}</span>\n <span>${use.used} / ${use.limit}</span>\n <div bar><i ?tight=${tight} style=\"width:${Math.round(ratio * 100)}%\"></i></div>\n </li>\n `\n })}\n </ul>\n </section>\n `\n }\n\n private renderMetrics() {\n const cost = this.cost\n if (!cost) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.weight')}</h3>\n <dl metrics>\n <dt>${i18next.t('figure.label.triangles')}</dt>\n <dd>${cost.triangles.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.triangles-at-100')}</dt>\n <dd>${cost.at.triangles.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.draw-calls-at-100')}</dt>\n <dd>${cost.at.drawCalls.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.distinct-shapes')}</dt>\n <dd>${cost.distinctShapes}</dd>\n\n <dt>${i18next.t('figure.label.distinct-materials')}</dt>\n <dd>${cost.distinctMaterials}</dd>\n </dl>\n </section>\n `\n }\n\n /**\n * 재활용 추이 — 「많이 놓으면 어떻게 되나」에 답한다.\n *\n * ## 그림을 사이드바에 밀어 넣지 않는다\n *\n * `costCurveSvg` 의 자연 크기는 680x380 이고 글자가 11px 다. 300px 칸에서\n * width:100% 로 줄이면 글자가 4px 이 되어 **아무것도 읽을 수 없다** — 화면을 띄워\n * 보고서야 드러났다. 읽을 수 없는 그림은 없는 것과 같으므로, 사이드바에는 저작자가\n * 실제로 궁금한 숫자(1만 개일 때)를 두고 그림은 선택해서 제 크기로 본다.\n */\n private renderCurve() {\n const many = this.many\n if (!many && !this.curve) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.reuse-trend')}</h3>\n ${many\n ? html`\n <dl metrics>\n <dt>${i18next.t('figure.label.triangles-at-10k')}</dt>\n <dd>${many.at.triangles.toLocaleString()}</dd>\n\n <dt>${i18next.t('figure.label.draw-calls-at-10k')}</dt>\n <dd>${many.at.drawCalls.toLocaleString()}</dd>\n </dl>\n `\n : nothing}\n ${this.curve\n ? html`\n <button expand @click=${() => (this.expanded = true)}>\n ${i18next.t('figure.button.see-the-trend-chart')}\n </button>\n `\n : nothing}\n </section>\n `\n }\n\n /** 그림을 제 크기로 보여 준다. 좁으면 줄이지 않고 넘겨 보게 둔다. */\n private renderSheet() {\n if (!this.expanded || !this.curve) return nothing\n\n return html`\n <div sheet @click=${() => (this.expanded = false)}>\n <figure @click=${(e: Event) => e.stopPropagation()}>\n ${unsafeSVG(this.curve)}\n <figcaption>\n <button close @click=${() => (this.expanded = false)}>${i18next.t('figure.button.close')}</button>\n </figcaption>\n </figure>\n </div>\n `\n }\n\n /**\n * 발견.\n *\n * 오류(막는 것)와 위반(알리는 것)을 **가려서** 보여 준다. 섞으면 저작자가 무엇을\n * 반드시 고쳐야 하는지 모른다. 아무것도 없으면 「없다」고 말한다 — 빈 자리로 두면\n * 검사가 돌았는지 알 수 없다.\n */\n private renderFindings() {\n if (this.errors.length === 0 && this.violations.length === 0 && this.sizing.length === 0) {\n return html`\n <section>\n <h3>${i18next.t('figure.label.findings')}</h3>\n <p quiet>${i18next.t('figure.text.no-findings')}</p>\n </section>\n `\n }\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.findings')}</h3>\n <ul findings>\n ${this.sizing.map(\n finding => html`\n <li ?violation=${!finding.blocking}>\n <md-icon>call_split</md-icon>\n <span>${finding.message} ${finding.at ? html`<code at>${finding.at}</code>` : ''}</span>\n </li>\n `\n )}\n ${this.errors.map(\n e => html`\n <li>\n <md-icon>error</md-icon>\n <span>${e.message} ${e.at ? html`<code at>${e.at}</code>` : ''}</span>\n </li>\n `\n )}\n ${this.violations.map(\n v => html`\n <li ?violation=${!v.blocking}>\n <md-icon>${v.blocking ? 'block' : 'info'}</md-icon>\n <span>\n ${v.message}${v.blocking && v.actual !== undefined\n ? html` <code at>${v.actual}</code>`\n : ''}\n </span>\n </li>\n `\n )}\n </ul>\n </section>\n `\n }\n\n /** 원본 데이터를 측정해 결과를 상태에 담는다. 셈은 전부 figure-model 이 한다. */\n private measure() {\n const source = this.source\n if (!source) {\n this.cost = undefined\n this.score = undefined\n this.many = undefined\n this.capacity = undefined\n this.curve = ''\n this.errors = []\n this.violations = []\n this.sizing = []\n return\n }\n\n /*\n 부품이 없으면 판정을 아예 하지 않는다 — 잴 것이 없다. 「무엇을 고쳐야 하나」가 아니라\n 「무엇부터 놓나」인 상태이고, 그 안내는 부품 목록이 이미 하고 있다.\n */\n this.bare = !source.parts?.length\n if (this.bare) {\n this.cost = undefined\n this.score = undefined\n this.many = undefined\n this.capacity = undefined\n this.curve = ''\n this.errors = []\n this.violations = []\n this.sizing = []\n return\n }\n\n const result = validate(source)\n this.errors = result.errors.map(e => ({ code: e.code, message: e.message, at: e.path }))\n this.violations = result.violations.map(v => ({\n code: v.code,\n message: v.message,\n blocking: blocksRelease(v.code),\n actual: v.actual\n }))\n\n if (this.errors.length > 0) {\n // 형식을 어겼으면 컴파일이 던진다. 셈을 비우고 오류만 보여 준다.\n this.cost = undefined\n this.score = undefined\n this.many = undefined\n this.capacity = undefined\n this.curve = ''\n this.sizing = []\n this.dispatchEvent(\n new CustomEvent('verdict-changed', { detail: {}, bubbles: true, composed: true })\n )\n return\n }\n\n const blueprint = compile(source)\n\n /*\n 크기를 바꿔 보는 일곱 조합. 판정 규칙은 `figure-model` 의 `gate.ts` 가 정한 것을 그대로\n 쓴다 — 무엇이 막는 것인지 화면이 다시 정하면 서버와 다른 말을 하게 된다.\n */\n const report = sizingReport(blueprint)\n this.sizing = report.findings.map(finding => ({\n code: finding.code,\n message: finding.message,\n /* 숫자가 아니라 자리를 말한다 — 갈라져 나온 부품 이름이 고칠 자리다. */\n at: finding.clumps.map(clump => [...clump].sort().join('+')).join(' | ') || undefined,\n blocking: finding.code === 'sizing-splits'\n }))\n\n this.cost = costOf(blueprint, 100)\n // 1만 개는 figure-model 에 다시 묻는다 — 화면이 곱셈으로 짐작하지 않는다.\n this.many = costOf(blueprint, 10000)\n this.score = scoreOf(blueprint)\n this.capacity = capacityOf(this.cost)\n\n // 개수를 밖으로 알린다. 탭 이름표가 이 값을 보여 주는데, 거기서 다시 셈하면\n // 같은 형상이 두 자리에서 다르게 나올 수 있다.\n this.dispatchEvent(\n new CustomEvent('verdict-changed', {\n detail: { instances: this.capacity?.instances, grade: this.score?.grade },\n bubbles: true,\n composed: true\n })\n )\n this.curve = costCurveSvg(blueprint)\n }\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { css, html, LitElement } from 'lit';
|
|
|
3
3
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
4
|
import { i18next, localize } from '@operato/i18n';
|
|
5
5
|
import { ScrollbarStyles } from '@operato/styles';
|
|
6
|
-
import { DEFAULT_VIEW, ENVIRONMENTS } from './figure-view.js';
|
|
6
|
+
import { DEFAULT_VIEW, ENVIRONMENTS, SCENE_REFERENCES } from './figure-view.js';
|
|
7
7
|
/**
|
|
8
8
|
* 저작 화면의 **보기 설정**.
|
|
9
9
|
*
|
|
@@ -99,6 +99,7 @@ let FigureSettings = class FigureSettings extends localize(i18next)(LitElement)
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
select,
|
|
102
|
+
input[type='number'],
|
|
102
103
|
input[type='color'] {
|
|
103
104
|
width: 100%;
|
|
104
105
|
padding: 5px 6px;
|
|
@@ -154,10 +155,49 @@ let FigureSettings = class FigureSettings extends localize(i18next)(LitElement)
|
|
|
154
155
|
render() {
|
|
155
156
|
const view = this.view ?? DEFAULT_VIEW;
|
|
156
157
|
return html `
|
|
158
|
+
<!--
|
|
159
|
+
놓이는 보드.
|
|
160
|
+
|
|
161
|
+
이 묶음이 맨 위인 것은, 나머지가 「어떻게 보이나」인데 이것은 「무엇으로 보고
|
|
162
|
+
있나」이기 때문이다. 미리보기가 보드에 놓인 모습을 말하는 자리이므로, 어떤 보드를
|
|
163
|
+
가정하는지가 먼저 정해져야 그 다음이 뜻을 갖는다.
|
|
164
|
+
|
|
165
|
+
도형 탭의 「배치 기준」과 짝이지 같은 것이 아니다. 저쪽은 이 도형이 어느 면에
|
|
166
|
+
붙는가(저장된다), 이쪽은 그 면을 가진 보드가 어느 쪽을 기준으로 재는가(저장 안 된다).
|
|
167
|
+
-->
|
|
157
168
|
<section>
|
|
158
|
-
<h3>${i18next.t('figure.label.
|
|
169
|
+
<h3>${i18next.t('figure.label.assumed-board')}</h3>
|
|
159
170
|
<p quiet>${i18next.t('figure.text.view-settings-are-not-saved')}</p>
|
|
160
171
|
|
|
172
|
+
<label field>
|
|
173
|
+
<span>${i18next.t('figure.label.scene-reference')}</span>
|
|
174
|
+
<select
|
|
175
|
+
.value=${view.placement}
|
|
176
|
+
@change=${(e) => this.put({ placement: e.target.value })}
|
|
177
|
+
>
|
|
178
|
+
${SCENE_REFERENCES.map(name => html `<option value=${name} ?selected=${view.placement === name}>
|
|
179
|
+
${i18next.t(`figure.label.scene-reference-${name}`)}
|
|
180
|
+
</option>`)}
|
|
181
|
+
</select>
|
|
182
|
+
</label>
|
|
183
|
+
<p quiet>${i18next.t('figure.text.scene-reference-explained')}</p>
|
|
184
|
+
|
|
185
|
+
<label field>
|
|
186
|
+
<span>${i18next.t('figure.label.ceiling-height')}</span>
|
|
187
|
+
<input
|
|
188
|
+
type="number"
|
|
189
|
+
min="0"
|
|
190
|
+
step="100"
|
|
191
|
+
.value=${String(view.ceilingHeight)}
|
|
192
|
+
@change=${(e) => this.put({ ceilingHeight: Number(e.target.value) || 0 })}
|
|
193
|
+
/>
|
|
194
|
+
</label>
|
|
195
|
+
<p quiet>${i18next.t('figure.text.ceiling-height-explained')}</p>
|
|
196
|
+
</section>
|
|
197
|
+
|
|
198
|
+
<section>
|
|
199
|
+
<h3>${i18next.t('figure.label.guides')}</h3>
|
|
200
|
+
|
|
161
201
|
${this.toggle('grid', view.grid, i18next.t('figure.label.coordinate-ground'), i18next.t('figure.text.coordinate-ground-explained'))}
|
|
162
202
|
${this.toggle('gridLabels', view.gridLabels, i18next.t('figure.label.axis-labels'))}
|
|
163
203
|
${this.toggle('floor', view.floor, i18next.t('figure.label.scene-floor'), i18next.t('figure.text.floor-explained'))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-settings.js","sourceRoot":"","sources":["../../client/modeller/figure-settings.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { DEFAULT_VIEW, ENVIRONMENTS } from './figure-view.js'\nimport type { EnvironmentName, ViewSettings } from './figure-view.js'\n\n/**\n * 저작 화면의 **보기 설정**.\n *\n * ## 자산이 아니라 작업대다\n *\n * 여기서 고치는 것은 저장되지 않는다. 조명을 밝게 하고 바닥을 켠 것은 지금 보기 편한\n * 상태일 뿐이다. 그래서 「저장 안 됨」을 화면에 적어 둔다 — 안 적으면 저작자가 조명을\n * 맞춰 두고 그것이 자산에 딸려 간다고 여긴다.\n *\n * ## 값을 들고 있지 않는다\n *\n * 이 패널은 받은 것을 그리고 바뀐 것을 알린다. 정본은 모델러 페이지가 갖는다 — 속성\n * 패널과 같은 방식이다. 여기서 들고 있으면 페이지가 다시 그릴 때 두 벌이 갈린다.\n */\n@customElement('figure-settings')\nexport class FigureSettings 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 font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 속성 패널의 묶음 제목과 같은 규격이다 — 두 탭이 다른 눈금을 쓰면 따로 논다 */\n h3 {\n margin: 0 0 2px 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 p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n label[row] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--spacing-medium, 8px);\n cursor: pointer;\n }\n label[row] span {\n display: flex;\n flex-direction: column;\n }\n label[row] em {\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n input[type='checkbox'] {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n\n label[field] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n label[field] > span {\n color: var(--md-sys-color-on-surface-variant);\n }\n\n select,\n input[type='color'] {\n width: 100%;\n padding: 5px 6px;\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);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n input[type='color'] {\n height: 30px;\n padding: 2px;\n cursor: pointer;\n }\n\n /* 값이 옆에 붙어야 끌면서 얼마인지 안다 */\n div[slider] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n 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.4em;\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\n button[reset] {\n align-self: flex-start;\n padding: 5px 12px;\n font: var(--label-font, inherit);\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n button[reset]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n `\n ]\n\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n\n render() {\n const view = this.view ?? DEFAULT_VIEW\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.guides')}</h3>\n <p quiet>${i18next.t('figure.text.view-settings-are-not-saved')}</p>\n\n ${this.toggle('grid', view.grid, i18next.t('figure.label.coordinate-ground'), i18next.t('figure.text.coordinate-ground-explained'))}\n ${this.toggle('gridLabels', view.gridLabels, i18next.t('figure.label.axis-labels'))}\n ${this.toggle('floor', view.floor, i18next.t('figure.label.scene-floor'), i18next.t('figure.text.floor-explained'))}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.environment')}</h3>\n\n <label field>\n <span>${i18next.t('figure.label.preset')}</span>\n <select\n .value=${view.environment}\n @change=${(e: Event) => this.put({ environment: (e.target as HTMLSelectElement).value as EnvironmentName })}\n >\n ${ENVIRONMENTS.map(\n name => html`<option value=${name} ?selected=${view.environment === name}>\n ${i18next.t(`figure.label.environment-${name}`)}\n </option>`\n )}\n </select>\n </label>\n <p quiet>${i18next.t('figure.text.environment-explained')}</p>\n\n <label field>\n <span>${i18next.t('figure.label.sky-color')}</span>\n <input\n type=\"color\"\n .value=${view.skyColor}\n @change=${(e: Event) => this.put({ skyColor: (e.target as HTMLInputElement).value })}\n />\n </label>\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.key-light')}</h3>\n\n ${this.slider('dirLightIntensity', view.dirLightIntensity, i18next.t('figure.label.intensity'), 0, 5, 0.1)}\n ${this.toggle(\n 'dirLightFollowCamera',\n view.dirLightFollowCamera,\n i18next.t('figure.label.follow-camera'),\n i18next.t('figure.text.follow-camera-explained')\n )}\n ${this.toggle('dirShadowEnabled', view.dirShadowEnabled, i18next.t('figure.label.cast-shadow'))}\n ${view.dirLightIntensity === 0 && view.hemiIntensity === 0\n ? html`<p quiet>${i18next.t('figure.text.all-lights-off')}</p>`\n : ''}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.hemisphere-light')}</h3>\n\n ${this.slider('hemiIntensity', view.hemiIntensity, i18next.t('figure.label.intensity'), 0, 8, 0.1)}\n <p quiet>${i18next.t('figure.text.hemisphere-light-explained')}</p>\n </section>\n\n <section>\n <button reset @click=${() => this.reset()}>${i18next.t('figure.button.reset-view')}</button>\n </section>\n `\n }\n\n private toggle(key: keyof ViewSettings, on: boolean, label: string, hint?: string) {\n return html`\n <label row>\n <span>\n ${label}${hint ? html`<em>${hint}</em>` : ''}\n </span>\n <input\n type=\"checkbox\"\n .checked=${on}\n @change=${(e: Event) => this.put({ [key]: (e.target as HTMLInputElement).checked } as Partial<ViewSettings>)}\n />\n </label>\n `\n }\n\n private slider(key: keyof ViewSettings, value: number, label: string, min: number, max: number, step: number) {\n return html`\n <label field>\n <span>${label}</span>\n <div slider>\n <input\n type=\"range\"\n min=${min}\n max=${max}\n step=${step}\n .value=${String(value)}\n @input=${(e: Event) =>\n this.put({ [key]: Number((e.target as HTMLInputElement).value) } as Partial<ViewSettings>)}\n />\n <output>${value.toFixed(1)}</output>\n </div>\n </label>\n `\n }\n\n /** 처음 값으로 되돌린다. 만지다 길을 잃었을 때 돌아올 자리가 있어야 한다. */\n private reset() {\n this.tell({ ...DEFAULT_VIEW })\n }\n\n private put(patch: Partial<ViewSettings>) {\n this.tell({ ...(this.view ?? DEFAULT_VIEW), ...patch })\n }\n\n /**\n * 바뀐 것을 알린다.\n *\n * **알리기 전에 내 것도 바꿔 둔다.** Lit 은 속성을 다음 렌더에 넣으므로, 같은 프레임에\n * 두 번 고치면 두 번째가 `this.view` 의 옛 값을 읽어 첫 번째를 덮는다. 실제로 그랬다 —\n * 축 가이드를 끄고 바닥판을 켰더니 축 가이드가 도로 켜져 있었고, 화면의 체크 상자와\n * 씬의 상태가 서로 다른 말을 했다.\n *\n * 정본은 여전히 모델러 페이지가 갖는다. 여기 값은 다음 렌더에 같은 것으로 덮인다.\n */\n private tell(view: ViewSettings) {\n this.view = view\n this.dispatchEvent(new CustomEvent('update-view', { detail: { view }, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-settings.js","sourceRoot":"","sources":["../../client/modeller/figure-settings.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { DEFAULT_VIEW, ENVIRONMENTS, SCENE_REFERENCES } from './figure-view.js'\nimport type { EnvironmentName, ViewSettings } from './figure-view.js'\n\n/**\n * 저작 화면의 **보기 설정**.\n *\n * ## 자산이 아니라 작업대다\n *\n * 여기서 고치는 것은 저장되지 않는다. 조명을 밝게 하고 바닥을 켠 것은 지금 보기 편한\n * 상태일 뿐이다. 그래서 「저장 안 됨」을 화면에 적어 둔다 — 안 적으면 저작자가 조명을\n * 맞춰 두고 그것이 자산에 딸려 간다고 여긴다.\n *\n * ## 값을 들고 있지 않는다\n *\n * 이 패널은 받은 것을 그리고 바뀐 것을 알린다. 정본은 모델러 페이지가 갖는다 — 속성\n * 패널과 같은 방식이다. 여기서 들고 있으면 페이지가 다시 그릴 때 두 벌이 갈린다.\n */\n@customElement('figure-settings')\nexport class FigureSettings 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 font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 속성 패널의 묶음 제목과 같은 규격이다 — 두 탭이 다른 눈금을 쓰면 따로 논다 */\n h3 {\n margin: 0 0 2px 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 p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n label[row] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--spacing-medium, 8px);\n cursor: pointer;\n }\n label[row] span {\n display: flex;\n flex-direction: column;\n }\n label[row] em {\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n input[type='checkbox'] {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n\n label[field] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n label[field] > span {\n color: var(--md-sys-color-on-surface-variant);\n }\n\n select,\n input[type='number'],\n input[type='color'] {\n width: 100%;\n padding: 5px 6px;\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);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n input[type='color'] {\n height: 30px;\n padding: 2px;\n cursor: pointer;\n }\n\n /* 값이 옆에 붙어야 끌면서 얼마인지 안다 */\n div[slider] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n 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.4em;\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\n button[reset] {\n align-self: flex-start;\n padding: 5px 12px;\n font: var(--label-font, inherit);\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n button[reset]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n `\n ]\n\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n\n render() {\n const view = this.view ?? DEFAULT_VIEW\n\n return html`\n <!--\n 놓이는 보드.\n\n 이 묶음이 맨 위인 것은, 나머지가 「어떻게 보이나」인데 이것은 「무엇으로 보고\n 있나」이기 때문이다. 미리보기가 보드에 놓인 모습을 말하는 자리이므로, 어떤 보드를\n 가정하는지가 먼저 정해져야 그 다음이 뜻을 갖는다.\n\n 도형 탭의 「배치 기준」과 짝이지 같은 것이 아니다. 저쪽은 이 도형이 어느 면에\n 붙는가(저장된다), 이쪽은 그 면을 가진 보드가 어느 쪽을 기준으로 재는가(저장 안 된다).\n -->\n <section>\n <h3>${i18next.t('figure.label.assumed-board')}</h3>\n <p quiet>${i18next.t('figure.text.view-settings-are-not-saved')}</p>\n\n <label field>\n <span>${i18next.t('figure.label.scene-reference')}</span>\n <select\n .value=${view.placement}\n @change=${(e: Event) =>\n this.put({ placement: (e.target as HTMLSelectElement).value as ViewSettings['placement'] })}\n >\n ${SCENE_REFERENCES.map(\n name => html`<option value=${name} ?selected=${view.placement === name}>\n ${i18next.t(`figure.label.scene-reference-${name}`)}\n </option>`\n )}\n </select>\n </label>\n <p quiet>${i18next.t('figure.text.scene-reference-explained')}</p>\n\n <label field>\n <span>${i18next.t('figure.label.ceiling-height')}</span>\n <input\n type=\"number\"\n min=\"0\"\n step=\"100\"\n .value=${String(view.ceilingHeight)}\n @change=${(e: Event) => this.put({ ceilingHeight: Number((e.target as HTMLInputElement).value) || 0 })}\n />\n </label>\n <p quiet>${i18next.t('figure.text.ceiling-height-explained')}</p>\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.guides')}</h3>\n\n ${this.toggle('grid', view.grid, i18next.t('figure.label.coordinate-ground'), i18next.t('figure.text.coordinate-ground-explained'))}\n ${this.toggle('gridLabels', view.gridLabels, i18next.t('figure.label.axis-labels'))}\n ${this.toggle('floor', view.floor, i18next.t('figure.label.scene-floor'), i18next.t('figure.text.floor-explained'))}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.environment')}</h3>\n\n <label field>\n <span>${i18next.t('figure.label.preset')}</span>\n <select\n .value=${view.environment}\n @change=${(e: Event) => this.put({ environment: (e.target as HTMLSelectElement).value as EnvironmentName })}\n >\n ${ENVIRONMENTS.map(\n name => html`<option value=${name} ?selected=${view.environment === name}>\n ${i18next.t(`figure.label.environment-${name}`)}\n </option>`\n )}\n </select>\n </label>\n <p quiet>${i18next.t('figure.text.environment-explained')}</p>\n\n <label field>\n <span>${i18next.t('figure.label.sky-color')}</span>\n <input\n type=\"color\"\n .value=${view.skyColor}\n @change=${(e: Event) => this.put({ skyColor: (e.target as HTMLInputElement).value })}\n />\n </label>\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.key-light')}</h3>\n\n ${this.slider('dirLightIntensity', view.dirLightIntensity, i18next.t('figure.label.intensity'), 0, 5, 0.1)}\n ${this.toggle(\n 'dirLightFollowCamera',\n view.dirLightFollowCamera,\n i18next.t('figure.label.follow-camera'),\n i18next.t('figure.text.follow-camera-explained')\n )}\n ${this.toggle('dirShadowEnabled', view.dirShadowEnabled, i18next.t('figure.label.cast-shadow'))}\n ${view.dirLightIntensity === 0 && view.hemiIntensity === 0\n ? html`<p quiet>${i18next.t('figure.text.all-lights-off')}</p>`\n : ''}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.hemisphere-light')}</h3>\n\n ${this.slider('hemiIntensity', view.hemiIntensity, i18next.t('figure.label.intensity'), 0, 8, 0.1)}\n <p quiet>${i18next.t('figure.text.hemisphere-light-explained')}</p>\n </section>\n\n <section>\n <button reset @click=${() => this.reset()}>${i18next.t('figure.button.reset-view')}</button>\n </section>\n `\n }\n\n private toggle(key: keyof ViewSettings, on: boolean, label: string, hint?: string) {\n return html`\n <label row>\n <span>\n ${label}${hint ? html`<em>${hint}</em>` : ''}\n </span>\n <input\n type=\"checkbox\"\n .checked=${on}\n @change=${(e: Event) => this.put({ [key]: (e.target as HTMLInputElement).checked } as Partial<ViewSettings>)}\n />\n </label>\n `\n }\n\n private slider(key: keyof ViewSettings, value: number, label: string, min: number, max: number, step: number) {\n return html`\n <label field>\n <span>${label}</span>\n <div slider>\n <input\n type=\"range\"\n min=${min}\n max=${max}\n step=${step}\n .value=${String(value)}\n @input=${(e: Event) =>\n this.put({ [key]: Number((e.target as HTMLInputElement).value) } as Partial<ViewSettings>)}\n />\n <output>${value.toFixed(1)}</output>\n </div>\n </label>\n `\n }\n\n /** 처음 값으로 되돌린다. 만지다 길을 잃었을 때 돌아올 자리가 있어야 한다. */\n private reset() {\n this.tell({ ...DEFAULT_VIEW })\n }\n\n private put(patch: Partial<ViewSettings>) {\n this.tell({ ...(this.view ?? DEFAULT_VIEW), ...patch })\n }\n\n /**\n * 바뀐 것을 알린다.\n *\n * **알리기 전에 내 것도 바꿔 둔다.** Lit 은 속성을 다음 렌더에 넣으므로, 같은 프레임에\n * 두 번 고치면 두 번째가 `this.view` 의 옛 값을 읽어 첫 번째를 덮는다. 실제로 그랬다 —\n * 축 가이드를 끄고 바닥판을 켰더니 축 가이드가 도로 켜져 있었고, 화면의 체크 상자와\n * 씬의 상태가 서로 다른 말을 했다.\n *\n * 정본은 여전히 모델러 페이지가 갖는다. 여기 값은 다음 렌더에 같은 것으로 덮인다.\n */\n private tell(view: ViewSettings) {\n this.view = view\n this.dispatchEvent(new CustomEvent('update-view', { detail: { view }, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -7,7 +7,7 @@ import './figure-settings.js';
|
|
|
7
7
|
import './figure-history-panel.js';
|
|
8
8
|
import { LitElement } from 'lit';
|
|
9
9
|
import type { FigureSource } from '@hatiolab/figure-model';
|
|
10
|
-
import type {
|
|
10
|
+
import type { FigureDraft, PartModel } from './figure-source.js';
|
|
11
11
|
import type { ViewSettings } from './figure-view.js';
|
|
12
12
|
declare const FigureSide_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
13
13
|
/**
|
|
@@ -39,7 +39,7 @@ export declare class FigureSide extends FigureSide_base {
|
|
|
39
39
|
figureState?: 'draft' | 'released';
|
|
40
40
|
figureVersion: number;
|
|
41
41
|
dirty: boolean;
|
|
42
|
-
|
|
42
|
+
draft?: FigureDraft;
|
|
43
43
|
parts: PartModel[];
|
|
44
44
|
source?: FigureSource;
|
|
45
45
|
selected: number;
|
|
@@ -155,7 +155,7 @@ let FigureSide = class FigureSide extends localize(i18next)(LitElement) {
|
|
|
155
155
|
* 0 이면 아무것도 안 붙인다 — 「0」을 붙이면 없는 것이 있는 것처럼 자리를 차지한다.
|
|
156
156
|
*/
|
|
157
157
|
get clipCount() {
|
|
158
|
-
const n = this.
|
|
158
|
+
const n = this.draft?.animations?.length ?? 0;
|
|
159
159
|
return n === 0 ? undefined : n;
|
|
160
160
|
}
|
|
161
161
|
render() {
|
|
@@ -175,20 +175,20 @@ let FigureSide = class FigureSide extends localize(i18next)(LitElement) {
|
|
|
175
175
|
.figureName=${this.figureName}
|
|
176
176
|
.figureCategory=${this.figureCategory}
|
|
177
177
|
.figureTags=${this.figureTags}
|
|
178
|
-
.
|
|
178
|
+
.draft=${this.draft}
|
|
179
179
|
.parts=${this.parts}
|
|
180
180
|
.selected=${this.selected}
|
|
181
181
|
></figure-inspector>
|
|
182
182
|
|
|
183
183
|
<figure-capabilities
|
|
184
184
|
?off=${this.tab !== 'capability'}
|
|
185
|
-
.
|
|
185
|
+
.draft=${this.draft}
|
|
186
186
|
.parts=${this.parts}
|
|
187
187
|
></figure-capabilities>
|
|
188
188
|
|
|
189
189
|
<figure-animations
|
|
190
190
|
?off=${this.tab !== 'animation'}
|
|
191
|
-
.
|
|
191
|
+
.draft=${this.draft}
|
|
192
192
|
.parts=${this.parts}
|
|
193
193
|
></figure-animations>
|
|
194
194
|
|
|
@@ -267,7 +267,7 @@ __decorate([
|
|
|
267
267
|
__decorate([
|
|
268
268
|
property({ type: Object }),
|
|
269
269
|
__metadata("design:type", Object)
|
|
270
|
-
], FigureSide.prototype, "
|
|
270
|
+
], FigureSide.prototype, "draft", void 0);
|
|
271
271
|
__decorate([
|
|
272
272
|
property({ type: Array }),
|
|
273
273
|
__metadata("design:type", Array)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-side.js","sourceRoot":"","sources":["../../client/modeller/figure-side.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport './figure-inspector.js'\nimport './figure-capabilities.js'\nimport './figure-animations.js'\nimport './figure-report.js'\nimport './figure-settings.js'\nimport './figure-history-panel.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureSource } from '@hatiolab/figure-model'\n\nimport type { BoardModel, PartModel } from './figure-source.js'\nimport { DEFAULT_VIEW } from './figure-view.js'\nimport type { ViewSettings } from './figure-view.js'\n\ntype Tab = 'properties' | 'capability' | 'animation' | 'verdict' | 'view' | 'history'\n\n/**\n * 저작면 오른쪽 칸 — 속성과 판정을 탭으로 겹쳐 둔다.\n *\n * ## 왜 위아래로 나누지 않나\n *\n * 둘 다 세로로 긴 화면이다. 위아래로 나누면 양쪽 다 잘려서, 속성은 재질까지 못\n * 내려가고 판정은 무게까지 못 내려간다. 탭으로 겹치면 각자 칸 전체를 쓴다.\n *\n * ## 그래도 개수는 늘 보인다\n *\n * 성숙도의 축 하나 —「몇 개까지 편한가」— 는 부품을 고칠 때마다 움직이고, 그것이\n * 보여야 저작자가 고칠 수 있다. 탭 뒤로 숨으면 저장하고 나서야 알게 된다. 그래서 그\n * 숫자만 **탭 이름표에 남긴다** — 성숙도를 덮어 두고 속성을 만져도 따라 움직이는\n * 것이 보인다.\n *\n * 판정은 화면에서 감춰도 **DOM 에는 남는다.** 떼었다 붙이면 다시 세느라 값이 잠깐\n * 비고, 펼쳐 둔 추이 그림 같은 상태도 날아간다.\n */\n@customElement('figure-side')\nexport class FigureSide extends localize(i18next)(LitElement) {\n static styles = 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-left: 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 div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n /*\n **아이콘 하나가 한 칸을 쓴다.**\n\n 전에는 이름을 적었는데, 넷이 좁은 칸을 나눠 쓰면서 접혀 「성숙 / 도」가 되었다. 접히면\n 그 탭만 두 줄이 되어 높이가 서로 달라진다. 다섯이 되면서 더는 줄여 짜 넣을 수 없어\n 아이콘으로 바꿨고, 이름은 title 과 aria-label 로 남는다.\n\n 배지는 여전히 자리를 차지하지 않고 **아이콘 위에 겹친다**(아래 span[badge]). 개수는\n 곁들이는 것이다.\n */\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n /* 고른 탭을 밑줄로 말한다 — 칸을 색으로 채우면 아래 내용과 경계가 흐려진다 */\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n font-weight: 700;\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n\n /*\n 판정을 덮어 두어도 개수는 여기 남는다.\n\n 자리를 먹지 않게 오른쪽 위에 겹쳐 둔다 — 탭 이름을 밀지 않으므로 이름이 접히지 않는다.\n 숫자가 커져도(1,200 같은 것) 이름 위로 넘칠 뿐 줄을 바꾸지 않는다.\n */\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n /* 이름 위에 겹치되 위로 붙지 않게 — 탭 가운데 글자와 눈높이를 조금 맞춘다 */\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n\n figure-inspector,\n figure-animations,\n figure-report,\n figure-settings {\n flex: 1;\n min-height: 0;\n border-left: none;\n }\n /* 감추되 떼지는 않는다 */\n [off] {\n display: none;\n }\n `\n\n /*\n 두 형태가 이 패널에서 갈린다.\n\n 속성은 **수정하는** 곳이라 편집 중인 씬 모델을 받는다. 판정은 **저장될 것을 측정하는**\n 곳이라 변환된 FigureSource 를 받는다. 변환은 모델러 페이지가 한 번만 해서 내려보낸다.\n */\n @property({ type: String }) figureName = ''\n /** 카탈로그 분류·태그. 속성 탭이 보여 주고 바꾼다. */\n @property({ type: String }) figureCategory?: string\n @property({ type: Array }) figureTags?: string[]\n /** 이력 탭이 발행하고 되돌리는 데 필요한 것들. 정본은 저작면이 든다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n @property({ type: Boolean }) dirty = false\n\n @property({ type: Object }) board?: BoardModel\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Object }) source?: FigureSource\n @property({ type: Number }) selected = -1\n /** 저작 화면의 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다. */\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n\n @state() private tab: Tab = 'properties'\n /** 판정이 알려 준 개수. 탭 이름표에 붙는다. */\n @state() private instances?: number\n\n /**\n * 이 도형이 든 clip 수. 탭 아이콘에 붙는다.\n *\n * 아이콘만으로는 「여기에 무언가 있다」를 알 수 없다. 대부분의 도형은 움직이지 않으므로\n * 이 탭은 늘 비어 있을 것이고, 그러면 움직이는 도형을 열었을 때 그 사실이 눈에 걸려야 한다.\n * 0 이면 아무것도 안 붙인다 — 「0」을 붙이면 없는 것이 있는 것처럼 자리를 차지한다.\n */\n private get clipCount(): number | undefined {\n const n = this.board?.animations?.length ?? 0\n return n === 0 ? undefined : n\n }\n\n render() {\n return html`\n <div tabs>\n ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}\n ${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}\n ${this.renderTab('animation', 'animation', i18next.t('figure.label.animation'), this.clipCount)}\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n\n <figure-inspector\n ?off=${this.tab !== 'properties'}\n .figureId=${this.figureId}\n .figureName=${this.figureName}\n .figureCategory=${this.figureCategory}\n .figureTags=${this.figureTags}\n .board=${this.board}\n .parts=${this.parts}\n .selected=${this.selected}\n ></figure-inspector>\n\n <figure-capabilities\n ?off=${this.tab !== 'capability'}\n .board=${this.board}\n .parts=${this.parts}\n ></figure-capabilities>\n\n <figure-animations\n ?off=${this.tab !== 'animation'}\n .board=${this.board}\n .parts=${this.parts}\n ></figure-animations>\n\n <figure-report\n ?off=${this.tab !== 'verdict'}\n .source=${this.source}\n @verdict-changed=${(e: CustomEvent) => (this.instances = e.detail.instances)}\n ></figure-report>\n\n <figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>\n\n <!--\n 이력은 감춰도 DOM 에 남긴다 — 다른 탭들과 같은 규칙이다. 떼었다 붙이면 판본을 다시 부른다.\n -->\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n .figureId=${this.figureId}\n .figureState=${this.figureState}\n .figureVersion=${this.figureVersion}\n .dirty=${this.dirty}\n ></figure-history-panel>\n `\n }\n\n /**\n * 탭 하나 — **아이콘이 이름을 대신한다.**\n *\n * 탭이 다섯이 되면서 이름이 좁은 칸을 나눠 쓰다 접혔다. 앞으로 더 늘어날 자리이므로 이름을\n * 줄여 짜 넣는 대신 아이콘으로 바꾼다.\n *\n * **이름을 버리지는 않는다.** 아이콘만으로는 무엇인지 모르는 사람이 있고, 특히 「능력」처럼\n * 이 도구에만 있는 개념이 그렇다. 그래서 이름은 `title` 로 남아 마우스를 올리면 나오고,\n * `aria-label` 로 읽는 기계에도 그대로 간다.\n */\n private renderTab(tab: Tab, icon: string, label: string, badge?: number) {\n return html`\n <button\n tab\n ?on=${this.tab === tab}\n title=${label}\n aria-label=${label}\n @click=${() => (this.tab = tab)}\n >\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-side.js","sourceRoot":"","sources":["../../client/modeller/figure-side.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport './figure-inspector.js'\nimport './figure-capabilities.js'\nimport './figure-animations.js'\nimport './figure-report.js'\nimport './figure-settings.js'\nimport './figure-history-panel.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureSource } from '@hatiolab/figure-model'\n\nimport type { FigureDraft, PartModel } from './figure-source.js'\nimport { DEFAULT_VIEW } from './figure-view.js'\nimport type { ViewSettings } from './figure-view.js'\n\ntype Tab = 'properties' | 'capability' | 'animation' | 'verdict' | 'view' | 'history'\n\n/**\n * 저작면 오른쪽 칸 — 속성과 판정을 탭으로 겹쳐 둔다.\n *\n * ## 왜 위아래로 나누지 않나\n *\n * 둘 다 세로로 긴 화면이다. 위아래로 나누면 양쪽 다 잘려서, 속성은 재질까지 못\n * 내려가고 판정은 무게까지 못 내려간다. 탭으로 겹치면 각자 칸 전체를 쓴다.\n *\n * ## 그래도 개수는 늘 보인다\n *\n * 성숙도의 축 하나 —「몇 개까지 편한가」— 는 부품을 고칠 때마다 움직이고, 그것이\n * 보여야 저작자가 고칠 수 있다. 탭 뒤로 숨으면 저장하고 나서야 알게 된다. 그래서 그\n * 숫자만 **탭 이름표에 남긴다** — 성숙도를 덮어 두고 속성을 만져도 따라 움직이는\n * 것이 보인다.\n *\n * 판정은 화면에서 감춰도 **DOM 에는 남는다.** 떼었다 붙이면 다시 세느라 값이 잠깐\n * 비고, 펼쳐 둔 추이 그림 같은 상태도 날아간다.\n */\n@customElement('figure-side')\nexport class FigureSide extends localize(i18next)(LitElement) {\n static styles = 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-left: 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 div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n /*\n **아이콘 하나가 한 칸을 쓴다.**\n\n 전에는 이름을 적었는데, 넷이 좁은 칸을 나눠 쓰면서 접혀 「성숙 / 도」가 되었다. 접히면\n 그 탭만 두 줄이 되어 높이가 서로 달라진다. 다섯이 되면서 더는 줄여 짜 넣을 수 없어\n 아이콘으로 바꿨고, 이름은 title 과 aria-label 로 남는다.\n\n 배지는 여전히 자리를 차지하지 않고 **아이콘 위에 겹친다**(아래 span[badge]). 개수는\n 곁들이는 것이다.\n */\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n /* 고른 탭을 밑줄로 말한다 — 칸을 색으로 채우면 아래 내용과 경계가 흐려진다 */\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n font-weight: 700;\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n\n /*\n 판정을 덮어 두어도 개수는 여기 남는다.\n\n 자리를 먹지 않게 오른쪽 위에 겹쳐 둔다 — 탭 이름을 밀지 않으므로 이름이 접히지 않는다.\n 숫자가 커져도(1,200 같은 것) 이름 위로 넘칠 뿐 줄을 바꾸지 않는다.\n */\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n /* 이름 위에 겹치되 위로 붙지 않게 — 탭 가운데 글자와 눈높이를 조금 맞춘다 */\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n\n figure-inspector,\n figure-animations,\n figure-report,\n figure-settings {\n flex: 1;\n min-height: 0;\n border-left: none;\n }\n /* 감추되 떼지는 않는다 */\n [off] {\n display: none;\n }\n `\n\n /*\n 두 형태가 이 패널에서 갈린다.\n\n 속성은 **수정하는** 곳이라 편집 중인 씬 모델을 받는다. 판정은 **저장될 것을 측정하는**\n 곳이라 변환된 FigureSource 를 받는다. 변환은 모델러 페이지가 한 번만 해서 내려보낸다.\n */\n @property({ type: String }) figureName = ''\n /** 카탈로그 분류·태그. 속성 탭이 보여 주고 바꾼다. */\n @property({ type: String }) figureCategory?: string\n @property({ type: Array }) figureTags?: string[]\n /** 이력 탭이 발행하고 되돌리는 데 필요한 것들. 정본은 저작면이 든다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n @property({ type: Boolean }) dirty = false\n\n @property({ type: Object }) draft?: FigureDraft\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Object }) source?: FigureSource\n @property({ type: Number }) selected = -1\n /** 저작 화면의 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다. */\n @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW\n\n @state() private tab: Tab = 'properties'\n /** 판정이 알려 준 개수. 탭 이름표에 붙는다. */\n @state() private instances?: number\n\n /**\n * 이 도형이 든 clip 수. 탭 아이콘에 붙는다.\n *\n * 아이콘만으로는 「여기에 무언가 있다」를 알 수 없다. 대부분의 도형은 움직이지 않으므로\n * 이 탭은 늘 비어 있을 것이고, 그러면 움직이는 도형을 열었을 때 그 사실이 눈에 걸려야 한다.\n * 0 이면 아무것도 안 붙인다 — 「0」을 붙이면 없는 것이 있는 것처럼 자리를 차지한다.\n */\n private get clipCount(): number | undefined {\n const n = this.draft?.animations?.length ?? 0\n return n === 0 ? undefined : n\n }\n\n render() {\n return html`\n <div tabs>\n ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}\n ${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}\n ${this.renderTab('animation', 'animation', i18next.t('figure.label.animation'), this.clipCount)}\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n\n <figure-inspector\n ?off=${this.tab !== 'properties'}\n .figureId=${this.figureId}\n .figureName=${this.figureName}\n .figureCategory=${this.figureCategory}\n .figureTags=${this.figureTags}\n .draft=${this.draft}\n .parts=${this.parts}\n .selected=${this.selected}\n ></figure-inspector>\n\n <figure-capabilities\n ?off=${this.tab !== 'capability'}\n .draft=${this.draft}\n .parts=${this.parts}\n ></figure-capabilities>\n\n <figure-animations\n ?off=${this.tab !== 'animation'}\n .draft=${this.draft}\n .parts=${this.parts}\n ></figure-animations>\n\n <figure-report\n ?off=${this.tab !== 'verdict'}\n .source=${this.source}\n @verdict-changed=${(e: CustomEvent) => (this.instances = e.detail.instances)}\n ></figure-report>\n\n <figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>\n\n <!--\n 이력은 감춰도 DOM 에 남긴다 — 다른 탭들과 같은 규칙이다. 떼었다 붙이면 판본을 다시 부른다.\n -->\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n .figureId=${this.figureId}\n .figureState=${this.figureState}\n .figureVersion=${this.figureVersion}\n .dirty=${this.dirty}\n ></figure-history-panel>\n `\n }\n\n /**\n * 탭 하나 — **아이콘이 이름을 대신한다.**\n *\n * 탭이 다섯이 되면서 이름이 좁은 칸을 나눠 쓰다 접혔다. 앞으로 더 늘어날 자리이므로 이름을\n * 줄여 짜 넣는 대신 아이콘으로 바꾼다.\n *\n * **이름을 버리지는 않는다.** 아이콘만으로는 무엇인지 모르는 사람이 있고, 특히 「능력」처럼\n * 이 도구에만 있는 개념이 그렇다. 그래서 이름은 `title` 로 남아 마우스를 올리면 나오고,\n * `aria-label` 로 읽는 기계에도 그대로 간다.\n */\n private renderTab(tab: Tab, icon: string, label: string, badge?: number) {\n return html`\n <button\n tab\n ?on=${this.tab === tab}\n title=${label}\n aria-label=${label}\n @click=${() => (this.tab = tab)}\n >\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
|