@things-factory/figure-ui 10.1.5 → 10.1.6

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 (75) hide show
  1. package/client/graphql/index.ts +35 -3
  2. package/client/modeller/figure-ask.ts +5 -5
  3. package/client/modeller/figure-canvas.ts +1 -1
  4. package/client/modeller/figure-capabilities.ts +372 -0
  5. package/client/modeller/figure-history-panel.ts +171 -18
  6. package/client/modeller/figure-inspector.ts +392 -76
  7. package/client/modeller/figure-parts.ts +15 -15
  8. package/client/modeller/figure-preview.ts +2 -2
  9. package/client/modeller/figure-report.ts +102 -37
  10. package/client/modeller/figure-settings.ts +20 -20
  11. package/client/modeller/figure-side.ts +37 -9
  12. package/client/modeller/figure-source.test.ts +15 -1
  13. package/client/modeller/figure-source.ts +31 -15
  14. package/client/modeller/figure-thumbnail.ts +49 -4
  15. package/client/modeller/maturity.ts +10 -10
  16. package/client/pages/figure-list-page.ts +152 -66
  17. package/client/pages/figure-modeller-page.ts +260 -43
  18. package/client/types.ts +25 -0
  19. package/client/viewparts/figure-thumb.ts +172 -0
  20. package/dist-client/graphql/index.d.ts +8 -1
  21. package/dist-client/graphql/index.js +32 -3
  22. package/dist-client/graphql/index.js.map +1 -1
  23. package/dist-client/modeller/figure-ask.js +5 -5
  24. package/dist-client/modeller/figure-ask.js.map +1 -1
  25. package/dist-client/modeller/figure-canvas.js +1 -1
  26. package/dist-client/modeller/figure-canvas.js.map +1 -1
  27. package/dist-client/modeller/figure-capabilities.d.ts +42 -0
  28. package/dist-client/modeller/figure-capabilities.js +344 -0
  29. package/dist-client/modeller/figure-capabilities.js.map +1 -0
  30. package/dist-client/modeller/figure-history-panel.d.ts +15 -1
  31. package/dist-client/modeller/figure-history-panel.js +165 -18
  32. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  33. package/dist-client/modeller/figure-inspector.d.ts +84 -0
  34. package/dist-client/modeller/figure-inspector.js +379 -75
  35. package/dist-client/modeller/figure-inspector.js.map +1 -1
  36. package/dist-client/modeller/figure-parts.js +15 -15
  37. package/dist-client/modeller/figure-parts.js.map +1 -1
  38. package/dist-client/modeller/figure-preview.js +2 -2
  39. package/dist-client/modeller/figure-preview.js.map +1 -1
  40. package/dist-client/modeller/figure-report.d.ts +18 -1
  41. package/dist-client/modeller/figure-report.js +105 -37
  42. package/dist-client/modeller/figure-report.js.map +1 -1
  43. package/dist-client/modeller/figure-settings.js +19 -19
  44. package/dist-client/modeller/figure-settings.js.map +1 -1
  45. package/dist-client/modeller/figure-side.d.ts +1 -0
  46. package/dist-client/modeller/figure-side.js +36 -8
  47. package/dist-client/modeller/figure-side.js.map +1 -1
  48. package/dist-client/modeller/figure-source.d.ts +15 -8
  49. package/dist-client/modeller/figure-source.js +16 -7
  50. package/dist-client/modeller/figure-source.js.map +1 -1
  51. package/dist-client/modeller/figure-thumbnail.js +40 -4
  52. package/dist-client/modeller/figure-thumbnail.js.map +1 -1
  53. package/dist-client/modeller/maturity.js +10 -10
  54. package/dist-client/modeller/maturity.js.map +1 -1
  55. package/dist-client/pages/figure-list-page.d.ts +1 -4
  56. package/dist-client/pages/figure-list-page.js +150 -63
  57. package/dist-client/pages/figure-list-page.js.map +1 -1
  58. package/dist-client/pages/figure-modeller-page.d.ts +45 -0
  59. package/dist-client/pages/figure-modeller-page.js +259 -43
  60. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  61. package/dist-client/tsconfig.tsbuildinfo +1 -1
  62. package/dist-client/types.d.ts +23 -0
  63. package/dist-client/types.js.map +1 -1
  64. package/dist-client/viewparts/figure-thumb.d.ts +29 -0
  65. package/dist-client/viewparts/figure-thumb.js +168 -0
  66. package/dist-client/viewparts/figure-thumb.js.map +1 -0
  67. package/dist-server/tsconfig.tsbuildinfo +1 -1
  68. package/package.json +7 -5
  69. package/test/i18n-prefix-guard.test.ts +129 -0
  70. package/test/korean-register-guard.test.ts +83 -0
  71. package/translations/en.json +307 -264
  72. package/translations/ja.json +275 -264
  73. package/translations/ko.json +307 -264
  74. package/translations/ms.json +275 -264
  75. package/translations/zh.json +275 -264
@@ -0,0 +1,344 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { css, html, LitElement } from 'lit';
3
+ import { customElement, property } from 'lit/decorators.js';
4
+ import { live } from 'lit/directives/live.js';
5
+ import { i18next, localize } from '@operato/i18n';
6
+ import { ScrollbarStyles } from '@operato/styles';
7
+ const RUNGS = Object.freeze({
8
+ none: [],
9
+ hold: ['Capacity', 'Holdable'],
10
+ flow: ['FlowNode']
11
+ });
12
+ /** 선언된 이름 집합이 사다리 어느 칸인가. 어느 칸도 아니면 `null` — 지어내지 않는다. */
13
+ function rungOf(names) {
14
+ const has = new Set(names ?? []);
15
+ if (has.size === 0)
16
+ return 'none';
17
+ for (const [rung, wanted] of Object.entries(RUNGS)) {
18
+ if (wanted.length === has.size && wanted.every(name => has.has(name)))
19
+ return rung;
20
+ }
21
+ return null;
22
+ }
23
+ let FigureCapabilities = class FigureCapabilities extends localize(i18next)(LitElement) {
24
+ constructor() {
25
+ super(...arguments);
26
+ this.parts = [];
27
+ }
28
+ static { this.styles = [
29
+ ScrollbarStyles,
30
+ css `
31
+ :host {
32
+ display: flex;
33
+ flex-direction: column;
34
+ min-height: 0;
35
+ overflow-y: auto;
36
+ background-color: var(--md-sys-color-surface-container-lowest);
37
+ color: var(--md-sys-color-on-surface);
38
+ font: var(--label-font, inherit);
39
+ font-size: 0.76rem;
40
+ }
41
+
42
+ section {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: var(--spacing-medium, 8px);
46
+ padding: var(--spacing-large, 12px);
47
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
48
+ }
49
+
50
+ /* 다른 탭들과 같은 눈금이다 — 탭마다 다른 제목 규격을 쓰면 따로 논다 */
51
+ h3 {
52
+ margin: 0 0 2px 0;
53
+ font-size: var(--ui-t-micro, 11px);
54
+ font-weight: 700;
55
+ letter-spacing: 0.04em;
56
+ text-transform: uppercase;
57
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
58
+ }
59
+
60
+ p[quiet] {
61
+ margin: 0;
62
+ font-size: 0.72rem;
63
+ line-height: 1.5;
64
+ color: var(--md-sys-color-on-surface-variant);
65
+ }
66
+
67
+ /* 사다리 — 고르는 것이 하나라 라디오다 */
68
+ label[rung] {
69
+ display: flex;
70
+ align-items: flex-start;
71
+ gap: var(--spacing-medium, 8px);
72
+ padding: 6px 8px;
73
+ border: 1px solid var(--md-sys-color-outline-variant);
74
+ border-radius: 4px;
75
+ cursor: pointer;
76
+ }
77
+ label[rung][on] {
78
+ border-color: var(--md-sys-color-primary);
79
+ background-color: var(--md-sys-color-surface-container);
80
+ }
81
+ label[rung]:hover:not([on]) {
82
+ background-color: var(--md-sys-color-surface-container-low);
83
+ }
84
+ label[rung] span {
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 1px;
88
+ }
89
+ label[rung] strong {
90
+ font-weight: 600;
91
+ }
92
+ label[rung] em {
93
+ font-style: normal;
94
+ font-size: 0.7rem;
95
+ line-height: 1.4;
96
+ color: var(--md-sys-color-on-surface-variant);
97
+ }
98
+
99
+ input[type='radio'] {
100
+ flex: none;
101
+ margin: 2px 0 0 0;
102
+ accent-color: var(--md-sys-color-primary);
103
+ cursor: pointer;
104
+ }
105
+
106
+ /* 실제로 씬에 붙는 이름. 고른 뒤에 보여 준다 */
107
+ code[names] {
108
+ font: var(--mono-font, ui-monospace, monospace);
109
+ font-size: 0.7rem;
110
+ color: var(--md-sys-color-on-surface-variant);
111
+ }
112
+
113
+ /* 지정된 부품 한 줄 — 읽는 것이지 고치는 것이 아니다. 눌러 그 부품으로 간다 */
114
+ button[marked] {
115
+ display: flex;
116
+ align-items: baseline;
117
+ gap: var(--spacing-medium, 8px);
118
+ width: 100%;
119
+ padding: 5px 8px;
120
+ text-align: left;
121
+ font: inherit;
122
+ color: inherit;
123
+ background: none;
124
+ border: 1px solid var(--md-sys-color-outline-variant);
125
+ border-radius: 4px;
126
+ cursor: pointer;
127
+ }
128
+ button[marked]:hover {
129
+ background-color: var(--md-sys-color-surface-container);
130
+ }
131
+ button[marked] b {
132
+ flex: none;
133
+ font-weight: 600;
134
+ }
135
+ button[marked] span {
136
+ flex: none;
137
+ font-size: 0.72rem;
138
+ color: var(--md-sys-color-primary);
139
+ }
140
+ button[marked] em {
141
+ flex: 1;
142
+ min-width: 0;
143
+ overflow: hidden;
144
+ text-overflow: ellipsis;
145
+ white-space: nowrap;
146
+ text-align: right;
147
+ font-style: normal;
148
+ font-size: 0.7rem;
149
+ color: var(--md-sys-color-on-surface-variant);
150
+ }
151
+
152
+ /* 부품마다 한 줄. 자리를 고른 것만 아래로 펼친다 */
153
+ div[part] {
154
+ display: flex;
155
+ flex-direction: column;
156
+ gap: 4px;
157
+ padding: 6px 0;
158
+ border-top: 1px solid var(--md-sys-color-outline-variant);
159
+ }
160
+ div[part]:first-of-type {
161
+ border-top: none;
162
+ }
163
+ div[head] {
164
+ display: flex;
165
+ align-items: center;
166
+ gap: var(--spacing-medium, 8px);
167
+ }
168
+ div[head] b {
169
+ flex: 1;
170
+ min-width: 0;
171
+ overflow: hidden;
172
+ text-overflow: ellipsis;
173
+ white-space: nowrap;
174
+ font-weight: 600;
175
+ }
176
+
177
+ select,
178
+ input[type='text'],
179
+ input[type='number'] {
180
+ padding: 4px 6px;
181
+ font: var(--input-field-font, inherit);
182
+ font-size: 0.74rem;
183
+ color: var(--md-sys-color-on-surface);
184
+ background-color: var(--md-sys-color-surface);
185
+ border: 1px solid var(--md-sys-color-outline-variant);
186
+ border-radius: 4px;
187
+ }
188
+ /* 이름이 길어질 수 있는 쪽(문)까지 안 잘리는 폭이다 */
189
+ div[head] select {
190
+ flex: none;
191
+ width: 8.5em;
192
+ }
193
+
194
+ div[detail] {
195
+ display: grid;
196
+ grid-template-columns: 4.5em 1fr;
197
+ align-items: center;
198
+ gap: 4px var(--spacing-medium, 8px);
199
+ padding-left: var(--spacing-medium, 8px);
200
+ }
201
+ div[detail] > span {
202
+ font-size: 0.72rem;
203
+ color: var(--md-sys-color-on-surface-variant);
204
+ }
205
+ div[detail] input {
206
+ width: 100%;
207
+ box-sizing: border-box;
208
+ }
209
+ div[detail] em {
210
+ grid-column: 2;
211
+ font-style: normal;
212
+ font-size: 0.7rem;
213
+ color: var(--md-sys-color-on-surface-variant);
214
+ }
215
+ `
216
+ ]; }
217
+ render() {
218
+ const rung = rungOf(this.board?.capabilities);
219
+ const names = this.board?.capabilities ?? [];
220
+ return html `
221
+ <section>
222
+ <h3>${i18next.t('figure.label.what-this-does')}</h3>
223
+
224
+ ${this.rung('none', rung)} ${this.rung('hold', rung)} ${this.rung('flow', rung)}
225
+ ${rung === null
226
+ ? html `<p quiet>${i18next.t('figure.text.capability-hand-written')}</p>`
227
+ : ''}
228
+ ${names.length > 0 ? html `<code names>${names.join(' · ')}</code>` : ''}
229
+ </section>
230
+
231
+ ${this.renderAnchors(rung)}
232
+ `;
233
+ }
234
+ /**
235
+ * 자리와 문 — **여기서는 읽기만 한다.**
236
+ *
237
+ * 지정은 속성 탭에서 한다. 부품을 고치는 곳이 한 군데여야 하기 때문이다 — 목록을 여기 또
238
+ * 두면 같은 부품을 두 화면에서 고치게 되고, 저작자는 어느 쪽이 이기는지 매번 생각해야 한다.
239
+ *
240
+ * 그래도 요약은 남긴다. 「어느 부품이 자리인가」는 부품을 하나씩 눌러 보며 알아낼 일이
241
+ * 아니다 — 자리는 그려지지 않으므로 화면을 봐서는 영영 못 찾는다.
242
+ */
243
+ renderAnchors(rung) {
244
+ if (rung === 'none') {
245
+ return html `
246
+ <section>
247
+ <h3>${i18next.t('figure.label.slots-and-ports')}</h3>
248
+ <p quiet>${i18next.t('figure.text.slots-need-capability')}</p>
249
+ </section>
250
+ `;
251
+ }
252
+ const marked = this.parts.filter(part => part.capability);
253
+ return html `
254
+ <section>
255
+ <h3>${i18next.t('figure.label.slots-and-ports')}</h3>
256
+ <p quiet>${i18next.t('figure.text.anchors-are-not-drawn')}</p>
257
+
258
+ ${marked.length === 0
259
+ ? html `<p quiet>${i18next.t('figure.text.designate-in-properties')}</p>`
260
+ : marked.map(part => this.renderMarked(part))}
261
+ </section>
262
+ `;
263
+ }
264
+ /** 지정된 부품 한 줄. 누르면 그 부품이 골라진다 — 고치는 자리는 속성 탭이다. */
265
+ renderMarked(part) {
266
+ const spec = part.capability;
267
+ return html `
268
+ <button marked @click=${() => this.pick(part)}>
269
+ <b>${part.name}</b>
270
+ <span>${spec.roles.map(role => i18next.t('figure.label.role-' + role)).join(' · ')}</span>
271
+ ${spec.accepts?.length ? html `<em>${spec.accepts.join(' · ')}</em>` : ''}
272
+ </button>
273
+ `;
274
+ }
275
+ /** 이 부품을 고르게 한다. 부품 목록에서 누른 것과 같은 알림이다. */
276
+ pick(part) {
277
+ const index = this.parts.indexOf(part);
278
+ if (index < 0)
279
+ return;
280
+ this.dispatchEvent(new CustomEvent('select-part', { detail: { index }, bubbles: true, composed: true }));
281
+ }
282
+ /**
283
+ * 사다리 한 칸.
284
+ *
285
+ * `checked` 를 `live()` 로 묶는다. 그냥 두면 Lit 이 **마지막으로 그린 값**과 견주므로,
286
+ * 브라우저가 DOM 을 바꿨는데 모델은 안 바뀐 경우 되돌리지 않는다. 실제로 그랬다 — 고르는
287
+ * 도중 도형이 다시 실렸더니 점은 「흐른다」에 있고 강조 상자는 「그리기만 한다」에 있었다.
288
+ * 화면이 모델과 다른 말을 하는 상태다. `live()` 는 **지금 DOM 의 값**과 견준다.
289
+ */
290
+ rung(which, chosen) {
291
+ return html `
292
+ <label rung ?on=${chosen === which}>
293
+ <input
294
+ type="radio"
295
+ name="rung"
296
+ .checked=${live(chosen === which)}
297
+ @change=${() => this.setRung(which)}
298
+ />
299
+ <span>
300
+ <strong>${i18next.t(`figure.label.rung-${which}`)}</strong>
301
+ <em>${i18next.t(`figure.text.rung-${which}-note`)}</em>
302
+ </span>
303
+ </label>
304
+ `;
305
+ }
306
+ /**
307
+ * 사다리를 내리면 자리도 함께 거둔다.
308
+ *
309
+ * 능력을 「그리기만 한다」로 되돌려 놓고 부품에 자리 표시가 남아 있으면, 형식은 자리를
310
+ * 그대로 실어 보내는데 씬은 그것을 쓸 클래스가 없다. **선언은 있고 길이 없는** 상태가
311
+ * 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.
312
+ */
313
+ setRung(which) {
314
+ const board = this.board;
315
+ if (!board)
316
+ return;
317
+ this.tellBoard({ ...board, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined });
318
+ if (which === 'none') {
319
+ this.parts.forEach((part, index) => {
320
+ if (!part.capability)
321
+ return;
322
+ const { capability, ...rest } = part;
323
+ this.dispatchEvent(new CustomEvent('update-part', { detail: { index, part: rest }, bubbles: true, composed: true }));
324
+ });
325
+ }
326
+ }
327
+ tellBoard(board) {
328
+ this.board = board;
329
+ this.dispatchEvent(new CustomEvent('update-board', { detail: { board }, bubbles: true, composed: true }));
330
+ }
331
+ };
332
+ __decorate([
333
+ property({ type: Object }),
334
+ __metadata("design:type", Object)
335
+ ], FigureCapabilities.prototype, "board", void 0);
336
+ __decorate([
337
+ property({ type: Array }),
338
+ __metadata("design:type", Array)
339
+ ], FigureCapabilities.prototype, "parts", void 0);
340
+ FigureCapabilities = __decorate([
341
+ customElement('figure-capabilities')
342
+ ], FigureCapabilities);
343
+ export { FigureCapabilities };
344
+ //# sourceMappingURL=figure-capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figure-capabilities.js","sourceRoot":"","sources":["../../client/modeller/figure-capabilities.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport type { BoardModel, PartModel } from './figure-source.js'\n\n/**\n * 이 도형이 **무엇을 하는가.**\n *\n * ## 왜 탭 하나가 필요한가\n *\n * 지금까지 저작한 것은 **생김새**뿐이었다. 컨베이어를 아무리 잘 그려도 보드에 놓으면\n * 상자 하나를 못 받는다 — 물건을 받고 담고 흘려보내는 것은 씬의 capability 가 하는 일이고,\n * 손으로 쓴 컴포넌트는 클래스 선언 한 줄로 그것을 졌다. 저작한 도형에는 그 줄을 쓸 자리가\n * 없어서, 여기서 고른 것이 그 줄이 된다.\n *\n * ## 사다리로 묻는다\n *\n * 능력의 이름(`Capacity` · `Holdable` · `Transferable` · `FlowNode`)을 그대로 늘어놓고\n * 고르라고 하면, 저작자는 넷의 관계를 먼저 배워야 한다. 실제로 물어야 하는 것은 하나다 —\n * **이 도형이 물건을 다루나, 다룬다면 지나가나.** 그래서 세 칸의 사다리로 묻고, 이름은\n * 고른 뒤에 보여 준다.\n *\n * 윗 칸이 아랫 칸을 포함한다(`FlowNode` 가 나머지 셋을 스스로 흡수한다). 그래서 사다리이지\n * 체크상자가 아니다.\n */\n\n/** 사다리 한 칸 — 화면이 묻는 말과, 그것이 되는 능력 이름. */\ntype Rung = 'none' | 'hold' | 'flow'\n\nconst RUNGS: Readonly<Record<Rung, readonly string[]>> = Object.freeze({\n none: [],\n hold: ['Capacity', 'Holdable'],\n flow: ['FlowNode']\n})\n\n/** 선언된 이름 집합이 사다리 어느 칸인가. 어느 칸도 아니면 `null` — 지어내지 않는다. */\nfunction rungOf(names: readonly string[] | undefined): Rung | null {\n const has = new Set(names ?? [])\n if (has.size === 0) return 'none'\n\n for (const [rung, wanted] of Object.entries(RUNGS) as [Rung, readonly string[]][]) {\n if (wanted.length === has.size && wanted.every(name => has.has(name))) return rung\n }\n return null\n}\n\n@customElement('figure-capabilities')\nexport class FigureCapabilities extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 다른 탭들과 같은 눈금이다 — 탭마다 다른 제목 규격을 쓰면 따로 논다 */\n h3 {\n margin: 0 0 2px 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 p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 사다리 — 고르는 것이 하나라 라디오다 */\n label[rung] {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-medium, 8px);\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n label[rung][on] {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container);\n }\n label[rung]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container-low);\n }\n label[rung] span {\n display: flex;\n flex-direction: column;\n gap: 1px;\n }\n label[rung] strong {\n font-weight: 600;\n }\n label[rung] em {\n font-style: normal;\n font-size: 0.7rem;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n input[type='radio'] {\n flex: none;\n margin: 2px 0 0 0;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n\n /* 실제로 씬에 붙는 이름. 고른 뒤에 보여 준다 */\n code[names] {\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 지정된 부품 한 줄 — 읽는 것이지 고치는 것이 아니다. 눌러 그 부품으로 간다 */\n button[marked] {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-medium, 8px);\n width: 100%;\n padding: 5px 8px;\n text-align: left;\n font: inherit;\n color: inherit;\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n button[marked]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n button[marked] b {\n flex: none;\n font-weight: 600;\n }\n button[marked] span {\n flex: none;\n font-size: 0.72rem;\n color: var(--md-sys-color-primary);\n }\n button[marked] em {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 부품마다 한 줄. 자리를 고른 것만 아래로 펼친다 */\n div[part] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 6px 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n div[part]:first-of-type {\n border-top: none;\n }\n div[head] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n div[head] b {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n }\n\n select,\n input[type='text'],\n input[type='number'] {\n padding: 4px 6px;\n font: var(--input-field-font, inherit);\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n /* 이름이 길어질 수 있는 쪽(문)까지 안 잘리는 폭이다 */\n div[head] select {\n flex: none;\n width: 8.5em;\n }\n\n div[detail] {\n display: grid;\n grid-template-columns: 4.5em 1fr;\n align-items: center;\n gap: 4px var(--spacing-medium, 8px);\n padding-left: var(--spacing-medium, 8px);\n }\n div[detail] > span {\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[detail] input {\n width: 100%;\n box-sizing: border-box;\n }\n div[detail] em {\n grid-column: 2;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n `\n ]\n\n @property({ type: Object }) board?: BoardModel\n @property({ type: Array }) parts: PartModel[] = []\n\n render() {\n const rung = rungOf(this.board?.capabilities)\n const names = this.board?.capabilities ?? []\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.what-this-does')}</h3>\n\n ${this.rung('none', rung)} ${this.rung('hold', rung)} ${this.rung('flow', rung)}\n ${rung === null\n ? html`<p quiet>${i18next.t('figure.text.capability-hand-written')}</p>`\n : ''}\n ${names.length > 0 ? html`<code names>${names.join(' · ')}</code>` : ''}\n </section>\n\n ${this.renderAnchors(rung)}\n `\n }\n\n /**\n * 자리와 문 — **여기서는 읽기만 한다.**\n *\n * 지정은 속성 탭에서 한다. 부품을 고치는 곳이 한 군데여야 하기 때문이다 — 목록을 여기 또\n * 두면 같은 부품을 두 화면에서 고치게 되고, 저작자는 어느 쪽이 이기는지 매번 생각해야 한다.\n *\n * 그래도 요약은 남긴다. 「어느 부품이 자리인가」는 부품을 하나씩 눌러 보며 알아낼 일이\n * 아니다 — 자리는 그려지지 않으므로 화면을 봐서는 영영 못 찾는다.\n */\n private renderAnchors(rung: Rung | null) {\n if (rung === 'none') {\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.slots-need-capability')}</p>\n </section>\n `\n }\n\n const marked = this.parts.filter(part => part.capability)\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.anchors-are-not-drawn')}</p>\n\n ${marked.length === 0\n ? html`<p quiet>${i18next.t('figure.text.designate-in-properties')}</p>`\n : marked.map(part => this.renderMarked(part))}\n </section>\n `\n }\n\n /** 지정된 부품 한 줄. 누르면 그 부품이 골라진다 — 고치는 자리는 속성 탭이다. */\n private renderMarked(part: PartModel) {\n const spec = part.capability!\n\n return html`\n <button marked @click=${() => this.pick(part)}>\n <b>${part.name}</b>\n <span>${spec.roles.map(role => i18next.t('figure.label.role-' + role)).join(' · ')}</span>\n ${spec.accepts?.length ? html`<em>${spec.accepts.join(' · ')}</em>` : ''}\n </button>\n `\n }\n\n /** 이 부품을 고르게 한다. 부품 목록에서 누른 것과 같은 알림이다. */\n private pick(part: PartModel) {\n const index = this.parts.indexOf(part)\n if (index < 0) return\n\n this.dispatchEvent(new CustomEvent('select-part', { detail: { index }, bubbles: true, composed: true }))\n }\n\n /**\n * 사다리 한 칸.\n *\n * `checked` 를 `live()` 로 묶는다. 그냥 두면 Lit 이 **마지막으로 그린 값**과 견주므로,\n * 브라우저가 DOM 을 바꿨는데 모델은 안 바뀐 경우 되돌리지 않는다. 실제로 그랬다 — 고르는\n * 도중 도형이 다시 실렸더니 점은 「흐른다」에 있고 강조 상자는 「그리기만 한다」에 있었다.\n * 화면이 모델과 다른 말을 하는 상태다. `live()` 는 **지금 DOM 의 값**과 견준다.\n */\n private rung(which: Rung, chosen: Rung | null) {\n return html`\n <label rung ?on=${chosen === which}>\n <input\n type=\"radio\"\n name=\"rung\"\n .checked=${live(chosen === which)}\n @change=${() => this.setRung(which)}\n />\n <span>\n <strong>${i18next.t(`figure.label.rung-${which}`)}</strong>\n <em>${i18next.t(`figure.text.rung-${which}-note`)}</em>\n </span>\n </label>\n `\n }\n\n /**\n * 사다리를 내리면 자리도 함께 거둔다.\n *\n * 능력을 「그리기만 한다」로 되돌려 놓고 부품에 자리 표시가 남아 있으면, 형식은 자리를\n * 그대로 실어 보내는데 씬은 그것을 쓸 클래스가 없다. **선언은 있고 길이 없는** 상태가\n * 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.\n */\n private setRung(which: Rung) {\n const board = this.board\n if (!board) return\n\n this.tellBoard({ ...board, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined })\n\n if (which === 'none') {\n this.parts.forEach((part, index) => {\n if (!part.capability) return\n const { capability, ...rest } = part\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index, part: rest }, bubbles: true, composed: true })\n )\n })\n }\n }\n\n private tellBoard(board: BoardModel) {\n this.board = board\n this.dispatchEvent(new CustomEvent('update-board', { detail: { board }, bubbles: true, composed: true }))\n }\n}\n"]}
@@ -21,7 +21,7 @@ declare const FigureHistoryPanel_base: (new (...args: any[]) => LitElement) & ty
21
21
  * 않는다 — 다시 내보내려면 발행을 한 번 더 하고, 그때 번호는 이미 쓴 다음 번호를 받는다.
22
22
  */
23
23
  export declare class FigureHistoryPanel extends FigureHistoryPanel_base {
24
- static styles: import("lit").CSSResult;
24
+ static styles: import("lit").CSSResult[];
25
25
  /** 지금 열려 있는 도형. 없으면(새로 만드는 중) 발행할 것이 없다. */
26
26
  figureId?: string;
27
27
  figureState?: 'draft' | 'released';
@@ -31,10 +31,24 @@ export declare class FigureHistoryPanel extends FigureHistoryPanel_base {
31
31
  private versions;
32
32
  private busy;
33
33
  private failure;
34
+ /**
35
+ * 발행 판정. **저장된 정본에 대한 것**이라 저장할 때마다 다시 묻는다(`figureVersion` 이 아니라
36
+ * `dirty` 가 풀리는 순간이 그때다).
37
+ */
38
+ private inspection?;
34
39
  updated(changes: Map<string, unknown>): void;
40
+ private inspect;
35
41
  private reload;
36
42
  render(): import("lit-html").TemplateResult<1>;
37
43
  private renderRelease;
44
+ /**
45
+ * 사유 한 줄 — **잰 것 · 왜 · 어떻게** 셋을 함께 말한다.
46
+ *
47
+ * 잰 것만 보여 줬더니 「1 조각이 2 조각으로 갈라진다」에서 사람이 멈췄다. 왜 나쁜지도, 무엇을
48
+ * 바꾸면 되는지도 알 수 없었다. 그 셋은 형식(`figure-model` 의 gate)이 만들고 여기서는 자리만
49
+ * 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.
50
+ */
51
+ private renderFinding;
38
52
  private renderVersion;
39
53
  private release;
40
54
  private revert;