@things-factory/figure-ui 10.1.7 → 10.1.9
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 +24 -0
- package/client/modeller/figure-animations.ts +61 -4
- package/client/modeller/figure-ask.ts +6 -1
- package/client/modeller/figure-history-panel.ts +52 -1
- package/client/modeller/figure-inspector.ts +477 -124
- package/client/modeller/proposal-session.ts +92 -0
- package/client/pages/figure-modeller-page.ts +179 -53
- package/client/types.ts +33 -0
- package/dist-client/graphql/index.d.ts +3 -1
- package/dist-client/graphql/index.js +21 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-animations.d.ts +4 -1
- package/dist-client/modeller/figure-animations.js +57 -4
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-ask.d.ts +3 -0
- package/dist-client/modeller/figure-ask.js +9 -0
- package/dist-client/modeller/figure-ask.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +7 -0
- package/dist-client/modeller/figure-history-panel.js +50 -0
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +3 -0
- package/dist-client/modeller/figure-inspector.js +464 -111
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/proposal-session.d.ts +23 -0
- package/dist-client/modeller/proposal-session.js +65 -0
- package/dist-client/modeller/proposal-session.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.d.ts +43 -9
- package/dist-client/pages/figure-modeller-page.js +173 -50
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +38 -0
- package/dist-client/types.js.map +1 -1
- package/package.json +4 -4
- package/test/ai-proposal-contract.test.ts +169 -0
- package/translations/en.json +32 -0
- package/translations/ja.json +32 -0
- package/translations/ko.json +42 -0
- package/translations/ms.json +32 -0
- package/translations/zh.json +32 -0
|
@@ -72,6 +72,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
72
72
|
display: flex;
|
|
73
73
|
flex-direction: column;
|
|
74
74
|
min-height: 0;
|
|
75
|
+
--figure-control-height: 30px;
|
|
75
76
|
overflow-y: auto;
|
|
76
77
|
background-color: var(--md-sys-color-surface-container-lowest);
|
|
77
78
|
color: var(--md-sys-color-on-surface);
|
|
@@ -96,9 +97,39 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
96
97
|
칸 높이는 건드리지 않는다(작게 만드는 것이 아니라 **빈 곳을 줄이는 것**이다).
|
|
97
98
|
*/
|
|
98
99
|
section {
|
|
99
|
-
padding:
|
|
100
|
+
padding: 8px 10px;
|
|
100
101
|
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
101
102
|
}
|
|
103
|
+
/* 입력 행은 촘촘하게, 서로 다른 편집 의도 사이에는 호흡을 둔다. */
|
|
104
|
+
section + section {
|
|
105
|
+
margin-top: 6px;
|
|
106
|
+
}
|
|
107
|
+
section[collapsible] {
|
|
108
|
+
padding: 0;
|
|
109
|
+
}
|
|
110
|
+
details[group] > summary {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: space-between;
|
|
114
|
+
min-height: 34px;
|
|
115
|
+
padding: 0 10px;
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
list-style: none;
|
|
118
|
+
}
|
|
119
|
+
details[group] > summary::-webkit-details-marker {
|
|
120
|
+
display: none;
|
|
121
|
+
}
|
|
122
|
+
details[group] > summary h3 {
|
|
123
|
+
margin: 0;
|
|
124
|
+
}
|
|
125
|
+
details[group] > summary md-icon {
|
|
126
|
+
--md-icon-size: 16px;
|
|
127
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
128
|
+
}
|
|
129
|
+
details[group] > div[group-content] {
|
|
130
|
+
padding: 4px 10px 8px;
|
|
131
|
+
border-top: 1px solid var(--md-sys-color-outline-variant);
|
|
132
|
+
}
|
|
102
133
|
h3 {
|
|
103
134
|
display: flex;
|
|
104
135
|
align-items: center;
|
|
@@ -111,6 +142,42 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
111
142
|
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
112
143
|
}
|
|
113
144
|
|
|
145
|
+
/* 자산 메타데이터는 부품을 고친다는 현재 문맥에서는 요약으로만 둔다. */
|
|
146
|
+
details[asset] {
|
|
147
|
+
margin: -1px 0;
|
|
148
|
+
}
|
|
149
|
+
details[asset] summary {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 6px;
|
|
153
|
+
min-height: 26px;
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
list-style: none;
|
|
156
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
157
|
+
}
|
|
158
|
+
details[asset] summary::-webkit-details-marker {
|
|
159
|
+
display: none;
|
|
160
|
+
}
|
|
161
|
+
details[asset] summary::before {
|
|
162
|
+
content: '›';
|
|
163
|
+
font-size: 1.1rem;
|
|
164
|
+
line-height: 1;
|
|
165
|
+
transition: transform 120ms ease;
|
|
166
|
+
}
|
|
167
|
+
details[asset][open] summary::before {
|
|
168
|
+
transform: rotate(90deg);
|
|
169
|
+
}
|
|
170
|
+
details[asset] summary b {
|
|
171
|
+
font-size: var(--ui-t-micro, 11px);
|
|
172
|
+
letter-spacing: 0.04em;
|
|
173
|
+
}
|
|
174
|
+
details[asset] summary small {
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
text-overflow: ellipsis;
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
font-size: 0.7rem;
|
|
179
|
+
}
|
|
180
|
+
|
|
114
181
|
label {
|
|
115
182
|
display: flex;
|
|
116
183
|
align-items: center;
|
|
@@ -156,6 +223,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
156
223
|
input,
|
|
157
224
|
select {
|
|
158
225
|
width: 100%;
|
|
226
|
+
min-height: var(--figure-control-height);
|
|
159
227
|
box-sizing: border-box;
|
|
160
228
|
padding: 4px 7px;
|
|
161
229
|
font: var(--input-field-font, inherit);
|
|
@@ -180,6 +248,9 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
180
248
|
*/
|
|
181
249
|
div[cell] {
|
|
182
250
|
display: flex;
|
|
251
|
+
box-sizing: border-box;
|
|
252
|
+
height: var(--figure-control-height);
|
|
253
|
+
min-height: var(--figure-control-height);
|
|
183
254
|
align-items: center;
|
|
184
255
|
gap: 2px;
|
|
185
256
|
padding-left: 6px;
|
|
@@ -200,6 +271,8 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
200
271
|
}
|
|
201
272
|
div[cell] input {
|
|
202
273
|
min-width: 0;
|
|
274
|
+
min-height: 0;
|
|
275
|
+
height: calc(var(--figure-control-height) - 2px);
|
|
203
276
|
padding: 4px 6px 4px 2px;
|
|
204
277
|
font-family: var(--mono-font, monospace);
|
|
205
278
|
text-align: right;
|
|
@@ -219,11 +292,163 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
219
292
|
}
|
|
220
293
|
div[field] + div[field],
|
|
221
294
|
div[triple] + div[field],
|
|
222
|
-
div[field] + div[triple]
|
|
295
|
+
div[field] + div[triple],
|
|
296
|
+
div[inline-field] + div[inline-field],
|
|
297
|
+
div[inline-field] + div[field],
|
|
298
|
+
div[field] + div[inline-field],
|
|
299
|
+
div[vector-row] + div[vector-row],
|
|
300
|
+
div[vector-row] + div[field],
|
|
301
|
+
div[field] + div[vector-row] {
|
|
223
302
|
/* 칸 사이 7 → 5. 칸 자체는 그대로 두고 사이만 좁힌다. */
|
|
224
303
|
margin-top: 5px;
|
|
225
304
|
}
|
|
226
305
|
|
|
306
|
+
/* 짧은 라벨과 단일 값은 같은 줄에서 읽는다. 복잡한 선택기에는 쓰지 않는다. */
|
|
307
|
+
div[inline-field] {
|
|
308
|
+
display: grid;
|
|
309
|
+
/* 같은 편집 밀도 안에서는 입력 시작선을 맞춘다. 라벨 길이가 칸 폭을 흔들면 안 된다. */
|
|
310
|
+
grid-template-columns: 64px minmax(0, 1fr);
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: 7px;
|
|
313
|
+
}
|
|
314
|
+
div[inline-field] > label {
|
|
315
|
+
margin: 0;
|
|
316
|
+
white-space: nowrap;
|
|
317
|
+
}
|
|
318
|
+
/* 한 줄 입력의 설명은 입력칸을 밀어내지 않고 바로 아래 전폭에서 읽는다. */
|
|
319
|
+
div[inline-field] > p[hint] {
|
|
320
|
+
grid-column: 1 / -1;
|
|
321
|
+
margin: -2px 0 1px;
|
|
322
|
+
}
|
|
323
|
+
div[inline-field] > code[fixed] {
|
|
324
|
+
min-width: 0;
|
|
325
|
+
overflow: hidden;
|
|
326
|
+
text-overflow: ellipsis;
|
|
327
|
+
white-space: nowrap;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* X·Y·Z는 함께 비교하는 값이므로, 묶음 제목만 왼쪽에 두고 입력은 한 행에 둔다. */
|
|
331
|
+
div[vector-row] {
|
|
332
|
+
display: grid;
|
|
333
|
+
grid-template-columns: 64px minmax(0, 1fr);
|
|
334
|
+
align-items: center;
|
|
335
|
+
gap: 7px;
|
|
336
|
+
}
|
|
337
|
+
div[vector-row] > label {
|
|
338
|
+
margin: 0;
|
|
339
|
+
white-space: nowrap;
|
|
340
|
+
}
|
|
341
|
+
div[vector-row] > p[hint] {
|
|
342
|
+
grid-column: 1 / -1;
|
|
343
|
+
margin: -2px 0 1px;
|
|
344
|
+
}
|
|
345
|
+
div[sizing-choices] {
|
|
346
|
+
display: grid;
|
|
347
|
+
grid-template-columns: 1fr 1fr;
|
|
348
|
+
gap: 5px;
|
|
349
|
+
}
|
|
350
|
+
button[sizing-choice] {
|
|
351
|
+
display: flex;
|
|
352
|
+
flex-direction: column;
|
|
353
|
+
align-items: flex-start;
|
|
354
|
+
gap: 2px;
|
|
355
|
+
min-height: 52px;
|
|
356
|
+
padding: 7px 8px;
|
|
357
|
+
color: var(--md-sys-color-on-surface);
|
|
358
|
+
background: var(--md-sys-color-surface-container-lowest);
|
|
359
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
360
|
+
border-radius: 7px;
|
|
361
|
+
text-align: left;
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
}
|
|
364
|
+
button[sizing-choice][on] {
|
|
365
|
+
color: var(--md-sys-color-on-primary-container);
|
|
366
|
+
background: var(--md-sys-color-primary-container);
|
|
367
|
+
border-color: transparent;
|
|
368
|
+
}
|
|
369
|
+
button[sizing-choice] b {
|
|
370
|
+
font-size: 0.74rem;
|
|
371
|
+
}
|
|
372
|
+
button[sizing-choice] small {
|
|
373
|
+
font-size: 0.64rem;
|
|
374
|
+
line-height: 1.35;
|
|
375
|
+
opacity: 0.8;
|
|
376
|
+
}
|
|
377
|
+
div[anchor-rows] {
|
|
378
|
+
display: grid;
|
|
379
|
+
gap: 6px;
|
|
380
|
+
}
|
|
381
|
+
div[anchor-row] {
|
|
382
|
+
display: grid;
|
|
383
|
+
grid-template-columns: 46px minmax(0, 1fr);
|
|
384
|
+
align-items: start;
|
|
385
|
+
gap: 8px;
|
|
386
|
+
padding: 6px 7px;
|
|
387
|
+
background: var(--md-sys-color-surface-container);
|
|
388
|
+
border-radius: 7px;
|
|
389
|
+
}
|
|
390
|
+
div[anchor-row] label {
|
|
391
|
+
margin: 0;
|
|
392
|
+
font-size: 0.7rem;
|
|
393
|
+
font-weight: 700;
|
|
394
|
+
}
|
|
395
|
+
div[anchor-options] {
|
|
396
|
+
display: grid;
|
|
397
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
398
|
+
gap: 4px;
|
|
399
|
+
}
|
|
400
|
+
button[anchor-choice] {
|
|
401
|
+
min-width: 0;
|
|
402
|
+
height: var(--figure-control-height);
|
|
403
|
+
box-sizing: border-box;
|
|
404
|
+
padding: 4px 3px;
|
|
405
|
+
overflow: hidden;
|
|
406
|
+
color: var(--md-sys-color-on-surface);
|
|
407
|
+
font: var(--label-font, inherit);
|
|
408
|
+
font-size: 0.63rem;
|
|
409
|
+
line-height: 1.2;
|
|
410
|
+
text-overflow: ellipsis;
|
|
411
|
+
white-space: nowrap;
|
|
412
|
+
background: var(--md-sys-color-surface-container-lowest);
|
|
413
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
414
|
+
border-radius: 5px;
|
|
415
|
+
cursor: pointer;
|
|
416
|
+
}
|
|
417
|
+
button[anchor-choice][on] {
|
|
418
|
+
color: var(--md-sys-color-on-primary-container);
|
|
419
|
+
background: var(--md-sys-color-primary-container);
|
|
420
|
+
border-color: transparent;
|
|
421
|
+
}
|
|
422
|
+
details[anchor-advanced] {
|
|
423
|
+
grid-column: 2;
|
|
424
|
+
margin-top: 4px;
|
|
425
|
+
}
|
|
426
|
+
details[anchor-advanced] summary {
|
|
427
|
+
cursor: pointer;
|
|
428
|
+
font-size: 0.62rem;
|
|
429
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
430
|
+
}
|
|
431
|
+
details[anchor-advanced] div[anchor-options] {
|
|
432
|
+
margin-top: 4px;
|
|
433
|
+
}
|
|
434
|
+
div[palette-meaning] {
|
|
435
|
+
display: grid;
|
|
436
|
+
gap: 2px;
|
|
437
|
+
margin-top: 7px;
|
|
438
|
+
padding: 6px 8px;
|
|
439
|
+
color: var(--md-sys-color-on-surface);
|
|
440
|
+
background: var(--md-sys-color-surface-container);
|
|
441
|
+
border-radius: 7px;
|
|
442
|
+
}
|
|
443
|
+
div[palette-meaning] b {
|
|
444
|
+
font-size: 0.72rem;
|
|
445
|
+
}
|
|
446
|
+
div[palette-meaning] small {
|
|
447
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
448
|
+
font-size: 0.64rem;
|
|
449
|
+
line-height: 1.35;
|
|
450
|
+
}
|
|
451
|
+
|
|
227
452
|
/*
|
|
228
453
|
정밀 제작 도구.
|
|
229
454
|
|
|
@@ -232,13 +457,17 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
232
457
|
늘 조금씩 어긋나고, 기준 치수를 바꾸면 전부 다시 해야 한다.
|
|
233
458
|
*/
|
|
234
459
|
div[tools] {
|
|
235
|
-
display:
|
|
236
|
-
|
|
237
|
-
gap:
|
|
238
|
-
margin-top: 6px;
|
|
460
|
+
display: grid;
|
|
461
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
462
|
+
gap: 4px;
|
|
239
463
|
}
|
|
240
464
|
div[tools] button {
|
|
241
|
-
|
|
465
|
+
display: grid;
|
|
466
|
+
place-items: center;
|
|
467
|
+
min-width: 0;
|
|
468
|
+
height: var(--figure-control-height);
|
|
469
|
+
box-sizing: border-box;
|
|
470
|
+
padding: 0;
|
|
242
471
|
font: var(--label-font, inherit);
|
|
243
472
|
font-size: 0.7rem;
|
|
244
473
|
color: var(--md-sys-color-on-surface);
|
|
@@ -247,6 +476,9 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
247
476
|
border-radius: 6px;
|
|
248
477
|
cursor: pointer;
|
|
249
478
|
}
|
|
479
|
+
div[tools] button md-icon {
|
|
480
|
+
font-size: 17px;
|
|
481
|
+
}
|
|
250
482
|
div[tools] button[disabled] {
|
|
251
483
|
opacity: 0.35;
|
|
252
484
|
cursor: default;
|
|
@@ -256,11 +488,6 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
256
488
|
background-color: var(--md-sys-color-primary-container);
|
|
257
489
|
border-color: transparent;
|
|
258
490
|
}
|
|
259
|
-
div[tools] span[sep] {
|
|
260
|
-
width: 1px;
|
|
261
|
-
margin: 2px 3px;
|
|
262
|
-
background-color: var(--md-sys-color-outline-variant);
|
|
263
|
-
}
|
|
264
491
|
|
|
265
492
|
/* 지금 어느 축이 늘어나나 — 크기를 고치면 바뀔 수 있어서 값으로 적는다 */
|
|
266
493
|
p[axis-now] {
|
|
@@ -319,7 +546,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
319
546
|
*/
|
|
320
547
|
div[segments] {
|
|
321
548
|
display: grid;
|
|
322
|
-
grid-template-columns: repeat(
|
|
549
|
+
grid-template-columns: repeat(4, 1fr);
|
|
323
550
|
gap: 4px;
|
|
324
551
|
}
|
|
325
552
|
/* 원 유지 · 끄기 — 고르는 값이라 분할 수와 같은 모양으로 둔다 */
|
|
@@ -377,8 +604,8 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
377
604
|
button[seg] svg {
|
|
378
605
|
display: block;
|
|
379
606
|
/* 작으면 12 와 16 이 둘 다 동그라미로 보인다 — 각이 읽힐 만큼은 키운다 */
|
|
380
|
-
width:
|
|
381
|
-
height:
|
|
607
|
+
width: 32px;
|
|
608
|
+
height: 32px;
|
|
382
609
|
}
|
|
383
610
|
button[seg] svg polygon {
|
|
384
611
|
fill: var(--md-sys-color-surface-container-highest);
|
|
@@ -401,7 +628,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
401
628
|
button[seg][on] small {
|
|
402
629
|
color: inherit;
|
|
403
630
|
}
|
|
404
|
-
/*
|
|
631
|
+
/* 성능·실루엣 검증을 마친 프리셋만 고른다. */
|
|
405
632
|
details[free] {
|
|
406
633
|
margin-top: 6px;
|
|
407
634
|
}
|
|
@@ -494,6 +721,26 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
494
721
|
label[toggle] input {
|
|
495
722
|
width: auto;
|
|
496
723
|
}
|
|
724
|
+
div[role-choices] {
|
|
725
|
+
display: grid;
|
|
726
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
727
|
+
gap: 4px;
|
|
728
|
+
}
|
|
729
|
+
div[role-choices] label[toggle] {
|
|
730
|
+
justify-content: center;
|
|
731
|
+
min-height: 30px;
|
|
732
|
+
margin: 0;
|
|
733
|
+
padding: 2px 4px;
|
|
734
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
735
|
+
border-radius: 6px;
|
|
736
|
+
font-size: 0.68rem;
|
|
737
|
+
text-align: center;
|
|
738
|
+
}
|
|
739
|
+
div[role-choices] label[toggle]:has(input:checked) {
|
|
740
|
+
color: var(--md-sys-color-on-primary-container);
|
|
741
|
+
background: var(--md-sys-color-primary-container);
|
|
742
|
+
border-color: transparent;
|
|
743
|
+
}
|
|
497
744
|
|
|
498
745
|
p[quiet] {
|
|
499
746
|
margin: 0;
|
|
@@ -552,7 +799,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
552
799
|
const part = this.chosen
|
|
553
800
|
if (!part || !this.board) {
|
|
554
801
|
return html`
|
|
555
|
-
${this.renderFigure()}
|
|
802
|
+
${this.renderFigure(false)}
|
|
556
803
|
<section>
|
|
557
804
|
<h3>${i18next.t('figure.label.properties')}</h3>
|
|
558
805
|
<p quiet>${i18next.t('figure.text.select-a-part-to-edit')}</p>
|
|
@@ -571,7 +818,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
571
818
|
묶음은 아예 나오지 않는다 — 해 둬도 아무 일도 안 일어나는 칸을 보여 주지 않는다.
|
|
572
819
|
*/
|
|
573
820
|
return html`
|
|
574
|
-
${this.renderFigure()} ${this.renderIdentity(shown)} ${this.renderRole(shown)}
|
|
821
|
+
${this.renderFigure(true)} ${this.renderIdentity(shown)} ${this.renderRole(shown)}
|
|
575
822
|
${this.renderPlacement(shown)} ${this.renderShape(shown)} ${this.renderMaterial(shown)}
|
|
576
823
|
${this.renderBehaviour(shown)}
|
|
577
824
|
`
|
|
@@ -597,18 +844,20 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
597
844
|
<section>
|
|
598
845
|
<h3>${i18next.t('figure.label.role-in-capability')}${this.help('figure.text.anchors-are-not-drawn')}</h3>
|
|
599
846
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
<
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
847
|
+
<div role-choices>
|
|
848
|
+
${(PART_ROLES as readonly PartRole[]).map(
|
|
849
|
+
one => html`
|
|
850
|
+
<label toggle>
|
|
851
|
+
<input
|
|
852
|
+
type="checkbox"
|
|
853
|
+
.checked=${live(roles.includes(one))}
|
|
854
|
+
@change=${(e: Event) => this.toggleRole(one, (e.target as HTMLInputElement).checked)}
|
|
855
|
+
/>
|
|
856
|
+
${i18next.t('figure.label.role-' + one)}
|
|
857
|
+
</label>
|
|
858
|
+
`
|
|
859
|
+
)}
|
|
860
|
+
</div>
|
|
612
861
|
${roles.length === 0 ? html`<p quiet>${i18next.t('figure.text.role-none-means-drawn')}</p>` : nothing}
|
|
613
862
|
|
|
614
863
|
${roles.includes('slot')
|
|
@@ -695,11 +944,9 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
695
944
|
*
|
|
696
945
|
* 저장되는 식별자다. 배치된 보드가 이 이름으로 타입을 찾으므로 만든 뒤에는 못 바꾼다.
|
|
697
946
|
*/
|
|
698
|
-
private renderFigure() {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
<h3>${i18next.t('figure.label.figure')}</h3>
|
|
702
|
-
<div field>
|
|
947
|
+
private renderFigure(compact = false) {
|
|
948
|
+
const fields = html`
|
|
949
|
+
<div inline-field>
|
|
703
950
|
<label>${i18next.t('figure.label.name')}</label>
|
|
704
951
|
<input
|
|
705
952
|
.value=${this.figureName}
|
|
@@ -714,34 +961,57 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
714
961
|
)}
|
|
715
962
|
/>
|
|
716
963
|
</div>
|
|
717
|
-
<div field>
|
|
718
|
-
<label>${i18next.t('figure.label.figure-type')}${this.
|
|
964
|
+
<div inline-field>
|
|
965
|
+
<label>${i18next.t('figure.label.figure-type')}${this.helpButton('figure.text.type-name-cannot-change')}</label>
|
|
719
966
|
<code fixed title=${i18next.t('figure.text.type-name-cannot-change')}>
|
|
720
967
|
<md-icon>lock</md-icon>${this.board?.figureType ?? ''}
|
|
721
968
|
</code>
|
|
969
|
+
${this.helpHint('figure.text.type-name-cannot-change')}
|
|
722
970
|
</div>
|
|
723
971
|
<!--
|
|
724
972
|
왜 못 고치는지 **적어 둔다.** 전에는 tooltip 에만 있었고, 그러면 눌러 보고 안 되는 것을
|
|
725
973
|
알게 된다 — 화면이 안 되는 이유를 말하지 않으면 사용자가 자기 실수라고 생각한다.
|
|
726
974
|
-->
|
|
727
975
|
|
|
728
|
-
<div field>
|
|
729
|
-
<label>${i18next.t('figure.label.category')}${this.
|
|
976
|
+
<div inline-field>
|
|
977
|
+
<label>${i18next.t('figure.label.category')}${this.helpButton('figure.text.category-explained')}</label>
|
|
730
978
|
<input
|
|
731
979
|
.value=${this.figureCategory ?? ''}
|
|
732
980
|
placeholder=${i18next.t('figure.text.category-placeholder')}
|
|
733
981
|
@change=${(e: Event) => this.tellCatalog({ category: (e.target as HTMLInputElement).value.trim() || undefined })}
|
|
734
982
|
/>
|
|
983
|
+
${this.helpHint('figure.text.category-explained')}
|
|
735
984
|
</div>
|
|
736
985
|
|
|
737
|
-
<div field>
|
|
738
|
-
<label>${i18next.t('figure.label.tags')}${this.
|
|
986
|
+
<div inline-field>
|
|
987
|
+
<label>${i18next.t('figure.label.tags')}${this.helpButton('figure.text.tags-explained')}</label>
|
|
739
988
|
<input
|
|
740
989
|
.value=${(this.figureTags ?? []).join(', ')}
|
|
741
990
|
placeholder=${i18next.t('figure.text.tags-placeholder')}
|
|
742
991
|
@change=${(e: Event) => this.tellTags((e.target as HTMLInputElement).value)}
|
|
743
992
|
/>
|
|
993
|
+
${this.helpHint('figure.text.tags-explained')}
|
|
744
994
|
</div>
|
|
995
|
+
`
|
|
996
|
+
|
|
997
|
+
if (compact) {
|
|
998
|
+
return html`
|
|
999
|
+
<section>
|
|
1000
|
+
<details asset>
|
|
1001
|
+
<summary>
|
|
1002
|
+
<b>${i18next.t('figure.label.figure')}</b>
|
|
1003
|
+
<small>${this.figureName || this.board?.figureType || ''}</small>
|
|
1004
|
+
</summary>
|
|
1005
|
+
${fields}
|
|
1006
|
+
</details>
|
|
1007
|
+
</section>
|
|
1008
|
+
`
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
return html`
|
|
1012
|
+
<section>
|
|
1013
|
+
<h3>${i18next.t('figure.label.figure')}</h3>
|
|
1014
|
+
${fields}
|
|
745
1015
|
</section>
|
|
746
1016
|
`
|
|
747
1017
|
}
|
|
@@ -775,14 +1045,15 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
775
1045
|
return html`
|
|
776
1046
|
<section>
|
|
777
1047
|
<h3>${i18next.t('figure.label.part')}</h3>
|
|
778
|
-
<div field>
|
|
779
|
-
<label>${i18next.t('figure.label.name')}${this.
|
|
1048
|
+
<div inline-field>
|
|
1049
|
+
<label>${i18next.t('figure.label.name')}${this.helpButton('figure.text.renaming-a-part-breaks-bindings')}</label>
|
|
780
1050
|
<input
|
|
781
1051
|
.value=${part.name}
|
|
782
1052
|
@change=${(e: Event) => this.put({ name: (e.target as HTMLInputElement).value })}
|
|
783
1053
|
/>
|
|
1054
|
+
${this.helpHint('figure.text.renaming-a-part-breaks-bindings')}
|
|
784
1055
|
</div>
|
|
785
|
-
<div field>
|
|
1056
|
+
<div inline-field>
|
|
786
1057
|
<label>${i18next.t('figure.label.shape')}</label>
|
|
787
1058
|
<select @change=${(e: Event) => this.changePrimitive((e.target as HTMLSelectElement).value as PrimitiveKind)}>
|
|
788
1059
|
${PRIMITIVE_KINDS.map(
|
|
@@ -806,16 +1077,18 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
806
1077
|
<section>
|
|
807
1078
|
<h3>${i18next.t('figure.label.placement')}</h3>
|
|
808
1079
|
|
|
809
|
-
<
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
this.
|
|
814
|
-
|
|
815
|
-
|
|
1080
|
+
<div vector-row>
|
|
1081
|
+
<label>${i18next.t('figure.label.part-position')}</label>
|
|
1082
|
+
<div triple>
|
|
1083
|
+
${(['x', 'y', 'z'] as const).map(axis =>
|
|
1084
|
+
this.number(axis.toUpperCase(), t.position[axis], value =>
|
|
1085
|
+
this.putTransform({ position: { ...t.position, [axis]: value } })
|
|
1086
|
+
)
|
|
1087
|
+
)}
|
|
1088
|
+
</div>
|
|
816
1089
|
</div>
|
|
817
1090
|
|
|
818
|
-
<div
|
|
1091
|
+
<div vector-row>
|
|
819
1092
|
<label>${i18next.t('figure.label.part-size')}</label>
|
|
820
1093
|
<div triple>
|
|
821
1094
|
${(['x', 'y', 'z'] as const).map(axis =>
|
|
@@ -829,48 +1102,64 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
829
1102
|
</div>
|
|
830
1103
|
</div>
|
|
831
1104
|
|
|
832
|
-
<div
|
|
833
|
-
<label>${i18next.t('figure.label.
|
|
1105
|
+
<div vector-row>
|
|
1106
|
+
<label>${i18next.t('figure.label.rotation-degrees')}</label>
|
|
1107
|
+
<div triple>
|
|
1108
|
+
${(['x', 'y', 'z'] as const).map(axis =>
|
|
1109
|
+
this.number(axis.toUpperCase(), rotation[axis] ?? 0, value =>
|
|
1110
|
+
this.putTransform({ rotation: { ...rotation, [axis]: value } })
|
|
1111
|
+
)
|
|
1112
|
+
)}
|
|
1113
|
+
</div>
|
|
1114
|
+
</div>
|
|
1115
|
+
|
|
1116
|
+
<div vector-row>
|
|
1117
|
+
<label>${i18next.t('figure.label.tools')}${this.helpButton('figure.text.tools-explained')}</label>
|
|
834
1118
|
<div tools>
|
|
835
|
-
<button
|
|
836
|
-
|
|
1119
|
+
<button
|
|
1120
|
+
aria-label=${i18next.t('figure.button.center-x')}
|
|
1121
|
+
title=${i18next.t('figure.text.center-on-x')}
|
|
1122
|
+
@click=${() => this.ask('center', 'x')}
|
|
1123
|
+
>
|
|
1124
|
+
<md-icon>horizontal_distribute</md-icon>
|
|
837
1125
|
</button>
|
|
838
|
-
<button
|
|
839
|
-
|
|
1126
|
+
<button
|
|
1127
|
+
aria-label=${i18next.t('figure.button.center-z')}
|
|
1128
|
+
title=${i18next.t('figure.text.center-on-z')}
|
|
1129
|
+
@click=${() => this.ask('center', 'z')}
|
|
1130
|
+
>
|
|
1131
|
+
<md-icon>vertical_align_center</md-icon>
|
|
840
1132
|
</button>
|
|
841
|
-
<button
|
|
842
|
-
|
|
1133
|
+
<button
|
|
1134
|
+
aria-label=${i18next.t('figure.button.to-floor')}
|
|
1135
|
+
title=${i18next.t('figure.text.put-on-floor')}
|
|
1136
|
+
@click=${() => this.ask('floor', 'y')}
|
|
1137
|
+
>
|
|
1138
|
+
<md-icon>vertical_align_bottom</md-icon>
|
|
843
1139
|
</button>
|
|
844
|
-
<span sep></span>
|
|
845
1140
|
${(['x', 'z'] as const).map(axis => {
|
|
846
1141
|
// 이미 가운데면 대칭이 제자리다 — 겹친 부품이 조용히 생기지 않게 막는다
|
|
847
1142
|
const able = this.board ? canMirror(this.board, this.parts, this.selected, axis) : false
|
|
848
1143
|
return html`
|
|
849
1144
|
<button
|
|
850
1145
|
?disabled=${!able}
|
|
1146
|
+
aria-label=${i18next.t('figure.button.mirror-' + axis)}
|
|
851
1147
|
title=${i18next.t(able ? 'figure.text.mirror-explained' : 'figure.text.mirror-would-overlap')}
|
|
852
1148
|
@click=${() => this.ask('mirror', axis)}
|
|
853
1149
|
>
|
|
854
|
-
|
|
1150
|
+
<md-icon>${axis === 'x' ? 'flip' : 'flip_camera_android'}</md-icon>
|
|
855
1151
|
</button>
|
|
856
1152
|
`
|
|
857
1153
|
})}
|
|
858
|
-
<
|
|
859
|
-
|
|
860
|
-
|
|
1154
|
+
<button
|
|
1155
|
+
aria-label=${i18next.t('figure.button.snap')}
|
|
1156
|
+
title=${i18next.t('figure.text.snap-explained')}
|
|
1157
|
+
@click=${() => this.ask('snap', 'x')}
|
|
1158
|
+
>
|
|
1159
|
+
<md-icon>grid_on</md-icon>
|
|
861
1160
|
</button>
|
|
862
1161
|
</div>
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
<div field>
|
|
866
|
-
<label>${i18next.t('figure.label.rotation-degrees')}</label>
|
|
867
|
-
<div triple>
|
|
868
|
-
${(['x', 'y', 'z'] as const).map(axis =>
|
|
869
|
-
this.number(axis.toUpperCase(), rotation[axis] ?? 0, value =>
|
|
870
|
-
this.putTransform({ rotation: { ...rotation, [axis]: value } })
|
|
871
|
-
)
|
|
872
|
-
)}
|
|
873
|
-
</div>
|
|
1162
|
+
${this.helpHint('figure.text.tools-explained')}
|
|
874
1163
|
</div>
|
|
875
1164
|
</section>
|
|
876
1165
|
`
|
|
@@ -907,11 +1196,11 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
907
1196
|
this.put({ shape: { ...(part.shape ?? {}), ...over } })
|
|
908
1197
|
|
|
909
1198
|
return html`
|
|
910
|
-
<div field>
|
|
1199
|
+
<div inline-field>
|
|
911
1200
|
<label>${i18next.t('figure.label.corner-round')}</label>
|
|
912
1201
|
${this.number('', part.shape?.round ?? 0, value => reshape({ round: value }))}
|
|
913
1202
|
</div>
|
|
914
|
-
<div field>
|
|
1203
|
+
<div inline-field>
|
|
915
1204
|
<label>${i18next.t('figure.label.hollow-wall')}</label>
|
|
916
1205
|
${this.number('', wall, value =>
|
|
917
1206
|
reshape({ hollow: value > 0 ? { ...(hollow ?? {}), wall: value } : undefined })
|
|
@@ -919,7 +1208,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
919
1208
|
</div>
|
|
920
1209
|
${wall > 0
|
|
921
1210
|
? html`
|
|
922
|
-
<div field>
|
|
1211
|
+
<div inline-field>
|
|
923
1212
|
<label>${i18next.t('figure.label.hollow-floor')}</label>
|
|
924
1213
|
${this.number('', hollow?.floor ?? wall, value => reshape({ hollow: { ...hollow, wall, floor: value } }))}
|
|
925
1214
|
</div>
|
|
@@ -964,12 +1253,14 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
964
1253
|
|
|
965
1254
|
private renderSegments(part: FigurePart) {
|
|
966
1255
|
const current = part.segments ?? SEGMENT_PRESETS[1]
|
|
1256
|
+
const common = SEGMENT_PRESETS.slice(0, 4)
|
|
1257
|
+
const advanced = SEGMENT_PRESETS.slice(4)
|
|
967
1258
|
|
|
968
1259
|
return html`
|
|
969
1260
|
<div field>
|
|
970
1261
|
<label>${i18next.t('figure.label.segments')}</label>
|
|
971
1262
|
<div segments>
|
|
972
|
-
${
|
|
1263
|
+
${common.map(preset => {
|
|
973
1264
|
const triangles = trianglesOf({ ...part, segments: preset })
|
|
974
1265
|
return html`
|
|
975
1266
|
<button seg ?on=${current === preset} @click=${() => this.put({ segments: preset })}>
|
|
@@ -981,9 +1272,13 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
981
1272
|
})}
|
|
982
1273
|
</div>
|
|
983
1274
|
|
|
984
|
-
<details free>
|
|
985
|
-
<summary>${i18next.t('figure.text.
|
|
986
|
-
|
|
1275
|
+
<details free ?open=${advanced.includes(current)}>
|
|
1276
|
+
<summary>${i18next.t('figure.text.more-segment-options')}</summary>
|
|
1277
|
+
<div presets>
|
|
1278
|
+
${advanced.map(preset => html`
|
|
1279
|
+
<button ?on=${current === preset} @click=${() => this.put({ segments: preset })}>${preset}</button>
|
|
1280
|
+
`)}
|
|
1281
|
+
</div>
|
|
987
1282
|
</details>
|
|
988
1283
|
</div>
|
|
989
1284
|
`
|
|
@@ -1011,17 +1306,38 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1011
1306
|
}
|
|
1012
1307
|
|
|
1013
1308
|
/** 견본 한 줄. 무리마다 부른다. */
|
|
1309
|
+
private palettePurpose(token?: string) {
|
|
1310
|
+
const purposes: Record<string, string> = {
|
|
1311
|
+
'palette.primary': 'primary',
|
|
1312
|
+
'palette.accent': 'accent',
|
|
1313
|
+
'palette.neutral': 'neutral',
|
|
1314
|
+
'palette.dark': 'dark',
|
|
1315
|
+
'palette.light': 'light',
|
|
1316
|
+
'palette.surface': 'surface',
|
|
1317
|
+
'palette.status-danger': 'danger',
|
|
1318
|
+
'palette.status-warning': 'warning',
|
|
1319
|
+
'palette.status-info': 'info',
|
|
1320
|
+
'palette.status-success': 'success'
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
return i18next.t(`figure.label.palette-purpose-${purposes[token ?? ''] ?? 'registered'}`)
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1014
1326
|
private swatches(entries: [string, string][], chosen?: string) {
|
|
1015
1327
|
return entries.map(
|
|
1016
|
-
([token, color]) =>
|
|
1328
|
+
([token, color]) => {
|
|
1329
|
+
const purpose = this.palettePurpose(token)
|
|
1330
|
+
return html`
|
|
1017
1331
|
<button
|
|
1018
1332
|
swatch
|
|
1019
1333
|
?on=${token === chosen}
|
|
1020
|
-
|
|
1334
|
+
aria-label=${purpose}
|
|
1335
|
+
title=${purpose}
|
|
1021
1336
|
style="--swatch:${color}"
|
|
1022
1337
|
@click=${() => this.putMaterial({ token })}
|
|
1023
1338
|
></button>
|
|
1024
1339
|
`
|
|
1340
|
+
}
|
|
1025
1341
|
)
|
|
1026
1342
|
}
|
|
1027
1343
|
|
|
@@ -1048,7 +1364,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1048
1364
|
<h3>${i18next.t('figure.label.part-material')}</h3>
|
|
1049
1365
|
|
|
1050
1366
|
<div field>
|
|
1051
|
-
<label>${i18next.t('figure.label.palette-
|
|
1367
|
+
<label>${i18next.t('figure.label.palette-role')}</label>
|
|
1052
1368
|
<div swatches>
|
|
1053
1369
|
${this.swatches(painted, material.token)}
|
|
1054
1370
|
</div>
|
|
@@ -1060,7 +1376,10 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1060
1376
|
</div>
|
|
1061
1377
|
`
|
|
1062
1378
|
: ''}
|
|
1063
|
-
<
|
|
1379
|
+
<div palette-meaning>
|
|
1380
|
+
<b>${material.token ? this.palettePurpose(material.token) : i18next.t('figure.label.no-color-chosen')}</b>
|
|
1381
|
+
<small>${i18next.t('figure.text.palette-purpose-explained')}</small>
|
|
1382
|
+
</div>
|
|
1064
1383
|
</div>
|
|
1065
1384
|
|
|
1066
1385
|
<div field>
|
|
@@ -1185,21 +1504,25 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1185
1504
|
return html`
|
|
1186
1505
|
<section>
|
|
1187
1506
|
<h3>${i18next.t('figure.label.sizing-rule')}${this.help('figure.text.sizing-' + sizing + '-explained')}</h3>
|
|
1188
|
-
<
|
|
1507
|
+
<div sizing-choices>
|
|
1189
1508
|
${SIZING_RULES.map(
|
|
1190
1509
|
rule => html`
|
|
1191
|
-
<
|
|
1510
|
+
<button sizing-choice ?on=${rule === sizing} @click=${() => this.put({ sizing: rule })}>
|
|
1511
|
+
<b>${i18next.t('figure.label.sizing-' + rule)}</b>
|
|
1512
|
+
<small>${i18next.t('figure.text.sizing-' + rule + '-explained')}</small>
|
|
1513
|
+
</button>
|
|
1192
1514
|
`
|
|
1193
1515
|
)}
|
|
1194
|
-
</
|
|
1195
|
-
${sizing === 'scale'
|
|
1196
|
-
|
|
1516
|
+
</div>
|
|
1517
|
+
${sizing === 'scale'
|
|
1518
|
+
? nothing
|
|
1519
|
+
: html`<p cost>${i18next.t('figure.text.sizing-costs-one-draw-call')}</p>`}
|
|
1197
1520
|
|
|
1198
1521
|
${this.renderAnchors(part)}
|
|
1199
1522
|
|
|
1200
1523
|
${sizing === 'repeat'
|
|
1201
1524
|
? html`
|
|
1202
|
-
<div field>
|
|
1525
|
+
<div inline-field>
|
|
1203
1526
|
<label>${i18next.t('figure.label.repeat-axis')}</label>
|
|
1204
1527
|
<select
|
|
1205
1528
|
@change=${(e: Event) =>
|
|
@@ -1210,9 +1533,10 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1210
1533
|
)}
|
|
1211
1534
|
</select>
|
|
1212
1535
|
</div>
|
|
1213
|
-
<div field>
|
|
1214
|
-
<label>${i18next.t('figure.label.repeat-pitch')}${this.
|
|
1536
|
+
<div inline-field>
|
|
1537
|
+
<label>${i18next.t('figure.label.repeat-pitch')}${this.helpButton('figure.text.repeat-pitch-explained')}</label>
|
|
1215
1538
|
${this.number('', repeat.pitch, value => this.put({ repeat: { ...repeat, pitch: value } }), 1)}
|
|
1539
|
+
${this.helpHint('figure.text.repeat-pitch-explained')}
|
|
1216
1540
|
</div>
|
|
1217
1541
|
${this.renderRepeatCount(part)}
|
|
1218
1542
|
`
|
|
@@ -1302,6 +1626,10 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1302
1626
|
* 계속 고치는 사람이 매번 다시 펴야 하면 접은 것이 되레 방해가 된다.
|
|
1303
1627
|
*/
|
|
1304
1628
|
private help(key: string) {
|
|
1629
|
+
return html`${this.helpButton(key)}${this.helpHint(key)}`
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
private helpButton(key: string) {
|
|
1305
1633
|
const shown = this.opened.has(key)
|
|
1306
1634
|
|
|
1307
1635
|
return html`
|
|
@@ -1316,20 +1644,38 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1316
1644
|
>
|
|
1317
1645
|
<md-icon>${shown ? 'help' : 'help_outline'}</md-icon>
|
|
1318
1646
|
</button>
|
|
1319
|
-
${shown ? html`<p hint>${i18next.t(key)}</p>` : nothing}
|
|
1320
1647
|
`
|
|
1321
1648
|
}
|
|
1322
1649
|
|
|
1650
|
+
private helpHint(key: string) {
|
|
1651
|
+
return this.opened.has(key) ? html`<p hint>${i18next.t(key)}</p>` : nothing
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1323
1654
|
private renderAnchors(part: FigurePart) {
|
|
1324
1655
|
const base = this.board
|
|
1325
1656
|
? { x: this.board.width, y: this.board.height, z: this.board.depth }
|
|
1326
1657
|
: { x: 0, y: 0, z: 0 }
|
|
1658
|
+
const nameOf = (rule: string, axis: Axis) =>
|
|
1659
|
+
`${i18next.t(rule === 'min' || rule === 'max' ? `figure.label.anchor-${rule}-${axis}` : `figure.label.anchor-${rule}`)} (${rule})`
|
|
1660
|
+
const setAnchor = (axis: Axis, rule?: AnchorRule) => {
|
|
1661
|
+
const anchor = { ...(part.anchor ?? {}) }
|
|
1662
|
+
/* 「자동」은 칸을 지우는 것이다 — 빈 값을 넣어 두면 형식이 그것을 값으로 읽는다. */
|
|
1663
|
+
if (rule) anchor[axis] = rule
|
|
1664
|
+
else delete anchor[axis]
|
|
1665
|
+
this.put({ anchor: Object.keys(anchor).length ? anchor : undefined })
|
|
1666
|
+
}
|
|
1667
|
+
const shortNameOf = (rule: AnchorRule | 'auto', axis: Axis) => {
|
|
1668
|
+
if (rule === 'auto') return i18next.t('figure.label.anchor-choice-auto')
|
|
1669
|
+
if (rule === 'min' || rule === 'max') return i18next.t(`figure.label.anchor-choice-${rule}-${axis}`)
|
|
1670
|
+
return i18next.t(`figure.label.anchor-choice-${rule}`)
|
|
1671
|
+
}
|
|
1327
1672
|
|
|
1328
1673
|
return html`
|
|
1329
1674
|
<div field>
|
|
1330
|
-
<!--
|
|
1675
|
+
<!-- 저장 키(anchor)는 남기되, 사용자는 축마다 어느 면을 기준으로 삼는지 읽는다. -->
|
|
1331
1676
|
<label>${i18next.t('figure.label.anchor')} (anchor)${this.help('figure.text.anchor-explained')}</label>
|
|
1332
|
-
|
|
1677
|
+
<div anchor-rows>
|
|
1678
|
+
${(['x', 'y', 'z'] as const).map(axis => {
|
|
1333
1679
|
const said = part.anchor?.[axis]
|
|
1334
1680
|
const auto = anchorOfPart(part, base, axis)
|
|
1335
1681
|
|
|
@@ -1348,35 +1694,42 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1348
1694
|
`max` 는 정본에 저장되는 이름이고 서버 오류·형식 문서·게이트 사유가 다 그 이름을 쓴다.
|
|
1349
1695
|
숨기면 안 되는 종류다. 동작이 먼저고 이름이 괄호로 따라온다.
|
|
1350
1696
|
*/
|
|
1351
|
-
const nameOf = (rule: string) =>
|
|
1352
|
-
`${i18next.t(rule === 'min' || rule === 'max' ? `figure.label.anchor-${rule}-${axis}` : `figure.label.anchor-${rule}`)} (${rule})`
|
|
1353
|
-
|
|
1354
1697
|
return html`
|
|
1355
|
-
<div
|
|
1356
|
-
<
|
|
1357
|
-
<
|
|
1358
|
-
|
|
1359
|
-
const
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1698
|
+
<div anchor-row>
|
|
1699
|
+
<label>${axis.toUpperCase()}축</label>
|
|
1700
|
+
<div anchor-options>
|
|
1701
|
+
${(['auto', 'scale', 'min', 'center', 'max', 'span'] as const).map(rule => {
|
|
1702
|
+
const selected = rule === 'auto' ? !said : rule === said
|
|
1703
|
+
const fullName = rule === 'auto' ? i18next.t('figure.label.anchor-auto', { rule: nameOf(auto, axis) }) : nameOf(rule, axis)
|
|
1704
|
+
return html`
|
|
1705
|
+
<button
|
|
1706
|
+
anchor-choice
|
|
1707
|
+
?on=${selected}
|
|
1708
|
+
aria-pressed=${selected ? 'true' : 'false'}
|
|
1709
|
+
title=${fullName}
|
|
1710
|
+
@click=${() => setAnchor(axis, rule === 'auto' ? undefined : rule)}
|
|
1711
|
+
>${shortNameOf(rule, axis)}</button>
|
|
1712
|
+
`
|
|
1713
|
+
})}
|
|
1714
|
+
</div>
|
|
1715
|
+
<details anchor-advanced>
|
|
1716
|
+
<summary>${i18next.t('figure.label.anchor-advanced')}</summary>
|
|
1717
|
+
<div anchor-options>
|
|
1718
|
+
${ANCHOR_RULES.filter(rule => rule.startsWith('aspect-')).map(rule => html`
|
|
1719
|
+
<button
|
|
1720
|
+
anchor-choice
|
|
1721
|
+
?on=${rule === said}
|
|
1722
|
+
aria-pressed=${rule === said ? 'true' : 'false'}
|
|
1723
|
+
title=${nameOf(rule, axis)}
|
|
1724
|
+
@click=${() => setAnchor(axis, rule)}
|
|
1725
|
+
>${i18next.t(`figure.label.anchor-choice-${rule}`)}</button>
|
|
1726
|
+
`)}
|
|
1727
|
+
</div>
|
|
1728
|
+
</details>
|
|
1377
1729
|
</div>
|
|
1378
1730
|
`
|
|
1379
|
-
|
|
1731
|
+
})}
|
|
1732
|
+
</div>
|
|
1380
1733
|
</div>
|
|
1381
1734
|
`
|
|
1382
1735
|
}
|