@things-factory/figure-ui 10.1.37 → 10.1.39

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 (62) hide show
  1. package/client/graphql/index.ts +53 -0
  2. package/client/graphql/v3-store.ts +8 -2
  3. package/client/modeller/figure-history-panel.ts +33 -87
  4. package/client/modeller/figure-v3-parts.ts +365 -0
  5. package/client/modeller/figure-v3-verdict.ts +247 -0
  6. package/client/modeller/gate-findings.ts +105 -0
  7. package/client/modeller/v3-editor.ts +11 -2
  8. package/client/modeller/v3-occupancy.ts +22 -0
  9. package/client/modeller/v3-product-session.ts +23 -4
  10. package/client/modeller/v3-renderer.ts +27 -18
  11. package/client/modeller/v3-thumbnail.ts +97 -0
  12. package/client/pages/figure-v3-modeller-page.ts +272 -12
  13. package/client/route.ts +8 -0
  14. package/dist-client/graphql/index.d.ts +8 -0
  15. package/dist-client/graphql/index.js +51 -0
  16. package/dist-client/graphql/index.js.map +1 -1
  17. package/dist-client/graphql/v3-store.js +8 -2
  18. package/dist-client/graphql/v3-store.js.map +1 -1
  19. package/dist-client/modeller/figure-history-panel.d.ts +21 -0
  20. package/dist-client/modeller/figure-history-panel.js +34 -87
  21. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  22. package/dist-client/modeller/figure-v3-parts.d.ts +41 -0
  23. package/dist-client/modeller/figure-v3-parts.js +352 -0
  24. package/dist-client/modeller/figure-v3-parts.js.map +1 -0
  25. package/dist-client/modeller/figure-v3-verdict.d.ts +50 -0
  26. package/dist-client/modeller/figure-v3-verdict.js +268 -0
  27. package/dist-client/modeller/figure-v3-verdict.js.map +1 -0
  28. package/dist-client/modeller/gate-findings.d.ts +12 -0
  29. package/dist-client/modeller/gate-findings.js +102 -0
  30. package/dist-client/modeller/gate-findings.js.map +1 -0
  31. package/dist-client/modeller/v3-editor.js +11 -2
  32. package/dist-client/modeller/v3-editor.js.map +1 -1
  33. package/dist-client/modeller/v3-occupancy.d.ts +14 -0
  34. package/dist-client/modeller/v3-occupancy.js +22 -0
  35. package/dist-client/modeller/v3-occupancy.js.map +1 -0
  36. package/dist-client/modeller/v3-product-session.d.ts +44 -2
  37. package/dist-client/modeller/v3-product-session.js +13 -2
  38. package/dist-client/modeller/v3-product-session.js.map +1 -1
  39. package/dist-client/modeller/v3-renderer.d.ts +9 -0
  40. package/dist-client/modeller/v3-renderer.js +22 -30
  41. package/dist-client/modeller/v3-renderer.js.map +1 -1
  42. package/dist-client/modeller/v3-thumbnail.d.ts +3 -0
  43. package/dist-client/modeller/v3-thumbnail.js +74 -0
  44. package/dist-client/modeller/v3-thumbnail.js.map +1 -0
  45. package/dist-client/pages/figure-v3-modeller-page.d.ts +52 -1
  46. package/dist-client/pages/figure-v3-modeller-page.js +294 -12
  47. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  48. package/dist-client/route.d.ts +1 -1
  49. package/dist-client/route.js +7 -0
  50. package/dist-client/route.js.map +1 -1
  51. package/dist-client/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +5 -5
  53. package/test/resolve-ts-siblings.mjs +18 -0
  54. package/test/v3-product-write.test.ts +14 -2
  55. package/test/v3-release-panel.test.ts +73 -0
  56. package/test/v3-verdict.test.ts +82 -0
  57. package/things-factory.config.js +6 -1
  58. package/translations/en.json +79 -1
  59. package/translations/ja.json +79 -1
  60. package/translations/ko.json +79 -1
  61. package/translations/ms.json +79 -1
  62. package/translations/zh.json +79 -1
@@ -0,0 +1,352 @@
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
+ /** 제공자마다의 아이콘. Material Symbols 이름이다. */
8
+ const PROVIDER_ICON = {
9
+ box: 'deployed_code',
10
+ cylinder: 'database',
11
+ ellipticCylinder: 'database',
12
+ frustum: 'change_history',
13
+ sphere: 'circle',
14
+ roundedBox: 'crop_square',
15
+ roundedBoxXZ: 'crop_square',
16
+ hollowBox: 'check_box_outline_blank',
17
+ hollowBoxXZ: 'check_box_outline_blank',
18
+ polygon: 'pentagon',
19
+ portal: 'door_front'
20
+ };
21
+ /** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */
22
+ const RECIPES = [
23
+ { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },
24
+ { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },
25
+ { kind: 'rounded-box', icon: 'crop_square', label: 'figure.label.primitive-rect' },
26
+ { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }
27
+ ];
28
+ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
29
+ constructor() {
30
+ super(...arguments);
31
+ this.parts = [];
32
+ /** 고른 부품의 id. 자리(index)가 아니다 — V3 부품은 id 로 안정하게 가리킨다. */
33
+ this.selected = '';
34
+ /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */
35
+ this.locked = false;
36
+ }
37
+ static { this.styles = [
38
+ ScrollbarStyles,
39
+ css `
40
+ :host {
41
+ display: flex;
42
+ flex-direction: column;
43
+ min-height: 0;
44
+ overflow: hidden;
45
+ background-color: var(--md-sys-color-surface-container-lowest);
46
+ border-right: 1px solid var(--md-sys-color-outline-variant);
47
+ color: var(--md-sys-color-on-surface);
48
+ font: var(--label-font, inherit);
49
+ font-size: 0.76rem;
50
+ }
51
+ section {
52
+ padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
53
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
54
+ }
55
+ section[list] {
56
+ flex: 1;
57
+ min-height: 0;
58
+ overflow-y: auto;
59
+ }
60
+ h3 {
61
+ display: flex;
62
+ align-items: baseline;
63
+ justify-content: space-between;
64
+ gap: 6px;
65
+ margin: 0 0 6px 0;
66
+ font-size: var(--ui-t-micro, 11px);
67
+ font-weight: 700;
68
+ letter-spacing: 0.04em;
69
+ text-transform: uppercase;
70
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
71
+ }
72
+ span[room] {
73
+ font-size: 0.7rem;
74
+ font-weight: 400;
75
+ letter-spacing: 0;
76
+ text-transform: none;
77
+ }
78
+ div[palette] {
79
+ display: grid;
80
+ grid-template-columns: repeat(2, 1fr);
81
+ gap: 4px;
82
+ }
83
+ button[shape] {
84
+ display: flex;
85
+ flex-direction: column;
86
+ align-items: center;
87
+ gap: 2px;
88
+ padding: 7px 2px 5px 2px;
89
+ font: var(--label-font, inherit);
90
+ font-size: 0.66rem;
91
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
92
+ background: none;
93
+ border: 1px solid var(--md-sys-color-outline-variant);
94
+ border-radius: 6px;
95
+ cursor: pointer;
96
+ }
97
+ button[shape]:hover:not([disabled]) {
98
+ color: var(--md-sys-color-on-primary-container);
99
+ background-color: var(--md-sys-color-primary-container);
100
+ border-color: transparent;
101
+ }
102
+ button[shape][disabled] {
103
+ opacity: 0.35;
104
+ cursor: default;
105
+ }
106
+ button[shape] md-icon {
107
+ --md-icon-size: 20px;
108
+ }
109
+ ol {
110
+ margin: 0;
111
+ padding: 0;
112
+ list-style: none;
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 2px;
116
+ }
117
+ li {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 6px;
121
+ padding: 4px 6px;
122
+ border-radius: 6px;
123
+ cursor: pointer;
124
+ }
125
+ li:hover {
126
+ background-color: var(--md-sys-color-surface-container);
127
+ }
128
+ li[selected] {
129
+ color: var(--md-sys-color-on-primary-container);
130
+ background-color: var(--md-sys-color-primary-container);
131
+ }
132
+ li md-icon {
133
+ --md-icon-size: 16px;
134
+ flex: none;
135
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
136
+ }
137
+ button[eye][all] {
138
+ margin-left: auto;
139
+ opacity: 0.75;
140
+ }
141
+ button[eye][all]:hover {
142
+ opacity: 1;
143
+ }
144
+ button[eye] {
145
+ display: flex;
146
+ flex: none;
147
+ padding: 1px;
148
+ color: inherit;
149
+ background: none;
150
+ border: none;
151
+ border-radius: 4px;
152
+ cursor: pointer;
153
+ opacity: 0;
154
+ }
155
+ li:hover button[eye],
156
+ li[selected] button[eye],
157
+ button[eye][off] {
158
+ opacity: 1;
159
+ }
160
+ button[eye][off] {
161
+ color: var(--md-sys-color-tertiary);
162
+ }
163
+ button[eye] md-icon {
164
+ --md-icon-size: 15px;
165
+ color: inherit;
166
+ }
167
+ li[off] span[label],
168
+ li[off] > md-icon,
169
+ li[invisible] span[label],
170
+ li[invisible] > md-icon {
171
+ opacity: 0.45;
172
+ }
173
+ li[selected] md-icon {
174
+ color: inherit;
175
+ }
176
+ span[label] {
177
+ flex: 1;
178
+ min-width: 0;
179
+ overflow: hidden;
180
+ text-overflow: ellipsis;
181
+ white-space: nowrap;
182
+ font-size: 0.78rem;
183
+ }
184
+ span[label] small {
185
+ margin-left: 4px;
186
+ font-size: 0.66rem;
187
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
188
+ }
189
+ div[ops] {
190
+ display: none;
191
+ gap: 1px;
192
+ }
193
+ li[selected] div[ops] {
194
+ display: flex;
195
+ }
196
+ div[ops] button {
197
+ display: flex;
198
+ padding: 2px;
199
+ color: inherit;
200
+ background: none;
201
+ border: none;
202
+ border-radius: 4px;
203
+ cursor: pointer;
204
+ }
205
+ div[ops] button:hover:not([disabled]) {
206
+ background-color: var(--md-sys-color-surface-container-lowest);
207
+ }
208
+ div[ops] button[disabled] {
209
+ opacity: 0.3;
210
+ cursor: default;
211
+ }
212
+ div[ops] md-icon {
213
+ --md-icon-size: 15px;
214
+ color: inherit;
215
+ }
216
+ p[quiet] {
217
+ margin: 0;
218
+ font-size: 0.76rem;
219
+ line-height: 1.6;
220
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
221
+ }
222
+ `
223
+ ]; }
224
+ render() {
225
+ return html `
226
+ <section>
227
+ <h3>${i18next.t('figure.label.shapes')}</h3>
228
+ <div palette>
229
+ ${RECIPES.map(recipe => html `
230
+ <button
231
+ shape
232
+ ?disabled=${this.locked}
233
+ title=${i18next.t(recipe.label)}
234
+ @click=${() => this.ask('add-part', { kind: recipe.kind })}
235
+ >
236
+ <md-icon>${recipe.icon}</md-icon>
237
+ <span>${i18next.t(recipe.label)}</span>
238
+ </button>
239
+ `)}
240
+ </div>
241
+ </section>
242
+
243
+ <section list>
244
+ <h3>
245
+ <span>${i18next.t('figure.label.parts')}</span>
246
+ <span room>${this.parts.length}</span>
247
+ ${this.renderShowAll()}
248
+ </h3>
249
+ ${this.parts.length === 0
250
+ ? html `<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`
251
+ : html `<ol>
252
+ ${this.parts.map((part, index) => this.renderRow(part, index))}
253
+ </ol>`}
254
+ </section>
255
+ `;
256
+ }
257
+ /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */
258
+ renderShowAll() {
259
+ if (this.parts.length === 0)
260
+ return nothing;
261
+ const anyHidden = this.parts.some(part => part.hidden);
262
+ return html `
263
+ <button
264
+ eye
265
+ all
266
+ ?off=${!anyHidden}
267
+ title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}
268
+ @click=${(e) => {
269
+ e.stopPropagation();
270
+ this.ask('set-all-parts-hidden', { hidden: !anyHidden });
271
+ }}
272
+ >
273
+ <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>
274
+ </button>
275
+ `;
276
+ }
277
+ renderRow(part, index) {
278
+ const selected = part.id === this.selected;
279
+ const off = !!part.hidden;
280
+ return html `
281
+ <li
282
+ ?selected=${selected}
283
+ ?off=${off}
284
+ ?invisible=${!!part.invisible}
285
+ @click=${() => this.ask('select-part', { id: part.id })}
286
+ >
287
+ <button
288
+ eye
289
+ ?off=${off}
290
+ title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}
291
+ @click=${(e) => {
292
+ e.stopPropagation();
293
+ this.ask('toggle-part-hidden', { id: part.id });
294
+ }}
295
+ >
296
+ <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>
297
+ </button>
298
+ <md-icon>${PROVIDER_ICON[part.provider] ?? 'deployed_code'}</md-icon>
299
+ <span label title=${part.id}
300
+ >${part.id}${part.invisible ? html `<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}</span
301
+ >
302
+ ${selected ? this.renderOps(part.id, index) : nothing}
303
+ </li>
304
+ `;
305
+ }
306
+ /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */
307
+ renderOps(id, index) {
308
+ const count = this.parts.length;
309
+ const op = (name, icon, detail, enabled = true) => html `
310
+ <button
311
+ op=${name}
312
+ ?disabled=${!enabled || this.locked}
313
+ title=${i18next.t('figure.button.' + name)}
314
+ @click=${(e) => {
315
+ e.stopPropagation();
316
+ if (enabled && !this.locked)
317
+ this.ask(name, detail);
318
+ }}
319
+ >
320
+ <md-icon>${icon}</md-icon>
321
+ </button>
322
+ `;
323
+ return html `
324
+ <div ops>
325
+ ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}
326
+ ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}
327
+ ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}
328
+ </div>
329
+ `;
330
+ }
331
+ /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */
332
+ ask(what, detail) {
333
+ this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }));
334
+ }
335
+ };
336
+ __decorate([
337
+ property({ type: Array }),
338
+ __metadata("design:type", Array)
339
+ ], FigureV3Parts.prototype, "parts", void 0);
340
+ __decorate([
341
+ property({ type: String }),
342
+ __metadata("design:type", Object)
343
+ ], FigureV3Parts.prototype, "selected", void 0);
344
+ __decorate([
345
+ property({ type: Boolean }),
346
+ __metadata("design:type", Object)
347
+ ], FigureV3Parts.prototype, "locked", void 0);
348
+ FigureV3Parts = __decorate([
349
+ customElement('figure-v3-parts')
350
+ ], FigureV3Parts);
351
+ export { FigureV3Parts };
352
+ //# sourceMappingURL=figure-v3-parts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figure-v3-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-v3-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport type { V3PrimitiveCreation } from '@hatiolab/figure-model'\n\n/**\n * 도형 팔레트와 부품 목록 — V3 자산의 것.\n *\n * `figure-parts`(V2) 와 **같은 자리, 같은 조작, 같은 낱말**이다. 저작자가 V2 에서 익힌 손이 그대로\n * 가야 한다: 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.\n *\n * 다른 것은 읽는 대상뿐이다. V2 는 부품의 `primitive` 를 읽었고 여기서는 V3 평가가 준 `provider` 를\n * 읽는다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라\n * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — V2 와 같은 규칙, 같은 사건 이름.\n */\n\n/** 한 줄 — V3 평가에서 이 패널이 읽는 것만. */\nexport interface V3PartRow {\n id: string\n provider: string\n /** 작업대에서 잠깐 가린 것. 저장되지 않는다. */\n hidden?: boolean\n /** 자산이 지금 상태에서 보이지 않는다고 한 것. 저장되는 것이고, 여기서는 표시만 한다. */\n invisible?: boolean\n}\n\n/** 제공자마다의 아이콘. Material Symbols 이름이다. */\nconst PROVIDER_ICON: Record<string, string> = {\n box: 'deployed_code',\n cylinder: 'database',\n ellipticCylinder: 'database',\n frustum: 'change_history',\n sphere: 'circle',\n roundedBox: 'crop_square',\n roundedBoxXZ: 'crop_square',\n hollowBox: 'check_box_outline_blank',\n hollowBoxXZ: 'check_box_outline_blank',\n polygon: 'pentagon',\n portal: 'door_front'\n}\n\n/** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */\nconst RECIPES: Array<{ kind: V3PrimitiveCreation['kind']; icon: string; label: string }> = [\n { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },\n { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },\n { kind: 'rounded-box', icon: 'crop_square', label: 'figure.label.primitive-rect' },\n { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }\n]\n\n@customElement('figure-v3-parts')\nexport class FigureV3Parts 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 font-size: 0.76rem;\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 span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n div[palette] {\n display: grid;\n grid-template-columns: repeat(2, 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 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 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 li[off] span[label],\n li[off] > md-icon,\n li[invisible] span[label],\n li[invisible] > 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 span[label] small {\n margin-left: 4px;\n font-size: 0.66rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\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 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 @property({ type: Array }) parts: V3PartRow[] = []\n /** 고른 부품의 id. 자리(index)가 아니다 — V3 부품은 id 로 안정하게 가리킨다. */\n @property({ type: String }) selected = ''\n /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */\n @property({ type: Boolean }) locked = false\n\n render() {\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${RECIPES.map(\n recipe => html`\n <button\n shape\n ?disabled=${this.locked}\n title=${i18next.t(recipe.label)}\n @click=${() => this.ask('add-part', { kind: recipe.kind })}\n >\n <md-icon>${recipe.icon}</md-icon>\n <span>${i18next.t(recipe.label)}</span>\n </button>\n `\n )}\n </div>\n </section>\n\n <section list>\n <h3>\n <span>${i18next.t('figure.label.parts')}</span>\n <span room>${this.parts.length}</span>\n ${this.renderShowAll()}\n </h3>\n ${this.parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`\n : html`<ol>\n ${this.parts.map((part, index) => this.renderRow(part, index))}\n </ol>`}\n </section>\n `\n }\n\n /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */\n private renderShowAll() {\n if (this.parts.length === 0) return nothing\n const anyHidden = this.parts.some(part => part.hidden)\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: V3PartRow, index: number) {\n const selected = part.id === this.selected\n const off = !!part.hidden\n return html`\n <li\n ?selected=${selected}\n ?off=${off}\n ?invisible=${!!part.invisible}\n @click=${() => this.ask('select-part', { id: part.id })}\n >\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 e.stopPropagation()\n this.ask('toggle-part-hidden', { id: part.id })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${PROVIDER_ICON[part.provider] ?? 'deployed_code'}</md-icon>\n <span label title=${part.id}\n >${part.id}${part.invisible ? html`<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}</span\n >\n ${selected ? this.renderOps(part.id, index) : nothing}\n </li>\n `\n }\n\n /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */\n private renderOps(id: string, index: number) {\n const count = this.parts.length\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled || this.locked}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled && !this.locked) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}\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"]}
@@ -0,0 +1,50 @@
1
+ import '@material/web/icon/icon.js';
2
+ import { LitElement } from 'lit';
3
+ /** How long an edit settles before the server is asked again. One question per pause, not one per keystroke. */
4
+ export declare const VERDICT_SETTLE_MS = 400;
5
+ declare const FigureV3Verdict_base: (new (...args: any[]) => LitElement) & typeof LitElement;
6
+ /**
7
+ * 판정 — the V3 modeller's verdict while editing (parity row ①).
8
+ *
9
+ * The V2 modeller shows, as the author edits, how many of the figure fit on one screen and what would stop it
10
+ * being published. The V3 page showed neither: the author learned what blocked a release only after saving, in the
11
+ * release panel. This tab answers both from the source being edited.
12
+ *
13
+ * The count is computed here, from `v3CostOf` and the same `capacityFrom` the catalogue cards use, so the tab and
14
+ * the card cannot disagree. The findings are not: they come from the server's release door (`inspectFigureDraft`),
15
+ * the same door and the same sentences as the release panel. A second judgement on the screen would say something
16
+ * different the first time either changed.
17
+ *
18
+ * No grade. V2's grade measures V2 reuse; nothing measures it for V3, so the tab shows no letter rather than one
19
+ * nothing computed.
20
+ */
21
+ export declare class FigureV3Verdict extends FigureV3Verdict_base {
22
+ static styles: import("lit").CSSResult[];
23
+ /** The source being edited, serialised. Every change of it is a new question. */
24
+ source?: string;
25
+ /** The saved row, if any — the server reads its type and last release from it. */
26
+ figureId?: string;
27
+ /** Changing the type changes the verdict (a temporary type is refused), so it is a reason to ask again. */
28
+ figureType?: string;
29
+ private capacity?;
30
+ /** Bodies in the asset, drawn or hidden. With none there is nothing to fit the space to, so the button is not offered. */
31
+ private bodies;
32
+ private inspection?;
33
+ private failure;
34
+ private asking;
35
+ private timer?;
36
+ /** Answers can come back out of order; only the newest question's answer is shown. */
37
+ private asked;
38
+ disconnectedCallback(): void;
39
+ updated(changes: Map<string, unknown>): void;
40
+ private measure;
41
+ private schedule;
42
+ private ask;
43
+ render(): import("lit-html").TemplateResult<1>;
44
+ private renderCount;
45
+ private renderGate;
46
+ /** The same way out the release panel offers for the same two findings — declare the space from the parts. */
47
+ private actionsFor;
48
+ private fit;
49
+ }
50
+ export {};