@things-factory/figure-ui 10.1.44 → 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 +6 -0
  41. package/translations/ko.json +6 -0
  42. package/translations/ms.json +6 -0
  43. package/translations/zh.json +6 -0
  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,328 +0,0 @@
1
- import { css, html, LitElement } from 'lit'
2
- import { customElement, property } from 'lit/decorators.js'
3
-
4
- import { i18next, localize } from '@operato/i18n'
5
- import { ScrollbarStyles } from '@operato/styles'
6
-
7
- import { DEFAULT_VIEW, ENVIRONMENTS, SCENE_REFERENCES } from './figure-view.js'
8
- import type { EnvironmentName, ViewSettings } from './figure-view.js'
9
-
10
- /**
11
- * 저작 화면의 **보기 설정**.
12
- *
13
- * ## 자산이 아니라 작업대다
14
- *
15
- * 여기서 고치는 것은 저장되지 않는다. 조명을 밝게 하고 바닥을 켠 것은 지금 보기 편한
16
- * 상태일 뿐이다. 그래서 「저장 안 됨」을 화면에 적어 둔다 — 안 적으면 저작자가 조명을
17
- * 맞춰 두고 그것이 자산에 딸려 간다고 여긴다.
18
- *
19
- * ## 값을 들고 있지 않는다
20
- *
21
- * 이 패널은 받은 것을 그리고 바뀐 것을 알린다. 정본은 모델러 페이지가 갖는다 — 속성
22
- * 패널과 같은 방식이다. 여기서 들고 있으면 페이지가 다시 그릴 때 두 벌이 갈린다.
23
- */
24
- @customElement('figure-settings')
25
- export class FigureSettings extends localize(i18next)(LitElement) {
26
- static styles = [
27
- ScrollbarStyles,
28
- css`
29
- :host {
30
- display: flex;
31
- flex-direction: column;
32
- min-height: 0;
33
- overflow-y: auto;
34
- background-color: var(--md-sys-color-surface-container-lowest);
35
- color: var(--md-sys-color-on-surface);
36
- font: var(--label-font, inherit);
37
- font-size: 0.76rem;
38
- }
39
-
40
- section {
41
- display: flex;
42
- flex-direction: column;
43
- gap: var(--spacing-medium, 8px);
44
- padding: var(--spacing-large, 12px);
45
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
46
- }
47
-
48
- /* 속성 패널의 묶음 제목과 같은 규격이다 — 두 탭이 다른 눈금을 쓰면 따로 논다 */
49
- h3 {
50
- margin: 0 0 2px 0;
51
- font-size: var(--ui-t-micro, 11px);
52
- font-weight: 700;
53
- letter-spacing: 0.04em;
54
- text-transform: uppercase;
55
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
56
- }
57
-
58
- /* 저장되지 않는다는 것을 한 번 말해 둔다 */
59
- p[quiet] {
60
- margin: 0;
61
- font-size: 0.72rem;
62
- line-height: 1.5;
63
- color: var(--md-sys-color-on-surface-variant);
64
- }
65
-
66
- label[row] {
67
- display: flex;
68
- align-items: center;
69
- justify-content: space-between;
70
- gap: var(--spacing-medium, 8px);
71
- cursor: pointer;
72
- }
73
- label[row] span {
74
- display: flex;
75
- flex-direction: column;
76
- }
77
- label[row] em {
78
- font-style: normal;
79
- font-size: 0.7rem;
80
- color: var(--md-sys-color-on-surface-variant);
81
- }
82
-
83
- input[type='checkbox'] {
84
- flex: none;
85
- width: 16px;
86
- height: 16px;
87
- margin: 0;
88
- accent-color: var(--md-sys-color-primary);
89
- cursor: pointer;
90
- }
91
-
92
- label[field] {
93
- display: flex;
94
- flex-direction: column;
95
- gap: 4px;
96
- }
97
- label[field] > span {
98
- color: var(--md-sys-color-on-surface-variant);
99
- }
100
-
101
- select,
102
- input[type='number'],
103
- input[type='color'] {
104
- width: 100%;
105
- padding: 5px 6px;
106
- font: var(--input-field-font, inherit);
107
- font-size: 0.76rem;
108
- color: var(--md-sys-color-on-surface);
109
- background-color: var(--md-sys-color-surface);
110
- border: 1px solid var(--md-sys-color-outline-variant);
111
- border-radius: 4px;
112
- }
113
- input[type='color'] {
114
- height: 30px;
115
- padding: 2px;
116
- cursor: pointer;
117
- }
118
-
119
- /* 값이 옆에 붙어야 끌면서 얼마인지 안다 */
120
- div[slider] {
121
- display: flex;
122
- align-items: center;
123
- gap: var(--spacing-medium, 8px);
124
- }
125
- input[type='range'] {
126
- flex: 1;
127
- accent-color: var(--md-sys-color-primary);
128
- cursor: pointer;
129
- }
130
- div[slider] output {
131
- flex: none;
132
- width: 2.4em;
133
- text-align: right;
134
- font: var(--mono-font, ui-monospace, monospace);
135
- font-size: 0.72rem;
136
- color: var(--md-sys-color-on-surface-variant);
137
- }
138
-
139
- button[reset] {
140
- align-self: flex-start;
141
- padding: 5px 12px;
142
- font: var(--label-font, inherit);
143
- font-size: 0.74rem;
144
- color: var(--md-sys-color-on-surface-variant);
145
- background: none;
146
- border: 1px solid var(--md-sys-color-outline-variant);
147
- border-radius: 4px;
148
- cursor: pointer;
149
- }
150
- button[reset]:hover {
151
- background-color: var(--md-sys-color-surface-container);
152
- }
153
- `
154
- ]
155
-
156
- @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW
157
-
158
- render() {
159
- const view = this.view ?? DEFAULT_VIEW
160
-
161
- return html`
162
- <!--
163
- 놓이는 도면.
164
-
165
- 이 묶음이 맨 위인 것은, 나머지가 「어떻게 보이나」인데 이것은 「무엇으로 보고
166
- 있나」이기 때문이다. 미리보기가 도면에 놓인 모습을 말하는 자리이므로, 어떤 도면을
167
- 가정하는지가 먼저 정해져야 그 다음이 뜻을 갖는다.
168
-
169
- 도형 탭의 「배치 기준」과 짝이지 같은 것이 아니다. 저쪽은 이 도형이 어느 면에
170
- 붙는가(저장된다), 이쪽은 그 면을 가진 도면이 어느 쪽을 기준으로 재는가(저장 안 된다).
171
- -->
172
- <section>
173
- <h3>${i18next.t('figure.label.assumed-drawing')}</h3>
174
- <p quiet>${i18next.t('figure.text.view-settings-are-not-saved')}</p>
175
-
176
- <label field>
177
- <span>${i18next.t('figure.label.scene-reference')}</span>
178
- <select
179
- .value=${view.placement}
180
- @change=${(e: Event) =>
181
- this.put({ placement: (e.target as HTMLSelectElement).value as ViewSettings['placement'] })}
182
- >
183
- ${SCENE_REFERENCES.map(
184
- name => html`<option value=${name} ?selected=${view.placement === name}>
185
- ${i18next.t(`figure.label.scene-reference-${name}`)}
186
- </option>`
187
- )}
188
- </select>
189
- </label>
190
- <p quiet>${i18next.t('figure.text.scene-reference-explained')}</p>
191
-
192
- <label field>
193
- <span>${i18next.t('figure.label.ceiling-height')}</span>
194
- <input
195
- type="number"
196
- min="0"
197
- step="100"
198
- .value=${String(view.ceilingHeight)}
199
- @change=${(e: Event) => this.put({ ceilingHeight: Number((e.target as HTMLInputElement).value) || 0 })}
200
- />
201
- </label>
202
- <p quiet>${i18next.t('figure.text.ceiling-height-explained')}</p>
203
- </section>
204
-
205
- <section>
206
- <h3>${i18next.t('figure.label.guides')}</h3>
207
-
208
- ${this.toggle('grid', view.grid, i18next.t('figure.label.coordinate-ground'), i18next.t('figure.text.coordinate-ground-explained'))}
209
- ${this.toggle('gridLabels', view.gridLabels, i18next.t('figure.label.axis-labels'))}
210
- ${this.toggle('floor', view.floor, i18next.t('figure.label.scene-floor'), i18next.t('figure.text.floor-explained'))}
211
- </section>
212
-
213
- <section>
214
- <h3>${i18next.t('figure.label.environment')}</h3>
215
-
216
- <label field>
217
- <span>${i18next.t('figure.label.preset')}</span>
218
- <select
219
- .value=${view.environment}
220
- @change=${(e: Event) => this.put({ environment: (e.target as HTMLSelectElement).value as EnvironmentName })}
221
- >
222
- ${ENVIRONMENTS.map(
223
- name => html`<option value=${name} ?selected=${view.environment === name}>
224
- ${i18next.t(`figure.label.environment-${name}`)}
225
- </option>`
226
- )}
227
- </select>
228
- </label>
229
- <p quiet>${i18next.t('figure.text.environment-explained')}</p>
230
-
231
- <label field>
232
- <span>${i18next.t('figure.label.sky-color')}</span>
233
- <input
234
- type="color"
235
- .value=${view.skyColor}
236
- @change=${(e: Event) => this.put({ skyColor: (e.target as HTMLInputElement).value })}
237
- />
238
- </label>
239
- </section>
240
-
241
- <section>
242
- <h3>${i18next.t('figure.label.key-light')}</h3>
243
-
244
- ${this.slider('dirLightIntensity', view.dirLightIntensity, i18next.t('figure.label.intensity'), 0, 5, 0.1)}
245
- ${this.toggle(
246
- 'dirLightFollowCamera',
247
- view.dirLightFollowCamera,
248
- i18next.t('figure.label.follow-camera'),
249
- i18next.t('figure.text.follow-camera-explained')
250
- )}
251
- ${this.toggle('dirShadowEnabled', view.dirShadowEnabled, i18next.t('figure.label.cast-shadow'))}
252
- ${view.dirLightIntensity === 0 && view.hemiIntensity === 0
253
- ? html`<p quiet>${i18next.t('figure.text.all-lights-off')}</p>`
254
- : ''}
255
- </section>
256
-
257
- <section>
258
- <h3>${i18next.t('figure.label.hemisphere-light')}</h3>
259
-
260
- ${this.slider('hemiIntensity', view.hemiIntensity, i18next.t('figure.label.intensity'), 0, 8, 0.1)}
261
- <p quiet>${i18next.t('figure.text.hemisphere-light-explained')}</p>
262
- </section>
263
-
264
- <section>
265
- <button reset @click=${() => this.reset()}>${i18next.t('figure.button.reset-view')}</button>
266
- </section>
267
- `
268
- }
269
-
270
- private toggle(key: keyof ViewSettings, on: boolean, label: string, hint?: string) {
271
- return html`
272
- <label row>
273
- <span>
274
- ${label}${hint ? html`<em>${hint}</em>` : ''}
275
- </span>
276
- <input
277
- type="checkbox"
278
- .checked=${on}
279
- @change=${(e: Event) => this.put({ [key]: (e.target as HTMLInputElement).checked } as Partial<ViewSettings>)}
280
- />
281
- </label>
282
- `
283
- }
284
-
285
- private slider(key: keyof ViewSettings, value: number, label: string, min: number, max: number, step: number) {
286
- return html`
287
- <label field>
288
- <span>${label}</span>
289
- <div slider>
290
- <input
291
- type="range"
292
- min=${min}
293
- max=${max}
294
- step=${step}
295
- .value=${String(value)}
296
- @input=${(e: Event) =>
297
- this.put({ [key]: Number((e.target as HTMLInputElement).value) } as Partial<ViewSettings>)}
298
- />
299
- <output>${value.toFixed(1)}</output>
300
- </div>
301
- </label>
302
- `
303
- }
304
-
305
- /** 처음 값으로 되돌린다. 만지다 길을 잃었을 때 돌아올 자리가 있어야 한다. */
306
- private reset() {
307
- this.tell({ ...DEFAULT_VIEW })
308
- }
309
-
310
- private put(patch: Partial<ViewSettings>) {
311
- this.tell({ ...(this.view ?? DEFAULT_VIEW), ...patch })
312
- }
313
-
314
- /**
315
- * 바뀐 것을 알린다.
316
- *
317
- * **알리기 전에 내 것도 바꿔 둔다.** Lit 은 속성을 다음 렌더에 넣으므로, 같은 프레임에
318
- * 두 번 고치면 두 번째가 `this.view` 의 옛 값을 읽어 첫 번째를 덮는다. 실제로 그랬다 —
319
- * 축 가이드를 끄고 바닥판을 켰더니 축 가이드가 도로 켜져 있었고, 화면의 체크 상자와
320
- * 씬의 상태가 서로 다른 말을 했다.
321
- *
322
- * 정본은 여전히 모델러 페이지가 갖는다. 여기 값은 다음 렌더에 같은 것으로 덮인다.
323
- */
324
- private tell(view: ViewSettings) {
325
- this.view = view
326
- this.dispatchEvent(new CustomEvent('update-view', { detail: { view }, bubbles: true, composed: true }))
327
- }
328
- }
@@ -1,272 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import './figure-inspector.js'
3
- import './figure-capabilities.js'
4
- import './figure-animations.js'
5
- import './figure-report.js'
6
- import './figure-settings.js'
7
- import './figure-history-panel.js'
8
-
9
- import { css, html, LitElement } from 'lit'
10
- import { customElement, property, state } from 'lit/decorators.js'
11
-
12
- import { i18next, localize } from '@operato/i18n'
13
- import type { ContractChange, FigureSource } from '@hatiolab/figure-model'
14
-
15
- import type { FigureDraft, PartModel } from './figure-source.js'
16
- import { DEFAULT_VIEW } from './figure-view.js'
17
- import type { ViewSettings } from './figure-view.js'
18
-
19
- type Tab = 'properties' | 'capability' | 'animation' | 'verdict' | 'view' | 'history'
20
-
21
- /**
22
- * 저작면 오른쪽 칸 — 속성과 판정을 탭으로 겹쳐 둔다.
23
- *
24
- * ## 왜 위아래로 나누지 않나
25
- *
26
- * 둘 다 세로로 긴 화면이다. 위아래로 나누면 양쪽 다 잘려서, 속성은 재질까지 못
27
- * 내려가고 판정은 무게까지 못 내려간다. 탭으로 겹치면 각자 칸 전체를 쓴다.
28
- *
29
- * ## 그래도 개수는 늘 보인다
30
- *
31
- * 성숙도의 축 하나 —「몇 개까지 편한가」— 는 부품을 고칠 때마다 움직이고, 그것이
32
- * 보여야 저작자가 고칠 수 있다. 탭 뒤로 숨으면 저장하고 나서야 알게 된다. 그래서 그
33
- * 숫자만 **탭 이름표에 남긴다** — 성숙도를 덮어 두고 속성을 만져도 따라 움직이는
34
- * 것이 보인다.
35
- *
36
- * 판정은 화면에서 감춰도 **DOM 에는 남는다.** 떼었다 붙이면 다시 세느라 값이 잠깐
37
- * 비고, 펼쳐 둔 추이 그림 같은 상태도 날아간다.
38
- */
39
- @customElement('figure-side')
40
- export class FigureSide extends localize(i18next)(LitElement) {
41
- static styles = css`
42
- :host {
43
- display: flex;
44
- flex-direction: column;
45
- min-height: 0;
46
- overflow: hidden;
47
- background-color: var(--md-sys-color-surface-container-lowest);
48
- border-left: 1px solid var(--md-sys-color-outline-variant);
49
- color: var(--md-sys-color-on-surface);
50
- font: var(--label-font, inherit);
51
- /*
52
- 이 패널의 본문 크기를 여기서 한 번 정한다.
53
-
54
- 전에는 규칙마다 따로 적었다. 그러면 빠뜨린 규칙이 본문 기본 크기(1rem)를
55
- 물려받아 그 줄만 제목처럼 커진다. 오류가 아니라서 화면을 띄워 봐야 안다.
56
- 이 파일에서만 두 번 났다.
57
-
58
- form 요소(input · select · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.
59
- 그것 말고는 여기 값을 따른다.
60
- */
61
- font-size: 0.76rem;
62
- }
63
-
64
- div[tabs] {
65
- display: flex;
66
- flex: none;
67
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
68
- }
69
- button[tab] {
70
- flex: 1;
71
- /*
72
- **아이콘 하나가 한 칸을 쓴다.**
73
-
74
- 전에는 이름을 적었는데, 넷이 좁은 칸을 나눠 쓰면서 접혀 「성숙 / 도」가 되었다. 접히면
75
- 그 탭만 두 줄이 되어 높이가 서로 달라진다. 다섯이 되면서 더는 줄여 짜 넣을 수 없어
76
- 아이콘으로 바꿨고, 이름은 title 과 aria-label 로 남는다.
77
-
78
- 배지는 여전히 자리를 차지하지 않고 **아이콘 위에 겹친다**(아래 span[badge]). 개수는
79
- 곁들이는 것이다.
80
- */
81
- position: relative;
82
- display: flex;
83
- align-items: center;
84
- justify-content: center;
85
- white-space: nowrap;
86
- padding: 7px 6px;
87
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
88
- background: none;
89
- border: none;
90
- /* 고른 탭을 밑줄로 말한다 — 칸을 색으로 채우면 아래 내용과 경계가 흐려진다 */
91
- border-bottom: 2px solid transparent;
92
- cursor: pointer;
93
- }
94
- button[tab]:hover:not([on]) {
95
- background-color: var(--md-sys-color-surface-container);
96
- }
97
- button[tab][on] {
98
- font-weight: 700;
99
- color: var(--md-sys-color-primary);
100
- border-bottom-color: var(--md-sys-color-primary);
101
- }
102
-
103
- /*
104
- 판정을 덮어 두어도 개수는 여기 남는다.
105
-
106
- 자리를 먹지 않게 오른쪽 위에 겹쳐 둔다 — 탭 이름을 밀지 않으므로 이름이 접히지 않는다.
107
- 숫자가 커져도(1,200 같은 것) 이름 위로 넘칠 뿐 줄을 바꾸지 않는다.
108
- */
109
- button[tab] md-icon {
110
- font-size: 20px;
111
- width: 20px;
112
- height: 20px;
113
- }
114
- span[badge] {
115
- position: absolute;
116
- /* 이름 위에 겹치되 위로 붙지 않게 — 탭 가운데 글자와 눈높이를 조금 맞춘다 */
117
- top: 5px;
118
- right: 2px;
119
- padding: 0 5px;
120
- font-size: 0.62rem;
121
- font-weight: 700;
122
- line-height: 1.5;
123
- color: var(--md-sys-color-on-primary-container);
124
- background-color: var(--md-sys-color-primary-container);
125
- border-radius: 999em;
126
- pointer-events: none;
127
- }
128
-
129
- figure-inspector,
130
- figure-animations,
131
- figure-report,
132
- figure-settings {
133
- flex: 1;
134
- min-height: 0;
135
- border-left: none;
136
- }
137
- /* 감추되 떼지는 않는다 */
138
- [off] {
139
- display: none;
140
- }
141
- `
142
-
143
- /*
144
- 두 형태가 이 패널에서 갈린다.
145
-
146
- 속성은 **수정하는** 곳이라 편집 중인 씬 모델을 받는다. 판정은 **저장될 것을 측정하는**
147
- 곳이라 변환된 FigureSource 를 받는다. 변환은 모델러 페이지가 한 번만 해서 내려보낸다.
148
- */
149
- @property({ type: String }) figureName = ''
150
- /** 카탈로그 분류·태그. 속성 탭이 보여 주고 바꾼다. */
151
- @property({ type: String }) figureCategory?: string
152
- @property({ type: Array }) figureTags?: string[]
153
- /** 이력 탭이 발행하고 되돌리는 데 필요한 것들. 정본은 저작면이 든다. */
154
- @property({ type: String }) figureId?: string
155
- @property({ type: String }) figureState?: 'draft' | 'released'
156
- @property({ type: Number }) figureVersion = 0
157
- @property({ type: Boolean }) dirty = false
158
-
159
- @property({ type: Object }) draft?: FigureDraft
160
- @property({ type: Array }) parts: PartModel[] = []
161
- @property({ type: Object }) source?: FigureSource
162
-
163
- /** 이 고침이 이미 놓인 인스턴스에 무엇을 하나(ADR-0063 결정 3). 발행 전 도형에는 비어 있다. */
164
- @property({ type: Array }) contract: ContractChange[] = []
165
- /** 직전 발행 기준 상자. 외형 변화 안내의 X·Z 복원 선택지에만 쓴다. */
166
- @property({ type: Object }) releasedBase?: FigureSource['base']
167
-
168
- /** 견주지 못한 까닭. 있으면 화면이 「확인하지 못했다」로 말한다. */
169
- @property({ type: String }) contractFailure?: string
170
- @property({ type: Number }) selected = -1
171
- /** 저작 화면의 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다. */
172
- @property({ type: Object }) view: ViewSettings = DEFAULT_VIEW
173
-
174
- @state() private tab: Tab = 'properties'
175
- /** 판정이 알려 준 개수. 탭 이름표에 붙는다. */
176
- @state() private instances?: number
177
-
178
- /**
179
- * 이 도형이 든 clip 수. 탭 아이콘에 붙는다.
180
- *
181
- * 아이콘만으로는 「여기에 무언가 있다」를 알 수 없다. 대부분의 도형은 움직이지 않으므로
182
- * 이 탭은 늘 비어 있을 것이고, 그러면 움직이는 도형을 열었을 때 그 사실이 눈에 걸려야 한다.
183
- * 0 이면 아무것도 안 붙인다 — 「0」을 붙이면 없는 것이 있는 것처럼 자리를 차지한다.
184
- */
185
- private get clipCount(): number | undefined {
186
- const n = (this.draft?.animations?.length ?? 0) + (this.draft?.parameters?.length ?? 0)
187
- return n === 0 ? undefined : n
188
- }
189
-
190
- render() {
191
- return html`
192
- <div tabs>
193
- ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
194
- ${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}
195
- ${this.renderTab('animation', 'animation', i18next.t('figure.label.animation'), this.clipCount)}
196
- ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
197
- ${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}
198
- ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
199
- </div>
200
-
201
- <figure-inspector
202
- ?off=${this.tab !== 'properties'}
203
- .figureId=${this.figureId}
204
- .figureVersion=${this.figureVersion}
205
- .figureName=${this.figureName}
206
- .figureCategory=${this.figureCategory}
207
- .figureTags=${this.figureTags}
208
- .draft=${this.draft}
209
- .parts=${this.parts}
210
- .contract=${this.contract}
211
- .contractFailure=${this.contractFailure}
212
- .selected=${this.selected}
213
- ></figure-inspector>
214
-
215
- <figure-capabilities
216
- ?off=${this.tab !== 'capability'}
217
- .draft=${this.draft}
218
- .parts=${this.parts}
219
- ></figure-capabilities>
220
-
221
- <figure-animations
222
- ?off=${this.tab !== 'animation'}
223
- .draft=${this.draft}
224
- .parts=${this.parts}
225
- ></figure-animations>
226
-
227
- <figure-report
228
- ?off=${this.tab !== 'verdict'}
229
- .source=${this.source}
230
- @verdict-changed=${(e: CustomEvent) => (this.instances = e.detail.instances)}
231
- ></figure-report>
232
-
233
- <figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>
234
-
235
- <!--
236
- 이력은 감춰도 DOM 에 남긴다 — 다른 탭들과 같은 규칙이다. 떼었다 붙이면 판본을 다시 부른다.
237
- -->
238
- <figure-history-panel
239
- ?off=${this.tab !== 'history'}
240
- .figureId=${this.figureId}
241
- .figureState=${this.figureState}
242
- .figureVersion=${this.figureVersion}
243
- .dirty=${this.dirty}
244
- .releasedBase=${this.releasedBase}
245
- ></figure-history-panel>
246
- `
247
- }
248
-
249
- /**
250
- * 탭 하나 — **아이콘이 이름을 대신한다.**
251
- *
252
- * 탭이 다섯이 되면서 이름이 좁은 칸을 나눠 쓰다 접혔다. 앞으로 더 늘어날 자리이므로 이름을
253
- * 줄여 짜 넣는 대신 아이콘으로 바꾼다.
254
- *
255
- * **이름을 버리지는 않는다.** 아이콘만으로는 무엇인지 모르는 사람이 있고, 특히 「능력」처럼
256
- * 이 도구에만 있는 개념이 그렇다. 그래서 이름은 `title` 로 남아 마우스를 올리면 나오고,
257
- * `aria-label` 로 읽는 기계에도 그대로 간다.
258
- */
259
- private renderTab(tab: Tab, icon: string, label: string, badge?: number) {
260
- return html`
261
- <button
262
- tab
263
- ?on=${this.tab === tab}
264
- title=${label}
265
- aria-label=${label}
266
- @click=${() => (this.tab = tab)}
267
- >
268
- <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}
269
- </button>
270
- `
271
- }
272
- }