@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
@@ -4,9 +4,10 @@ import { css, html, LitElement, nothing } from 'lit'
4
4
  import { customElement, property, state } from 'lit/decorators.js'
5
5
 
6
6
  import { i18next, localize } from '@operato/i18n'
7
+ import { ScrollbarStyles } from '@operato/styles'
7
8
 
8
- import { fetchFigureVersions, releaseFigure, revertFigureVersion } from '../graphql/index.js'
9
- import type { FigureVersion } from '../types.js'
9
+ import { fetchFigureVersions, inspectFigure, releaseFigure, revertFigureVersion } from '../graphql/index.js'
10
+ import type { FigureGateFinding, FigureInspection, FigureVersion } from '../types.js'
10
11
 
11
12
  /**
12
13
  * 이력 — **발행하는 자리이자, 발행된 판들을 보는 자리.**
@@ -29,7 +30,9 @@ import type { FigureVersion } from '../types.js'
29
30
  */
30
31
  @customElement('figure-history-panel')
31
32
  export class FigureHistoryPanel extends localize(i18next)(LitElement) {
32
- static styles = css`
33
+ static styles = [
34
+ ScrollbarStyles,
35
+ css`
33
36
  :host {
34
37
  display: flex;
35
38
  flex-direction: column;
@@ -184,12 +187,79 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
184
187
  border-color: transparent;
185
188
  }
186
189
 
190
+ /*
191
+ 게이트 사유. 막는 것은 색으로도 말하고, 알리는 것은 조용히 둔다 — 둘을 같은 톤으로 두면
192
+ 「이 중 어느 것이 발행을 막았나」를 사람이 다시 세어야 한다.
193
+ */
194
+ ul[gate] {
195
+ margin: var(--spacing-small, 6px) 0 0;
196
+ padding: 0;
197
+ list-style: none;
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 3px;
201
+ }
202
+
203
+ li[head] {
204
+ font: var(--label-font, inherit);
205
+ font-size: 0.72rem;
206
+ color: var(--md-sys-color-error);
207
+ }
208
+
209
+ li[head][quiet] {
210
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
211
+ }
212
+
213
+ li[finding] {
214
+ display: flex;
215
+ flex-direction: column;
216
+ align-items: flex-start;
217
+ gap: 3px;
218
+ padding: 3px 6px;
219
+ font-size: 0.72rem;
220
+ line-height: 1.5;
221
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
222
+ background-color: var(--md-sys-color-surface-container);
223
+ border-radius: 4px;
224
+ }
225
+
226
+ li[finding][blocking] {
227
+ color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));
228
+ background-color: var(--md-sys-color-error-container);
229
+ }
230
+
231
+ li[finding] code[where] {
232
+ font-family: var(--mono-font, monospace);
233
+ font-size: 0.68rem;
234
+ opacity: 0.85;
235
+ }
236
+
237
+ /* 왜 나쁜지는 곁글로 — 읽어야 하지만 잰 것보다 먼저 눈에 들 필요는 없다 */
238
+ li[finding] span[why-line] {
239
+ opacity: 0.8;
240
+ }
241
+
242
+ /* 무엇을 바꾸면 되나 — 이 줄이 실제로 사람이 할 일이다. 연장 표를 달아 눈에 걸리게 한다 */
243
+ li[finding] span[how-line] {
244
+ display: flex;
245
+ align-items: flex-start;
246
+ gap: 4px;
247
+ padding-top: 2px;
248
+ font-weight: 600;
249
+ }
250
+ li[finding] span[how-line] md-icon {
251
+ --md-icon-size: 13px;
252
+ flex: none;
253
+ margin-top: 1px;
254
+ }
255
+
187
256
  p[quiet] {
188
257
  margin: 0;
189
258
  line-height: 1.6;
190
259
  color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
191
260
  }
192
261
  `
262
+ ]
193
263
 
194
264
  /** 지금 열려 있는 도형. 없으면(새로 만드는 중) 발행할 것이 없다. */
195
265
  @property({ type: String }) figureId?: string
@@ -201,11 +271,39 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
201
271
  @state() private versions: FigureVersion[] = []
202
272
  @state() private busy = false
203
273
  @state() private failure = ''
274
+ /**
275
+ * 발행 판정. **저장된 정본에 대한 것**이라 저장할 때마다 다시 묻는다(`figureVersion` 이 아니라
276
+ * `dirty` 가 풀리는 순간이 그때다).
277
+ */
278
+ @state() private inspection?: FigureInspection
204
279
 
205
280
  updated(changes: Map<string, unknown>) {
206
281
  if (changes.has('figureId') || changes.has('figureVersion')) {
207
282
  void this.reload()
208
283
  }
284
+
285
+ /*
286
+ 저장이 끝난 순간(dirty 가 풀림) 정본이 바뀌었으니 판정을 다시 묻는다. 저작 중(dirty)에는
287
+ 묻지 않는다 — 서버에 있는 것은 저장 전 정본이라, 그 답은 화면에 있는 것과 다른 것을 말한다.
288
+ */
289
+ if (changes.has('figureId') || changes.has('dirty')) {
290
+ void this.inspect()
291
+ }
292
+ }
293
+
294
+ private async inspect() {
295
+ if (!this.figureId || this.dirty) {
296
+ this.inspection = undefined
297
+ return
298
+ }
299
+
300
+ try {
301
+ this.inspection = await inspectFigure(this.figureId)
302
+ } catch (e) {
303
+ /* 판정을 못 받은 것을 「통과」로 두지 않는다 — 모르는 것이다. 단추는 서버가 막는다. */
304
+ this.inspection = undefined
305
+ this.failure = (e as Error).message
306
+ }
209
307
  }
210
308
 
211
309
  private async reload() {
@@ -225,57 +323,105 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
225
323
  render() {
226
324
  return html`
227
325
  <section>
228
- <h3>${i18next.t('label.release')}</h3>
229
- ${this.figureId ? this.renderRelease() : html`<p quiet>${i18next.t('text.save-the-figure-before-releasing')}</p>`}
326
+ <h3>${i18next.t('figure.label.release')}</h3>
327
+ ${this.figureId ? this.renderRelease() : html`<p quiet>${i18next.t('figure.text.save-the-figure-before-releasing')}</p>`}
230
328
  </section>
231
329
 
232
330
  <section>
233
- <h3>${i18next.t('label.released-versions')}</h3>
331
+ <h3>${i18next.t('figure.label.released-versions')}</h3>
234
332
  ${this.versions.length
235
333
  ? html`<ul>
236
334
  ${this.versions.map(version => this.renderVersion(version))}
237
335
  </ul>`
238
- : html`<p quiet>${i18next.t('text.nothing-released-yet')}</p>`}
336
+ : html`<p quiet>${i18next.t('figure.text.nothing-released-yet')}</p>`}
239
337
  </section>
240
338
  `
241
339
  }
242
340
 
243
341
  private renderRelease() {
244
342
  const released = this.figureState === 'released'
245
- /* 막는 이유는 하나씩 말한다. 「발행할 수 없습니다」로 뭉치면 무엇을 하면 되는지 모른다. */
343
+
344
+ /*
345
+ 게이트가 막는 것과 알리는 것을 함께 낸다. **막는 것은 단추를 잠근다** — 눌러 보고 서버에게
346
+ 거절당하는 것으로 알게 하지 않는다. 알리는 것은 잠그지 않고 그대로 보여 준다.
347
+ */
348
+ const blocking = (this.inspection?.findings ?? []).filter(finding => finding.blocking)
349
+ const advisory = (this.inspection?.findings ?? []).filter(finding => !finding.blocking)
350
+
351
+ /*
352
+ ── 「발행되나」에 **한 번만** 답한다 ─────────────────────────────────────
353
+ 전에는 막는 목록의 머리가 「이것들이 발행을 막습니다」, 알리는 목록의 머리가 「발행은 되지만
354
+ 확인해 보십시오」였다. 두 줄이 나란히 서서 화면이 「막힌다」와 「된다」를 함께 말했고, 사람이
355
+ 「그래서 되는 건가」를 되물었다.
356
+
357
+ 답은 이 한 줄이 하고, 아래 목록들은 **이유**일 뿐이다 — 그래서 목록 머리는 문장이 아니라
358
+ 이름씨다(「고쳐야 하는 것」·「막지는 않는 것」).
359
+ */
246
360
  const why = this.dirty
247
- ? i18next.t('text.release-needs-a-save-first')
361
+ ? i18next.t('figure.text.release-needs-a-save-first')
248
362
  : released
249
- ? i18next.t('text.already-released-edit-to-release-again')
250
- : ''
363
+ ? i18next.t('figure.text.already-released-edit-to-release-again')
364
+ : blocking.length
365
+ ? i18next.t('figure.text.cannot-release-now')
366
+ : ''
251
367
 
252
368
  return html`
253
369
  <div now>
254
- <span state ?released=${released}>${i18next.t(released ? 'label.released' : 'label.draft')}</span>
255
- <span ver>${i18next.t('text.version-number', { version: this.figureVersion ?? 0 })}</span>
370
+ <span state ?released=${released}>${i18next.t(released ? 'figure.label.figure-released' : 'figure.label.draft')}</span>
371
+ <span ver>${i18next.t('figure.text.version-number', { version: this.figureVersion ?? 0 })}</span>
256
372
  </div>
257
373
 
258
- <input note placeholder=${i18next.t('text.what-changed-in-this-release')} ?disabled=${!!why || this.busy} />
374
+ <input note placeholder=${i18next.t('figure.text.what-changed-in-this-release')} ?disabled=${!!why || this.busy} />
259
375
 
260
376
  <button release ?disabled=${!!why || this.busy} @click=${() => this.release()}>
261
- ${this.busy ? i18next.t('text.releasing') : i18next.t('button.release')}
377
+ ${this.busy ? i18next.t('figure.text.releasing') : i18next.t('figure.button.release-figure')}
262
378
  </button>
263
379
 
264
380
  ${why ? html`<p why>${why}</p>` : nothing}
381
+ ${blocking.length
382
+ ? html`<ul gate>
383
+ <li head>${i18next.t('figure.text.must-fix-to-release')}</li>
384
+ ${blocking.map(finding => this.renderFinding(finding, true))}
385
+ </ul>`
386
+ : nothing}
387
+ ${advisory.length
388
+ ? html`<ul gate>
389
+ <li head quiet>${i18next.t('figure.text.does-not-block')}</li>
390
+ ${advisory.map(finding => this.renderFinding(finding, false))}
391
+ </ul>`
392
+ : nothing}
265
393
  ${this.failure ? html`<p failed>${this.failure}</p>` : nothing}
266
394
  `
267
395
  }
268
396
 
397
+ /**
398
+ * 사유 한 줄 — **잰 것 · 왜 · 어떻게** 셋을 함께 말한다.
399
+ *
400
+ * 잰 것만 보여 줬더니 「1 조각이 2 조각으로 갈라진다」에서 사람이 멈췄다. 왜 나쁜지도, 무엇을
401
+ * 바꾸면 되는지도 알 수 없었다. 그 셋은 형식(`figure-model` 의 gate)이 만들고 여기서는 자리만
402
+ * 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.
403
+ */
404
+ private renderFinding(finding: FigureGateFinding, blocking: boolean) {
405
+ return html`
406
+ <li finding ?blocking=${blocking}>
407
+ <span msg>${finding.message}</span>
408
+ ${finding.at ? html`<code where>${finding.at}</code>` : nothing}
409
+ ${finding.why ? html`<span why-line>${finding.why}</span>` : nothing}
410
+ ${finding.how ? html`<span how-line><md-icon>build</md-icon>${finding.how}</span>` : nothing}
411
+ </li>
412
+ `
413
+ }
414
+
269
415
  private renderVersion(version: FigureVersion) {
270
416
  return html`
271
417
  <li>
272
- <span no>${i18next.t('text.version-number', { version: version.version })}</span>
418
+ <span no>${i18next.t('figure.text.version-number', { version: version.version })}</span>
273
419
  <span when>
274
420
  ${version.updatedAt ? new Date(version.updatedAt).toLocaleString() : ''}
275
421
  ${version.updater?.name ? ` · ${version.updater.name}` : ''}
276
422
  </span>
277
423
  <button revert ?disabled=${this.busy} @click=${() => this.revert(version.version)}>
278
- ${i18next.t('button.revert')}
424
+ ${i18next.t('figure.button.revert')}
279
425
  </button>
280
426
  ${version.comment ? html`<span comment>${version.comment}</span>` : nothing}
281
427
  </li>
@@ -295,6 +441,7 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
295
441
  /* 정본을 든 자리가 갱신한다 — 여기서 페이지 상태를 고쳐 쓰지 않는다. */
296
442
  this.dispatchEvent(new CustomEvent('figure-released', { detail: { figure }, bubbles: true, composed: true }))
297
443
  await this.reload()
444
+ await this.inspect()
298
445
  } catch (e) {
299
446
  this.failure = (e as Error).message
300
447
  } finally {
@@ -304,7 +451,7 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
304
451
 
305
452
  private async revert(version: number) {
306
453
  /* 되돌아갈 수 없는 조작은 아니지만(판본은 남는다), 지금 화면의 것이 덮인다 — 이름으로 확인한다. */
307
- if (!confirm(i18next.t('text.revert-to-version-confirm', { version }))) {
454
+ if (!confirm(i18next.t('figure.text.revert-to-version-confirm', { version }))) {
308
455
  return
309
456
  }
310
457
 
@@ -315,6 +462,12 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
315
462
  const result = await revertFigureVersion(this.figureId!, version)
316
463
  this.dispatchEvent(new CustomEvent('figure-reverted', { detail: result, bubbles: true, composed: true }))
317
464
  await this.reload()
465
+ /*
466
+ **되돌리면 정본이 바뀌므로 판정도 바뀐다.** 이것을 안 하면 옛 판의 사유가 새 정본 위에
467
+ 남아 화면이 거짓말한다 — 되돌리기는 `dirty` 도 `figureVersion` 도 건드리지 않으므로
468
+ `updated` 의 감시로는 다시 묻지 않는다.
469
+ */
470
+ await this.inspect()
318
471
  } catch (e) {
319
472
  this.failure = (e as Error).message
320
473
  } finally {