@things-factory/figure-ui 10.1.43 → 10.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/client/modeller/figure-v3-links.ts +303 -0
  2. package/client/modeller/modeller-route.ts +6 -21
  3. package/client/modeller/v3-editor.ts +44 -3
  4. package/client/modeller/v3-gizmo.ts +13 -2
  5. package/client/modeller/v3-links.ts +138 -0
  6. package/client/pages/figure-v3-modeller-page.ts +2 -5
  7. package/client/route.ts +2 -6
  8. package/dist-client/modeller/figure-v3-links.d.ts +42 -0
  9. package/dist-client/modeller/figure-v3-links.js +306 -0
  10. package/dist-client/modeller/figure-v3-links.js.map +1 -0
  11. package/dist-client/modeller/modeller-route.d.ts +4 -11
  12. package/dist-client/modeller/modeller-route.js +5 -22
  13. package/dist-client/modeller/modeller-route.js.map +1 -1
  14. package/dist-client/modeller/v3-editor.d.ts +5 -0
  15. package/dist-client/modeller/v3-editor.js +46 -2
  16. package/dist-client/modeller/v3-editor.js.map +1 -1
  17. package/dist-client/modeller/v3-gizmo.js +11 -1
  18. package/dist-client/modeller/v3-gizmo.js.map +1 -1
  19. package/dist-client/modeller/v3-links.d.ts +54 -0
  20. package/dist-client/modeller/v3-links.js +110 -0
  21. package/dist-client/modeller/v3-links.js.map +1 -0
  22. package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -5
  23. package/dist-client/pages/figure-v3-modeller-page.js +2 -5
  24. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  25. package/dist-client/route.d.ts +1 -1
  26. package/dist-client/route.js +2 -5
  27. package/dist-client/route.js.map +1 -1
  28. package/dist-client/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +4 -4
  30. package/test/ai-proposal-contract.test.ts +0 -88
  31. package/test/figure-source.test.ts +0 -102
  32. package/test/i18n-prefix-guard.test.ts +0 -24
  33. package/test/modeller-route.test.ts +14 -25
  34. package/test/part-edits.test.ts +0 -7
  35. package/test/scene-budget.test.ts +0 -7
  36. package/test/v3-gizmo.test.ts +10 -0
  37. package/test/v3-links.test.ts +123 -0
  38. package/things-factory.config.js +2 -5
  39. package/translations/en.json +6 -0
  40. package/translations/ja.json +11 -1
  41. package/translations/ko.json +6 -0
  42. package/translations/ms.json +11 -1
  43. package/translations/zh.json +11 -1
  44. package/client/modeller/angle-dial.ts +0 -70
  45. package/client/modeller/base-box.ts +0 -175
  46. package/client/modeller/figure-animations.ts +0 -1249
  47. package/client/modeller/figure-canvas.ts +0 -567
  48. package/client/modeller/figure-capabilities.ts +0 -379
  49. package/client/modeller/figure-inspector.ts +0 -2208
  50. package/client/modeller/figure-parts.ts +0 -479
  51. package/client/modeller/figure-preview.ts +0 -598
  52. package/client/modeller/figure-report.ts +0 -966
  53. package/client/modeller/figure-settings.ts +0 -328
  54. package/client/modeller/figure-side.ts +0 -272
  55. package/client/modeller/figure-thumbnail.ts +0 -123
  56. package/client/modeller/figure-view.ts +0 -152
  57. package/client/modeller/joint-edits.ts +0 -262
  58. package/client/modeller/joint-gizmo.ts +0 -180
  59. package/client/modeller/maturity.ts +0 -288
  60. package/client/modeller/new-figure-entry.ts +0 -4
  61. package/client/modeller/pose-defaults.ts +0 -67
  62. package/client/pages/figure-modeller-page.ts +0 -2082
  63. package/dist-client/modeller/angle-dial.d.ts +0 -32
  64. package/dist-client/modeller/angle-dial.js +0 -41
  65. package/dist-client/modeller/angle-dial.js.map +0 -1
  66. package/dist-client/modeller/base-box.d.ts +0 -71
  67. package/dist-client/modeller/base-box.js +0 -110
  68. package/dist-client/modeller/base-box.js.map +0 -1
  69. package/dist-client/modeller/figure-animations.d.ts +0 -159
  70. package/dist-client/modeller/figure-animations.js +0 -1132
  71. package/dist-client/modeller/figure-animations.js.map +0 -1
  72. package/dist-client/modeller/figure-canvas.d.ts +0 -150
  73. package/dist-client/modeller/figure-canvas.js +0 -506
  74. package/dist-client/modeller/figure-canvas.js.map +0 -1
  75. package/dist-client/modeller/figure-capabilities.d.ts +0 -42
  76. package/dist-client/modeller/figure-capabilities.js +0 -349
  77. package/dist-client/modeller/figure-capabilities.js.map +0 -1
  78. package/dist-client/modeller/figure-inspector.d.ts +0 -329
  79. package/dist-client/modeller/figure-inspector.js +0 -2095
  80. package/dist-client/modeller/figure-inspector.js.map +0 -1
  81. package/dist-client/modeller/figure-parts.d.ts +0 -64
  82. package/dist-client/modeller/figure-parts.js +0 -478
  83. package/dist-client/modeller/figure-parts.js.map +0 -1
  84. package/dist-client/modeller/figure-preview.d.ts +0 -138
  85. package/dist-client/modeller/figure-preview.js +0 -549
  86. package/dist-client/modeller/figure-preview.js.map +0 -1
  87. package/dist-client/modeller/figure-report.d.ts +0 -165
  88. package/dist-client/modeller/figure-report.js +0 -952
  89. package/dist-client/modeller/figure-report.js.map +0 -1
  90. package/dist-client/modeller/figure-settings.d.ts +0 -39
  91. package/dist-client/modeller/figure-settings.js +0 -317
  92. package/dist-client/modeller/figure-settings.js.map +0 -1
  93. package/dist-client/modeller/figure-side.d.ts +0 -78
  94. package/dist-client/modeller/figure-side.js +0 -317
  95. package/dist-client/modeller/figure-side.js.map +0 -1
  96. package/dist-client/modeller/figure-thumbnail.d.ts +0 -3
  97. package/dist-client/modeller/figure-thumbnail.js +0 -95
  98. package/dist-client/modeller/figure-thumbnail.js.map +0 -1
  99. package/dist-client/modeller/figure-view.d.ts +0 -118
  100. package/dist-client/modeller/figure-view.js +0 -78
  101. package/dist-client/modeller/figure-view.js.map +0 -1
  102. package/dist-client/modeller/joint-edits.d.ts +0 -49
  103. package/dist-client/modeller/joint-edits.js +0 -223
  104. package/dist-client/modeller/joint-edits.js.map +0 -1
  105. package/dist-client/modeller/joint-gizmo.d.ts +0 -34
  106. package/dist-client/modeller/joint-gizmo.js +0 -158
  107. package/dist-client/modeller/joint-gizmo.js.map +0 -1
  108. package/dist-client/modeller/maturity.d.ts +0 -55
  109. package/dist-client/modeller/maturity.js +0 -212
  110. package/dist-client/modeller/maturity.js.map +0 -1
  111. package/dist-client/modeller/new-figure-entry.d.ts +0 -2
  112. package/dist-client/modeller/new-figure-entry.js +0 -5
  113. package/dist-client/modeller/new-figure-entry.js.map +0 -1
  114. package/dist-client/modeller/pose-defaults.d.ts +0 -9
  115. package/dist-client/modeller/pose-defaults.js +0 -35
  116. package/dist-client/modeller/pose-defaults.js.map +0 -1
  117. package/dist-client/pages/figure-modeller-page.d.ts +0 -419
  118. package/dist-client/pages/figure-modeller-page.js +0 -2060
  119. package/dist-client/pages/figure-modeller-page.js.map +0 -1
  120. package/test/angle-dial.test.ts +0 -78
  121. package/test/base-box.test.ts +0 -235
  122. package/test/joint-edits.test.ts +0 -165
  123. package/test/joint-gizmo.test.ts +0 -174
  124. package/test/joint-needle.test.ts +0 -162
  125. package/test/modeller-undo-redo.test.ts +0 -33
  126. package/test/new-figure-entry.test.ts +0 -15
  127. package/test/pose-defaults.test.ts +0 -71
  128. package/test/preview-board-depth.test.ts +0 -27
@@ -1,478 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import '@material/web/icon/icon.js';
3
- import { css, html, LitElement, nothing } from 'lit';
4
- import { customElement, property } from 'lit/decorators.js';
5
- import { i18next, localize } from '@operato/i18n';
6
- import { ScrollbarStyles } from '@operato/styles';
7
- import { DETAIL_LEVELS, PART_LIMIT, PRIMITIVE_KINDS } from '@hatiolab/figure-model';
8
- /** 도형마다의 아이콘. Material Symbols 이름이다. */
9
- const SHAPE_ICON = {
10
- cube: 'deployed_code',
11
- wall: 'rectangle',
12
- cylinder: 'database',
13
- sphere: 'circle',
14
- rect: 'crop_square',
15
- polygon: 'pentagon'
16
- };
17
- /**
18
- * 도형 팔레트와 부품 목록.
19
- *
20
- * ## 팔레트를 목록 위에 둔다
21
- *
22
- * 처음 온 사람은 「무엇을 놓을 수 있나」를 먼저 알아야 한다. 빈 목록 아래에 팔레트를
23
- * 두면 아무것도 없는 화면에서 다음 동작이 안 보인다.
24
- *
25
- * ## 한도를 미리 보여 준다
26
- *
27
- * 부품 수 한도는 디테일 등급이 정한다(`PART_LIMIT`). 넘긴 뒤에 판정 패널이
28
- * 나무라는 것보다, **넣기 전에 몇 개 남았는지** 보이는 것이 낫다.
29
- *
30
- * ## 고치지 않는다
31
- *
32
- * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — 정본 한 벌을
33
- * 저작면이 갖고, 거기서만 바뀐다.
34
- */
35
- let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
36
- constructor() {
37
- super(...arguments);
38
- /**
39
- * 초안 위의 부품들. **씬 모델 그대로 받는다.**
40
- *
41
- * 이 패널은 이름과 도형만 읽으므로 어느 쪽 형태로 받아도 화면은 같다. 그래도 씬
42
- * 모델로 받는 것은 모델러 페이지가 데이터를 **한 벌만** 갖게 하기 위해서다. 여기만
43
- * FigureSource 로 받으면 페이지가 이 패널을 위해 변환본을 따로 들고 있어야 하고,
44
- * 그 변환본이 언제 낡는지를 또 관리해야 한다.
45
- */
46
- this.parts = [];
47
- /** 디테일 등급. 부품 수 한도가 여기서 결정된다. */
48
- this.level = 'M';
49
- /** 고른 부품의 자리. 고르지 않았으면 -1. */
50
- this.selected = -1;
51
- }
52
- static { this.styles = [
53
- ScrollbarStyles,
54
- css `
55
- :host {
56
- display: flex;
57
- flex-direction: column;
58
- min-height: 0;
59
- overflow: hidden;
60
- background-color: var(--md-sys-color-surface-container-lowest);
61
- border-right: 1px solid var(--md-sys-color-outline-variant);
62
- color: var(--md-sys-color-on-surface);
63
- font: var(--label-font, inherit);
64
- /*
65
- 이 패널의 본문 크기를 여기서 한 번 정한다.
66
-
67
- 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를
68
- 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.
69
- 이 파일에서만 두 번 났다.
70
-
71
- form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.
72
- 그것 말고는 여기 값을 따른다.
73
- */
74
- font-size: 0.76rem;
75
- }
76
-
77
- section {
78
- padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
79
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
80
- }
81
- section[list] {
82
- flex: 1;
83
- min-height: 0;
84
- overflow-y: auto;
85
- }
86
- h3 {
87
- display: flex;
88
- align-items: baseline;
89
- justify-content: space-between;
90
- gap: 6px;
91
- margin: 0 0 6px 0;
92
- font-size: var(--ui-t-micro, 11px);
93
- font-weight: 700;
94
- letter-spacing: 0.04em;
95
- text-transform: uppercase;
96
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
97
- }
98
- /*
99
- 부품 수와 한도.
100
-
101
- 전에는 이 숫자가 「도형」 절 제목에 붙어 있었다. 부품 수인데 도형 옆에 있으니
102
- 「도형 8종 중 2종」으로 읽힌다 — 실제로 그렇게 읽혔다. 세는 대상이 있는 절로
103
- 옮기고, 한도가 어디서 나온 값인지(디테일 등급)를 함께 적는다.
104
- */
105
- span[room] {
106
- font-size: 0.7rem;
107
- font-weight: 400;
108
- letter-spacing: 0;
109
- text-transform: none;
110
- }
111
- span[room][full] {
112
- color: var(--md-sys-color-error);
113
- }
114
-
115
- /* ── 팔레트 ─────────────────────────────────────────────────────── */
116
- div[palette] {
117
- display: grid;
118
- grid-template-columns: repeat(3, 1fr);
119
- gap: 4px;
120
- }
121
- button[shape] {
122
- display: flex;
123
- flex-direction: column;
124
- align-items: center;
125
- gap: 2px;
126
- padding: 7px 2px 5px 2px;
127
- font: var(--label-font, inherit);
128
- font-size: 0.66rem;
129
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
130
- background: none;
131
- border: 1px solid var(--md-sys-color-outline-variant);
132
- border-radius: 6px;
133
- cursor: pointer;
134
- }
135
- button[shape]:hover:not([disabled]) {
136
- color: var(--md-sys-color-on-primary-container);
137
- background-color: var(--md-sys-color-primary-container);
138
- border-color: transparent;
139
- }
140
- button[shape][disabled] {
141
- opacity: 0.35;
142
- cursor: default;
143
- }
144
- button[shape] md-icon {
145
- --md-icon-size: 20px;
146
- }
147
-
148
- /* ── 부품 목록 ──────────────────────────────────────────────────── */
149
- ol {
150
- margin: 0;
151
- padding: 0;
152
- list-style: none;
153
- display: flex;
154
- flex-direction: column;
155
- gap: 2px;
156
- }
157
- li {
158
- display: flex;
159
- align-items: center;
160
- gap: 6px;
161
- padding: 4px 6px;
162
- border-radius: 6px;
163
- cursor: pointer;
164
- }
165
- li:hover {
166
- background-color: var(--md-sys-color-surface-container);
167
- }
168
- li[selected] {
169
- color: var(--md-sys-color-on-primary-container);
170
- background-color: var(--md-sys-color-primary-container);
171
- }
172
- li md-icon {
173
- --md-icon-size: 16px;
174
- flex: none;
175
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
176
- }
177
-
178
- /*
179
- 숨김 단추.
180
-
181
- 고른 줄에서만 보이는 조작(div[ops])과 달리 **늘 자리를 차지한다.** 무엇이 숨겨져
182
- 있는지는 목록을 훑을 때 한눈에 보여야 한다 — 눌러 봐야 알면 숨겨 둔 것을 잊는다.
183
- 다만 켜지지 않은 눈은 줄에 마우스를 올렸을 때만 보인다. 여덟 개 줄에 회색 눈이
184
- 여덟 개 떠 있으면 목록이 시끄럽다.
185
- */
186
- /* 머리줄의 것은 늘 보인다 — 줄에 마우스를 올려야 나타나는 것이 아니다 */
187
- button[eye][all] {
188
- margin-left: auto;
189
- opacity: 0.75;
190
- }
191
- button[eye][all]:hover {
192
- opacity: 1;
193
- }
194
- button[eye] {
195
- display: flex;
196
- flex: none;
197
- padding: 1px;
198
- color: inherit;
199
- background: none;
200
- border: none;
201
- border-radius: 4px;
202
- cursor: pointer;
203
- opacity: 0;
204
- }
205
- li:hover button[eye],
206
- li[selected] button[eye],
207
- button[eye][off] {
208
- opacity: 1;
209
- }
210
- button[eye][off] {
211
- color: var(--md-sys-color-tertiary);
212
- }
213
- button[eye] md-icon {
214
- --md-icon-size: 15px;
215
- color: inherit;
216
- }
217
-
218
- /* 숨긴 줄은 목록에서도 옅게 — 미리보기에서 faint 로 보이는 것과 같은 뜻이어야 한다 */
219
- li[off] span[label],
220
- li[off] > md-icon {
221
- opacity: 0.45;
222
- }
223
- li[selected] md-icon {
224
- color: inherit;
225
- }
226
- span[label] {
227
- flex: 1;
228
- min-width: 0;
229
- overflow: hidden;
230
- text-overflow: ellipsis;
231
- white-space: nowrap;
232
- font-size: 0.78rem;
233
- }
234
-
235
- /* 조작은 고른 줄에서만 보인다 — 늘 보이면 목록이 시끄럽다 */
236
- div[ops] {
237
- display: none;
238
- gap: 1px;
239
- }
240
- li[selected] div[ops] {
241
- display: flex;
242
- }
243
- div[ops] button {
244
- display: flex;
245
- padding: 2px;
246
- color: inherit;
247
- background: none;
248
- border: none;
249
- border-radius: 4px;
250
- cursor: pointer;
251
- }
252
- div[ops] button:hover:not([disabled]) {
253
- background-color: var(--md-sys-color-surface-container-lowest);
254
- }
255
- div[ops] button[disabled] {
256
- opacity: 0.3;
257
- cursor: default;
258
- }
259
- div[ops] md-icon {
260
- --md-icon-size: 15px;
261
- color: inherit;
262
- }
263
-
264
- /*
265
- 디테일 등급 선택.
266
-
267
- 전에는 등급이 "16 / 20 · L 등급" 안의 글자 하나로만 있었다. S·M·L 이 무엇인지
268
- 어디에도 없었고, 한도를 다 쓰면 "등급을 올리세요" 라고 해 놓고 올릴 칸이 없었다.
269
-
270
- 부품 목록 위에 둔다. 등급이 정하는 것이 바로 아래 목록의 한도이고, 팔레트가
271
- 잠기는 것도 이 값 때문이다. 결과가 보이는 자리에서 고치게 한다.
272
- */
273
- div[level] {
274
- display: flex;
275
- align-items: center;
276
- gap: 6px;
277
- margin: 0 0 7px 0;
278
- }
279
- div[level] label {
280
- flex: none;
281
- margin: 0;
282
- font-size: 0.68rem;
283
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
284
- }
285
- div[level] select {
286
- flex: 1;
287
- min-width: 0;
288
- padding: 3px 5px;
289
- font: var(--input-field-font, inherit);
290
- font-size: 0.72rem;
291
- color: var(--md-sys-color-on-surface);
292
- background-color: var(--md-sys-color-surface-container-lowest);
293
- border: 1px solid var(--md-sys-color-outline-variant);
294
- border-radius: 6px;
295
- }
296
-
297
- /* 팔레트가 잠긴 이유를 말한다. 흐린 단추만 두면 왜 안 눌리는지 모른다 */
298
- p[full] {
299
- margin: 6px 0 0 0;
300
- font-size: 0.72rem;
301
- line-height: 1.5;
302
- color: var(--md-sys-color-error);
303
- }
304
-
305
- p[quiet] {
306
- margin: 0;
307
- font-size: 0.76rem;
308
- line-height: 1.6;
309
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
310
- }
311
- `
312
- ]; }
313
- render() {
314
- const parts = this.parts ?? [];
315
- const level = this.level;
316
- const limit = PART_LIMIT[level];
317
- const full = parts.length >= limit;
318
- return html `
319
- <section>
320
- <h3>${i18next.t('figure.label.shapes')}</h3>
321
- <div palette>
322
- ${PRIMITIVE_KINDS.map(kind => html `
323
- <button
324
- shape
325
- ?disabled=${full}
326
- title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.add-a-shape')}
327
- @click=${() => this.ask('add-part', { primitive: kind })}
328
- >
329
- <md-icon>${SHAPE_ICON[kind]}</md-icon>
330
- ${i18next.t('figure.label.primitive-' + kind)}
331
- </button>
332
- `)}
333
- </div>
334
- ${full
335
- ? html `<p full>${i18next.t('figure.text.part-limit-reached-short', { level, limit })}</p>`
336
- : ''}
337
- </section>
338
-
339
- <section list>
340
- <h3>
341
- ${i18next.t('figure.label.parts')}
342
- <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}>
343
- ${i18next.t('figure.text.n-of-limit', { used: parts.length, limit })}
344
- </span>
345
- ${this.renderShowAll(parts)}
346
- </h3>
347
-
348
- <div level>
349
- <label for="detail-level">${i18next.t('figure.label.detail-level')}</label>
350
- <select
351
- id="detail-level"
352
- title=${i18next.t('figure.text.detail-level-explained')}
353
- @change=${(e) => this.ask('change-detail-level', { level: e.target.value })}
354
- >
355
- ${DETAIL_LEVELS.map(one => html `
356
- <option value=${one} ?selected=${one === level}>
357
- ${one} ${i18next.t('figure.label.detail-' + one)} ·
358
- ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}
359
- </option>
360
- `)}
361
- </select>
362
- </div>
363
-
364
- ${parts.length === 0
365
- ? html `<p quiet>${i18next.t('figure.text.pick-a-shape-above-to-start')}</p>`
366
- : html `
367
- <ol>
368
- ${parts.map((part, index) => this.renderRow(part, index, parts.length))}
369
- </ol>
370
- `}
371
- </section>
372
- `;
373
- }
374
- /**
375
- * 한꺼번에 감추고 한꺼번에 되돌린다.
376
- *
377
- * 부품 스물짜리를 들여다볼 때 하나만 남기려면 열아홉을 눌러야 했다. 안쪽에 있는 것을
378
- * 보려고 겉을 걷어내는 일은 저작 중에 늘 하는 일이다.
379
- *
380
- * 무엇을 할지는 **지금 상태가 정한다** — 하나라도 감춰져 있으면 다 되돌리고, 다
381
- * 보이고 있으면 다 감춘다. 단추 하나로 오가는 것이 두 개보다 낫다.
382
- */
383
- renderShowAll(parts) {
384
- if (parts.length === 0)
385
- return nothing;
386
- const anyHidden = parts.some(part => part.hidden);
387
- return html `
388
- <button
389
- eye
390
- all
391
- ?off=${!anyHidden}
392
- title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}
393
- @click=${(e) => {
394
- e.stopPropagation();
395
- this.ask('set-all-parts-hidden', { hidden: !anyHidden });
396
- }}
397
- >
398
- <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>
399
- </button>
400
- `;
401
- }
402
- renderRow(part, index, count) {
403
- const selected = index === this.selected;
404
- const off = !!part.hidden;
405
- return html `
406
- <li ?selected=${selected} ?off=${off} @click=${() => this.ask('select-part', { index })}>
407
- <button
408
- eye
409
- ?off=${off}
410
- title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}
411
- @click=${(e) => {
412
- // 눌렀을 때 줄 고르기가 함께 일어나지 않게 막는다 — 가리려던 것이지 고르려던 것이 아니다
413
- e.stopPropagation();
414
- this.ask('toggle-part-hidden', { index });
415
- }}
416
- >
417
- <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>
418
- </button>
419
- <md-icon>${SHAPE_ICON[part.primitive]}</md-icon>
420
- <span label title=${part.name}>${part.name}</span>
421
- ${selected ? this.renderOps(index, count) : nothing}
422
- </li>
423
- `;
424
- }
425
- /**
426
- * 고른 줄의 조작.
427
- *
428
- * 못 하는 것을 **빼지 않고 흐리게 둔다.** 빼면 남은 단추가 그 자리로 밀려서, 첫
429
- * 줄과 마지막 줄에서 「지우기」가 다른 자리에 온다 — 연달아 지우다 엉뚱한 것을
430
- * 누르게 된다.
431
- *
432
- * 눌렀을 때 줄 고르기가 다시 일어나지 않게 전파를 멈춘다.
433
- */
434
- renderOps(index, count) {
435
- const op = (name, icon, detail, enabled = true) => html `
436
- <button
437
- op=${name}
438
- ?disabled=${!enabled}
439
- title=${i18next.t('figure.button.' + name)}
440
- @click=${(e) => {
441
- e.stopPropagation();
442
- if (enabled)
443
- this.ask(name, detail);
444
- }}
445
- >
446
- <md-icon>${icon}</md-icon>
447
- </button>
448
- `;
449
- return html `
450
- <div ops>
451
- ${op('move-part-up', 'arrow_upward', { index, to: index - 1 }, index > 0)}
452
- ${op('move-part-down', 'arrow_downward', { index, to: index + 1 }, index < count - 1)}
453
- ${op('duplicate-part', 'content_copy', { index })} ${op('remove-part', 'delete', { index })}
454
- </div>
455
- `;
456
- }
457
- /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */
458
- ask(what, detail) {
459
- this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }));
460
- }
461
- };
462
- __decorate([
463
- property({ type: Array }),
464
- __metadata("design:type", Array)
465
- ], FigureParts.prototype, "parts", void 0);
466
- __decorate([
467
- property({ type: String }),
468
- __metadata("design:type", String)
469
- ], FigureParts.prototype, "level", void 0);
470
- __decorate([
471
- property({ type: Number }),
472
- __metadata("design:type", Object)
473
- ], FigureParts.prototype, "selected", void 0);
474
- FigureParts = __decorate([
475
- customElement('figure-parts')
476
- ], FigureParts);
477
- export { FigureParts };
478
- //# sourceMappingURL=figure-parts.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"figure-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { DETAIL_LEVELS, PART_LIMIT, PRIMITIVE_KINDS } from '@hatiolab/figure-model'\nimport type { DetailLevel, PrimitiveKind } from '@hatiolab/figure-model'\n\nimport type { PartModel } from './figure-source.js'\n\n/** 도형마다의 아이콘. Material Symbols 이름이다. */\nconst SHAPE_ICON: Record<PrimitiveKind, string> = {\n cube: 'deployed_code',\n wall: 'rectangle',\n cylinder: 'database',\n sphere: 'circle',\n rect: 'crop_square',\n polygon: 'pentagon'\n}\n\n/**\n * 도형 팔레트와 부품 목록.\n *\n * ## 팔레트를 목록 위에 둔다\n *\n * 처음 온 사람은 「무엇을 놓을 수 있나」를 먼저 알아야 한다. 빈 목록 아래에 팔레트를\n * 두면 아무것도 없는 화면에서 다음 동작이 안 보인다.\n *\n * ## 한도를 미리 보여 준다\n *\n * 부품 수 한도는 디테일 등급이 정한다(`PART_LIMIT`). 넘긴 뒤에 판정 패널이\n * 나무라는 것보다, **넣기 전에 몇 개 남았는지** 보이는 것이 낫다.\n *\n * ## 고치지 않는다\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — 정본 한 벌을\n * 저작면이 갖고, 거기서만 바뀐다.\n */\n@customElement('figure-parts')\nexport class FigureParts extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n border-right: 1px solid var(--md-sys-color-outline-variant);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n /*\n 이 패널의 본문 크기를 여기서 한 번 정한다.\n\n 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를\n 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.\n 이 파일에서만 두 번 났다.\n\n form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n 그것 말고는 여기 값을 따른다.\n */\n font-size: 0.76rem;\n }\n\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section[list] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n h3 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 6px;\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n /*\n 부품 수와 한도.\n\n 전에는 이 숫자가 「도형」 절 제목에 붙어 있었다. 부품 수인데 도형 옆에 있으니\n 「도형 8종 중 2종」으로 읽힌다 — 실제로 그렇게 읽혔다. 세는 대상이 있는 절로\n 옮기고, 한도가 어디서 나온 값인지(디테일 등급)를 함께 적는다.\n */\n span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n }\n\n /* ── 팔레트 ─────────────────────────────────────────────────────── */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 4px;\n }\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding: 7px 2px 5px 2px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[shape]:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[shape][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n button[shape] md-icon {\n --md-icon-size: 20px;\n }\n\n /* ── 부품 목록 ──────────────────────────────────────────────────── */\n ol {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n }\n li:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n li[selected] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n li md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /*\n 숨김 단추.\n\n 고른 줄에서만 보이는 조작(div[ops])과 달리 **늘 자리를 차지한다.** 무엇이 숨겨져\n 있는지는 목록을 훑을 때 한눈에 보여야 한다 — 눌러 봐야 알면 숨겨 둔 것을 잊는다.\n 다만 켜지지 않은 눈은 줄에 마우스를 올렸을 때만 보인다. 여덟 개 줄에 회색 눈이\n 여덟 개 떠 있으면 목록이 시끄럽다.\n */\n /* 머리줄의 것은 늘 보인다 — 줄에 마우스를 올려야 나타나는 것이 아니다 */\n button[eye][all] {\n margin-left: auto;\n opacity: 0.75;\n }\n button[eye][all]:hover {\n opacity: 1;\n }\n button[eye] {\n display: flex;\n flex: none;\n padding: 1px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n }\n li:hover button[eye],\n li[selected] button[eye],\n button[eye][off] {\n opacity: 1;\n }\n button[eye][off] {\n color: var(--md-sys-color-tertiary);\n }\n button[eye] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n\n /* 숨긴 줄은 목록에서도 옅게 — 미리보기에서 faint 로 보이는 것과 같은 뜻이어야 한다 */\n li[off] span[label],\n li[off] > md-icon {\n opacity: 0.45;\n }\n li[selected] md-icon {\n color: inherit;\n }\n span[label] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.78rem;\n }\n\n /* 조작은 고른 줄에서만 보인다 — 늘 보이면 목록이 시끄럽다 */\n div[ops] {\n display: none;\n gap: 1px;\n }\n li[selected] div[ops] {\n display: flex;\n }\n div[ops] button {\n display: flex;\n padding: 2px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n div[ops] button:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[ops] button[disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[ops] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n\n /*\n 디테일 등급 선택.\n\n 전에는 등급이 \"16 / 20 · L 등급\" 안의 글자 하나로만 있었다. S·M·L 이 무엇인지\n 어디에도 없었고, 한도를 다 쓰면 \"등급을 올리세요\" 라고 해 놓고 올릴 칸이 없었다.\n\n 부품 목록 위에 둔다. 등급이 정하는 것이 바로 아래 목록의 한도이고, 팔레트가\n 잠기는 것도 이 값 때문이다. 결과가 보이는 자리에서 고치게 한다.\n */\n div[level] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0 0 7px 0;\n }\n div[level] label {\n flex: none;\n margin: 0;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[level] select {\n flex: 1;\n min-width: 0;\n padding: 3px 5px;\n font: var(--input-field-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n\n /* 팔레트가 잠긴 이유를 말한다. 흐린 단추만 두면 왜 안 눌리는지 모른다 */\n p[full] {\n margin: 6px 0 0 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.76rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n /**\n * 초안 위의 부품들. **씬 모델 그대로 받는다.**\n *\n * 이 패널은 이름과 도형만 읽으므로 어느 쪽 형태로 받아도 화면은 같다. 그래도 씬\n * 모델로 받는 것은 모델러 페이지가 데이터를 **한 벌만** 갖게 하기 위해서다. 여기만\n * FigureSource 로 받으면 페이지가 이 패널을 위해 변환본을 따로 들고 있어야 하고,\n * 그 변환본이 언제 낡는지를 또 관리해야 한다.\n */\n @property({ type: Array }) parts: PartModel[] = []\n /** 디테일 등급. 부품 수 한도가 여기서 결정된다. */\n @property({ type: String }) level: DetailLevel = 'M'\n /** 고른 부품의 자리. 고르지 않았으면 -1. */\n @property({ type: Number }) selected = -1\n\n render() {\n const parts = this.parts ?? []\n const level = this.level\n const limit = PART_LIMIT[level]\n const full = parts.length >= limit\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${PRIMITIVE_KINDS.map(\n kind => html`\n <button\n shape\n ?disabled=${full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.add-a-shape')}\n @click=${() => this.ask('add-part', { primitive: kind })}\n >\n <md-icon>${SHAPE_ICON[kind]}</md-icon>\n ${i18next.t('figure.label.primitive-' + kind)}\n </button>\n `\n )}\n </div>\n ${full\n ? html`<p full>${i18next.t('figure.text.part-limit-reached-short', { level, limit })}</p>`\n : ''}\n </section>\n\n <section list>\n <h3>\n ${i18next.t('figure.label.parts')}\n <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}>\n ${i18next.t('figure.text.n-of-limit', { used: parts.length, limit })}\n </span>\n ${this.renderShowAll(parts)}\n </h3>\n\n <div level>\n <label for=\"detail-level\">${i18next.t('figure.label.detail-level')}</label>\n <select\n id=\"detail-level\"\n title=${i18next.t('figure.text.detail-level-explained')}\n @change=${(e: Event) =>\n this.ask('change-detail-level', { level: (e.target as HTMLSelectElement).value })}\n >\n ${DETAIL_LEVELS.map(\n one => html`\n <option value=${one} ?selected=${one === level}>\n ${one} ${i18next.t('figure.label.detail-' + one)} ·\n ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>\n `\n )}\n </select>\n </div>\n\n ${parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.pick-a-shape-above-to-start')}</p>`\n : html`\n <ol>\n ${parts.map((part, index) => this.renderRow(part, index, parts.length))}\n </ol>\n `}\n </section>\n `\n }\n\n /**\n * 한꺼번에 감추고 한꺼번에 되돌린다.\n *\n * 부품 스물짜리를 들여다볼 때 하나만 남기려면 열아홉을 눌러야 했다. 안쪽에 있는 것을\n * 보려고 겉을 걷어내는 일은 저작 중에 늘 하는 일이다.\n *\n * 무엇을 할지는 **지금 상태가 정한다** — 하나라도 감춰져 있으면 다 되돌리고, 다\n * 보이고 있으면 다 감춘다. 단추 하나로 오가는 것이 두 개보다 낫다.\n */\n private renderShowAll(parts: PartModel[]) {\n if (parts.length === 0) return nothing\n\n const anyHidden = parts.some(part => part.hidden)\n\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('set-all-parts-hidden', { hidden: !anyHidden })\n }}\n >\n <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n `\n }\n\n private renderRow(part: PartModel, index: number, count: number) {\n const selected = index === this.selected\n const off = !!part.hidden\n\n return html`\n <li ?selected=${selected} ?off=${off} @click=${() => this.ask('select-part', { index })}>\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}\n @click=${(e: Event) => {\n // 눌렀을 때 줄 고르기가 함께 일어나지 않게 막는다 — 가리려던 것이지 고르려던 것이 아니다\n e.stopPropagation()\n this.ask('toggle-part-hidden', { index })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${SHAPE_ICON[part.primitive]}</md-icon>\n <span label title=${part.name}>${part.name}</span>\n ${selected ? this.renderOps(index, count) : nothing}\n </li>\n `\n }\n\n /**\n * 고른 줄의 조작.\n *\n * 못 하는 것을 **빼지 않고 흐리게 둔다.** 빼면 남은 단추가 그 자리로 밀려서, 첫\n * 줄과 마지막 줄에서 「지우기」가 다른 자리에 온다 — 연달아 지우다 엉뚱한 것을\n * 누르게 된다.\n *\n * 눌렀을 때 줄 고르기가 다시 일어나지 않게 전파를 멈춘다.\n */\n private renderOps(index: number, count: number) {\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { index, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { index, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { index })} ${op('remove-part', 'delete', { index })}\n </div>\n `\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
@@ -1,138 +0,0 @@
1
- import { LitElement } from 'lit';
2
- import type { FigureSource } from '@hatiolab/figure-model';
3
- import type { ViewSettings } from './figure-view.js';
4
- import type { FigureDraft } from './figure-source.js';
5
- /**
6
- * 저작 중인 것을 **도면에 놓았을 때**의 모습.
7
- *
8
- * ## 편집 캔버스로는 볼 수 없다
9
- *
10
- * 편집 캔버스는 부품을 컴포넌트로 늘어놓는다. 그래서 늘 기준 크기다. 그런데 이 형식의
11
- * 값 절반은 **크기가 달라졌을 때 어떻게 반응하는가**에 있다. 기둥만 늘어나는 시그널타워,
12
- * 롤러 개수가 느는 컨베이어는 기준 크기에서는 아무것도 보여 주지 못한다.
13
- *
14
- * 그래서 여기서는 저작한 것을 실제로 컴파일해 `FigureInstance` 로 세운다. 도면이 하는
15
- * 일을 그대로 한다 — 청사진을 등록하고, 타입 이름만 적은 컴포넌트를 놓는다.
16
- *
17
- * ## 여러 크기를 한 번에 놓는다
18
- *
19
- * 하나만 놓으면 "이 크기에서는 이렇다" 밖에 알 수 없다. 기준 크기와 늘린 것을 나란히
20
- * 놓아야 무엇이 따라 늘고 무엇이 그대로인지 보인다.
21
- *
22
- * ## 편집하지 않는다
23
- *
24
- * 여기서 집거나 끌 수 없다. 편집은 캔버스에서 한다. 보여 주기만 하는 자리라 씬을
25
- * 뷰 모드로 세운다.
26
- */
27
- /** 얼마나 키워 보나. */
28
- export declare const PREVIEW_FACTOR = 2;
29
- /**
30
- * 어느 축을 키워 놓아 보나 — **일곱 조합 전부.**
31
- *
32
- * 1 배 · 2 배 · 3 배를 나란히 놓던 자리다. 그러면 세 축이 늘 같이 늘어나므로, 축마다
33
- * 따로 걸리는 규칙을 눈으로 볼 수가 없었다. 실제로 못 보고 넘어간 것들이 있다 — 컨베이어의
34
- * 롤러가 폭을 안 따라가던 것, 문의 폭이 높이를 안 따라가던 것이 다 한 축만 늘렸을 때
35
- * 드러나는 것이었다.
36
- *
37
- * 여덟이다. 첫째가 저작한 크기이고 나머지 일곱이 조합이다. 한 줄로 늘어놓으면 화면에
38
- * 안 담겨서 **네 칸씩 두 줄**로 놓는다.
39
- */
40
- export declare const PREVIEW_WAYS: readonly {
41
- name: string;
42
- axes: readonly ('x' | 'y' | 'z')[];
43
- }[];
44
- declare const FigurePreview_base: (new (...args: any[]) => LitElement) & typeof LitElement;
45
- export declare class FigurePreview extends FigurePreview_base {
46
- static styles: import("lit").CSSResult;
47
- /** 저작 중인 것. 저장 형식으로 받는다 — 도면이 소비하는 것이 이 형식이다. */
48
- source?: FigureSource;
49
- /**
50
- * 저작 중인 초안. **기본값이 지금 무엇인지 알려고만 쥔다** — 고치지 않는다.
51
- *
52
- * `source` 로도 파라미터를 읽을 수 있지만, 후보를 미리 볼 때는 `source` 가 후보의 것이고
53
- * 초안은 열려 있는 도형의 것이다. 「이 자세를 기본으로」는 열려 있는 도형에만 뜻이 있다.
54
- */
55
- draft?: FigureDraft;
56
- /**
57
- * 보기 설정. 저장되지 않는다(`figure-view.ts`).
58
- *
59
- * 한동안 이 화면만 조명과 환경을 **하드코딩**했다. 저작자가 환경 탭에서 무엇을 바꿔도
60
- * 편집 캔버스만 달라지고 여기는 그대로였다 — 두 화면이 같은 것을 다르게 보여 주었다.
61
- */
62
- view: ViewSettings;
63
- private stage;
64
- /** 세울 수 없는 사유. 없으면 세운 것이다. */
65
- private cannot;
66
- private scene?;
67
- /** 지금 세워 둔 것의 값. 같으면 다시 세우지 않는다. */
68
- private standing?;
69
- private watcher?;
70
- /** 지금 민 clip 배속. clip 이름 → 수. **저장되지 않는다.** */
71
- private driving;
72
- /** 지금 민 파라미터 값. 이름 → `range` 의 단위로 잰 수. **저장되지 않는다.** */
73
- private giving;
74
- render(): import("lit-html").TemplateResult<1>;
75
- /**
76
- * 움직임 하나에 손잡이 하나.
77
- *
78
- * 두 갈래를 **눈금이 다른 채로** 낸다. clip 은 배속이라 0 이 멈춤이고 0~3 이 뜻이
79
- * 있으며, 파라미터는 물리량이라 그 `range` 가 곧 눈금이고 단위가 따라 붙는다. 하나의
80
- * 0~1 로 뭉뚱그리면 「1」이 무슨 뜻인지 매번 헷갈리고, 1200mm 를 넣어 보려던 저작자가
81
- * 넣을 자리를 못 찾는다.
82
- */
83
- private renderDrivers;
84
- /**
85
- * 각도 파라미터에만 다는 눈.
86
- *
87
- * 미리보기의 3D 는 여덟 대를 한 화면에 세우느라 한 대가 20 픽셀쯤이다. 관절이 돌았다는
88
- * 표시를 그 안에 그리면 몇 픽셀에 그친다 — 부품에 단 바늘이 4 × 2 픽셀로 나왔다(잰 값).
89
- * 각을 읽는 일은 배율에서 떼어 여기에 둔다.
90
- *
91
- * 각이 아닌 파라미터(밀리미터 · 퍼센트 · 초)에는 안 단다. 눈금이 도는 것이 아니라서
92
- * 원으로 그리면 없는 뜻이 생긴다.
93
- */
94
- private renderDial;
95
- /**
96
- * 세워 둔 인스턴스 전부에 같은 값을 준다.
97
- *
98
- * 하나만 몰면 세 배율이 서로 다른 자세로 서서, 크기 반응을 보려고 나란히 놓은 뜻이
99
- * 사라진다.
100
- */
101
- private drive;
102
- /**
103
- * 파라미터 값을 준다 — **`range` 의 단위 그대로다.**
104
- *
105
- * 0~1 로 바꿔서 주지 않는다. 그 환산은 런타임의 일이고, 여기서 한 번 더 하면 두 곳이
106
- * 갈릴 수 있다. 저작자가 화면에서 보는 1200 이 씬에 그대로 간다.
107
- */
108
- private give;
109
- /**
110
- * 세운 자세를 기본값으로 — **셈은 여기서 하지 않는다.**
111
- *
112
- * 초안을 쥐고 있는 것은 페이지이고, 되돌리기와 「저장 안 됨」도 거기서 매긴다. 여기서
113
- * 초안을 만들어 보내면 그 셋이 두 곳으로 갈린다. 민 값만 올린다.
114
- */
115
- private poseAsDefault;
116
- private tellInstances;
117
- /**
118
- * 보기 설정이 바뀌면 **다시 세우지 않고 씬에 넣는다** — 두 개만 빼고.
119
- *
120
- * 조명·환경·바탕은 씬이 스스로 반응한다(`three-capability` 의 `onchange`). 슬라이더를
121
- * 끄는 동안 다시 세우면 카메라가 매 프레임 처음으로 돌아간다.
122
- *
123
- * 좌표 기준과 천장 높이는 다르다. 씬이 그 둘로 인스턴스 높이를 정하는데, 그 계산은 세울
124
- * 때 한 번 돈다 — 이미 세워 둔 것에 넣어 봐야 자리가 안 바뀐다. 그것만 다시 세운다.
125
- */
126
- willUpdate(changed: Map<string, unknown>): void;
127
- firstUpdated(): void;
128
- disconnectedCallback(): void;
129
- /**
130
- * 저작한 것을 컴파일해 세운다.
131
- *
132
- * **형식을 어겼으면 세우지 않는다.** 반쯤 세운 것을 보여 주면 무엇이 잘못됐는지 알 수
133
- * 없다. 판정 패널이 이미 사유를 말하고 있으므로 여기서는 세울 수 없다고만 한다.
134
- */
135
- private standUp;
136
- private teardown;
137
- }
138
- export {};