@things-factory/figure-ui 10.1.3 → 10.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/graphql/index.ts +87 -12
- package/client/modeller/figure-ask.ts +14 -4
- package/client/modeller/figure-canvas.ts +53 -0
- package/client/modeller/figure-history-panel.ts +324 -0
- package/client/modeller/figure-inspector.ts +78 -2
- package/client/modeller/figure-side.ts +22 -1
- package/client/modeller/figure-thumbnail.ts +78 -0
- package/client/pages/figure-list-page.ts +391 -195
- package/client/pages/figure-modeller-page.ts +114 -19
- package/client/types.ts +17 -0
- package/dist-client/graphql/index.d.ts +24 -4
- package/dist-client/graphql/index.js +70 -9
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-ask.js +14 -4
- package/dist-client/modeller/figure-ask.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +11 -0
- package/dist-client/modeller/figure-canvas.js +42 -0
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +42 -0
- package/dist-client/modeller/figure-history-panel.js +343 -0
- package/dist-client/modeller/figure-history-panel.js.map +1 -0
- package/dist-client/modeller/figure-inspector.d.ts +17 -0
- package/dist-client/modeller/figure-inspector.js +77 -2
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +7 -0
- package/dist-client/modeller/figure-side.js +43 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +3 -0
- package/dist-client/modeller/figure-thumbnail.js +59 -0
- package/dist-client/modeller/figure-thumbnail.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +45 -17
- package/dist-client/pages/figure-list-page.js +374 -209
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +27 -0
- package/dist-client/pages/figure-modeller-page.js +109 -19
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-client/types.d.ts +19 -0
- package/dist-client/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +5 -3
- package/translations/en.json +19 -0
- package/translations/ja.json +21 -2
- package/translations/ko.json +21 -2
- package/translations/ms.json +21 -2
- package/translations/zh.json +21 -2
- package/client/viewparts/figure-card.ts +0 -226
- package/dist-client/viewparts/figure-card.d.ts +0 -24
- package/dist-client/viewparts/figure-card.js +0 -221
- package/dist-client/viewparts/figure-card.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
2
3
|
import { css, html, LitElement, nothing, svg } from 'lit';
|
|
3
4
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
5
|
import { i18next, localize } from '@operato/i18n';
|
|
@@ -50,6 +51,9 @@ import { canMirror } from './part-edits.js';
|
|
|
50
51
|
let FigureInspector = class FigureInspector extends localize(i18next)(LitElement) {
|
|
51
52
|
constructor() {
|
|
52
53
|
super(...arguments);
|
|
54
|
+
/** 보드. 위치를 보드 중심 기준으로 바꾸려면 이것이 있어야 한다. */
|
|
55
|
+
/** 도형의 표시 이름. 정본은 저작면이 들고, 여기서는 보여 주고 바꾼다(`figure-rename` 으로 알린다). */
|
|
56
|
+
this.figureName = '';
|
|
53
57
|
/** 보드 위의 부품들. 씬 모델 그대로다. */
|
|
54
58
|
this.parts = [];
|
|
55
59
|
this.selected = -1;
|
|
@@ -445,6 +449,29 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
445
449
|
line-height: 1.6;
|
|
446
450
|
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
447
451
|
}
|
|
452
|
+
|
|
453
|
+
/*
|
|
454
|
+
고칠 수 없는 값 — 입력칸처럼 보이지 않게 한다.
|
|
455
|
+
|
|
456
|
+
전에는 그냥 글자였고, 옆의 이름 칸과 나란히 있으니 눌러도 되는 것처럼 보였다. 자물쇠와
|
|
457
|
+
칸 없는 바탕이 「읽는 값」이라고 먼저 말한다.
|
|
458
|
+
*/
|
|
459
|
+
code[fixed] {
|
|
460
|
+
display: inline-flex;
|
|
461
|
+
align-items: center;
|
|
462
|
+
gap: 4px;
|
|
463
|
+
padding: 4px 8px;
|
|
464
|
+
border-radius: 6px;
|
|
465
|
+
background-color: var(--md-sys-color-surface-container-high, rgba(0, 0, 0, 0.05));
|
|
466
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
467
|
+
font-family: var(--mono-font, monospace);
|
|
468
|
+
font-size: 0.76rem;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
code[fixed] md-icon {
|
|
472
|
+
--md-icon-size: 13px;
|
|
473
|
+
opacity: 0.7;
|
|
474
|
+
}
|
|
448
475
|
`
|
|
449
476
|
]; }
|
|
450
477
|
/** `part` 는 HTMLElement 의 속성(DOMTokenList)이다 — 가리면 형이 어긋난다. */
|
|
@@ -455,6 +482,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
455
482
|
const part = this.chosen;
|
|
456
483
|
if (!part || !this.board) {
|
|
457
484
|
return html `
|
|
485
|
+
${this.renderFigure()}
|
|
458
486
|
<section>
|
|
459
487
|
<h3>${i18next.t('label.properties')}</h3>
|
|
460
488
|
<p quiet>${i18next.t('text.select-a-part-to-edit')}</p>
|
|
@@ -464,8 +492,51 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
464
492
|
// 여기부터는 사용자 용어다 — X·Y·Z, 위치는 부품 중심, 회전은 도(degree).
|
|
465
493
|
const shown = partToFigure(part, this.board);
|
|
466
494
|
return html `
|
|
467
|
-
${this.
|
|
468
|
-
${this.renderMaterial(shown)} ${this.renderBehaviour(shown)}
|
|
495
|
+
${this.renderFigure()} ${this.renderIdentity(shown)} ${this.renderPlacement(shown)}
|
|
496
|
+
${this.renderShape(shown)} ${this.renderMaterial(shown)} ${this.renderBehaviour(shown)}
|
|
497
|
+
`;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* 도형 자체 — 이름과 타입.
|
|
501
|
+
*
|
|
502
|
+
* ## 왜 머리줄이 아니라 여기인가
|
|
503
|
+
*
|
|
504
|
+
* 저작면 위에 줄을 하나 더 두고 이름 칸을 놓았었다. 앱에서는 셸이 페이지 제목으로 그 이름을
|
|
505
|
+
* 이미 그리므로 같은 글자가 두 번 나오고, 캔버스가 주인공인 화면에서 그 줄만큼 높이를 잃었다.
|
|
506
|
+
* 속성 패널은 「이 도형이 무엇인가」를 다루는 자리이므로 이름도 여기 산다.
|
|
507
|
+
*
|
|
508
|
+
* ## 타입은 읽기만 한다
|
|
509
|
+
*
|
|
510
|
+
* 저장되는 식별자다. 배치된 보드가 이 이름으로 타입을 찾으므로 만든 뒤에는 못 바꾼다.
|
|
511
|
+
*/
|
|
512
|
+
renderFigure() {
|
|
513
|
+
return html `
|
|
514
|
+
<section>
|
|
515
|
+
<h3>${i18next.t('label.figure')}</h3>
|
|
516
|
+
<div field>
|
|
517
|
+
<label>${i18next.t('label.name')}</label>
|
|
518
|
+
<input
|
|
519
|
+
.value=${this.figureName}
|
|
520
|
+
placeholder=${i18next.t('text.figure-display-name')}
|
|
521
|
+
@change=${(e) => this.dispatchEvent(new CustomEvent('figure-rename', {
|
|
522
|
+
detail: { name: e.target.value },
|
|
523
|
+
bubbles: true,
|
|
524
|
+
composed: true
|
|
525
|
+
}))}
|
|
526
|
+
/>
|
|
527
|
+
</div>
|
|
528
|
+
<div field>
|
|
529
|
+
<label>${i18next.t('label.type')}</label>
|
|
530
|
+
<code fixed title=${i18next.t('text.type-name-cannot-change')}>
|
|
531
|
+
<md-icon>lock</md-icon>${this.board?.figureType ?? ''}
|
|
532
|
+
</code>
|
|
533
|
+
</div>
|
|
534
|
+
<!--
|
|
535
|
+
왜 못 고치는지 **적어 둔다.** 전에는 tooltip 에만 있었고, 그러면 눌러 보고 안 되는 것을
|
|
536
|
+
알게 된다 — 화면이 안 되는 이유를 말하지 않으면 사용자가 자기 실수라고 생각한다.
|
|
537
|
+
-->
|
|
538
|
+
<p quiet>${i18next.t('text.type-name-cannot-change')}</p>
|
|
539
|
+
</section>
|
|
469
540
|
`;
|
|
470
541
|
}
|
|
471
542
|
renderIdentity(part) {
|
|
@@ -972,6 +1043,10 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
972
1043
|
this.dispatchEvent(new CustomEvent('update-part', { detail: { index: this.selected, part }, bubbles: true, composed: true }));
|
|
973
1044
|
}
|
|
974
1045
|
};
|
|
1046
|
+
__decorate([
|
|
1047
|
+
property({ type: String }),
|
|
1048
|
+
__metadata("design:type", Object)
|
|
1049
|
+
], FigureInspector.prototype, "figureName", void 0);
|
|
975
1050
|
__decorate([
|
|
976
1051
|
property({ type: Object }),
|
|
977
1052
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-inspector.js","sourceRoot":"","sources":["../../client/modeller/figure-inspector.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, svg } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport {\n EXTRUDE_PRIMITIVES,\n MATERIAL_PRESETS,\n PRIMITIVE_KINDS,\n SEGMENTED_PRIMITIVES,\n SEGMENT_PRESETS,\n SIZING_RULES,\n trianglesOf\n} from '@hatiolab/figure-model'\nimport type { Axis, FigurePart, MaterialPreset, PrimitiveKind } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\n\nimport { partToFigure, partFromFigure } from './figure-source.js'\nimport type { BoardModel, PartModel } from './figure-source.js'\nimport { canMirror } from './part-edits.js'\n\n/**\n * 고른 부품의 속성.\n *\n * ## 여기가 사용자 용어로 보여 주는 자리다\n *\n * 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 — 보드 위에 부품 컴포넌트가 놓인 상태.\n * 그런데 사용자에게 보여야 하는 것은 씬 속성 이름(`left`·`top`·`zPos`)이 아니라\n * **X·Y·Z** 다. 다른 3D 도구가 모두 그렇게 부르고, 위치도 부품의 **중심**으로 읽는다.\n *\n * 그래서 이 패널은 받은 부품을 FigureSource 형태로 **변환해서 보여 주고, 수정한 것을\n * 다시 씬 모델로 되돌려 알린다.** 변환은 `figure-source.ts` 가 한다 — 여기서 축을 다시 계산하지\n * 않는다.\n *\n * ## 있는 것만 보여 준다\n *\n * 분할 수는 곡면이 있는 도형에만, 모서리 굴림은 단면을 뽑는 도형에만 뜻이 있다.\n * 뜻 없는 칸을 회색으로 늘어놓으면 저작자가 「왜 안 먹지」를 고민하게 된다 —\n * 아예 내지 않는다.\n *\n * ## 이름은 저장되는 식별자다\n *\n * `FigurePart.name` 은 바인딩·애니메이션·슬롯이 가리키는 이름이다. 고치면 배치된\n * 인스턴스의 바인딩이 **조용히** 끊긴다. 그래서 이름 칸에만 경고를 붙인다.\n *\n * ## 「상태 색이 붙는 자리」를 걷었다\n *\n * `FigurePart.materialSlot`(당시 이름은 `slot`) 을 고르는 칸이 있었는데 **아무 일도 하지 않았다.** things-scene 의\n * figure 런타임이 `slot` 을 읽지 않는다 — 선언 한 줄이 전부다. 되는 줄 알고 만들었고,\n * 확인하지 않은 것이 잘못이다.\n *\n * 그리고 되게 만들더라도 이 모양이 아니다. 상태 색은 특별한 것이 아니라 **속성 하나**이고,\n * 툴팁·라벨·표시 여부와 같은 자리에서 다뤄져야 한다. 그 설계가 정해지면(figure-model#2)\n * 이 자리에 **속성 선언과 부품 바인딩** 편집기가 들어온다.\n *\n * 그때까지 빈 약속을 두지 않는다. 아무 일도 안 하는 칸은 없느니만 못하다.\n *\n * ## 고치지 않는다\n *\n * 정본 한 벌은 저작면이 갖는다. 여기서는 바뀐 부품을 알릴 뿐이다.\n */\n@customElement('figure-inspector')\nexport class FigureInspector extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\n\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n h3 {\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n label {\n display: block;\n margin-bottom: 2px;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n input,\n select {\n width: 100%;\n box-sizing: border-box;\n padding: 4px 7px;\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input:focus,\n select:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n\n /*\n 숫자 칸 — 라벨을 칸 **안** 왼쪽에 둔다.\n\n 벡터 저작도구(Sketch·Figma)가 X·Y·W·H 를 이렇게 둔다. 라벨을 위에 얹으면 줄마다\n 두 줄을 먹어서, 자리·크기·회전 아홉 칸이 화면 절반을 차지한다. 안에 두면 한 줄에\n 셋이 들어가고 무엇이 무엇인지도 붙어 읽힌다.\n */\n div[cell] {\n display: flex;\n align-items: center;\n gap: 2px;\n padding-left: 6px;\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n div[cell]:focus-within {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n border-color: transparent;\n }\n div[cell] span[axis] {\n flex: none;\n font-size: 0.64rem;\n letter-spacing: 0.02em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[cell] input {\n min-width: 0;\n padding: 4px 6px 4px 2px;\n font-family: var(--mono-font, monospace);\n text-align: right;\n background: none;\n border: none;\n border-radius: 0;\n }\n div[cell] input:focus {\n outline: none;\n }\n\n /* 축 셋을 한 줄에 — 좌표는 셋이 같이 읽혀야 한다 */\n div[triple] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n div[field] + div[field],\n div[triple] + div[field],\n div[field] + div[triple] {\n margin-top: 7px;\n }\n\n /*\n 정밀 제작 도구.\n\n 보드 편집기는 자산을 배치하는 곳이라 대충 놓아도 되지만, 여기는 자산을 만드는\n 곳이다. 「몸통 한가운데」·「바닥에 붙이기」·「반대쪽에도 하나」를 손으로 계산하면\n 늘 조금씩 어긋나고, 기준 치수를 바꾸면 전부 다시 해야 한다.\n */\n div[tools] {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n margin-top: 6px;\n }\n div[tools] button {\n padding: 3px 8px;\n font: var(--label-font, inherit);\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n div[tools] button[disabled] {\n opacity: 0.35;\n cursor: default;\n }\n div[tools] button:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n div[tools] span[sep] {\n width: 1px;\n margin: 2px 3px;\n background-color: var(--md-sys-color-outline-variant);\n }\n\n /* 지금 어느 축이 늘어나나 — 크기를 고치면 바뀔 수 있어서 값으로 적는다 */\n p[axis-now] {\n margin: 4px 0 0 0;\n font-size: 0.7rem;\n color: var(--md-sys-color-primary);\n }\n /* 이 선택이 무엇을 무는지 — 성숙도의 묶음 수에 그대로 잡힌다 */\n p[cost] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 이름을 바꾸면 무엇이 끊기는지 말한다 */\n p[warn] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-tertiary);\n }\n\n /* 분할 수 프리셋 — 자유 입력보다 이 셋을 권한다 */\n div[presets] {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n div[presets] button {\n flex: 1;\n padding: 3px 0;\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n div[presets] button[on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 분할 수 선택 — 모양과 삼각형 수를 **함께** 보여 준다.\n\n 숫자만 늘어놓으면 8 과 16 이 무엇이 다른지 알 수 없다. 로우폴리에서 이 선택이\n 곧 「몇 개까지 편하게 놓을 수 있나」를 정하므로, 각지는 정도와 그 값이 무는\n 삼각형 수를 한자리에서 보아야 한다.\n\n 실루엣은 지어낸 그림이 아니다 — 곡면 프리미티브를 위에서 본 윤곽이 정확히\n 정n각형이다. 삼각형 수는 화면이 셈하지 않고 figure-model 의 trianglesOf 가 낸다.\n */\n div[segments] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n /* 원 유지 · 끄기 — 고르는 값이라 분할 수와 같은 모양으로 둔다 */\n div[choices] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 4px;\n }\n button[choice] {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 1px;\n padding: 6px 8px;\n text-align: left;\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[choice]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[choice][on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[choice] small {\n opacity: 0.75;\n }\n button[seg] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n padding: 6px 2px 4px 2px;\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[seg]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[seg][on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[seg] svg {\n display: block;\n /* 작으면 12 와 16 이 둘 다 동그라미로 보인다 — 각이 읽힐 만큼은 키운다 */\n width: 40px;\n height: 40px;\n }\n button[seg] svg polygon {\n fill: var(--md-sys-color-surface-container-highest);\n stroke: currentColor;\n stroke-width: 1.1;\n stroke-linejoin: miter;\n }\n button[seg][on] svg polygon {\n fill: var(--md-sys-color-surface-container-lowest);\n }\n button[seg] b {\n font-size: 0.8rem;\n font-weight: 700;\n line-height: 1.1;\n }\n button[seg] small {\n font-size: 0.62rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[seg][on] small {\n color: inherit;\n }\n /* 프리셋 밖의 값도 쓸 수 있다. 다만 권하는 쪽을 크게 둔다 */\n details[free] {\n margin-top: 6px;\n }\n details[free] summary {\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n cursor: pointer;\n }\n details[free] div[cell] {\n margin-top: 4px;\n }\n\n /*\n 색 견본.\n\n 토큰을 글자로 받지 않는다. things-scene 의 팔레트 선언이 「여기 든 이름이 저작에서\n 고를 수 있는 전부이고, 자유색을 허용하지 않는 것이 감각적인 로우폴리의 첫 규칙」\n 이라고 못박고 있다. 글자 칸으로 두면 그 규칙이 화면에서 사라지고, 오타 하나로\n resolveToken 이 던진다.\n */\n div[swatches] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n /*\n 두 무리의 경계. 「칠한 색」과 「켜진 색」이 한 줄에 섞이면 accent(주황)와\n status-warning(주황)이 나란히 놓여 무엇이 무엇인지 알 수 없다.\n */\n label[group] {\n margin-top: 8px;\n padding-top: 8px;\n font-size: var(--ui-t-micro, 11px);\n letter-spacing: 0.03em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 값이 옆에 붙어야 끌면서 얼마인지 안다 */\n div[slider] {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n div[slider] input[type='range'] {\n flex: 1;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n div[slider] output {\n flex: none;\n width: 2.6em;\n text-align: right;\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[swatch] {\n width: 26px;\n height: 26px;\n padding: 0;\n background-color: var(--swatch);\n border: 1px solid rgba(0, 0, 0, 0.16);\n border-radius: 50%;\n cursor: pointer;\n }\n button[swatch][on] {\n /* 고른 것은 테두리가 아니라 고리로 표시한다 — 색 자체를 가리지 않으려고 */\n box-shadow:\n 0 0 0 2px var(--md-sys-color-surface-container-lowest),\n 0 0 0 4px var(--md-sys-color-primary);\n }\n span[token] {\n display: block;\n margin-top: 6px;\n font-family: var(--mono-font, monospace);\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n label[toggle] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 7px 0 0 0;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n label[toggle] input {\n width: auto;\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.76rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n /** 보드. 위치를 보드 중심 기준으로 바꾸려면 이것이 있어야 한다. */\n @property({ type: Object }) board?: BoardModel\n /** 보드 위의 부품들. 씬 모델 그대로다. */\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Number }) selected = -1\n\n /** `part` 는 HTMLElement 의 속성(DOMTokenList)이다 — 가리면 형이 어긋난다. */\n private get chosen(): PartModel | undefined {\n return this.parts?.[this.selected]\n }\n\n render() {\n const part = this.chosen\n if (!part || !this.board) {\n return html`\n <section>\n <h3>${i18next.t('label.properties')}</h3>\n <p quiet>${i18next.t('text.select-a-part-to-edit')}</p>\n </section>\n `\n }\n\n // 여기부터는 사용자 용어다 — X·Y·Z, 위치는 부품 중심, 회전은 도(degree).\n const shown = partToFigure(part, this.board)\n\n return html`\n ${this.renderIdentity(shown)} ${this.renderPlacement(shown)} ${this.renderShape(shown)}\n ${this.renderMaterial(shown)} ${this.renderBehaviour(shown)}\n `\n }\n\n private renderIdentity(part: FigurePart) {\n return html`\n <section>\n <h3>${i18next.t('label.part')}</h3>\n <div field>\n <label>${i18next.t('label.name')}</label>\n <input\n .value=${part.name}\n @change=${(e: Event) => this.put({ name: (e.target as HTMLInputElement).value })}\n />\n <p warn>${i18next.t('text.renaming-a-part-breaks-bindings')}</p>\n </div>\n <div field>\n <label>${i18next.t('label.shape')}</label>\n <select @change=${(e: Event) => this.changePrimitive((e.target as HTMLSelectElement).value as PrimitiveKind)}>\n ${PRIMITIVE_KINDS.map(\n kind => html`\n <option value=${kind} ?selected=${kind === part.primitive}>\n ${i18next.t('label.primitive-' + kind)}\n </option>\n `\n )}\n </select>\n </div>\n </section>\n `\n }\n\n private renderPlacement(part: FigurePart) {\n const t = part.transform\n const rotation = t.rotation ?? {}\n\n return html`\n <section>\n <h3>${i18next.t('label.placement')}</h3>\n\n <label>${i18next.t('label.position')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), t.position[axis], value =>\n this.putTransform({ position: { ...t.position, [axis]: value } })\n )\n )}\n </div>\n\n <div field>\n <label>${i18next.t('label.size')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(\n axis.toUpperCase(),\n t.size[axis],\n value => this.putTransform({ size: { ...t.size, [axis]: value } }),\n 1\n )\n )}\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('label.tools')}</label>\n <div tools>\n <button title=${i18next.t('text.center-on-x')} @click=${() => this.ask('center', 'x')}>\n ${i18next.t('button.center-x')}\n </button>\n <button title=${i18next.t('text.center-on-z')} @click=${() => this.ask('center', 'z')}>\n ${i18next.t('button.center-z')}\n </button>\n <button title=${i18next.t('text.put-on-floor')} @click=${() => this.ask('floor', 'y')}>\n ${i18next.t('button.to-floor')}\n </button>\n <span sep></span>\n ${(['x', 'z'] as const).map(axis => {\n // 이미 가운데면 대칭이 제자리다 — 겹친 부품이 조용히 생기지 않게 막는다\n const able = this.board ? canMirror(this.board, this.parts, this.selected, axis) : false\n return html`\n <button\n ?disabled=${!able}\n title=${i18next.t(able ? 'text.mirror-explained' : 'text.mirror-would-overlap')}\n @click=${() => this.ask('mirror', axis)}\n >\n ${i18next.t('button.mirror-' + axis)}\n </button>\n `\n })}\n <span sep></span>\n <button title=${i18next.t('text.snap-explained')} @click=${() => this.ask('snap', 'x')}>\n ${i18next.t('button.snap')}\n </button>\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('label.rotation-degrees')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), rotation[axis] ?? 0, value =>\n this.putTransform({ rotation: { ...rotation, [axis]: value } })\n )\n )}\n </div>\n </div>\n </section>\n `\n }\n\n /** 분할 수와 모서리 굴림 — 해당하는 도형에만 낸다. */\n private renderShape(part: FigurePart) {\n const segmented = SEGMENTED_PRIMITIVES.includes(part.primitive)\n const extruded = EXTRUDE_PRIMITIVES.includes(part.primitive)\n if (!segmented && !extruded) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('label.shape-detail')}</h3>\n ${segmented ? this.renderSegments(part) : nothing}\n ${segmented ? this.renderKeepRound(part) : nothing}\n ${extruded ? this.renderExtruded(part) : nothing}\n </section>\n `\n }\n\n /**\n * 민 단면 — 모서리와 **속을 파는 두께.**\n *\n * 벽 두께를 0 으로 두면 통짜다. 0 보다 크면 그만큼 안으로 들어온 테가 생기고, 그것이\n * 트레이 · 토트 · 통 · 케이스가 되는 자리다. 바닥은 비워 두면 벽과 같고, 0 으로 두면\n * 위아래가 다 뚫린 테두리가 된다.\n */\n private renderExtruded(part: FigurePart) {\n const hollow = part.shape?.hollow\n const wall = hollow?.wall ?? 0\n\n const reshape = (over: Record<string, unknown>) =>\n this.put({ shape: { ...(part.shape ?? {}), ...over } })\n\n return html`\n <div field>\n <label>${i18next.t('label.corner-round')}</label>\n ${this.number('', part.shape?.round ?? 0, value => reshape({ round: value }))}\n </div>\n <div field>\n <label>${i18next.t('label.hollow-wall')}</label>\n ${this.number('', wall, value =>\n reshape({ hollow: value > 0 ? { ...(hollow ?? {}), wall: value } : undefined })\n )}\n </div>\n ${wall > 0\n ? html`\n <div field>\n <label>${i18next.t('label.hollow-floor')}</label>\n ${this.number('', hollow?.floor ?? wall, value => reshape({ hollow: { ...hollow, wall, floor: value } }))}\n </div>\n `\n : nothing}\n `\n }\n\n /**\n * 분할 수 — 모양과 무게를 함께 고른다.\n *\n * 삼각형 수는 화면이 셈하지 않는다. `trianglesOf` 에 그 분할 수를 가진 부품을 주고\n * 물어본다 — 화면이 제 나름의 공식을 들면 figure-model 이 규칙을 바꿔도 화면만\n * 옛 답을 계속 낸다.\n */\n /**\n * 원통을 원통으로 둘까 — **고르는 값이고, 값을 치른다.**\n *\n * 인스턴스가 축마다 다른 배율을 받으면 원기둥이 타원 기둥이 된다. 지키려면 제 변환이\n * 있어야 하고, 합친 형상은 변환을 하나만 받으므로 지키는 곡면 부품 하나가 draw call\n * 하나다. 저작자가 그 대가를 모르고 고르면 안 되므로 그 자리에 적는다.\n */\n private renderKeepRound(part: FigurePart) {\n const on = part.keepRound !== false\n\n return html`\n <div field>\n <label>${i18next.t('label.keep-round')}</label>\n <div choices>\n <button choice ?on=${on} @click=${() => this.put({ keepRound: undefined })}>\n <b>${i18next.t('text.keep-round-on')}</b>\n <small>${i18next.t('text.keep-round-cost')}</small>\n </button>\n <button choice ?on=${!on} @click=${() => this.put({ keepRound: false })}>\n <b>${i18next.t('text.keep-round-off')}</b>\n <small>${i18next.t('text.keep-round-merged')}</small>\n </button>\n </div>\n </div>\n `\n }\n\n private renderSegments(part: FigurePart) {\n const current = part.segments ?? SEGMENT_PRESETS[1]\n\n return html`\n <div field>\n <label>${i18next.t('label.segments')}</label>\n <div segments>\n ${SEGMENT_PRESETS.map(preset => {\n const triangles = trianglesOf({ ...part, segments: preset })\n return html`\n <button seg ?on=${current === preset} @click=${() => this.put({ segments: preset })}>\n ${this.silhouette(preset)}\n <b>${preset}</b>\n <small>${i18next.t('text.n-triangles', { count: triangles })}</small>\n </button>\n `\n })}\n </div>\n\n <details free>\n <summary>${i18next.t('text.other-segment-count')}</summary>\n ${this.number('', current, value => this.put({ segments: value }), 3)}\n </details>\n </div>\n `\n }\n\n /**\n * 분할 수의 실루엣.\n *\n * 곡면 프리미티브를 위에서 본 윤곽이 정확히 정n각형이다 — 원기둥의 단면도, 구의\n * 적도도. 그림을 지어내는 것이 아니라 실제로 만들어지는 모양이다.\n */\n private silhouette(segments: number) {\n const points: string[] = []\n for (let i = 0; i < segments; i++) {\n // 꼭짓점 하나가 위로 오게 -90도에서 시작한다\n const angle = -Math.PI / 2 + (i * 2 * Math.PI) / segments\n points.push(`${(16 + 13 * Math.cos(angle)).toFixed(2)},${(16 + 13 * Math.sin(angle)).toFixed(2)}`)\n }\n\n return svg`\n <svg viewBox=\"0 0 32 32\" aria-hidden=\"true\">\n <polygon points=${points.join(' ')}></polygon>\n </svg>\n `\n }\n\n /** 견본 한 줄. 무리마다 부른다. */\n private swatches(entries: [string, string][], chosen?: string) {\n return entries.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === chosen}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ token })}\n ></button>\n `\n )\n }\n\n private renderMaterial(part: FigurePart) {\n const material = part.material ?? {}\n const palette = activePalette() as Record<string, string>\n\n /*\n 두 무리로 나눠 보인다 — **칠한 색**과 **켜진 색**.\n\n `palette.status-*` 는 램프 · 표시등에 쓰라고 있는 색이다. 한 줄에 섞어 두면\n `accent`(주황)와 `status-warning`(주황)이 나란히 놓여 무엇이 무엇인지 알 수\n 없다. 값을 비틀어 벌리는 대신 **고르는 자리에서 경계를 긋는다.**\n */\n const entries = Object.entries(palette)\n const painted = entries.filter(([token]) => !token.startsWith('palette.status-'))\n const lit = entries.filter(([token]) => token.startsWith('palette.status-'))\n\n /** 자체발광. 없으면 안 빛나는 부품이다. */\n const glow = material.emissive\n\n return html`\n <section>\n <h3>${i18next.t('label.material')}</h3>\n\n <div field>\n <label>${i18next.t('label.palette-token')}</label>\n <div swatches>\n ${this.swatches(painted, material.token)}\n </div>\n ${lit.length\n ? html`\n <label group>${i18next.t('label.status-colors')}</label>\n <div swatches>\n ${this.swatches(lit, material.token)}\n </div>\n `\n : ''}\n <span token>${material.token ?? i18next.t('label.no-color-chosen')}</span>\n </div>\n\n <div field>\n <label>${i18next.t('label.preset')}</label>\n <select\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.putMaterial({ preset: (value || undefined) as MaterialPreset | undefined })\n }}\n >\n <option value=\"\" ?selected=${!material.preset}>${i18next.t('label.none')}</option>\n ${MATERIAL_PRESETS.map(\n preset => html`\n <option value=${preset} ?selected=${preset === material.preset}>\n ${i18next.t('label.preset-' + preset)}\n </option>\n `\n )}\n </select>\n </div>\n\n <!-- 값이 없으면 각지게 그린다 — 형식이 그렇게 못 박았다. 체크상자도 그대로 보인다 -->\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${material.flatShading ?? true}\n @change=${(e: Event) => this.putMaterial({ flatShading: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('label.flat-shading')}\n </label>\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!material.transparent}\n @change=${(e: Event) => this.putMaterial({ transparent: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('label.transparent')}\n </label>\n\n <div field>\n <label>${i18next.t('label.emissive')}</label>\n <p cost>${i18next.t('text.emissive-explained')}</p>\n\n <div slider>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n .value=${String(glow?.intensity ?? 0)}\n @input=${(e: Event) => this.putEmissive(Number((e.target as HTMLInputElement).value))}\n />\n <output>${(glow?.intensity ?? 0).toFixed(2)}</output>\n </div>\n </div>\n\n ${glow\n ? html`\n <div field>\n <label>${i18next.t('label.emissive-color')}</label>\n <div swatches>\n <button\n swatch\n same\n ?on=${!glow.token}\n title=${i18next.t('label.same-as-off')}\n style=\"--swatch:${(material.token && palette[material.token]) || palette['palette.neutral']}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token: undefined } })}\n ></button>\n ${lit.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === glow.token}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token } })}\n ></button>\n `\n )}\n </div>\n <span token>${glow.token ?? i18next.t('label.same-as-off')}</span>\n </div>\n\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!glow.on}\n @change=${(e: Event) =>\n this.putMaterial({ emissive: { ...glow, on: (e.target as HTMLInputElement).checked } })}\n />\n ${i18next.t('label.on-by-default')}\n </label>\n <p cost>${i18next.t('text.on-by-default-explained')}</p>\n `\n : ''}\n\n </section>\n `\n }\n\n /**\n * 인스턴스 크기가 바뀔 때 이 부품이 어떻게 변하나.\n *\n * ## 늘어나는 축은 아직 못 보여 준다\n *\n * `stretch` 는 형식에 축을 적는 자리가 없어서 그리는 쪽이 **가장 길게 뻗은 축**으로\n * 정한다. 그 답을 화면에 적어 주려면 규칙을 가진 쪽에 물어야 하는데, 그 함수\n * (`stretchAxisOf` · `stretchAxis`)가 아직 배포된 판에 없다.\n *\n * 화면이 제 나름대로 계산하지 않는다. 규칙이 두 벌이 되면 보이는 축과 실제로 늘어나는\n * 축이 갈리고, 그 어긋남은 화면을 믿게 만든 다음에 드러난다. 배포되면 잇는다.\n *\n * ## 값이 있는 것을 밝힌다\n *\n * `scale` 이 아닌 부품은 합쳐 그릴 수 없어 draw call 을 하나 더 쓴다. 그 비용이\n * 성숙도의 묶음 수에 그대로 잡힌다. 고르는 자리에서 미리 말해 준다.\n */\n private renderBehaviour(part: FigurePart) {\n const sizing = part.sizing ?? 'scale'\n const repeat = part.repeat ?? { axis: 'x' as Axis, pitch: 0 }\n\n return html`\n <section>\n <h3>${i18next.t('label.sizing-rule')}</h3>\n <select @change=${(e: Event) => this.put({ sizing: (e.target as HTMLSelectElement).value as never })}>\n ${SIZING_RULES.map(\n rule => html`\n <option value=${rule} ?selected=${rule === sizing}>${i18next.t('label.sizing-' + rule)}</option>\n `\n )}\n </select>\n <p warn>${i18next.t('text.sizing-' + sizing + '-explained')}</p>\n\n ${sizing === 'scale'\n ? nothing\n : html`<p cost>${i18next.t('text.sizing-costs-one-draw-call')}</p>`}\n ${sizing === 'repeat'\n ? html`<p cost>${i18next.t('text.repeat-edits-the-unit')}</p>`\n : nothing}\n\n ${sizing === 'repeat'\n ? html`\n <div field>\n <label>${i18next.t('label.repeat-axis')}</label>\n <select\n @change=${(e: Event) =>\n this.put({ repeat: { ...repeat, axis: (e.target as HTMLSelectElement).value as Axis } })}\n >\n ${(['x', 'y', 'z'] as const).map(\n axis => html`<option value=${axis} ?selected=${axis === repeat.axis}>${axis}</option>`\n )}\n </select>\n </div>\n <div field>\n <label>${i18next.t('label.repeat-pitch')}</label>\n ${this.number('', repeat.pitch, value => this.put({ repeat: { ...repeat, pitch: value } }), 1)}\n <p cost>${i18next.t('text.repeat-pitch-explained')}</p>\n </div>\n `\n : nothing}\n </section>\n `\n }\n\n /**\n * 숫자 칸.\n *\n * 빈 칸이나 글자를 넣었을 때 0 으로 바꿔치지 않는다 — 저작자가 지우는 중일 수 있고,\n * 조용히 0 이 되면 부품이 사라진 것처럼 보인다. 숫자가 아니면 그냥 안 받는다.\n */\n private number(label: string, value: number, set: (value: number) => void, min?: number) {\n return html`\n <div cell>\n ${label ? html`<span axis>${label}</span>` : nothing}\n <input\n type=\"number\"\n .value=${String(value)}\n min=${min ?? nothing}\n @change=${(e: Event) => {\n const raw = (e.target as HTMLInputElement).value\n const parsed = Number(raw)\n if (raw === '' || !Number.isFinite(parsed)) {\n // 되돌려 놓는다. 안 그러면 화면과 정본이 갈린다.\n ;(e.target as HTMLInputElement).value = String(value)\n return\n }\n set(parsed)\n }}\n />\n </div>\n `\n }\n\n /**\n * 도형을 바꾼다.\n *\n * 도형이 바뀌면 그 도형에 뜻 없는 값이 남는다. 남겨 두면 검증이 「무시됨」 위반을\n * 내므로 여기서 걷는다.\n */\n private changePrimitive(primitive: PrimitiveKind) {\n this.change(part => {\n const next: FigurePart = { ...part, primitive }\n if (!SEGMENTED_PRIMITIVES.includes(primitive)) delete next.segments\n if (!EXTRUDE_PRIMITIVES.includes(primitive)) delete next.shape\n if (SEGMENTED_PRIMITIVES.includes(primitive) && next.segments === undefined) {\n next.segments = SEGMENT_PRESETS[1]\n }\n return next\n })\n }\n\n private put(patch: Partial<FigurePart>) {\n this.change(part => ({ ...part, ...patch }))\n }\n\n private putTransform(patch: Record<string, unknown>) {\n this.change(part => ({ ...part, transform: { ...part.transform, ...patch } as FigurePart['transform'] }))\n }\n\n /**\n * 발광 세기를 고친다. **0 이면 블록을 통째로 지운다.**\n *\n * 세기 0 인 발광을 남겨 두면 「안 빛나는데 램프인 것」이 되어, 재질 키가 갈려 묶음이\n * 헛되이 하나 는다. 0 은 「램프가 아니다」로 읽는 것이 맞다.\n *\n * `Number(value) || undefined` 를 쓰면 안 된다 — 0 이 falsy 라 변경으로 안 잡혀 옛\n * 값이 그대로 남는다. 실제로 램프가 안 꺼졌다.\n */\n private putEmissive(intensity: number) {\n // `chosen` 은 씬 모델이다. 형식으로 옮긴 값은 `partToFigure` 이 만든 것이라 여기서 다시 꺼낸다.\n const glow = this.board && this.chosen ? partToFigure(this.chosen, this.board).material?.emissive : undefined\n\n this.putMaterial({ emissive: intensity > 0 ? { ...glow, intensity } : undefined })\n }\n\n private putMaterial(patch: Record<string, unknown>) {\n this.change(part => ({\n ...part,\n material: { ...(part.material ?? {}), ...patch } as FigurePart['material']\n }))\n }\n\n /**\n * 수정한 것을 씬 모델로 되돌려 알린다.\n *\n * 사용자는 X·Y·Z 로 수정하고, 모델러 페이지는 씬 모델을 갖는다. 그 사이를 여기서\n * 잇는다 — 변환해서 수정하고 다시 되돌린다. **왕복 변환이 무손실이라야** 위치 하나\n * 고쳤을 뿐인데 회전값이 조금씩 밀리는 일이 없다(`fold.test.ts` 가 이를 확인한다).\n */\n private change(edit: (part: FigurePart) => FigurePart) {\n const part = this.chosen\n const board = this.board\n if (!part || !board) return\n\n this.emit(partFromFigure(edit(partToFigure(part, board)), board))\n }\n\n /** 정밀 도구를 시킨다. 정본은 저작면이 갖는다 — 여기서는 알리기만 한다. */\n private ask(what: string, axis: string) {\n this.dispatchEvent(\n new CustomEvent('part-tool', {\n detail: { index: this.selected, tool: what, axis },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private emit(part: PartModel) {\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index: this.selected, part }, bubbles: true, composed: true })\n )\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-inspector.js","sourceRoot":"","sources":["../../client/modeller/figure-inspector.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, svg } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport {\n EXTRUDE_PRIMITIVES,\n MATERIAL_PRESETS,\n PRIMITIVE_KINDS,\n SEGMENTED_PRIMITIVES,\n SEGMENT_PRESETS,\n SIZING_RULES,\n trianglesOf\n} from '@hatiolab/figure-model'\nimport type { Axis, FigurePart, MaterialPreset, PrimitiveKind } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\n\nimport { partToFigure, partFromFigure } from './figure-source.js'\nimport type { BoardModel, PartModel } from './figure-source.js'\nimport { canMirror } from './part-edits.js'\n\n/**\n * 고른 부품의 속성.\n *\n * ## 여기가 사용자 용어로 보여 주는 자리다\n *\n * 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 — 보드 위에 부품 컴포넌트가 놓인 상태.\n * 그런데 사용자에게 보여야 하는 것은 씬 속성 이름(`left`·`top`·`zPos`)이 아니라\n * **X·Y·Z** 다. 다른 3D 도구가 모두 그렇게 부르고, 위치도 부품의 **중심**으로 읽는다.\n *\n * 그래서 이 패널은 받은 부품을 FigureSource 형태로 **변환해서 보여 주고, 수정한 것을\n * 다시 씬 모델로 되돌려 알린다.** 변환은 `figure-source.ts` 가 한다 — 여기서 축을 다시 계산하지\n * 않는다.\n *\n * ## 있는 것만 보여 준다\n *\n * 분할 수는 곡면이 있는 도형에만, 모서리 굴림은 단면을 뽑는 도형에만 뜻이 있다.\n * 뜻 없는 칸을 회색으로 늘어놓으면 저작자가 「왜 안 먹지」를 고민하게 된다 —\n * 아예 내지 않는다.\n *\n * ## 이름은 저장되는 식별자다\n *\n * `FigurePart.name` 은 바인딩·애니메이션·슬롯이 가리키는 이름이다. 고치면 배치된\n * 인스턴스의 바인딩이 **조용히** 끊긴다. 그래서 이름 칸에만 경고를 붙인다.\n *\n * ## 「상태 색이 붙는 자리」를 걷었다\n *\n * `FigurePart.materialSlot`(당시 이름은 `slot`) 을 고르는 칸이 있었는데 **아무 일도 하지 않았다.** things-scene 의\n * figure 런타임이 `slot` 을 읽지 않는다 — 선언 한 줄이 전부다. 되는 줄 알고 만들었고,\n * 확인하지 않은 것이 잘못이다.\n *\n * 그리고 되게 만들더라도 이 모양이 아니다. 상태 색은 특별한 것이 아니라 **속성 하나**이고,\n * 툴팁·라벨·표시 여부와 같은 자리에서 다뤄져야 한다. 그 설계가 정해지면(figure-model#2)\n * 이 자리에 **속성 선언과 부품 바인딩** 편집기가 들어온다.\n *\n * 그때까지 빈 약속을 두지 않는다. 아무 일도 안 하는 칸은 없느니만 못하다.\n *\n * ## 고치지 않는다\n *\n * 정본 한 벌은 저작면이 갖는다. 여기서는 바뀐 부품을 알릴 뿐이다.\n */\n@customElement('figure-inspector')\nexport class FigureInspector extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\n\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n h3 {\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n label {\n display: block;\n margin-bottom: 2px;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n input,\n select {\n width: 100%;\n box-sizing: border-box;\n padding: 4px 7px;\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input:focus,\n select:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n\n /*\n 숫자 칸 — 라벨을 칸 **안** 왼쪽에 둔다.\n\n 벡터 저작도구(Sketch·Figma)가 X·Y·W·H 를 이렇게 둔다. 라벨을 위에 얹으면 줄마다\n 두 줄을 먹어서, 자리·크기·회전 아홉 칸이 화면 절반을 차지한다. 안에 두면 한 줄에\n 셋이 들어가고 무엇이 무엇인지도 붙어 읽힌다.\n */\n div[cell] {\n display: flex;\n align-items: center;\n gap: 2px;\n padding-left: 6px;\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n div[cell]:focus-within {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n border-color: transparent;\n }\n div[cell] span[axis] {\n flex: none;\n font-size: 0.64rem;\n letter-spacing: 0.02em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[cell] input {\n min-width: 0;\n padding: 4px 6px 4px 2px;\n font-family: var(--mono-font, monospace);\n text-align: right;\n background: none;\n border: none;\n border-radius: 0;\n }\n div[cell] input:focus {\n outline: none;\n }\n\n /* 축 셋을 한 줄에 — 좌표는 셋이 같이 읽혀야 한다 */\n div[triple] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n div[field] + div[field],\n div[triple] + div[field],\n div[field] + div[triple] {\n margin-top: 7px;\n }\n\n /*\n 정밀 제작 도구.\n\n 보드 편집기는 자산을 배치하는 곳이라 대충 놓아도 되지만, 여기는 자산을 만드는\n 곳이다. 「몸통 한가운데」·「바닥에 붙이기」·「반대쪽에도 하나」를 손으로 계산하면\n 늘 조금씩 어긋나고, 기준 치수를 바꾸면 전부 다시 해야 한다.\n */\n div[tools] {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n margin-top: 6px;\n }\n div[tools] button {\n padding: 3px 8px;\n font: var(--label-font, inherit);\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n div[tools] button[disabled] {\n opacity: 0.35;\n cursor: default;\n }\n div[tools] button:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n div[tools] span[sep] {\n width: 1px;\n margin: 2px 3px;\n background-color: var(--md-sys-color-outline-variant);\n }\n\n /* 지금 어느 축이 늘어나나 — 크기를 고치면 바뀔 수 있어서 값으로 적는다 */\n p[axis-now] {\n margin: 4px 0 0 0;\n font-size: 0.7rem;\n color: var(--md-sys-color-primary);\n }\n /* 이 선택이 무엇을 무는지 — 성숙도의 묶음 수에 그대로 잡힌다 */\n p[cost] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 이름을 바꾸면 무엇이 끊기는지 말한다 */\n p[warn] {\n margin: 4px 0 0 0;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-tertiary);\n }\n\n /* 분할 수 프리셋 — 자유 입력보다 이 셋을 권한다 */\n div[presets] {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n div[presets] button {\n flex: 1;\n padding: 3px 0;\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n div[presets] button[on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 분할 수 선택 — 모양과 삼각형 수를 **함께** 보여 준다.\n\n 숫자만 늘어놓으면 8 과 16 이 무엇이 다른지 알 수 없다. 로우폴리에서 이 선택이\n 곧 「몇 개까지 편하게 놓을 수 있나」를 정하므로, 각지는 정도와 그 값이 무는\n 삼각형 수를 한자리에서 보아야 한다.\n\n 실루엣은 지어낸 그림이 아니다 — 곡면 프리미티브를 위에서 본 윤곽이 정확히\n 정n각형이다. 삼각형 수는 화면이 셈하지 않고 figure-model 의 trianglesOf 가 낸다.\n */\n div[segments] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n /* 원 유지 · 끄기 — 고르는 값이라 분할 수와 같은 모양으로 둔다 */\n div[choices] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 4px;\n }\n button[choice] {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 1px;\n padding: 6px 8px;\n text-align: left;\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[choice]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[choice][on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[choice] small {\n opacity: 0.75;\n }\n button[seg] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n padding: 6px 2px 4px 2px;\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[seg]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[seg][on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[seg] svg {\n display: block;\n /* 작으면 12 와 16 이 둘 다 동그라미로 보인다 — 각이 읽힐 만큼은 키운다 */\n width: 40px;\n height: 40px;\n }\n button[seg] svg polygon {\n fill: var(--md-sys-color-surface-container-highest);\n stroke: currentColor;\n stroke-width: 1.1;\n stroke-linejoin: miter;\n }\n button[seg][on] svg polygon {\n fill: var(--md-sys-color-surface-container-lowest);\n }\n button[seg] b {\n font-size: 0.8rem;\n font-weight: 700;\n line-height: 1.1;\n }\n button[seg] small {\n font-size: 0.62rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[seg][on] small {\n color: inherit;\n }\n /* 프리셋 밖의 값도 쓸 수 있다. 다만 권하는 쪽을 크게 둔다 */\n details[free] {\n margin-top: 6px;\n }\n details[free] summary {\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n cursor: pointer;\n }\n details[free] div[cell] {\n margin-top: 4px;\n }\n\n /*\n 색 견본.\n\n 토큰을 글자로 받지 않는다. things-scene 의 팔레트 선언이 「여기 든 이름이 저작에서\n 고를 수 있는 전부이고, 자유색을 허용하지 않는 것이 감각적인 로우폴리의 첫 규칙」\n 이라고 못박고 있다. 글자 칸으로 두면 그 규칙이 화면에서 사라지고, 오타 하나로\n resolveToken 이 던진다.\n */\n div[swatches] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n /*\n 두 무리의 경계. 「칠한 색」과 「켜진 색」이 한 줄에 섞이면 accent(주황)와\n status-warning(주황)이 나란히 놓여 무엇이 무엇인지 알 수 없다.\n */\n label[group] {\n margin-top: 8px;\n padding-top: 8px;\n font-size: var(--ui-t-micro, 11px);\n letter-spacing: 0.03em;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 값이 옆에 붙어야 끌면서 얼마인지 안다 */\n div[slider] {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n div[slider] input[type='range'] {\n flex: 1;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n div[slider] output {\n flex: none;\n width: 2.6em;\n text-align: right;\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[swatch] {\n width: 26px;\n height: 26px;\n padding: 0;\n background-color: var(--swatch);\n border: 1px solid rgba(0, 0, 0, 0.16);\n border-radius: 50%;\n cursor: pointer;\n }\n button[swatch][on] {\n /* 고른 것은 테두리가 아니라 고리로 표시한다 — 색 자체를 가리지 않으려고 */\n box-shadow:\n 0 0 0 2px var(--md-sys-color-surface-container-lowest),\n 0 0 0 4px var(--md-sys-color-primary);\n }\n span[token] {\n display: block;\n margin-top: 6px;\n font-family: var(--mono-font, monospace);\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n label[toggle] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 7px 0 0 0;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n label[toggle] input {\n width: auto;\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.76rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /*\n 고칠 수 없는 값 — 입력칸처럼 보이지 않게 한다.\n\n 전에는 그냥 글자였고, 옆의 이름 칸과 나란히 있으니 눌러도 되는 것처럼 보였다. 자물쇠와\n 칸 없는 바탕이 「읽는 값」이라고 먼저 말한다.\n */\n code[fixed] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 6px;\n background-color: var(--md-sys-color-surface-container-high, rgba(0, 0, 0, 0.05));\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n font-family: var(--mono-font, monospace);\n font-size: 0.76rem;\n }\n\n code[fixed] md-icon {\n --md-icon-size: 13px;\n opacity: 0.7;\n }\n `\n ]\n\n /** 보드. 위치를 보드 중심 기준으로 바꾸려면 이것이 있어야 한다. */\n /** 도형의 표시 이름. 정본은 저작면이 들고, 여기서는 보여 주고 바꾼다(`figure-rename` 으로 알린다). */\n @property({ type: String }) figureName = ''\n\n @property({ type: Object }) board?: BoardModel\n /** 보드 위의 부품들. 씬 모델 그대로다. */\n @property({ type: Array }) parts: PartModel[] = []\n @property({ type: Number }) selected = -1\n\n /** `part` 는 HTMLElement 의 속성(DOMTokenList)이다 — 가리면 형이 어긋난다. */\n private get chosen(): PartModel | undefined {\n return this.parts?.[this.selected]\n }\n\n render() {\n const part = this.chosen\n if (!part || !this.board) {\n return html`\n ${this.renderFigure()}\n <section>\n <h3>${i18next.t('label.properties')}</h3>\n <p quiet>${i18next.t('text.select-a-part-to-edit')}</p>\n </section>\n `\n }\n\n // 여기부터는 사용자 용어다 — X·Y·Z, 위치는 부품 중심, 회전은 도(degree).\n const shown = partToFigure(part, this.board)\n\n return html`\n ${this.renderFigure()} ${this.renderIdentity(shown)} ${this.renderPlacement(shown)}\n ${this.renderShape(shown)} ${this.renderMaterial(shown)} ${this.renderBehaviour(shown)}\n `\n }\n\n /**\n * 도형 자체 — 이름과 타입.\n *\n * ## 왜 머리줄이 아니라 여기인가\n *\n * 저작면 위에 줄을 하나 더 두고 이름 칸을 놓았었다. 앱에서는 셸이 페이지 제목으로 그 이름을\n * 이미 그리므로 같은 글자가 두 번 나오고, 캔버스가 주인공인 화면에서 그 줄만큼 높이를 잃었다.\n * 속성 패널은 「이 도형이 무엇인가」를 다루는 자리이므로 이름도 여기 산다.\n *\n * ## 타입은 읽기만 한다\n *\n * 저장되는 식별자다. 배치된 보드가 이 이름으로 타입을 찾으므로 만든 뒤에는 못 바꾼다.\n */\n private renderFigure() {\n return html`\n <section>\n <h3>${i18next.t('label.figure')}</h3>\n <div field>\n <label>${i18next.t('label.name')}</label>\n <input\n .value=${this.figureName}\n placeholder=${i18next.t('text.figure-display-name')}\n @change=${(e: Event) =>\n this.dispatchEvent(\n new CustomEvent('figure-rename', {\n detail: { name: (e.target as HTMLInputElement).value },\n bubbles: true,\n composed: true\n })\n )}\n />\n </div>\n <div field>\n <label>${i18next.t('label.type')}</label>\n <code fixed title=${i18next.t('text.type-name-cannot-change')}>\n <md-icon>lock</md-icon>${this.board?.figureType ?? ''}\n </code>\n </div>\n <!--\n 왜 못 고치는지 **적어 둔다.** 전에는 tooltip 에만 있었고, 그러면 눌러 보고 안 되는 것을\n 알게 된다 — 화면이 안 되는 이유를 말하지 않으면 사용자가 자기 실수라고 생각한다.\n -->\n <p quiet>${i18next.t('text.type-name-cannot-change')}</p>\n </section>\n `\n }\n\n private renderIdentity(part: FigurePart) {\n return html`\n <section>\n <h3>${i18next.t('label.part')}</h3>\n <div field>\n <label>${i18next.t('label.name')}</label>\n <input\n .value=${part.name}\n @change=${(e: Event) => this.put({ name: (e.target as HTMLInputElement).value })}\n />\n <p warn>${i18next.t('text.renaming-a-part-breaks-bindings')}</p>\n </div>\n <div field>\n <label>${i18next.t('label.shape')}</label>\n <select @change=${(e: Event) => this.changePrimitive((e.target as HTMLSelectElement).value as PrimitiveKind)}>\n ${PRIMITIVE_KINDS.map(\n kind => html`\n <option value=${kind} ?selected=${kind === part.primitive}>\n ${i18next.t('label.primitive-' + kind)}\n </option>\n `\n )}\n </select>\n </div>\n </section>\n `\n }\n\n private renderPlacement(part: FigurePart) {\n const t = part.transform\n const rotation = t.rotation ?? {}\n\n return html`\n <section>\n <h3>${i18next.t('label.placement')}</h3>\n\n <label>${i18next.t('label.position')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), t.position[axis], value =>\n this.putTransform({ position: { ...t.position, [axis]: value } })\n )\n )}\n </div>\n\n <div field>\n <label>${i18next.t('label.size')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(\n axis.toUpperCase(),\n t.size[axis],\n value => this.putTransform({ size: { ...t.size, [axis]: value } }),\n 1\n )\n )}\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('label.tools')}</label>\n <div tools>\n <button title=${i18next.t('text.center-on-x')} @click=${() => this.ask('center', 'x')}>\n ${i18next.t('button.center-x')}\n </button>\n <button title=${i18next.t('text.center-on-z')} @click=${() => this.ask('center', 'z')}>\n ${i18next.t('button.center-z')}\n </button>\n <button title=${i18next.t('text.put-on-floor')} @click=${() => this.ask('floor', 'y')}>\n ${i18next.t('button.to-floor')}\n </button>\n <span sep></span>\n ${(['x', 'z'] as const).map(axis => {\n // 이미 가운데면 대칭이 제자리다 — 겹친 부품이 조용히 생기지 않게 막는다\n const able = this.board ? canMirror(this.board, this.parts, this.selected, axis) : false\n return html`\n <button\n ?disabled=${!able}\n title=${i18next.t(able ? 'text.mirror-explained' : 'text.mirror-would-overlap')}\n @click=${() => this.ask('mirror', axis)}\n >\n ${i18next.t('button.mirror-' + axis)}\n </button>\n `\n })}\n <span sep></span>\n <button title=${i18next.t('text.snap-explained')} @click=${() => this.ask('snap', 'x')}>\n ${i18next.t('button.snap')}\n </button>\n </div>\n </div>\n\n <div field>\n <label>${i18next.t('label.rotation-degrees')}</label>\n <div triple>\n ${(['x', 'y', 'z'] as const).map(axis =>\n this.number(axis.toUpperCase(), rotation[axis] ?? 0, value =>\n this.putTransform({ rotation: { ...rotation, [axis]: value } })\n )\n )}\n </div>\n </div>\n </section>\n `\n }\n\n /** 분할 수와 모서리 굴림 — 해당하는 도형에만 낸다. */\n private renderShape(part: FigurePart) {\n const segmented = SEGMENTED_PRIMITIVES.includes(part.primitive)\n const extruded = EXTRUDE_PRIMITIVES.includes(part.primitive)\n if (!segmented && !extruded) return nothing\n\n return html`\n <section>\n <h3>${i18next.t('label.shape-detail')}</h3>\n ${segmented ? this.renderSegments(part) : nothing}\n ${segmented ? this.renderKeepRound(part) : nothing}\n ${extruded ? this.renderExtruded(part) : nothing}\n </section>\n `\n }\n\n /**\n * 민 단면 — 모서리와 **속을 파는 두께.**\n *\n * 벽 두께를 0 으로 두면 통짜다. 0 보다 크면 그만큼 안으로 들어온 테가 생기고, 그것이\n * 트레이 · 토트 · 통 · 케이스가 되는 자리다. 바닥은 비워 두면 벽과 같고, 0 으로 두면\n * 위아래가 다 뚫린 테두리가 된다.\n */\n private renderExtruded(part: FigurePart) {\n const hollow = part.shape?.hollow\n const wall = hollow?.wall ?? 0\n\n const reshape = (over: Record<string, unknown>) =>\n this.put({ shape: { ...(part.shape ?? {}), ...over } })\n\n return html`\n <div field>\n <label>${i18next.t('label.corner-round')}</label>\n ${this.number('', part.shape?.round ?? 0, value => reshape({ round: value }))}\n </div>\n <div field>\n <label>${i18next.t('label.hollow-wall')}</label>\n ${this.number('', wall, value =>\n reshape({ hollow: value > 0 ? { ...(hollow ?? {}), wall: value } : undefined })\n )}\n </div>\n ${wall > 0\n ? html`\n <div field>\n <label>${i18next.t('label.hollow-floor')}</label>\n ${this.number('', hollow?.floor ?? wall, value => reshape({ hollow: { ...hollow, wall, floor: value } }))}\n </div>\n `\n : nothing}\n `\n }\n\n /**\n * 분할 수 — 모양과 무게를 함께 고른다.\n *\n * 삼각형 수는 화면이 셈하지 않는다. `trianglesOf` 에 그 분할 수를 가진 부품을 주고\n * 물어본다 — 화면이 제 나름의 공식을 들면 figure-model 이 규칙을 바꿔도 화면만\n * 옛 답을 계속 낸다.\n */\n /**\n * 원통을 원통으로 둘까 — **고르는 값이고, 값을 치른다.**\n *\n * 인스턴스가 축마다 다른 배율을 받으면 원기둥이 타원 기둥이 된다. 지키려면 제 변환이\n * 있어야 하고, 합친 형상은 변환을 하나만 받으므로 지키는 곡면 부품 하나가 draw call\n * 하나다. 저작자가 그 대가를 모르고 고르면 안 되므로 그 자리에 적는다.\n */\n private renderKeepRound(part: FigurePart) {\n const on = part.keepRound !== false\n\n return html`\n <div field>\n <label>${i18next.t('label.keep-round')}</label>\n <div choices>\n <button choice ?on=${on} @click=${() => this.put({ keepRound: undefined })}>\n <b>${i18next.t('text.keep-round-on')}</b>\n <small>${i18next.t('text.keep-round-cost')}</small>\n </button>\n <button choice ?on=${!on} @click=${() => this.put({ keepRound: false })}>\n <b>${i18next.t('text.keep-round-off')}</b>\n <small>${i18next.t('text.keep-round-merged')}</small>\n </button>\n </div>\n </div>\n `\n }\n\n private renderSegments(part: FigurePart) {\n const current = part.segments ?? SEGMENT_PRESETS[1]\n\n return html`\n <div field>\n <label>${i18next.t('label.segments')}</label>\n <div segments>\n ${SEGMENT_PRESETS.map(preset => {\n const triangles = trianglesOf({ ...part, segments: preset })\n return html`\n <button seg ?on=${current === preset} @click=${() => this.put({ segments: preset })}>\n ${this.silhouette(preset)}\n <b>${preset}</b>\n <small>${i18next.t('text.n-triangles', { count: triangles })}</small>\n </button>\n `\n })}\n </div>\n\n <details free>\n <summary>${i18next.t('text.other-segment-count')}</summary>\n ${this.number('', current, value => this.put({ segments: value }), 3)}\n </details>\n </div>\n `\n }\n\n /**\n * 분할 수의 실루엣.\n *\n * 곡면 프리미티브를 위에서 본 윤곽이 정확히 정n각형이다 — 원기둥의 단면도, 구의\n * 적도도. 그림을 지어내는 것이 아니라 실제로 만들어지는 모양이다.\n */\n private silhouette(segments: number) {\n const points: string[] = []\n for (let i = 0; i < segments; i++) {\n // 꼭짓점 하나가 위로 오게 -90도에서 시작한다\n const angle = -Math.PI / 2 + (i * 2 * Math.PI) / segments\n points.push(`${(16 + 13 * Math.cos(angle)).toFixed(2)},${(16 + 13 * Math.sin(angle)).toFixed(2)}`)\n }\n\n return svg`\n <svg viewBox=\"0 0 32 32\" aria-hidden=\"true\">\n <polygon points=${points.join(' ')}></polygon>\n </svg>\n `\n }\n\n /** 견본 한 줄. 무리마다 부른다. */\n private swatches(entries: [string, string][], chosen?: string) {\n return entries.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === chosen}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ token })}\n ></button>\n `\n )\n }\n\n private renderMaterial(part: FigurePart) {\n const material = part.material ?? {}\n const palette = activePalette() as Record<string, string>\n\n /*\n 두 무리로 나눠 보인다 — **칠한 색**과 **켜진 색**.\n\n `palette.status-*` 는 램프 · 표시등에 쓰라고 있는 색이다. 한 줄에 섞어 두면\n `accent`(주황)와 `status-warning`(주황)이 나란히 놓여 무엇이 무엇인지 알 수\n 없다. 값을 비틀어 벌리는 대신 **고르는 자리에서 경계를 긋는다.**\n */\n const entries = Object.entries(palette)\n const painted = entries.filter(([token]) => !token.startsWith('palette.status-'))\n const lit = entries.filter(([token]) => token.startsWith('palette.status-'))\n\n /** 자체발광. 없으면 안 빛나는 부품이다. */\n const glow = material.emissive\n\n return html`\n <section>\n <h3>${i18next.t('label.material')}</h3>\n\n <div field>\n <label>${i18next.t('label.palette-token')}</label>\n <div swatches>\n ${this.swatches(painted, material.token)}\n </div>\n ${lit.length\n ? html`\n <label group>${i18next.t('label.status-colors')}</label>\n <div swatches>\n ${this.swatches(lit, material.token)}\n </div>\n `\n : ''}\n <span token>${material.token ?? i18next.t('label.no-color-chosen')}</span>\n </div>\n\n <div field>\n <label>${i18next.t('label.preset')}</label>\n <select\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.putMaterial({ preset: (value || undefined) as MaterialPreset | undefined })\n }}\n >\n <option value=\"\" ?selected=${!material.preset}>${i18next.t('label.none')}</option>\n ${MATERIAL_PRESETS.map(\n preset => html`\n <option value=${preset} ?selected=${preset === material.preset}>\n ${i18next.t('label.preset-' + preset)}\n </option>\n `\n )}\n </select>\n </div>\n\n <!-- 값이 없으면 각지게 그린다 — 형식이 그렇게 못 박았다. 체크상자도 그대로 보인다 -->\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${material.flatShading ?? true}\n @change=${(e: Event) => this.putMaterial({ flatShading: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('label.flat-shading')}\n </label>\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!material.transparent}\n @change=${(e: Event) => this.putMaterial({ transparent: (e.target as HTMLInputElement).checked })}\n />\n ${i18next.t('label.transparent')}\n </label>\n\n <div field>\n <label>${i18next.t('label.emissive')}</label>\n <p cost>${i18next.t('text.emissive-explained')}</p>\n\n <div slider>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n .value=${String(glow?.intensity ?? 0)}\n @input=${(e: Event) => this.putEmissive(Number((e.target as HTMLInputElement).value))}\n />\n <output>${(glow?.intensity ?? 0).toFixed(2)}</output>\n </div>\n </div>\n\n ${glow\n ? html`\n <div field>\n <label>${i18next.t('label.emissive-color')}</label>\n <div swatches>\n <button\n swatch\n same\n ?on=${!glow.token}\n title=${i18next.t('label.same-as-off')}\n style=\"--swatch:${(material.token && palette[material.token]) || palette['palette.neutral']}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token: undefined } })}\n ></button>\n ${lit.map(\n ([token, color]) => html`\n <button\n swatch\n ?on=${token === glow.token}\n title=${token}\n style=\"--swatch:${color}\"\n @click=${() => this.putMaterial({ emissive: { ...glow, token } })}\n ></button>\n `\n )}\n </div>\n <span token>${glow.token ?? i18next.t('label.same-as-off')}</span>\n </div>\n\n <label toggle>\n <input\n type=\"checkbox\"\n .checked=${!!glow.on}\n @change=${(e: Event) =>\n this.putMaterial({ emissive: { ...glow, on: (e.target as HTMLInputElement).checked } })}\n />\n ${i18next.t('label.on-by-default')}\n </label>\n <p cost>${i18next.t('text.on-by-default-explained')}</p>\n `\n : ''}\n\n </section>\n `\n }\n\n /**\n * 인스턴스 크기가 바뀔 때 이 부품이 어떻게 변하나.\n *\n * ## 늘어나는 축은 아직 못 보여 준다\n *\n * `stretch` 는 형식에 축을 적는 자리가 없어서 그리는 쪽이 **가장 길게 뻗은 축**으로\n * 정한다. 그 답을 화면에 적어 주려면 규칙을 가진 쪽에 물어야 하는데, 그 함수\n * (`stretchAxisOf` · `stretchAxis`)가 아직 배포된 판에 없다.\n *\n * 화면이 제 나름대로 계산하지 않는다. 규칙이 두 벌이 되면 보이는 축과 실제로 늘어나는\n * 축이 갈리고, 그 어긋남은 화면을 믿게 만든 다음에 드러난다. 배포되면 잇는다.\n *\n * ## 값이 있는 것을 밝힌다\n *\n * `scale` 이 아닌 부품은 합쳐 그릴 수 없어 draw call 을 하나 더 쓴다. 그 비용이\n * 성숙도의 묶음 수에 그대로 잡힌다. 고르는 자리에서 미리 말해 준다.\n */\n private renderBehaviour(part: FigurePart) {\n const sizing = part.sizing ?? 'scale'\n const repeat = part.repeat ?? { axis: 'x' as Axis, pitch: 0 }\n\n return html`\n <section>\n <h3>${i18next.t('label.sizing-rule')}</h3>\n <select @change=${(e: Event) => this.put({ sizing: (e.target as HTMLSelectElement).value as never })}>\n ${SIZING_RULES.map(\n rule => html`\n <option value=${rule} ?selected=${rule === sizing}>${i18next.t('label.sizing-' + rule)}</option>\n `\n )}\n </select>\n <p warn>${i18next.t('text.sizing-' + sizing + '-explained')}</p>\n\n ${sizing === 'scale'\n ? nothing\n : html`<p cost>${i18next.t('text.sizing-costs-one-draw-call')}</p>`}\n ${sizing === 'repeat'\n ? html`<p cost>${i18next.t('text.repeat-edits-the-unit')}</p>`\n : nothing}\n\n ${sizing === 'repeat'\n ? html`\n <div field>\n <label>${i18next.t('label.repeat-axis')}</label>\n <select\n @change=${(e: Event) =>\n this.put({ repeat: { ...repeat, axis: (e.target as HTMLSelectElement).value as Axis } })}\n >\n ${(['x', 'y', 'z'] as const).map(\n axis => html`<option value=${axis} ?selected=${axis === repeat.axis}>${axis}</option>`\n )}\n </select>\n </div>\n <div field>\n <label>${i18next.t('label.repeat-pitch')}</label>\n ${this.number('', repeat.pitch, value => this.put({ repeat: { ...repeat, pitch: value } }), 1)}\n <p cost>${i18next.t('text.repeat-pitch-explained')}</p>\n </div>\n `\n : nothing}\n </section>\n `\n }\n\n /**\n * 숫자 칸.\n *\n * 빈 칸이나 글자를 넣었을 때 0 으로 바꿔치지 않는다 — 저작자가 지우는 중일 수 있고,\n * 조용히 0 이 되면 부품이 사라진 것처럼 보인다. 숫자가 아니면 그냥 안 받는다.\n */\n private number(label: string, value: number, set: (value: number) => void, min?: number) {\n return html`\n <div cell>\n ${label ? html`<span axis>${label}</span>` : nothing}\n <input\n type=\"number\"\n .value=${String(value)}\n min=${min ?? nothing}\n @change=${(e: Event) => {\n const raw = (e.target as HTMLInputElement).value\n const parsed = Number(raw)\n if (raw === '' || !Number.isFinite(parsed)) {\n // 되돌려 놓는다. 안 그러면 화면과 정본이 갈린다.\n ;(e.target as HTMLInputElement).value = String(value)\n return\n }\n set(parsed)\n }}\n />\n </div>\n `\n }\n\n /**\n * 도형을 바꾼다.\n *\n * 도형이 바뀌면 그 도형에 뜻 없는 값이 남는다. 남겨 두면 검증이 「무시됨」 위반을\n * 내므로 여기서 걷는다.\n */\n private changePrimitive(primitive: PrimitiveKind) {\n this.change(part => {\n const next: FigurePart = { ...part, primitive }\n if (!SEGMENTED_PRIMITIVES.includes(primitive)) delete next.segments\n if (!EXTRUDE_PRIMITIVES.includes(primitive)) delete next.shape\n if (SEGMENTED_PRIMITIVES.includes(primitive) && next.segments === undefined) {\n next.segments = SEGMENT_PRESETS[1]\n }\n return next\n })\n }\n\n private put(patch: Partial<FigurePart>) {\n this.change(part => ({ ...part, ...patch }))\n }\n\n private putTransform(patch: Record<string, unknown>) {\n this.change(part => ({ ...part, transform: { ...part.transform, ...patch } as FigurePart['transform'] }))\n }\n\n /**\n * 발광 세기를 고친다. **0 이면 블록을 통째로 지운다.**\n *\n * 세기 0 인 발광을 남겨 두면 「안 빛나는데 램프인 것」이 되어, 재질 키가 갈려 묶음이\n * 헛되이 하나 는다. 0 은 「램프가 아니다」로 읽는 것이 맞다.\n *\n * `Number(value) || undefined` 를 쓰면 안 된다 — 0 이 falsy 라 변경으로 안 잡혀 옛\n * 값이 그대로 남는다. 실제로 램프가 안 꺼졌다.\n */\n private putEmissive(intensity: number) {\n // `chosen` 은 씬 모델이다. 형식으로 옮긴 값은 `partToFigure` 이 만든 것이라 여기서 다시 꺼낸다.\n const glow = this.board && this.chosen ? partToFigure(this.chosen, this.board).material?.emissive : undefined\n\n this.putMaterial({ emissive: intensity > 0 ? { ...glow, intensity } : undefined })\n }\n\n private putMaterial(patch: Record<string, unknown>) {\n this.change(part => ({\n ...part,\n material: { ...(part.material ?? {}), ...patch } as FigurePart['material']\n }))\n }\n\n /**\n * 수정한 것을 씬 모델로 되돌려 알린다.\n *\n * 사용자는 X·Y·Z 로 수정하고, 모델러 페이지는 씬 모델을 갖는다. 그 사이를 여기서\n * 잇는다 — 변환해서 수정하고 다시 되돌린다. **왕복 변환이 무손실이라야** 위치 하나\n * 고쳤을 뿐인데 회전값이 조금씩 밀리는 일이 없다(`fold.test.ts` 가 이를 확인한다).\n */\n private change(edit: (part: FigurePart) => FigurePart) {\n const part = this.chosen\n const board = this.board\n if (!part || !board) return\n\n this.emit(partFromFigure(edit(partToFigure(part, board)), board))\n }\n\n /** 정밀 도구를 시킨다. 정본은 저작면이 갖는다 — 여기서는 알리기만 한다. */\n private ask(what: string, axis: string) {\n this.dispatchEvent(\n new CustomEvent('part-tool', {\n detail: { index: this.selected, tool: what, axis },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private emit(part: PartModel) {\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index: this.selected, part }, bubbles: true, composed: true })\n )\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './figure-inspector.js';
|
|
2
2
|
import './figure-report.js';
|
|
3
3
|
import './figure-settings.js';
|
|
4
|
+
import './figure-history-panel.js';
|
|
4
5
|
import { LitElement } from 'lit';
|
|
5
6
|
import type { FigureSource } from '@hatiolab/figure-model';
|
|
6
7
|
import type { BoardModel, PartModel } from './figure-source.js';
|
|
@@ -26,6 +27,12 @@ declare const FigureSide_base: (new (...args: any[]) => LitElement) & typeof Lit
|
|
|
26
27
|
*/
|
|
27
28
|
export declare class FigureSide extends FigureSide_base {
|
|
28
29
|
static styles: import("lit").CSSResult;
|
|
30
|
+
figureName: string;
|
|
31
|
+
/** 이력 탭이 발행하고 되돌리는 데 필요한 것들. 정본은 저작면이 든다. */
|
|
32
|
+
figureId?: string;
|
|
33
|
+
figureState?: 'draft' | 'released';
|
|
34
|
+
figureVersion: number;
|
|
35
|
+
dirty: boolean;
|
|
29
36
|
board?: BoardModel;
|
|
30
37
|
parts: PartModel[];
|
|
31
38
|
source?: FigureSource;
|
|
@@ -2,6 +2,7 @@ import { __decorate, __metadata } from "tslib";
|
|
|
2
2
|
import './figure-inspector.js';
|
|
3
3
|
import './figure-report.js';
|
|
4
4
|
import './figure-settings.js';
|
|
5
|
+
import './figure-history-panel.js';
|
|
5
6
|
import { css, html, LitElement } from 'lit';
|
|
6
7
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
7
8
|
import { i18next, localize } from '@operato/i18n';
|
|
@@ -27,6 +28,15 @@ import { DEFAULT_VIEW } from './figure-view.js';
|
|
|
27
28
|
let FigureSide = class FigureSide extends localize(i18next)(LitElement) {
|
|
28
29
|
constructor() {
|
|
29
30
|
super(...arguments);
|
|
31
|
+
/*
|
|
32
|
+
두 형태가 이 패널에서 갈린다.
|
|
33
|
+
|
|
34
|
+
속성은 **수정하는** 곳이라 편집 중인 씬 모델을 받는다. 판정은 **저장될 것을 측정하는**
|
|
35
|
+
곳이라 변환된 FigureSource 를 받는다. 변환은 모델러 페이지가 한 번만 해서 내려보낸다.
|
|
36
|
+
*/
|
|
37
|
+
this.figureName = '';
|
|
38
|
+
this.figureVersion = 0;
|
|
39
|
+
this.dirty = false;
|
|
30
40
|
this.parts = [];
|
|
31
41
|
this.selected = -1;
|
|
32
42
|
/** 저작 화면의 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다. */
|
|
@@ -114,10 +124,12 @@ let FigureSide = class FigureSide extends localize(i18next)(LitElement) {
|
|
|
114
124
|
${this.renderTab('properties', i18next.t('label.properties'))}
|
|
115
125
|
${this.renderTab('verdict', i18next.t('label.maturity'), this.instances)}
|
|
116
126
|
${this.renderTab('view', i18next.t('label.view'))}
|
|
127
|
+
${this.renderTab('history', i18next.t('label.history'))}
|
|
117
128
|
</div>
|
|
118
129
|
|
|
119
130
|
<figure-inspector
|
|
120
131
|
?off=${this.tab !== 'properties'}
|
|
132
|
+
.figureName=${this.figureName}
|
|
121
133
|
.board=${this.board}
|
|
122
134
|
.parts=${this.parts}
|
|
123
135
|
.selected=${this.selected}
|
|
@@ -130,6 +142,17 @@ let FigureSide = class FigureSide extends localize(i18next)(LitElement) {
|
|
|
130
142
|
></figure-report>
|
|
131
143
|
|
|
132
144
|
<figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>
|
|
145
|
+
|
|
146
|
+
<!--
|
|
147
|
+
이력은 감춰도 DOM 에 남긴다 — 다른 탭들과 같은 규칙이다. 떼었다 붙이면 판본을 다시 부른다.
|
|
148
|
+
-->
|
|
149
|
+
<figure-history-panel
|
|
150
|
+
?off=${this.tab !== 'history'}
|
|
151
|
+
.figureId=${this.figureId}
|
|
152
|
+
.figureState=${this.figureState}
|
|
153
|
+
.figureVersion=${this.figureVersion}
|
|
154
|
+
.dirty=${this.dirty}
|
|
155
|
+
></figure-history-panel>
|
|
133
156
|
`;
|
|
134
157
|
}
|
|
135
158
|
renderTab(tab, label, badge) {
|
|
@@ -140,6 +163,26 @@ let FigureSide = class FigureSide extends localize(i18next)(LitElement) {
|
|
|
140
163
|
`;
|
|
141
164
|
}
|
|
142
165
|
};
|
|
166
|
+
__decorate([
|
|
167
|
+
property({ type: String }),
|
|
168
|
+
__metadata("design:type", Object)
|
|
169
|
+
], FigureSide.prototype, "figureName", void 0);
|
|
170
|
+
__decorate([
|
|
171
|
+
property({ type: String }),
|
|
172
|
+
__metadata("design:type", String)
|
|
173
|
+
], FigureSide.prototype, "figureId", void 0);
|
|
174
|
+
__decorate([
|
|
175
|
+
property({ type: String }),
|
|
176
|
+
__metadata("design:type", String)
|
|
177
|
+
], FigureSide.prototype, "figureState", void 0);
|
|
178
|
+
__decorate([
|
|
179
|
+
property({ type: Number }),
|
|
180
|
+
__metadata("design:type", Object)
|
|
181
|
+
], FigureSide.prototype, "figureVersion", void 0);
|
|
182
|
+
__decorate([
|
|
183
|
+
property({ type: Boolean }),
|
|
184
|
+
__metadata("design:type", Object)
|
|
185
|
+
], FigureSide.prototype, "dirty", void 0);
|
|
143
186
|
__decorate([
|
|
144
187
|
property({ type: Object }),
|
|
145
188
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-side.js","sourceRoot":"","sources":["../../client/modeller/figure-side.ts"],"names":[],"mappings":";AAAA,OAAO,uBAAuB,CAAA;AAC9B,OAAO,oBAAoB,CAAA;AAC3B,OAAO,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"figure-side.js","sourceRoot":"","sources":["../../client/modeller/figure-side.ts"],"names":[],"mappings":"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","sourcesContent":["import './figure-inspector.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' | '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 display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 8px 6px;\n font: var(--label-font, inherit);\n font-size: 0.76rem;\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 span[badge] {\n padding: 1px 7px;\n font-size: 0.72rem;\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\n figure-inspector,\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 }) 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 render() {\n return html`\n <div tabs>\n ${this.renderTab('properties', i18next.t('label.properties'))}\n ${this.renderTab('verdict', i18next.t('label.maturity'), this.instances)}\n ${this.renderTab('view', i18next.t('label.view'))}\n ${this.renderTab('history', i18next.t('label.history'))}\n </div>\n\n <figure-inspector\n ?off=${this.tab !== 'properties'}\n .figureName=${this.figureName}\n .board=${this.board}\n .parts=${this.parts}\n .selected=${this.selected}\n ></figure-inspector>\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 private renderTab(tab: Tab, label: string, badge?: number) {\n return html`\n <button tab ?on=${this.tab === tab} @click=${() => (this.tab = tab)}>\n ${label}${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import './figure-canvas.js';
|
|
2
|
+
/**
|
|
3
|
+
* 카드에 쓸 그림을 찍는다 — **숨은 자리에 저작 캔버스를 하나 더 세워서.**
|
|
4
|
+
*
|
|
5
|
+
* ## 왜 보이는 캔버스를 찍지 않나
|
|
6
|
+
*
|
|
7
|
+
* 저작면의 캔버스는 저작자가 돌려 둔 각도와 확대로 서 있고, 칸의 비율도 창 크기와 접힌 패널에 따라
|
|
8
|
+
* 다르다. 그것을 찍으면 카드마다 프레임이 달라지고, 방금 확대해 보던 나사 하나가 그 자산의 얼굴이
|
|
9
|
+
* 된다. 목록은 견주는 자리이므로 그림은 **정해진 크기와 정해진 카메라**로 찍는다.
|
|
10
|
+
*
|
|
11
|
+
* ## 왜 씬을 여기서 세우지 않고 부품을 세우나
|
|
12
|
+
*
|
|
13
|
+
* 씬 값(환경 · 하늘 · 조명 · 픽셀 비율 · 좌표 바탕)은 `figure-canvas` 가 든다. 여기서 다시 적으면
|
|
14
|
+
* 두 벌이 되고, 그러면 **카드에 보이는 그림과 저작면에서 보던 것이 아무 오류 없이 갈린다.** 그래서
|
|
15
|
+
* 같은 부품을 `viewOnly` 로 하나 더 세워 그 부품이 자기 씬을 세우게 하고, 찍은 뒤 버린다.
|
|
16
|
+
*
|
|
17
|
+
* ## 왜 서버가 아니라 브라우저인가
|
|
18
|
+
*
|
|
19
|
+
* 보드는 저장할 때 서버가 찍는데, 백엔드에 GPU 가 없는 배포가 많아 헤드리스로 3D 를 찍는 것이 자주
|
|
20
|
+
* 실패한다. 도형을 그릴 GPU 는 이미 보는 사람 손에 있다.
|
|
21
|
+
*/
|
|
22
|
+
/** 카드는 4:3 이다. */
|
|
23
|
+
const WIDTH = 480;
|
|
24
|
+
const HEIGHT = 360;
|
|
25
|
+
/** 프레임 두 장 뒤에 한 번 더 쉰다 — 씬이 서고 카메라가 자리를 잡을 틈. 씬에 그 신호가 없다. */
|
|
26
|
+
function settle() {
|
|
27
|
+
return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(resolve, 150))));
|
|
28
|
+
}
|
|
29
|
+
export async function captureThumbnail(board, parts) {
|
|
30
|
+
if (!board || !parts.length) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/*
|
|
34
|
+
화면 밖에 둔다. `display: none` 이면 캔버스가 크기를 못 얻어 아무것도 그리지 못한다.
|
|
35
|
+
WebGL context 는 브라우저가 8~16개에서 끊으므로 찍은 뒤 반드시 버린다(아래 finally).
|
|
36
|
+
*/
|
|
37
|
+
const host = document.createElement('div');
|
|
38
|
+
host.setAttribute('figure-thumbnail-stage', '');
|
|
39
|
+
host.style.cssText = `position: fixed; left: -10000px; top: 0; width: ${WIDTH}px; height: ${HEIGHT}px; pointer-events: none;`;
|
|
40
|
+
document.body.appendChild(host);
|
|
41
|
+
const shooter = document.createElement('figure-canvas');
|
|
42
|
+
shooter.viewOnly = true;
|
|
43
|
+
shooter.board = board;
|
|
44
|
+
shooter.parts = parts;
|
|
45
|
+
shooter.style.cssText = 'display: block; width: 100%; height: 100%;';
|
|
46
|
+
try {
|
|
47
|
+
host.appendChild(shooter);
|
|
48
|
+
await settle();
|
|
49
|
+
return shooter.snapshot ? await shooter.snapshot(WIDTH, HEIGHT) : undefined;
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
console.warn(`[thumbnail] 세우지 못했습니다 — ${e.message}`);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
host.remove();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=figure-thumbnail.js.map
|