@things-factory/figure-library 10.1.121

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 (66) hide show
  1. package/dist-client/bootstrap.d.ts +1 -0
  2. package/dist-client/bootstrap.js +2 -0
  3. package/dist-client/bootstrap.js.map +1 -0
  4. package/dist-client/index.d.ts +1 -0
  5. package/dist-client/index.js +2 -0
  6. package/dist-client/index.js.map +1 -0
  7. package/dist-client/pages/figure-library-page.d.ts +78 -0
  8. package/dist-client/pages/figure-library-page.js +781 -0
  9. package/dist-client/pages/figure-library-page.js.map +1 -0
  10. package/dist-client/route.d.ts +1 -0
  11. package/dist-client/route.js +8 -0
  12. package/dist-client/route.js.map +1 -0
  13. package/dist-client/tsconfig.tsbuildinfo +1 -0
  14. package/dist-server/index.d.ts +2 -0
  15. package/dist-server/index.js +6 -0
  16. package/dist-server/index.js.map +1 -0
  17. package/dist-server/routers/figure-library-file-router.d.ts +5 -0
  18. package/dist-server/routers/figure-library-file-router.js +43 -0
  19. package/dist-server/routers/figure-library-file-router.js.map +1 -0
  20. package/dist-server/routes.d.ts +1 -0
  21. package/dist-server/routes.js +16 -0
  22. package/dist-server/routes.js.map +1 -0
  23. package/dist-server/service/figure-library-release/figure-library-release.d.ts +54 -0
  24. package/dist-server/service/figure-library-release/figure-library-release.js +206 -0
  25. package/dist-server/service/figure-library-release/figure-library-release.js.map +1 -0
  26. package/dist-server/service/figure-library-resolver.d.ts +20 -0
  27. package/dist-server/service/figure-library-resolver.js +196 -0
  28. package/dist-server/service/figure-library-resolver.js.map +1 -0
  29. package/dist-server/service/figure-library-types.d.ts +23 -0
  30. package/dist-server/service/figure-library-types.js +85 -0
  31. package/dist-server/service/figure-library-types.js.map +1 -0
  32. package/dist-server/service/figure-type-map/figure-type-map-entry.d.ts +23 -0
  33. package/dist-server/service/figure-type-map/figure-type-map-entry.js +98 -0
  34. package/dist-server/service/figure-type-map/figure-type-map-entry.js.map +1 -0
  35. package/dist-server/service/figure-type-map/figure-type-map.d.ts +34 -0
  36. package/dist-server/service/figure-type-map/figure-type-map.js +120 -0
  37. package/dist-server/service/figure-type-map/figure-type-map.js.map +1 -0
  38. package/dist-server/service/index.d.ts +20 -0
  39. package/dist-server/service/index.js +28 -0
  40. package/dist-server/service/index.js.map +1 -0
  41. package/dist-server/service/library/blueprints.d.ts +28 -0
  42. package/dist-server/service/library/blueprints.js +53 -0
  43. package/dist-server/service/library/blueprints.js.map +1 -0
  44. package/dist-server/service/library/board-walk.d.ts +1 -0
  45. package/dist-server/service/library/board-walk.js +31 -0
  46. package/dist-server/service/library/board-walk.js.map +1 -0
  47. package/dist-server/service/library/derived.d.ts +16 -0
  48. package/dist-server/service/library/derived.js +45 -0
  49. package/dist-server/service/library/derived.js.map +1 -0
  50. package/dist-server/service/library/import.d.ts +30 -0
  51. package/dist-server/service/library/import.js +116 -0
  52. package/dist-server/service/library/import.js.map +1 -0
  53. package/dist-server/service/library/inspection.d.ts +63 -0
  54. package/dist-server/service/library/inspection.js +144 -0
  55. package/dist-server/service/library/inspection.js.map +1 -0
  56. package/dist-server/service/library/library-files.d.ts +32 -0
  57. package/dist-server/service/library/library-files.js +58 -0
  58. package/dist-server/service/library/library-files.js.map +1 -0
  59. package/dist-server/service/library/type-map.d.ts +35 -0
  60. package/dist-server/service/library/type-map.js +133 -0
  61. package/dist-server/service/library/type-map.js.map +1 -0
  62. package/dist-server/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +51 -0
  64. package/things-factory.config.js +13 -0
  65. package/translations/en.json +98 -0
  66. package/translations/ko.json +98 -0
@@ -0,0 +1,781 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@operato/layout';
3
+ import '@operato/list-state/ox-page-state.js';
4
+ import gql from 'graphql-tag';
5
+ import { css, html, nothing } from 'lit';
6
+ import { customElement, state } from 'lit/decorators.js';
7
+ import { client } from '@operato/graphql';
8
+ import { i18next, localize } from '@operato/i18n';
9
+ import { PageView } from '@operato/shell';
10
+ import { actionHeight, DocumentPageStyles, FormFieldStyles, ScrollbarStyles } from '@operato/styles';
11
+ import { getPathInfo } from '@operato/utils';
12
+ /*
13
+ * Screen layout (operato-application/design/ui/page-standard.md): document frame, the tab in the address
14
+ * (ox-url-tabs), one section per thing the operator looks at, and the decisions of each tab in the shell's
15
+ * action bar - upload on the library tab; save, check, preview and activate on the type map tab. Activate
16
+ * stays off until the candidate has been checked, and the button says so.
17
+ *
18
+ * The board server's figures (ADR-0114): the library of imported releases, and the domain type map that picks the
19
+ * release each figure draws with. Importing puts a release into the candidate; the candidate is checked against the
20
+ * domain's boards and activated; an archived map is activated again to roll back.
21
+ */
22
+ const RELEASES = gql `
23
+ query {
24
+ figureLibraryReleases(pagination: { page: 1, limit: 1000 }, sortings: [{ name: "ref" }]) {
25
+ items {
26
+ id
27
+ name
28
+ ref
29
+ namespace
30
+ type
31
+ version
32
+ formatSupported
33
+ deprecated
34
+ supersededBy
35
+ dependencies
36
+ sourceUrl
37
+ createdAt
38
+ }
39
+ total
40
+ }
41
+ }
42
+ `;
43
+ const MAPS = gql `
44
+ query {
45
+ figureTypeMaps {
46
+ version
47
+ name
48
+ state
49
+ revision
50
+ activatedAt
51
+ }
52
+ }
53
+ `;
54
+ const LINES = gql `
55
+ query ($version: Int!) {
56
+ figureTypeMapEntries(version: $version) {
57
+ name
58
+ release {
59
+ id
60
+ ref
61
+ }
62
+ }
63
+ }
64
+ `;
65
+ const CHECK = gql `
66
+ query {
67
+ figureTypeMapCheck
68
+ }
69
+ `;
70
+ const IMPORT = gql `
71
+ mutation ($file: Upload!) {
72
+ importFigurePackage(file: $file) {
73
+ problems {
74
+ code
75
+ path
76
+ }
77
+ outcomes {
78
+ ref
79
+ outcome
80
+ code
81
+ differs
82
+ }
83
+ }
84
+ }
85
+ `;
86
+ const SAVE = gql `
87
+ mutation ($lines: [FigureTypeMapLineInput!]!) {
88
+ saveFigureTypeMapCandidate(lines: $lines) {
89
+ version
90
+ revision
91
+ }
92
+ }
93
+ `;
94
+ const ACTIVATE = gql `
95
+ mutation ($version: Int!) {
96
+ activateFigureTypeMap(version: $version) {
97
+ version
98
+ state
99
+ }
100
+ }
101
+ `;
102
+ const fullName = (release) => `${release.namespace}/${release.type}`;
103
+ /** A time in the reader's language (a bare toLocaleString follows the browser, not the app). */
104
+ const when = (value) => (value ? new Date(value).toLocaleString(i18next.language) : '');
105
+ /**
106
+ * A contract change in words. figure-model gives a code and values; `detail` is English for logs and is
107
+ * the sentence only when this module has no words for a new code (open question P8 in figure-library.md).
108
+ */
109
+ const changeText = (change) => change.code
110
+ ? i18next.t(`figure-library.contract.${change.code}`, { ...(change.values ?? {}), defaultValue: change.detail ?? change.code })
111
+ : change.detail ?? '';
112
+ let FigureLibraryPage = class FigureLibraryPage extends localize(i18next)(PageView) {
113
+ constructor() {
114
+ super(...arguments);
115
+ this.tab = 'library';
116
+ this.releases = [];
117
+ this.maps = [];
118
+ this.candidate = new Map();
119
+ /** The candidate's lines as last saved — to tell an unsaved choice from a saved one. */
120
+ this.saved = new Map();
121
+ this.busy = false;
122
+ this.loading = true;
123
+ /** The first read failed; nothing is drawn yet, so the whole screen says it could not read. */
124
+ this.loadError = null;
125
+ /** A later step failed (save, activate, a re-read); what is drawn stays, this line says why. */
126
+ this.failure = '';
127
+ this.importFile = async (event) => {
128
+ const input = event.target;
129
+ const file = input.files?.[0];
130
+ if (!file)
131
+ return;
132
+ this.busy = true;
133
+ try {
134
+ const response = await client.mutate({ mutation: IMPORT, variables: { file }, context: { hasUpload: true } });
135
+ this.imported = response.data.importFigurePackage;
136
+ await this.refresh();
137
+ }
138
+ catch (e) {
139
+ this.imported = { problems: [{ code: e?.message ?? 'error' }], outcomes: [] };
140
+ }
141
+ finally {
142
+ this.busy = false;
143
+ input.value = '';
144
+ }
145
+ };
146
+ /* A window of its own draws the candidate: its figures never mix with the active map of this one (ADR-0114 결정 12). */
147
+ this.preview = () => {
148
+ const { contextPath } = getPathInfo(location.pathname);
149
+ window.open(`${contextPath}/board-list?figure-map=candidate`, '_blank', 'noopener');
150
+ };
151
+ this.saveCandidate = async () => {
152
+ this.busy = true;
153
+ try {
154
+ const lines = [...this.candidate].map(([name, releaseId]) => ({ name, releaseId }));
155
+ await client.mutate({ mutation: SAVE, variables: { lines } });
156
+ this.report = undefined;
157
+ await this.refresh();
158
+ }
159
+ catch (e) {
160
+ this.failure = e?.message ?? '';
161
+ }
162
+ finally {
163
+ this.busy = false;
164
+ }
165
+ };
166
+ this.check = async () => {
167
+ this.busy = true;
168
+ try {
169
+ const response = await client.query({ query: CHECK, fetchPolicy: 'network-only' });
170
+ if (response.errors?.length)
171
+ throw new Error(response.errors[0].message);
172
+ this.report = response.data.figureTypeMapCheck;
173
+ this.failure = '';
174
+ }
175
+ catch (e) {
176
+ this.failure = e?.message ?? '';
177
+ }
178
+ finally {
179
+ this.busy = false;
180
+ }
181
+ };
182
+ }
183
+ static { this.styles = [
184
+ ScrollbarStyles,
185
+ DocumentPageStyles,
186
+ FormFieldStyles,
187
+ css `
188
+ :host {
189
+ color: var(--ui-ink, var(--md-sys-color-on-surface));
190
+ font-size: var(--ui-t-body, 13px);
191
+ line-height: 1.5;
192
+ }
193
+
194
+ /* 표 — 격자와 같은 모양(머리글 · 줄 여백 · 경계선이 --grid-* 토큰). */
195
+ table {
196
+ width: 100%;
197
+ border-collapse: collapse;
198
+ font-size: var(--grid-record-fontsize, 14px);
199
+ }
200
+
201
+ th {
202
+ padding: var(--spacing-small, 4px) var(--spacing-medium, 8px);
203
+ border-top: var(--grid-header-top-border, 2px solid var(--md-sys-color-primary));
204
+ border-bottom: var(--grid-header-bottom-border, 1px solid var(--md-sys-color-outline-variant));
205
+ background: var(--grid-header-background-color, var(--md-sys-color-surface-container-low));
206
+ font: var(--grid-header-font, 500 14px inherit);
207
+ color: var(--grid-header-color, var(--md-sys-color-primary));
208
+ text-align: left;
209
+ white-space: nowrap;
210
+ }
211
+
212
+ td {
213
+ padding: var(--grid-record-padding, 4px 8px);
214
+ border-bottom: var(--grid-record-border-bottom, 1px solid var(--md-sys-color-outline-variant));
215
+ vertical-align: middle;
216
+ word-break: keep-all;
217
+ overflow-wrap: anywhere;
218
+ }
219
+
220
+ td.thumb {
221
+ width: 64px;
222
+ }
223
+
224
+ img[thumb] {
225
+ display: block;
226
+ width: 48px;
227
+ height: 36px;
228
+ object-fit: contain;
229
+ }
230
+
231
+ /* 식별자(정식 이름 · 발행본 참조)는 고정폭 숫자, 줄을 바꾸지 않는다. */
232
+ .ref {
233
+ font-family: var(--ui-mono, ui-monospace, Menlo, monospace);
234
+ font-size: var(--ui-t-small, 11.5px);
235
+ white-space: nowrap;
236
+ }
237
+
238
+ .sub {
239
+ display: block;
240
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
241
+ font-size: var(--ui-t-small, 11.5px);
242
+ }
243
+
244
+ .when {
245
+ font-variant-numeric: tabular-nums;
246
+ white-space: nowrap;
247
+ }
248
+
249
+ /* 상태 표 — 같은 줄에 둘 이상 붙을 수 있다(형식 미지원 · 퇴역). */
250
+ .marks {
251
+ display: flex;
252
+ flex-wrap: wrap;
253
+ gap: var(--spacing-small, 4px);
254
+ }
255
+
256
+ .mark {
257
+ padding: 1px var(--spacing-medium, 8px);
258
+ border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
259
+ border-radius: 999px;
260
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
261
+ font-size: var(--ui-t-small, 11.5px);
262
+ white-space: nowrap;
263
+ }
264
+
265
+ .mark.crit {
266
+ border-color: color-mix(in srgb, var(--ui-crit, var(--md-sys-color-error)) 40%, transparent);
267
+ color: var(--ui-crit, var(--md-sys-color-error));
268
+ }
269
+
270
+ .mark.on {
271
+ border-color: var(--ui-accent, var(--md-sys-color-primary));
272
+ color: var(--ui-accent, var(--md-sys-color-primary));
273
+ font-weight: 600;
274
+ }
275
+
276
+ /* 찾아낸 것 · 거부 — 왼쪽 선 하나. */
277
+ .finding {
278
+ margin: 0;
279
+ padding: var(--spacing-medium, 8px) 0 var(--spacing-medium, 8px) var(--spacing-large, 12px);
280
+ border-left: 2px solid var(--ui-crit, var(--md-sys-color-error));
281
+ }
282
+
283
+ .outcomes,
284
+ .report-list {
285
+ margin: 0;
286
+ padding: 0;
287
+ list-style: none;
288
+ }
289
+
290
+ .outcomes > li,
291
+ .report-list > li {
292
+ display: flex;
293
+ flex-wrap: wrap;
294
+ align-items: baseline;
295
+ gap: var(--spacing-small, 4px) var(--spacing-large, 12px);
296
+ padding: var(--spacing-medium, 8px) 0;
297
+ border-top: 1px solid var(--ui-line-soft, var(--md-sys-color-surface-container-high));
298
+ }
299
+
300
+ .outcomes > li:first-child,
301
+ .report-list > li:first-child {
302
+ padding-top: 0;
303
+ border-top: none;
304
+ }
305
+
306
+ .outcome-refused {
307
+ color: var(--ui-crit, var(--md-sys-color-error));
308
+ font-weight: 600;
309
+ }
310
+
311
+ .changes {
312
+ flex-basis: 100%;
313
+ margin: 0;
314
+ padding-left: 1.2em;
315
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
316
+ }
317
+
318
+ .changes .breaking {
319
+ color: var(--ui-crit, var(--md-sys-color-error));
320
+ }
321
+
322
+ .report-group + .report-group {
323
+ margin-top: var(--spacing-huge, 24px);
324
+ }
325
+
326
+ .report-group h4 {
327
+ margin: 0 0 var(--spacing-medium, 8px);
328
+ font-size: var(--ui-t-body, 13px);
329
+ font-weight: 600;
330
+ }
331
+
332
+ select {
333
+ height: var(--form-element-height-medium, 30px);
334
+ min-width: 240px;
335
+ font-size: var(--ui-t-body, 13px);
336
+ }
337
+
338
+ .empty {
339
+ margin: 0;
340
+ padding: var(--spacing-huge, 24px);
341
+ border: 1px dashed var(--ui-line, var(--md-sys-color-outline-variant));
342
+ border-radius: var(--ui-r, 10px);
343
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
344
+ text-align: center;
345
+ }
346
+
347
+ /* Row buttons take the action-bar look until @operato/styles ButtonStyles is installed (ADR-0117). */
348
+ td button {
349
+ box-sizing: border-box;
350
+ height: ${actionHeight('--cell-action-height')};
351
+ padding: 0 var(--spacing-medium, 8px);
352
+ border: 1px solid var(--md-sys-color-outline-variant);
353
+ border-radius: var(--md-sys-shape-corner-small);
354
+ background: var(--md-sys-color-surface-container-lowest);
355
+ color: var(--md-sys-color-on-surface);
356
+ font: inherit;
357
+ font-size: var(--ui-t-body, 13px);
358
+ font-weight: 600;
359
+ white-space: nowrap;
360
+ cursor: pointer;
361
+ }
362
+
363
+ td button:hover:not(:disabled) {
364
+ background: var(--md-sys-color-secondary-container);
365
+ border-color: var(--md-sys-color-secondary);
366
+ }
367
+
368
+ td button:disabled {
369
+ opacity: 0.38;
370
+ cursor: default;
371
+ }
372
+
373
+ td.end {
374
+ text-align: right;
375
+ }
376
+ `
377
+ ]; }
378
+ get context() {
379
+ const candidate = this.maps.find(map => map.state === 'CANDIDATE');
380
+ const dirty = this.isDirty();
381
+ const actions = this.tab === 'library'
382
+ ? [
383
+ {
384
+ title: i18next.t('figure-library.button.upload'),
385
+ icon: 'upload_file',
386
+ action: () => this.chooseFile(),
387
+ disabled: this.busy,
388
+ emphasis: { raised: true }
389
+ }
390
+ ]
391
+ : [
392
+ {
393
+ title: i18next.t('figure-library.button.save-candidate'),
394
+ icon: 'save',
395
+ action: () => this.saveCandidate(),
396
+ /* With no candidate yet, saving is how one is made from the active map — so it stays on. */
397
+ disabled: this.busy || (!dirty && !!candidate)
398
+ },
399
+ {
400
+ title: i18next.t('figure-library.button.check'),
401
+ icon: 'fact_check',
402
+ action: () => this.check(),
403
+ disabled: this.busy || !candidate || dirty,
404
+ disabledTitle: dirty ? i18next.t('figure-library.text.save-before-check') : ''
405
+ },
406
+ {
407
+ title: i18next.t('figure-library.button.preview'),
408
+ icon: 'open_in_new',
409
+ action: () => this.preview(),
410
+ disabled: !candidate || dirty,
411
+ disabledTitle: dirty ? i18next.t('figure-library.text.save-before-check') : ''
412
+ },
413
+ {
414
+ title: i18next.t('figure-library.button.activate'),
415
+ icon: 'published_with_changes',
416
+ action: () => candidate && this.activate(candidate.version),
417
+ disabled: this.busy || !candidate || dirty || !this.report,
418
+ disabledTitle: i18next.t('figure-library.text.check-before-activate'),
419
+ emphasis: { raised: true }
420
+ }
421
+ ];
422
+ return {
423
+ title: i18next.t('figure-library.title'),
424
+ description: i18next.t('figure-library.description'),
425
+ frame: 'document',
426
+ actions
427
+ };
428
+ }
429
+ /*
430
+ * Opened straight from the address, pageUpdated runs before the page stands and the first read never
431
+ * starts - the screen sat on "loading" (before ox-page-state, on empty tables). pageInitialized reads too.
432
+ */
433
+ async pageInitialized() {
434
+ await this.enter();
435
+ }
436
+ async pageUpdated(changes, lifecycle) {
437
+ if (this.active)
438
+ await this.enter();
439
+ }
440
+ async enter() {
441
+ /* ox-url-tabs reads the address on connect but announces only later changes; the body follows the same address. */
442
+ this.tab = new URLSearchParams(location.search).get('tab') === 'type-map' ? 'type-map' : 'library';
443
+ await this.refresh();
444
+ }
445
+ updated(changes) {
446
+ /* The action bar follows the tab and what can be done now (dirty · checked · busy). */
447
+ if (['tab', 'busy', 'report', 'maps', 'candidate', 'saved'].some(key => changes.has(key)))
448
+ this.updateContext();
449
+ }
450
+ isDirty() {
451
+ if (this.candidate.size !== this.saved.size)
452
+ return true;
453
+ for (const [name, id] of this.candidate)
454
+ if (this.saved.get(name) !== id)
455
+ return true;
456
+ return false;
457
+ }
458
+ async refresh() {
459
+ try {
460
+ const [releases, maps] = await Promise.all([
461
+ client.query({ query: RELEASES, fetchPolicy: 'network-only' }),
462
+ client.query({ query: MAPS, fetchPolicy: 'network-only' })
463
+ ]);
464
+ if (releases.errors?.length || maps.errors?.length)
465
+ throw releases.errors?.length ? releases.errors : maps.errors;
466
+ this.releases = releases.data.figureLibraryReleases.items;
467
+ this.maps = maps.data.figureTypeMaps;
468
+ const candidate = this.maps.find(map => map.state === 'CANDIDATE') ?? this.maps.find(map => map.state === 'ACTIVE');
469
+ let lines = new Map();
470
+ if (candidate) {
471
+ const entries = await client.query({ query: LINES, variables: { version: candidate.version }, fetchPolicy: 'network-only' });
472
+ lines = new Map(entries.data.figureTypeMapEntries.map((line) => [line.name, line.release?.id]));
473
+ }
474
+ this.candidate = lines;
475
+ this.saved = new Map(lines);
476
+ this.failure = '';
477
+ this.loadError = null;
478
+ }
479
+ catch (e) {
480
+ if (this.loading)
481
+ this.loadError = e;
482
+ else
483
+ this.failure = i18next.t('figure-library.text.load-failed');
484
+ }
485
+ finally {
486
+ this.loading = false;
487
+ }
488
+ }
489
+ render() {
490
+ return html `
491
+ <ox-page-state .loading=${this.loading} .error=${this.loadError} @retry=${() => this.refresh()}>
492
+ <div class="page">
493
+ <ox-url-tabs
494
+ .tabs=${[
495
+ { key: 'library', label: i18next.t('figure-library.tab.library') },
496
+ { key: 'type-map', label: i18next.t('figure-library.tab.type-map') }
497
+ ]}
498
+ @tab-change=${(e) => (this.tab = e.detail.key)}
499
+ ></ox-url-tabs>
500
+ <input id="file" type="file" accept=".figpkg,.zip" hidden @change=${this.importFile} />
501
+ ${this.failure ? html `<p class="finding" role="alert">${this.failure}</p>` : nothing}
502
+ ${this.tab === 'library' ? this.renderLibrary() : this.renderTypeMap()}
503
+ </div>
504
+ </ox-page-state>
505
+ `;
506
+ }
507
+ thumbnailOf(release) {
508
+ const hash = release.dependencies?.find(d => d.role === 'thumbnail')?.hash;
509
+ if (!hash)
510
+ return nothing;
511
+ const { contextPath } = getPathInfo(location.pathname);
512
+ return html `<img thumb alt="" src=${`${contextPath}/figure-library-file/${hash}`} />`;
513
+ }
514
+ chooseFile() {
515
+ ;
516
+ this.renderRoot.querySelector('#file')?.click();
517
+ }
518
+ renderLibrary() {
519
+ return html `
520
+ ${this.imported ? this.renderImported(this.imported) : nothing}
521
+
522
+ <section class="page-section">
523
+ <header class="page-section-header">
524
+ <h3>${i18next.t('figure-library.title.releases')}</h3>
525
+ </header>
526
+ ${this.releases.length
527
+ ? html `<p class="page-section-note">${i18next.t('figure-library.text.release-count', { count: this.releases.length })}</p>
528
+ <table>
529
+ <thead>
530
+ <tr>
531
+ <th></th>
532
+ <th>${i18next.t('figure-library.label.name')}</th>
533
+ <th>${i18next.t('figure-library.label.release')}</th>
534
+ <th>${i18next.t('figure-library.label.source')}</th>
535
+ <th>${i18next.t('figure-library.label.imported-at')}</th>
536
+ <th>${i18next.t('figure-library.label.state')}</th>
537
+ </tr>
538
+ </thead>
539
+ <tbody>
540
+ ${this.releases.map(release => html `
541
+ <tr>
542
+ <td class="thumb">${this.thumbnailOf(release)}</td>
543
+ <td>${release.name}<span class="sub ref">${fullName(release)}</span></td>
544
+ <td><span class="ref">${release.ref}</span></td>
545
+ <td>${release.sourceUrl ?? ''}</td>
546
+ <td class="when">${when(release.createdAt)}</td>
547
+ <td>
548
+ <span class="marks">
549
+ ${release.formatSupported
550
+ ? nothing
551
+ : html `<span class="mark crit">${i18next.t('figure-library.text.format-unsupported')}</span>`}
552
+ ${release.deprecated ? html `<span class="mark">${i18next.t('figure-library.text.deprecated')}</span>` : nothing}
553
+ </span>
554
+ </td>
555
+ </tr>
556
+ `)}
557
+ </tbody>
558
+ </table>`
559
+ : html `<p class="empty">${i18next.t('figure-library.text.no-releases')}</p>`}
560
+ </section>
561
+ `;
562
+ }
563
+ renderImported(result) {
564
+ return html `
565
+ <section class="page-section">
566
+ <header class="page-section-header">
567
+ <h3>${i18next.t('figure-library.title.import-result')}</h3>
568
+ </header>
569
+ ${result.problems.length
570
+ ? html `<p class="finding" role="alert">
571
+ ${i18next.t('figure-library.text.package-refused', { code: result.problems.map(p => p.code).join(', ') })}
572
+ </p>`
573
+ : html `<div class="page-card">
574
+ <ul class="outcomes">
575
+ ${result.outcomes.map(o => html `<li>
576
+ <span class="ref">${o.ref}</span>
577
+ <span class=${o.outcome === 'refused' ? 'outcome-refused' : ''}>${i18next.t(`figure-library.outcome.${o.outcome}`)}</span>
578
+ ${o.differs?.length
579
+ ? html `<span class="sub">${i18next.t('figure-library.text.differs', { fields: o.differs.join(', ') })}</span>`
580
+ : nothing}
581
+ ${o.outcome === 'refused' && o.code ? html `<span class="sub">${o.code}</span>` : nothing}
582
+ </li>`)}
583
+ </ul>
584
+ </div>`}
585
+ </section>
586
+ `;
587
+ }
588
+ renderTypeMap() {
589
+ const candidate = this.maps.find(map => map.state === 'CANDIDATE');
590
+ const names = [...new Set(this.releases.map(fullName))].sort();
591
+ const dirty = this.isDirty();
592
+ return html `
593
+ <section class="page-section">
594
+ <header class="page-section-header">
595
+ <h3>${candidate ? i18next.t('figure-library.text.candidate', { name: candidate.name }) : i18next.t('figure-library.text.new-candidate')}</h3>
596
+ </header>
597
+ <p class="page-section-note">
598
+ ${dirty
599
+ ? i18next.t('figure-library.text.candidate-unsaved')
600
+ : this.report
601
+ ? i18next.t('figure-library.text.candidate-checked')
602
+ : i18next.t('figure-library.text.check-before-activate')}
603
+ </p>
604
+ ${names.length
605
+ ? html `<table>
606
+ <thead>
607
+ <tr>
608
+ <th>${i18next.t('figure-library.label.figure')}</th>
609
+ <th>${i18next.t('figure-library.label.release')}</th>
610
+ </tr>
611
+ </thead>
612
+ <tbody>
613
+ ${names.map(name => {
614
+ const versions = this.releases.filter(release => fullName(release) === name).sort((a, b) => b.version - a.version);
615
+ const chosen = this.candidate.get(name) ?? '';
616
+ return html `
617
+ <tr>
618
+ <td><span class="ref">${name}</span></td>
619
+ <td>
620
+ <select @change=${(e) => this.choose(name, e.target.value)}>
621
+ <option value="" ?selected=${!chosen}>${i18next.t('figure-library.text.not-in-map')}</option>
622
+ ${versions.map(release => html `<option value=${release.id} ?selected=${chosen === release.id}>${release.ref}</option>`)}
623
+ </select>
624
+ </td>
625
+ </tr>
626
+ `;
627
+ })}
628
+ </tbody>
629
+ </table>`
630
+ : html `<p class="empty">${i18next.t('figure-library.text.no-releases')}</p>`}
631
+ </section>
632
+
633
+ ${this.report ? this.renderReport(this.report) : nothing}
634
+
635
+ <section class="page-section">
636
+ <header class="page-section-header">
637
+ <h3>${i18next.t('figure-library.title.map-releases')}</h3>
638
+ </header>
639
+ ${this.maps.length
640
+ ? html `<table>
641
+ <thead>
642
+ <tr>
643
+ <th>${i18next.t('figure-library.label.type-map')}</th>
644
+ <th>${i18next.t('figure-library.label.state')}</th>
645
+ <th>${i18next.t('figure-library.label.activated-at')}</th>
646
+ <th></th>
647
+ </tr>
648
+ </thead>
649
+ <tbody>
650
+ ${this.maps.map(map => html `
651
+ <tr>
652
+ <td>${map.name}</td>
653
+ <td>
654
+ <span class="mark ${map.state === 'ACTIVE' ? 'on' : ''}">${i18next.t(`figure-library.state.${map.state.toLowerCase()}`)}</span>
655
+ </td>
656
+ <td class="when">${when(map.activatedAt)}</td>
657
+ <td class="end">
658
+ ${map.state === 'ARCHIVED'
659
+ ? html `<button ?disabled=${this.busy} @click=${() => this.activate(map.version)}>
660
+ ${i18next.t('figure-library.button.activate-again')}
661
+ </button>`
662
+ : nothing}
663
+ </td>
664
+ </tr>
665
+ `)}
666
+ </tbody>
667
+ </table>`
668
+ : html `<p class="empty">${i18next.t('figure-library.text.no-maps')}</p>`}
669
+ </section>
670
+ `;
671
+ }
672
+ choose(name, releaseId) {
673
+ const next = new Map(this.candidate);
674
+ if (releaseId)
675
+ next.set(name, releaseId);
676
+ else
677
+ next.delete(name);
678
+ this.candidate = next;
679
+ this.report = undefined;
680
+ }
681
+ async activate(version) {
682
+ this.busy = true;
683
+ try {
684
+ await client.mutate({ mutation: ACTIVATE, variables: { version } });
685
+ this.report = undefined;
686
+ await this.refresh();
687
+ }
688
+ catch (e) {
689
+ this.failure = e?.message ?? '';
690
+ }
691
+ finally {
692
+ this.busy = false;
693
+ }
694
+ }
695
+ renderReport(report) {
696
+ const reach = (item) => html `<span class="sub">${i18next.t('figure-library.text.reach', { boards: item.boards, nodes: item.nodes })}</span>`;
697
+ const group = (key, items, line) => items?.length
698
+ ? html `<div class="report-group">
699
+ <h4>${i18next.t(`figure-library.check.${key}`)}</h4>
700
+ <ul class="report-list">
701
+ ${items.map(item => html `<li>${line(item)}</li>`)}
702
+ </ul>
703
+ </div>`
704
+ : nothing;
705
+ const nothingToReport = ['missing', 'changed', 'added', 'deprecated', 'unsupported', 'dependencies'].every(key => !report[key]?.length);
706
+ return html `
707
+ <section class="page-section">
708
+ <header class="page-section-header">
709
+ <h3>${i18next.t('figure-library.title.check-report')}</h3>
710
+ </header>
711
+ <div class="page-card">
712
+ ${nothingToReport ? html `<p class="sub">${i18next.t('figure-library.check.nothing')}</p>` : nothing}
713
+ ${group('missing', report.missing, item => html `<span class="ref">${item.name}</span> ${reach(item)}`)}
714
+ ${group('changed', report.changed, item => html `<span class="ref">${item.from}</span> → <span class="ref">${item.to}</span> ${reach(item)}
715
+ ${item.contract.checked
716
+ ? item.contract.changes.length
717
+ ? html `<ul class="changes">
718
+ ${item.contract.changes.map((change) => html `<li class=${change.blocking ? 'breaking' : ''}>
719
+ ${changeText(change)}${change.blocking ? html ` · ${i18next.t('figure-library.text.breaking')}` : nothing}
720
+ </li>`)}
721
+ </ul>`
722
+ : nothing
723
+ : html `<span class="sub">${i18next.t('figure-library.check.uncheckable')}</span>`}`)}
724
+ ${group('added', report.added, item => html `<span class="ref">${item.ref}</span>`)}
725
+ ${group('deprecated', report.deprecated, item => html `<span class="ref">${item.ref}</span> ${reach(item)}`)}
726
+ ${group('unsupported', report.unsupported, item => html `<span class="ref">${item.ref}</span> ${reach(item)}`)}
727
+ ${group('dependencies', report.dependencies, item => html `<span class="ref">${item.ref}</span> ${reach(item)}`)}
728
+ </div>
729
+ </section>
730
+ `;
731
+ }
732
+ };
733
+ __decorate([
734
+ state(),
735
+ __metadata("design:type", String)
736
+ ], FigureLibraryPage.prototype, "tab", void 0);
737
+ __decorate([
738
+ state(),
739
+ __metadata("design:type", Array)
740
+ ], FigureLibraryPage.prototype, "releases", void 0);
741
+ __decorate([
742
+ state(),
743
+ __metadata("design:type", Array)
744
+ ], FigureLibraryPage.prototype, "maps", void 0);
745
+ __decorate([
746
+ state(),
747
+ __metadata("design:type", Map)
748
+ ], FigureLibraryPage.prototype, "candidate", void 0);
749
+ __decorate([
750
+ state(),
751
+ __metadata("design:type", Map)
752
+ ], FigureLibraryPage.prototype, "saved", void 0);
753
+ __decorate([
754
+ state(),
755
+ __metadata("design:type", Object)
756
+ ], FigureLibraryPage.prototype, "imported", void 0);
757
+ __decorate([
758
+ state(),
759
+ __metadata("design:type", Object)
760
+ ], FigureLibraryPage.prototype, "report", void 0);
761
+ __decorate([
762
+ state(),
763
+ __metadata("design:type", Object)
764
+ ], FigureLibraryPage.prototype, "busy", void 0);
765
+ __decorate([
766
+ state(),
767
+ __metadata("design:type", Object)
768
+ ], FigureLibraryPage.prototype, "loading", void 0);
769
+ __decorate([
770
+ state(),
771
+ __metadata("design:type", Object)
772
+ ], FigureLibraryPage.prototype, "loadError", void 0);
773
+ __decorate([
774
+ state(),
775
+ __metadata("design:type", Object)
776
+ ], FigureLibraryPage.prototype, "failure", void 0);
777
+ FigureLibraryPage = __decorate([
778
+ customElement('figure-library-page')
779
+ ], FigureLibraryPage);
780
+ export { FigureLibraryPage };
781
+ //# sourceMappingURL=figure-library-page.js.map