@things-factory/figure-ui 10.1.225 → 10.1.227
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.
- package/client/graphql/index.ts +6 -6
- package/client/modeller/consumer-capabilities.ts +15 -0
- package/client/modeller/editor-styles.ts +18 -1
- package/client/modeller/editor.ts +38 -26
- package/client/modeller/figure-capability-panel.ts +31 -10
- package/client/modeller/figure-clips.ts +2 -1
- package/client/modeller/figure-controls.ts +4 -3
- package/client/modeller/figure-data-panel.ts +24 -7
- package/client/modeller/figure-history-panel.ts +7 -7
- package/client/modeller/figure-links.ts +5 -5
- package/client/modeller/figure-material-panel.ts +2 -0
- package/client/modeller/figure-preview.ts +69 -2
- package/client/modeller/figure-recipe-catalog.ts +4 -0
- package/client/modeller/figure-slot-layout-panel.ts +25 -8
- package/client/modeller/figure-verdict.ts +4 -1
- package/client/modeller/unit-words.ts +11 -0
- package/client/pages/figure-list-page.ts +2 -2
- package/client/pages/figure-modeller-page.ts +7 -6
- package/client/types.ts +2 -2
- package/client/viewparts/figure-thumb.ts +1 -1
- package/dist-client/graphql/index.d.ts +5 -5
- package/dist-client/graphql/index.js +6 -6
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/consumer-capabilities.d.ts +11 -0
- package/dist-client/modeller/consumer-capabilities.js +14 -0
- package/dist-client/modeller/consumer-capabilities.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +18 -1
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +38 -26
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +3 -0
- package/dist-client/modeller/figure-capability-panel.js +29 -11
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +2 -1
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/figure-controls.js +4 -3
- package/dist-client/modeller/figure-controls.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.js +24 -7
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +5 -5
- package/dist-client/modeller/figure-history-panel.js +7 -7
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.js +5 -5
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +2 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +3 -0
- package/dist-client/modeller/figure-preview.js +78 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-recipe-catalog.js +4 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.d.ts +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +25 -8
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/figure-verdict.js +4 -1
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/unit-words.d.ts +5 -0
- package/dist-client/modeller/unit-words.js +12 -0
- package/dist-client/modeller/unit-words.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +1 -1
- package/dist-client/pages/figure-list-page.js +2 -2
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +3 -3
- package/dist-client/pages/figure-modeller-page.js +7 -6
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -2
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.js +1 -1
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +9 -9
- package/test/browser/detail-page.js +1 -1
- package/test/browser/editing-page.js +3 -3
- package/test/browser/editing.check.mjs +5 -5
- package/test/browser/links-page.js +1 -1
- package/test/browser/links.check.mjs +2 -3
- package/test/browser/modeller-page.js +4 -4
- package/test/browser/modeller-revision.check.mjs +5 -5
- package/test/browser/modeller.check.mjs +5 -5
- package/test/browser/parts.check.mjs +2 -2
- package/test/browser/product-page.check.mjs +6 -0
- package/test/browser/props.check.mjs +3 -4
- package/test/browser/row-preview.check.mjs +3 -3
- package/test/browser/screen.check.mjs +13 -11
- package/test/consumer-capabilities.test.ts +7 -0
- package/test/links.test.ts +1 -1
- package/test/refusals.test.ts +1 -1
- package/test/resolve-ts-siblings.mjs +3 -3
- package/translations/en.json +23 -12
- package/translations/es.json +23 -12
- package/translations/ja.json +24 -13
- package/translations/ko.json +83 -72
- package/translations/ms.json +23 -12
- package/translations/zh.json +23 -12
|
@@ -2,6 +2,8 @@ import { css, html, LitElement, nothing, unsafeCSS } from 'lit'
|
|
|
2
2
|
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
3
3
|
|
|
4
4
|
import { i18next, localize } from '@operato/i18n'
|
|
5
|
+
import { unitWord } from './unit-words.js'
|
|
6
|
+
import * as THREE from 'three'
|
|
5
7
|
import { compileFigureAsset } from '@hatiolab/figure-model'
|
|
6
8
|
import type { FigureAsset, FigureDataRow, FigureDataValue, FigureGraphStateInput } from '@hatiolab/figure-model'
|
|
7
9
|
import { create, figureLegendSourcesChanged, registerFigure, unregisterFigure } from '@hatiolab/things-scene'
|
|
@@ -68,6 +70,23 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
68
70
|
stage (the 1024 width, both side panels open) the controls lay over the help's button. One row that wraps keeps both
|
|
69
71
|
whole: side by side when there is room, the controls above the help when there is not.
|
|
70
72
|
*/
|
|
73
|
+
/* A variant's name over its box: small, on the stage, never in the way of the pointer. */
|
|
74
|
+
div[tags] {
|
|
75
|
+
position: absolute;
|
|
76
|
+
inset: 0;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
}
|
|
80
|
+
div[tags] span {
|
|
81
|
+
position: absolute;
|
|
82
|
+
transform: translate(-50%, calc(-100% - 4px));
|
|
83
|
+
padding: 1px 6px;
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
font-size: var(--ui-t-micro, 11px);
|
|
86
|
+
color: rgba(255, 255, 255, 0.92);
|
|
87
|
+
background-color: rgba(28, 28, 30, 0.72);
|
|
88
|
+
border-radius: 4px;
|
|
89
|
+
}
|
|
71
90
|
div[foot] {
|
|
72
91
|
position: absolute;
|
|
73
92
|
left: 8px;
|
|
@@ -328,6 +347,9 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
328
347
|
}
|
|
329
348
|
|
|
330
349
|
private scene?: SceneHandle
|
|
350
|
+
/* The eight variants named over each one, where the camera shows it (sweep 2026-10-27 M4). */
|
|
351
|
+
@state() private tags: { name: string; x: number; y: number }[] = []
|
|
352
|
+
private tagFrame = 0
|
|
331
353
|
private standing?: string
|
|
332
354
|
private registered?: string
|
|
333
355
|
private watcher?: ResizeObserver
|
|
@@ -347,6 +369,7 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
347
369
|
/* The part the help speaks of: the one picked in it, else the one chosen in the editor, else the first. */
|
|
348
370
|
const setupPart = setupPartOf(this.setupPart, this.selected, asset?.appearance.map(p => p.target) ?? [])
|
|
349
371
|
return html`<div stage></div>
|
|
372
|
+
${this.tags.length ? html`<div tags>${this.tags.map(t => html`<span style=${`left:${t.x}px;top:${t.y}px`}>${t.name}</span>`)}</div>` : nothing}
|
|
350
373
|
${this.cannot
|
|
351
374
|
? html`<div cannot>${this.cannot}</div>`
|
|
352
375
|
: html`<div legend>
|
|
@@ -393,10 +416,50 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
393
416
|
</div>`
|
|
394
417
|
}
|
|
395
418
|
|
|
419
|
+
/*
|
|
420
|
+
Each variant's name over its top, followed while the eight stand: the legend alone listed 「x · y · x·y …」 with
|
|
421
|
+
nothing saying which box was which (sweep 2026-10-27 M4).
|
|
422
|
+
*/
|
|
423
|
+
private followTags() {
|
|
424
|
+
cancelAnimationFrame(this.tagFrame)
|
|
425
|
+
if (this.way !== 'eight' || !this.scene) {
|
|
426
|
+
if (this.tags.length) this.tags = []
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
const names = new Map(PREVIEW_WAYS.map(way => [`at-${way.name}`, way.axes.length ? way.name : i18next.t('figure.label.preview-base')]))
|
|
430
|
+
const box = new THREE.Box3()
|
|
431
|
+
const top = new THREE.Vector3()
|
|
432
|
+
const step = () => {
|
|
433
|
+
const root = this.scene?.root as { activeCamera3d?: THREE.Camera; renderer3d?: THREE.WebGLRenderer; components?: { model?: { id?: string }; _realObject?: { object3d?: THREE.Object3D } }[] } | undefined
|
|
434
|
+
const camera = root?.activeCamera3d
|
|
435
|
+
const canvas = root?.renderer3d?.domElement
|
|
436
|
+
if (!camera || !canvas || this.way !== 'eight') return
|
|
437
|
+
const at = canvas.getBoundingClientRect()
|
|
438
|
+
const host = this.getBoundingClientRect()
|
|
439
|
+
const tags: { name: string; x: number; y: number }[] = []
|
|
440
|
+
for (const component of root?.components ?? []) {
|
|
441
|
+
const name = names.get(component.model?.id ?? '')
|
|
442
|
+
const object = component._realObject?.object3d
|
|
443
|
+
if (!name || !object) continue
|
|
444
|
+
box.setFromObject(object)
|
|
445
|
+
if (box.isEmpty()) continue
|
|
446
|
+
box.getCenter(top)
|
|
447
|
+
top.y = box.max.y
|
|
448
|
+
top.project(camera)
|
|
449
|
+
if (top.z > 1) continue
|
|
450
|
+
tags.push({ name, x: Math.round((top.x * 0.5 + 0.5) * at.width + at.left - host.left), y: Math.round((-top.y * 0.5 + 0.5) * at.height + at.top - host.top) })
|
|
451
|
+
}
|
|
452
|
+
if (JSON.stringify(tags) !== JSON.stringify(this.tags)) this.tags = tags
|
|
453
|
+
this.tagFrame = requestAnimationFrame(step)
|
|
454
|
+
}
|
|
455
|
+
this.tagFrame = requestAnimationFrame(step)
|
|
456
|
+
}
|
|
457
|
+
|
|
396
458
|
private chooseWay(way: 'one' | 'eight') {
|
|
397
459
|
if (way === this.way) return
|
|
398
460
|
this.way = way
|
|
399
461
|
this.standUp()
|
|
462
|
+
this.followTags()
|
|
400
463
|
/* The content is made again inside the scene standing, which keeps the camera: from one figure to eight it stood inside them. */
|
|
401
464
|
requestAnimationFrame(() => this.fitView())
|
|
402
465
|
}
|
|
@@ -434,7 +497,7 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
434
497
|
const step = Math.max((control.max - control.min) / 100, 0.01)
|
|
435
498
|
const label = asset.stateInputs?.[control.id]?.label ?? control.id
|
|
436
499
|
return html`<label>
|
|
437
|
-
<span>${label} <span drive>${control.unit}</span></span>
|
|
500
|
+
<span>${label} <span drive>${unitWord(control.unit)}</span></span>
|
|
438
501
|
${this.renderDial(control, value)}
|
|
439
502
|
<input
|
|
440
503
|
type="range"
|
|
@@ -494,7 +557,7 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
494
557
|
if (node.kind === 'number') {
|
|
495
558
|
const ranged = node.min !== undefined && node.max !== undefined && node.max > node.min
|
|
496
559
|
return html`<label>
|
|
497
|
-
<span>${label} <span drive>${node.unit
|
|
560
|
+
<span>${label} <span drive>${unitWord(node.unit)}</span></span>
|
|
498
561
|
<span></span>
|
|
499
562
|
${ranged
|
|
500
563
|
? html`<input type="range" min=${node.min!} max=${node.max!} step=${Math.max((node.max! - node.min!) / 100, 0.01)} .value=${String(value ?? node.min)} @input=${(e: Event) => this.value(row, (e.target as HTMLInputElement).value)} />
|
|
@@ -679,6 +742,7 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
679
742
|
this.cannot = ''
|
|
680
743
|
this.tellLegends()
|
|
681
744
|
this.tellSample()
|
|
745
|
+
this.followTags()
|
|
682
746
|
return
|
|
683
747
|
} catch {
|
|
684
748
|
this.teardown()
|
|
@@ -698,12 +762,15 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
698
762
|
this.scene.root?.fitCameraToModel?.()
|
|
699
763
|
this.cannot = ''
|
|
700
764
|
this.tellSample()
|
|
765
|
+
this.followTags()
|
|
701
766
|
} catch (error) {
|
|
702
767
|
this.cannot = (error as Error).message
|
|
703
768
|
}
|
|
704
769
|
}
|
|
705
770
|
|
|
706
771
|
private teardown() {
|
|
772
|
+
cancelAnimationFrame(this.tagFrame)
|
|
773
|
+
this.tags = []
|
|
707
774
|
this.scene?.dispose?.()
|
|
708
775
|
this.scene = undefined
|
|
709
776
|
this.standing = undefined
|
|
@@ -218,8 +218,12 @@ export class FigureRecipeCatalog extends localize(i18next)(LitElement) {
|
|
|
218
218
|
background: var(--md-sys-color-error-container);
|
|
219
219
|
border-radius: 6px;
|
|
220
220
|
}
|
|
221
|
+
/* The guide's titles under the recipe's own (13px): they stood at 18px over it (sweep 2026-10-27 U29). */
|
|
221
222
|
ox-markdown {
|
|
222
223
|
display: block;
|
|
224
|
+
--markdown-h1-font-size: var(--ui-t-body);
|
|
225
|
+
--markdown-h2-font-size: var(--ui-t-body);
|
|
226
|
+
--markdown-h3-font-size: var(--ui-t-small);
|
|
223
227
|
}
|
|
224
228
|
`
|
|
225
229
|
]
|
|
@@ -3,6 +3,7 @@ import { customElement, property, state } from 'lit/decorators.js'
|
|
|
3
3
|
import { i18next, localize } from '@operato/i18n'
|
|
4
4
|
import '@material/web/icon/icon.js'
|
|
5
5
|
import '@operato/slot-layout-editor'
|
|
6
|
+
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
|
|
6
7
|
import { compileFigureAsset, type FigureAsset, type SlotLayout } from '@hatiolab/figure-model'
|
|
7
8
|
|
|
8
9
|
import {
|
|
@@ -29,15 +30,30 @@ import {
|
|
|
29
30
|
*/
|
|
30
31
|
@customElement('figure-slot-layout-panel')
|
|
31
32
|
export class FigureSlotLayoutPanel extends localize(i18next)(LitElement) {
|
|
32
|
-
|
|
33
|
+
/*
|
|
34
|
+
The panel's controls, and the shared editor given the same (sweep 2026-10-27 M2): its own 12px, boxed 24px fields and
|
|
35
|
+
right-aligned labels at 34% stood under the panel's 11.5px, 26px underlined fields and 72px labels.
|
|
36
|
+
*/
|
|
37
|
+
static styles = [SidePanelStyles, css`
|
|
33
38
|
:host {
|
|
34
39
|
display: flex;
|
|
35
40
|
flex-direction: column;
|
|
36
41
|
gap: 6px;
|
|
37
42
|
}
|
|
43
|
+
ox-slot-layout-editor {
|
|
44
|
+
--slot-layout-font-size: var(--ui-t-small, 11.5px);
|
|
45
|
+
--slot-layout-label-column: var(--figure-label-width);
|
|
46
|
+
--slot-layout-label-align: left;
|
|
47
|
+
--property-editor-field-height: var(--figure-control-height);
|
|
48
|
+
--slot-layout-field-border: 0 solid transparent;
|
|
49
|
+
--slot-layout-field-border-bottom: 1px solid var(--md-sys-color-outline-variant, #c4c7c5);
|
|
50
|
+
--slot-layout-field-radius: 0;
|
|
51
|
+
--slot-layout-field-background: transparent;
|
|
52
|
+
--property-editor-label-color: var(--md-sys-color-on-surface-variant);
|
|
53
|
+
}
|
|
38
54
|
div[form] {
|
|
39
55
|
display: grid;
|
|
40
|
-
grid-template-columns:
|
|
56
|
+
grid-template-columns: var(--figure-label-width) minmax(0, 1fr);
|
|
41
57
|
gap: 4px 8px;
|
|
42
58
|
align-items: center;
|
|
43
59
|
}
|
|
@@ -77,7 +93,7 @@ export class FigureSlotLayoutPanel extends localize(i18next)(LitElement) {
|
|
|
77
93
|
small[hint] {
|
|
78
94
|
color: var(--md-sys-color-on-surface-variant);
|
|
79
95
|
}
|
|
80
|
-
`
|
|
96
|
+
`]
|
|
81
97
|
|
|
82
98
|
@property({ attribute: false }) asset?: FigureAsset
|
|
83
99
|
/** The part selected on the canvas: what a new anchor stands on. */
|
|
@@ -133,14 +149,14 @@ export class FigureSlotLayoutPanel extends localize(i18next)(LitElement) {
|
|
|
133
149
|
${(['top', 'bottom'] as const).map(f => html`<option value=${f} ?selected=${f === a.face}>${i18next.t('figure.label.face-' + f)}</option>`)}
|
|
134
150
|
</select>
|
|
135
151
|
<button ?disabled=${this.locked || form.anchors.length <= 1} title=${i18next.t('figure.button.remove-slot-anchor')} @click=${() => this.edit({ anchors: form.anchors.filter((_, k) => k !== i) })}>
|
|
136
|
-
<md-icon>
|
|
152
|
+
<md-icon>delete</md-icon>
|
|
137
153
|
</button>
|
|
138
154
|
</li>`
|
|
139
155
|
)}
|
|
140
156
|
</ul>
|
|
141
157
|
<div actions>
|
|
142
158
|
${(['top', 'bottom'] as const).map(
|
|
143
|
-
face => html`<button ?disabled=${this.locked || !this.partId} @click=${() => this.addAnchor(face)}>
|
|
159
|
+
face => html`<button act ?disabled=${this.locked || !this.partId} @click=${() => this.addAnchor(face)}>
|
|
144
160
|
<md-icon>add</md-icon>${i18next.t(face === 'top' ? 'figure.button.add-slot-anchor-top' : 'figure.button.add-slot-anchor-bottom', { part: this.partId })}
|
|
145
161
|
</button>`
|
|
146
162
|
)}
|
|
@@ -151,10 +167,10 @@ export class FigureSlotLayoutPanel extends localize(i18next)(LitElement) {
|
|
|
151
167
|
|
|
152
168
|
<div actions>
|
|
153
169
|
${stored.layout && changed
|
|
154
|
-
? html`<button ?disabled=${this.locked} @click=${() => this.apply(stored.layout)}><md-icon>check</md-icon>${i18next.t('figure.button.apply-slot-anchors')}</button>`
|
|
170
|
+
? html`<button act ?disabled=${this.locked} @click=${() => this.apply(stored.layout)}><md-icon>check</md-icon>${i18next.t('figure.button.apply-slot-anchors')}</button>`
|
|
155
171
|
: nothing}
|
|
156
172
|
${stored.layout
|
|
157
|
-
? html`<button ?disabled=${this.locked} @click=${() => this.command({ kind: 'remove-slot-layout' })}><md-icon>delete</md-icon>${i18next.t('figure.button.remove-slot-layout')}</button>`
|
|
173
|
+
? html`<button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'remove-slot-layout' })}><md-icon>delete</md-icon>${i18next.t('figure.button.remove-slot-layout')}</button>`
|
|
158
174
|
: nothing}
|
|
159
175
|
</div>
|
|
160
176
|
`
|
|
@@ -176,7 +192,8 @@ export class FigureSlotLayoutPanel extends localize(i18next)(LitElement) {
|
|
|
176
192
|
presetSize: startSizeOf(form.anchors, anchorFaces, anchorRoomOf(form.anchors, geometry)),
|
|
177
193
|
/* No layout yet: the whole face is one slot, shown at once and put in on Apply. */
|
|
178
194
|
startFrom: 'single',
|
|
179
|
-
|
|
195
|
+
/* The face names its part: an anchor named as its part is not named twice (「tub · tub 윗면」, sweep 2026-10-27 M5). */
|
|
196
|
+
anchors: form.anchors.map(a => ({ id: a.id, label: `${a.id === a.part ? '' : `${a.id} · `}${a.part} ${i18next.t('figure.label.face-' + a.face)}` })),
|
|
180
197
|
anchorFaces,
|
|
181
198
|
validate: (layout: SlotLayout) => figureSlotIssues(layout, form.anchors.map(a => a.id)),
|
|
182
199
|
issueText: (issue: { code: string; path: string }) =>
|
|
@@ -759,7 +759,10 @@ export class FigureVerdict extends localize(i18next)(LitElement) {
|
|
|
759
759
|
? html`<p quiet>${i18next.t(this.asking ? 'figure.text.verdict-asking' : 'figure.text.verdict-waiting')}</p>`
|
|
760
760
|
: blocking.length
|
|
761
761
|
? html`<p>${i18next.t('figure.text.cannot-release-now')}</p>`
|
|
762
|
-
:
|
|
762
|
+
: this.revision && !this.revision.asset.appearance.length
|
|
763
|
+
? /* No part: nothing to judge, not 「nothing blocks」 (sweep 2026-10-27 U24). */
|
|
764
|
+
html`<p quiet>${i18next.t('figure.text.release-needs-a-part')}</p>`
|
|
765
|
+
: html`<p clear>${i18next.t('figure.text.nothing-blocks-release')}</p>`}
|
|
763
766
|
${blocking.length
|
|
764
767
|
? html`<ul gate>
|
|
765
768
|
<li head>${i18next.t('figure.text.must-fix-to-release')}</li>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { i18next } from '@operato/i18n'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A unit as the screen names it: the graph's codes (deg, percent, count …) are not words a person reads, and a unit
|
|
5
|
+
* typed by the author (rpm) stays as typed (sweep 2026-10-27 U9).
|
|
6
|
+
*/
|
|
7
|
+
export function unitWord(unit: string | undefined): string {
|
|
8
|
+
if (!unit) return ''
|
|
9
|
+
const key = `figure.label.unit-${unit}`
|
|
10
|
+
return i18next.exists(key) ? i18next.t(key) : unit
|
|
11
|
+
}
|
|
@@ -65,7 +65,7 @@ const FigureListPageBase = p13n(localize(i18next)(PageView))
|
|
|
65
65
|
/**
|
|
66
66
|
* Figure 카탈로그.
|
|
67
67
|
*
|
|
68
|
-
* 카드를 고르면 상세 패널에서 저장본과
|
|
68
|
+
* 카드를 고르면 상세 패널에서 저장본과 발행 버전을 살펴본다. 크기와 움직임을 시험한 뒤
|
|
69
69
|
* 「편집 화면 열기」로 저작면에 들어간다. 목록 질의는 자산 원본을 싣지 않는다.
|
|
70
70
|
*
|
|
71
71
|
* ## 왜 손으로 만든 카드가 아니라 격자인가
|
|
@@ -302,7 +302,7 @@ export class FigureListPage extends FigureListPageBase {
|
|
|
302
302
|
editable: false,
|
|
303
303
|
/*
|
|
304
304
|
**발행된 것에는 아무 말도 하지 않는다.** 카탈로그에 있는 것은 대개 발행된 것이라,
|
|
305
|
-
카드마다 「발행됨」이 붙으면 그 말이 아무것도 가르지 않고 자리만 먹는다.
|
|
305
|
+
카드마다 「발행됨」이 붙으면 그 말이 아무것도 가르지 않고 자리만 먹는다. 버전 번호도
|
|
306
306
|
고를 때 쓰는 값이 아니다 — 이력 탭에 있다.
|
|
307
307
|
|
|
308
308
|
표 모드에서는 칸에 값이 있어야 하므로 그때는 발행됨도 적는다(`wide` 가 아니라 label
|
|
@@ -130,9 +130,9 @@ export class FigureModellerPage extends PageView {
|
|
|
130
130
|
private thumbnailOf = (name: string) => `${getPathInfo(location.pathname).contextPath}/figure-recipe-thumbnail/${name}`
|
|
131
131
|
@state() private error = ''
|
|
132
132
|
/**
|
|
133
|
-
* 발행 패널이 읽는 행의 사실 — 상태와 발행
|
|
133
|
+
* 발행 패널이 읽는 행의 사실 — 상태와 발행 버전 번호. 원본은 세션이 들고, 이 둘은 행에 있다.
|
|
134
134
|
*
|
|
135
|
-
* 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 ·
|
|
135
|
+
* 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 버전 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서
|
|
136
136
|
* `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 피규어는 공간을 선언하기 전에는 전부
|
|
137
137
|
* 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 그래서 발행 패널을 건다.
|
|
138
138
|
*/
|
|
@@ -255,7 +255,7 @@ export class FigureModellerPage extends PageView {
|
|
|
255
255
|
tags: figure.tags ?? undefined
|
|
256
256
|
}
|
|
257
257
|
} catch {
|
|
258
|
-
/* 행을 못 읽으면 패널은
|
|
258
|
+
/* 행을 못 읽으면 패널은 버전 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
/**
|
|
@@ -356,7 +356,7 @@ export class FigureModellerPage extends PageView {
|
|
|
356
356
|
return { ok: false, reason: code === 'SAVE_UNREACHABLE' ? i18next.t('figure.text.save-unreachable') : error instanceof Error ? error.message : String(error) }
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
|
-
/** 과거
|
|
359
|
+
/** 과거 버전으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 저장 안 된 것이 있으면 패널이 먼저 저장·버리기·취소를 묻는다. */
|
|
360
360
|
private reverted = async () => {
|
|
361
361
|
const id = this.session.read().id
|
|
362
362
|
if (!id) return
|
|
@@ -499,9 +499,10 @@ export class FigureModellerPage extends PageView {
|
|
|
499
499
|
title: view.name || i18next.t('figure.label.figure'),
|
|
500
500
|
/*
|
|
501
501
|
A figure that has an id was opened from the catalog (or saved there), so the rail marks the catalog. Without this
|
|
502
|
-
the rail matches the page name and marks "new figure" while an existing one is being edited.
|
|
502
|
+
the rail matches the page name and marks "new figure" while an existing one is being edited. The address names
|
|
503
|
+
the figure before it is read: opened by its address, the rail marked "new figure" (sweep 2026-10-27 U30).
|
|
503
504
|
*/
|
|
504
|
-
menu: view.id ? 'figure-list' : undefined,
|
|
505
|
+
menu: view.id || this.lifecycle?.resourceId ? 'figure-list' : undefined,
|
|
505
506
|
/* The canvas needs the width: the rail folds while the modeller is open and comes back as it was on leaving. */
|
|
506
507
|
rail: 'collapsed',
|
|
507
508
|
actions: [],
|
package/client/types.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { hasBeenReleased } from '../figure-released.js'
|
|
|
15
15
|
* 그림을 부르는 주소.
|
|
16
16
|
*
|
|
17
17
|
* **base64 를 목록 응답에 실어 오지 않는다.** data URL 은 브라우저가 캐시할 수 없어, 카탈로그를
|
|
18
|
-
* 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고,
|
|
18
|
+
* 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고, 버전을
|
|
19
19
|
* 가리는 `v` 가 바뀐 것만 다시 받는다. 내주는 자리는 `figure-thumbnail-router` 다.
|
|
20
20
|
*
|
|
21
21
|
* 도메인 조각(`/domain/{subdomain}`)은 셸이 쓰는 것과 같은 함수로 읽는다 — 화면이 주소를
|
|
@@ -40,7 +40,7 @@ export declare function setFigurePublic(id: string, isPublic: boolean): Promise<
|
|
|
40
40
|
export declare function setFigureDeprecation(id: string, deprecated: boolean, supersededBy?: string | null): Promise<Figure>;
|
|
41
41
|
export declare function deleteFigure(id: string): Promise<boolean>;
|
|
42
42
|
/**
|
|
43
|
-
* 발행한다 —
|
|
43
|
+
* 발행한다 — 버전 번호가 오르고, 그 순간이 버전으로 남는다.
|
|
44
44
|
*
|
|
45
45
|
* 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은
|
|
46
46
|
* 발행을 푼다(초안으로 돌아온다).
|
|
@@ -78,7 +78,7 @@ export interface KeptDraft {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* 옛
|
|
81
|
+
* 옛 버전을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 덮이는 초안은 같은 트랜잭션에서 서버가
|
|
82
82
|
* 보관한다. `expectedSource` 는 서버가 들고 있다고 확인된 정본 -- 그 사이 바뀌었으면 거절된다. 답을 못 받았으면
|
|
83
83
|
* (REVERT_UNREACHABLE) 같은 `requestId` 로 다시 묻는다: 서버는 같은 되돌리기로 답하고 두 번 하지 않는다.
|
|
84
84
|
*/
|
|
@@ -115,12 +115,12 @@ export declare function inspectFigure(id: string): Promise<FigureInspection>;
|
|
|
115
115
|
*
|
|
116
116
|
* 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는
|
|
117
117
|
* 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —
|
|
118
|
-
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전
|
|
118
|
+
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행 버전을 읽는다.
|
|
119
119
|
*/
|
|
120
120
|
export declare function inspectFigureDraft(source: string, id?: string): Promise<FigureInspection>;
|
|
121
|
-
/** 발행된
|
|
121
|
+
/** 발행된 버전들 — 최근 것부터 열 개. */
|
|
122
122
|
/**
|
|
123
|
-
* 직전
|
|
123
|
+
* 직전 발행 버전의 정본. 한 번도 발행 안 했으면 `undefined`.
|
|
124
124
|
*
|
|
125
125
|
* 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장
|
|
126
126
|
* 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.
|
|
@@ -30,7 +30,7 @@ function unwrap(response, field) {
|
|
|
30
30
|
* **`thumbnail` 도 여기 없다.** base64 문자열이라 목록 응답에 실으면 카탈로그를 열 때마다 그림
|
|
31
31
|
* 전부가 다시 오고(표본 14개 227KB), data URL 은 브라우저가 캐시하지 못한다. 그림은 주소로
|
|
32
32
|
* 부른다(`figure-thumbnail` 라우트). 대신 `thumbnailUpdatedAt` 을 받는다 — 그림이 있나 없나를
|
|
33
|
-
* 그것으로 알고,
|
|
33
|
+
* 그것으로 알고, 버전을 가리는 값으로도 쓴다.
|
|
34
34
|
*/
|
|
35
35
|
const FIGURE_LIST_FIELDS = `
|
|
36
36
|
id
|
|
@@ -181,7 +181,7 @@ export async function deleteFigure(id) {
|
|
|
181
181
|
return unwrap(response, 'deleteFigure');
|
|
182
182
|
}
|
|
183
183
|
/**
|
|
184
|
-
* 발행한다 —
|
|
184
|
+
* 발행한다 — 버전 번호가 오르고, 그 순간이 버전으로 남는다.
|
|
185
185
|
*
|
|
186
186
|
* 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은
|
|
187
187
|
* 발행을 푼다(초안으로 돌아온다).
|
|
@@ -265,7 +265,7 @@ export async function setFigureNamespaceState(name, state) {
|
|
|
265
265
|
}
|
|
266
266
|
const KEPT_FIELDS = `id kind state version revertedToVersion restoredAt createdAt creator { name }`;
|
|
267
267
|
/**
|
|
268
|
-
* 옛
|
|
268
|
+
* 옛 버전을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 덮이는 초안은 같은 트랜잭션에서 서버가
|
|
269
269
|
* 보관한다. `expectedSource` 는 서버가 들고 있다고 확인된 정본 -- 그 사이 바뀌었으면 거절된다. 답을 못 받았으면
|
|
270
270
|
* (REVERT_UNREACHABLE) 같은 `requestId` 로 다시 묻는다: 서버는 같은 되돌리기로 답하고 두 번 하지 않는다.
|
|
271
271
|
*/
|
|
@@ -364,7 +364,7 @@ export async function inspectFigure(id) {
|
|
|
364
364
|
*
|
|
365
365
|
* 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는
|
|
366
366
|
* 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —
|
|
367
|
-
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전
|
|
367
|
+
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행 버전을 읽는다.
|
|
368
368
|
*/
|
|
369
369
|
export async function inspectFigureDraft(source, id) {
|
|
370
370
|
const response = await client.query({
|
|
@@ -388,9 +388,9 @@ export async function inspectFigureDraft(source, id) {
|
|
|
388
388
|
});
|
|
389
389
|
return unwrap(response, 'inspectFigureDraft');
|
|
390
390
|
}
|
|
391
|
-
/** 발행된
|
|
391
|
+
/** 발행된 버전들 — 최근 것부터 열 개. */
|
|
392
392
|
/**
|
|
393
|
-
* 직전
|
|
393
|
+
* 직전 발행 버전의 정본. 한 번도 발행 안 했으면 `undefined`.
|
|
394
394
|
*
|
|
395
395
|
* 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장
|
|
396
396
|
* 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../client/graphql/index.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { i18next } from '@operato/i18n'\nimport { client, uploadContext, type UploadProgress } from '@operato/graphql'\n\nimport { reached, refusedIn } from './reached.js'\n\nimport type {\n Figure,\n FigureFinding,\n FigureInspection,\n FigureListResult,\n FigurePatch,\n FigureVersion,\n NewFigure\n} from '../types.js'\n\n/**\n * 응답에서 값을 꺼낸다. **서버가 말한 사유를 그대로 올린다.**\n *\n * 전에는 `response.data.figures` 처럼 바로 꺼냈다. 서버가 오류를 돌려주면 `data` 가\n * 없으므로 `Cannot read properties of undefined (reading 'figures')` 가 났고, 화면에는\n * 그 문장이 그대로 떴다. 사용자는 자기가 무엇을 잘못했는지 알 수 없다 — 실제로 AI\n * 요청에서 그렇게 났다.\n *\n * 값이 `null` 인 것은 오류가 아니다. 지운 것을 물었을 때처럼 없는 것이 답인 경우가 있다.\n * 여기서 막는 것은 **`data` 자체가 없는 것**이다.\n */\nfunction unwrap<T>(response: { data?: Record<string, unknown>; errors?: readonly { message: string }[] }, field: string): T {\n const said = response.errors?.[0]?.message\n if (said) throw new Error(said)\n\n if (!response.data) {\n /* The field name stays off the screen because the user cannot act on it; it goes to the console. */\n console.error(`[figure-ui] the server returned no data for ${field}`)\n throw new Error(i18next.t('figure.text.server-returned-no-data'))\n }\n\n return response.data[field] as T\n}\n\n\n/**\n * 목록에 필요한 필드만 가져온다 — `source` 는 무겁고 목록에서 쓰지 않는다.\n *\n * **`thumbnail` 도 여기 없다.** base64 문자열이라 목록 응답에 실으면 카탈로그를 열 때마다 그림\n * 전부가 다시 오고(표본 14개 227KB), data URL 은 브라우저가 캐시하지 못한다. 그림은 주소로\n * 부른다(`figure-thumbnail` 라우트). 대신 `thumbnailUpdatedAt` 을 받는다 — 그림이 있나 없나를\n * 그것으로 알고, 판을 가리는 값으로도 쓴다.\n */\nconst FIGURE_LIST_FIELDS = `\n id\n namespace\n type\n name\n description\n category\n tags\n state\n version\n deprecated\n supersededBy\n public\n score\n triangles\n groups\n thumbnailUpdatedAt\n updatedAt\n updater { id name }\n`\n\n/**\n * 목록을 가져온다.\n *\n * 걸러 보기·정렬·쪽 나누기를 **그대로 넘긴다.** 화면이 `search`·`state` 같은 이름을 따로 만들어\n * filter 로 옮기던 것을 그만두었다 — 그 이름들은 격자(ox-grist)가 이미 컬럼 설정에서 만들어 주고,\n * 중간에 한 벌 더 두면 격자가 아는 조건과 서버에 가는 조건이 어긋난다.\n */\nexport async function fetchFigureList(params: {\n page?: number\n limit?: number\n filters?: unknown[]\n sortings?: { name: string; desc?: boolean }[]\n /** 'public': other domains' public figures, with their domain (LV figure-server design G2). The domain's own by default. */\n scope?: 'own' | 'public'\n}): Promise<FigureListResult> {\n const field = params.scope === 'public' ? 'publicFigures' : 'figures'\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n ${field}(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items { ${FIGURE_LIST_FIELDS} ${params.scope === 'public' ? 'domain { id name subdomain }' : ''} }\n total\n }\n }\n `,\n variables: {\n filters: params.filters ?? [],\n pagination: { page: params.page ?? 1, limit: params.limit ?? 30 },\n sortings: params.sortings?.length ? params.sortings : [{ name: 'updatedAt', desc: true }]\n },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureListResult>(response, field)\n}\n\n/** 저작면에 필요한 전부 — `source` 를 포함한다. */\nexport async function fetchFigure(id: string): Promise<Figure> {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n figure(id: $id) {\n ${FIGURE_LIST_FIELDS}\n source\n properties\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<Figure>(response, 'figure')\n}\n\n/**\n * 이미 쓰이고 있는 타입 이름.\n *\n * `type` 은 만든 뒤에 고칠 수 없으므로 **만들기 전에** 알려 줘야 한다. 저장을 눌러\n * 거절당하고 나서 아는 것은 늦다.\n */\nexport async function fetchFigureTypeNames(): Promise<string[]> {\n const response = await client.query({\n query: gql`\n query {\n figureTypeNames\n }\n `,\n fetchPolicy: 'network-only'\n })\n\n return unwrap<string[] | null>(response, 'figureTypeNames') ?? []\n}\n\nexport interface SaveResult {\n figure: Figure\n /** 막지 않는 발견. 저장은 됐지만 화면이 보여 줘야 한다. */\n violations: FigureFinding[]\n}\n\nexport async function createFigure(figure: NewFigure): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($figure: NewFigure!) {\n createFigure(figure: $figure) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { figure }\n })\n\n return unwrap<SaveResult>(response, 'createFigure')\n}\n\nexport async function updateFigure(id: string, patch: FigurePatch): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: FigurePatch!) {\n updateFigure(id: $id, patch: $patch) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, patch }\n })\n\n return unwrap<SaveResult>(response, 'updateFigure')\n}\n\n/** Makes a released figure public to other domains, or private again (LV figure-server design G2). */\nexport async function setFigurePublic(id: string, isPublic: boolean): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $public: Boolean!) {\n setFigurePublic(id: $id, public: $public) {\n ${FIGURE_LIST_FIELDS}\n }\n }\n `,\n variables: { id, public: isPublic }\n })\n\n return unwrap<Figure>(response, 'setFigurePublic')\n}\n\n/** Retires a released figure, naming the type to use instead, or brings it back. Release rows are not touched. */\nexport async function setFigureDeprecation(id: string, deprecated: boolean, supersededBy?: string | null): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $deprecated: Boolean!, $supersededBy: String) {\n setFigureDeprecation(id: $id, deprecated: $deprecated, supersededBy: $supersededBy) {\n ${FIGURE_LIST_FIELDS}\n }\n }\n `,\n variables: { id, deprecated, supersededBy: supersededBy || null }\n })\n\n return unwrap<Figure>(response, 'setFigureDeprecation')\n}\n\nexport async function deleteFigure(id: string): Promise<boolean> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteFigure(id: $id)\n }\n `,\n variables: { id }\n })\n\n return unwrap<boolean>(response, 'deleteFigure')\n}\n\n/**\n * 발행한다 — 판 번호가 오르고, 그 순간이 판본으로 남는다.\n *\n * 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은\n * 발행을 푼다(초안으로 돌아온다).\n */\nexport async function releaseFigure(id: string, comment?: string): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ReleaseFigure($id: String!, $comment: String) {\n releaseFigure(id: $id, comment: $comment) {\n id\n version\n state\n updatedAt\n }\n }\n `,\n variables: { id, comment }\n })\n\n return unwrap<Figure>(response, 'releaseFigure')\n}\n\n/** A namespace the domain owns (LV figure-server design G1): the `{namespace}` of the full name `{namespace}/{type}`. */\nexport interface FigureNamespace {\n id: string\n name: string\n description?: string | null\n state: 'active' | 'suspended'\n isDefault: boolean\n}\n\nconst FIGURE_NAMESPACE_FIELDS = 'id name description state isDefault'\n\n/** The categories the server allows, or null when a category is free text (LV figure-server design G5). */\nexport async function fetchFigureCategories(): Promise<string[] | null> {\n const response = await client.query({\n query: gql`\n query {\n figureCategories\n }\n `,\n fetchPolicy: 'network-only'\n })\n return unwrap<string[] | null>(response, 'figureCategories') ?? null\n}\n\n/** The domain's namespaces, its default first. */\nexport async function fetchFigureNamespaces(): Promise<FigureNamespace[]> {\n const response = await client.query({\n query: gql`\n query {\n figureNamespaces { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n fetchPolicy: 'network-only'\n })\n return unwrap<FigureNamespace[]>(response, 'figureNamespaces') ?? []\n}\n\n/** Adds a namespace to the domain; the server says why when it refuses (not a namespace, another domain's). */\nexport async function createFigureNamespace(name: string, isDefault = false): Promise<FigureNamespace> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($name: String!, $isDefault: Boolean) {\n createFigureNamespace(name: $name, isDefault: $isDefault) { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n variables: { name, isDefault }\n })\n return unwrap<FigureNamespace>(response, 'createFigureNamespace')\n}\n\n/** Makes one of the domain's active namespaces its default. */\nexport async function setDefaultFigureNamespace(name: string): Promise<FigureNamespace> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($name: String!) {\n setDefaultFigureNamespace(name: $name) { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n variables: { name }\n })\n return unwrap<FigureNamespace>(response, 'setDefaultFigureNamespace')\n}\n\n/** Suspends a namespace or makes it active again. */\nexport async function setFigureNamespaceState(name: string, state: 'active' | 'suspended'): Promise<FigureNamespace> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($name: String!, $state: String!) {\n setFigureNamespaceState(name: $name, state: $state) { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n variables: { name, state }\n })\n return unwrap<FigureNamespace>(response, 'setFigureNamespaceState')\n}\n\n/** A draft a revert kept when it wrote over it (ADR-0097 §5.6). Not a released version. */\nexport interface KeptDraft {\n id: string\n kind: 'revert' | 'undo'\n state?: string\n version?: number\n revertedToVersion?: number\n restoredAt?: string\n createdAt?: string\n creator?: { name?: string }\n}\n\nconst KEPT_FIELDS = `id kind state version revertedToVersion restoredAt createdAt creator { name }`\n\n/**\n * 옛 판을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 덮이는 초안은 같은 트랜잭션에서 서버가\n * 보관한다. `expectedSource` 는 서버가 들고 있다고 확인된 정본 -- 그 사이 바뀌었으면 거절된다. 답을 못 받았으면\n * (REVERT_UNREACHABLE) 같은 `requestId` 로 다시 묻는다: 서버는 같은 되돌리기로 답하고 두 번 하지 않는다.\n */\nexport async function revertFigureVersion(\n id: string,\n version: number,\n requestId: string,\n expectedSource: string\n): Promise<SaveResult & { kept: KeptDraft }> {\n const response = await reached(\n client.mutate({\n mutation: gql`\n mutation RevertFigureVersion($id: String!, $version: Float!, $requestId: String!, $expectedSource: String!) {\n revertFigureVersion(id: $id, version: $version, requestId: $requestId, expectedSource: $expectedSource) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n kept { ${KEPT_FIELDS} }\n }\n }\n `,\n variables: { id, version, requestId, expectedSource }\n }),\n 'REVERT_UNREACHABLE'\n )\n refusedIn(response)\n return unwrap<SaveResult & { kept: KeptDraft }>(response, 'revertFigureVersion')\n}\n\n/** 되돌리기 취소 -- 보관한 초안을 되살린다. 되돌린 뒤 모델이 바뀌었으면 서버가 거절하고 보관본은 남는다. */\nexport async function undoFigureRevert(keptId: string, requestId: string): Promise<SaveResult & { kept: KeptDraft }> {\n const response = await reached(\n client.mutate({\n mutation: gql`\n mutation UndoFigureRevert($keptId: String!, $requestId: String!) {\n undoFigureRevert(keptId: $keptId, requestId: $requestId) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n kept { ${KEPT_FIELDS} }\n }\n }\n `,\n variables: { keptId, requestId }\n }),\n 'REVERT_UNREACHABLE'\n )\n refusedIn(response)\n return unwrap<SaveResult & { kept: KeptDraft }>(response, 'undoFigureRevert')\n}\n\n/** A figure an AI proposal was saved into (ADR-0097 §5.6 item 5). */\nexport interface ProposalOutcome {\n figureId: string\n type?: string\n name?: string\n recordedAt?: string\n}\n\n/**\n * 제안이 무엇이 됐나 -- 그 제안을 담은 저장이 들어간 피규어들. 비었으면 저장되지 않았다(적용만 하고 저장 전이거나, 되돌렸거나,\n * 적용하지 않았다). 도크의 「결과 확인」이 이것을 묻는다: 도크의 기억이 아니라 서버의 기록이다.\n */\nexport async function fetchProposalOutcome(proposalKey: string): Promise<ProposalOutcome[]> {\n const response = await client.query({\n query: gql`\n query FigureProposalOutcome($proposalKey: String!) {\n figureProposalOutcome(proposalKey: $proposalKey) { figureId type name recordedAt }\n }\n `,\n variables: { proposalKey },\n fetchPolicy: 'network-only'\n })\n return unwrap<ProposalOutcome[]>(response, 'figureProposalOutcome') ?? []\n}\n\n/** 되돌리기가 보관한 초안들, 새것부터 -- 다시 접속해도 여기서 찾는다. */\nexport async function fetchReplacedDrafts(id: string): Promise<KeptDraft[]> {\n const response = await client.query({\n query: gql`\n query FigureReplacedDrafts($id: String!) {\n figureReplacedDrafts(id: $id) { ${KEPT_FIELDS} }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n return unwrap<KeptDraft[]>(response, 'figureReplacedDrafts')\n}\n\n/**\n * 발행해도 되나 — **누르기 전에** 묻는다.\n *\n * 막는 것은 서버의 `releaseFigure` 이고 이것은 이유를 미리 보여 주기 위한 것이다. 허가증이\n * 아니므로 이 답이 통과라 해도 발행은 다시 판정을 받는다.\n */\nexport async function inspectFigure(id: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigure($id: String!) {\n inspectFigure(id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n blocking\n }\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigure')\n}\n\n/**\n * 편집 중인 원본의 발행 판정 — 저장하지 않은 것을 잰다.\n *\n * 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는\n * 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —\n * 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행판을 읽는다.\n */\nexport async function inspectFigureDraft(source: string, id?: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigureDraft($source: String!, $id: String) {\n inspectFigureDraft(source: $source, id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n blocking\n }\n }\n }\n `,\n variables: { source, id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigureDraft')\n}\n\n/** 발행된 판들 — 최근 것부터 열 개. */\n/**\n * 직전 발행판의 정본. 한 번도 발행 안 했으면 `undefined`.\n *\n * 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장\n * 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.\n */\nexport async function fetchReleasedSource(id: string): Promise<string | undefined> {\n return (await fetchReleasedFigure(id))?.source\n}\n\n/** Read-only catalogue preview: source and version come from the same released snapshot. */\nexport async function fetchReleasedFigure(id: string): Promise<{ version: number; source: string } | undefined> {\n const response = await client.query({\n query: gql`\n query FigureReleased($id: String!) {\n figureReleased(id: $id) {\n version\n source\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<{ version: number; source: string } | undefined>(response, 'figureReleased') ?? undefined\n}\n\nexport async function fetchFigureVersions(id: string): Promise<FigureVersion[]> {\n const response = await client.query({\n query: gql`\n query FigureVersions($id: String!) {\n figureVersions(id: $id) {\n version\n comment\n state\n updatedAt\n updater { id name }\n score\n triangles\n groups\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureVersion[]>(response, 'figureVersions') ?? []\n}\n\n/** A recipe template as the server gives it (ADR-0095, amended): words in the language asked, the recipe as JSON. */\nexport interface RecipeTemplateRow {\n name: string\n category: string\n tags: string[]\n target: 'part' | 'figure'\n title: string\n description: string\n source: string\n hasThumbnail: boolean\n}\n\nexport async function fetchRecipeTemplates(lang: string): Promise<RecipeTemplateRow[]> {\n const response = await client.query({\n query: gql`\n query ($lang: String) {\n figureRecipeTemplates(lang: $lang) {\n name\n category\n tags\n target\n title\n description\n source\n hasThumbnail\n }\n }\n `,\n variables: { lang }\n })\n return unwrap<RecipeTemplateRow[] | null>(response, 'figureRecipeTemplates') ?? []\n}\n\n/** One template's guide in the language asked, or '' while none is written. */\nexport async function fetchRecipeGuide(name: string, lang: string): Promise<string> {\n const response = await client.query({\n query: gql`\n query ($name: String!, $lang: String) {\n figureRecipeTemplate(name: $name, lang: $lang) {\n guide\n }\n }\n `,\n variables: { name, lang }\n })\n return unwrap<{ guide?: string } | null>(response, 'figureRecipeTemplate')?.guide ?? ''\n}\n\n/**\n * A GLB for a mesh part (ADR-0129): the server reads it, keeps it by its hash and answers what it holds. A file a board\n * cannot draw is refused with the reason in the screen's words.\n */\nexport async function uploadFigureMesh(file: File, onProgress: UploadProgress): Promise<import('../modeller/mesh-import.js').MeshUpload> {\n const response = await client.mutate({\n mutation: gql`\n mutation UploadFigureMesh($file: Upload!) {\n uploadFigureMesh(file: $file) {\n hash\n summary\n }\n }\n `,\n variables: { file },\n context: uploadContext(onProgress)\n })\n return unwrap(response, 'uploadFigureMesh')\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../client/graphql/index.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { i18next } from '@operato/i18n'\nimport { client, uploadContext, type UploadProgress } from '@operato/graphql'\n\nimport { reached, refusedIn } from './reached.js'\n\nimport type {\n Figure,\n FigureFinding,\n FigureInspection,\n FigureListResult,\n FigurePatch,\n FigureVersion,\n NewFigure\n} from '../types.js'\n\n/**\n * 응답에서 값을 꺼낸다. **서버가 말한 사유를 그대로 올린다.**\n *\n * 전에는 `response.data.figures` 처럼 바로 꺼냈다. 서버가 오류를 돌려주면 `data` 가\n * 없으므로 `Cannot read properties of undefined (reading 'figures')` 가 났고, 화면에는\n * 그 문장이 그대로 떴다. 사용자는 자기가 무엇을 잘못했는지 알 수 없다 — 실제로 AI\n * 요청에서 그렇게 났다.\n *\n * 값이 `null` 인 것은 오류가 아니다. 지운 것을 물었을 때처럼 없는 것이 답인 경우가 있다.\n * 여기서 막는 것은 **`data` 자체가 없는 것**이다.\n */\nfunction unwrap<T>(response: { data?: Record<string, unknown>; errors?: readonly { message: string }[] }, field: string): T {\n const said = response.errors?.[0]?.message\n if (said) throw new Error(said)\n\n if (!response.data) {\n /* The field name stays off the screen because the user cannot act on it; it goes to the console. */\n console.error(`[figure-ui] the server returned no data for ${field}`)\n throw new Error(i18next.t('figure.text.server-returned-no-data'))\n }\n\n return response.data[field] as T\n}\n\n\n/**\n * 목록에 필요한 필드만 가져온다 — `source` 는 무겁고 목록에서 쓰지 않는다.\n *\n * **`thumbnail` 도 여기 없다.** base64 문자열이라 목록 응답에 실으면 카탈로그를 열 때마다 그림\n * 전부가 다시 오고(표본 14개 227KB), data URL 은 브라우저가 캐시하지 못한다. 그림은 주소로\n * 부른다(`figure-thumbnail` 라우트). 대신 `thumbnailUpdatedAt` 을 받는다 — 그림이 있나 없나를\n * 그것으로 알고, 버전을 가리는 값으로도 쓴다.\n */\nconst FIGURE_LIST_FIELDS = `\n id\n namespace\n type\n name\n description\n category\n tags\n state\n version\n deprecated\n supersededBy\n public\n score\n triangles\n groups\n thumbnailUpdatedAt\n updatedAt\n updater { id name }\n`\n\n/**\n * 목록을 가져온다.\n *\n * 걸러 보기·정렬·쪽 나누기를 **그대로 넘긴다.** 화면이 `search`·`state` 같은 이름을 따로 만들어\n * filter 로 옮기던 것을 그만두었다 — 그 이름들은 격자(ox-grist)가 이미 컬럼 설정에서 만들어 주고,\n * 중간에 한 벌 더 두면 격자가 아는 조건과 서버에 가는 조건이 어긋난다.\n */\nexport async function fetchFigureList(params: {\n page?: number\n limit?: number\n filters?: unknown[]\n sortings?: { name: string; desc?: boolean }[]\n /** 'public': other domains' public figures, with their domain (LV figure-server design G2). The domain's own by default. */\n scope?: 'own' | 'public'\n}): Promise<FigureListResult> {\n const field = params.scope === 'public' ? 'publicFigures' : 'figures'\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n ${field}(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items { ${FIGURE_LIST_FIELDS} ${params.scope === 'public' ? 'domain { id name subdomain }' : ''} }\n total\n }\n }\n `,\n variables: {\n filters: params.filters ?? [],\n pagination: { page: params.page ?? 1, limit: params.limit ?? 30 },\n sortings: params.sortings?.length ? params.sortings : [{ name: 'updatedAt', desc: true }]\n },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureListResult>(response, field)\n}\n\n/** 저작면에 필요한 전부 — `source` 를 포함한다. */\nexport async function fetchFigure(id: string): Promise<Figure> {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n figure(id: $id) {\n ${FIGURE_LIST_FIELDS}\n source\n properties\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<Figure>(response, 'figure')\n}\n\n/**\n * 이미 쓰이고 있는 타입 이름.\n *\n * `type` 은 만든 뒤에 고칠 수 없으므로 **만들기 전에** 알려 줘야 한다. 저장을 눌러\n * 거절당하고 나서 아는 것은 늦다.\n */\nexport async function fetchFigureTypeNames(): Promise<string[]> {\n const response = await client.query({\n query: gql`\n query {\n figureTypeNames\n }\n `,\n fetchPolicy: 'network-only'\n })\n\n return unwrap<string[] | null>(response, 'figureTypeNames') ?? []\n}\n\nexport interface SaveResult {\n figure: Figure\n /** 막지 않는 발견. 저장은 됐지만 화면이 보여 줘야 한다. */\n violations: FigureFinding[]\n}\n\nexport async function createFigure(figure: NewFigure): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($figure: NewFigure!) {\n createFigure(figure: $figure) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { figure }\n })\n\n return unwrap<SaveResult>(response, 'createFigure')\n}\n\nexport async function updateFigure(id: string, patch: FigurePatch): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: FigurePatch!) {\n updateFigure(id: $id, patch: $patch) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, patch }\n })\n\n return unwrap<SaveResult>(response, 'updateFigure')\n}\n\n/** Makes a released figure public to other domains, or private again (LV figure-server design G2). */\nexport async function setFigurePublic(id: string, isPublic: boolean): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $public: Boolean!) {\n setFigurePublic(id: $id, public: $public) {\n ${FIGURE_LIST_FIELDS}\n }\n }\n `,\n variables: { id, public: isPublic }\n })\n\n return unwrap<Figure>(response, 'setFigurePublic')\n}\n\n/** Retires a released figure, naming the type to use instead, or brings it back. Release rows are not touched. */\nexport async function setFigureDeprecation(id: string, deprecated: boolean, supersededBy?: string | null): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $deprecated: Boolean!, $supersededBy: String) {\n setFigureDeprecation(id: $id, deprecated: $deprecated, supersededBy: $supersededBy) {\n ${FIGURE_LIST_FIELDS}\n }\n }\n `,\n variables: { id, deprecated, supersededBy: supersededBy || null }\n })\n\n return unwrap<Figure>(response, 'setFigureDeprecation')\n}\n\nexport async function deleteFigure(id: string): Promise<boolean> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteFigure(id: $id)\n }\n `,\n variables: { id }\n })\n\n return unwrap<boolean>(response, 'deleteFigure')\n}\n\n/**\n * 발행한다 — 버전 번호가 오르고, 그 순간이 버전으로 남는다.\n *\n * 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은\n * 발행을 푼다(초안으로 돌아온다).\n */\nexport async function releaseFigure(id: string, comment?: string): Promise<Figure> {\n const response = await client.mutate({\n mutation: gql`\n mutation ReleaseFigure($id: String!, $comment: String) {\n releaseFigure(id: $id, comment: $comment) {\n id\n version\n state\n updatedAt\n }\n }\n `,\n variables: { id, comment }\n })\n\n return unwrap<Figure>(response, 'releaseFigure')\n}\n\n/** A namespace the domain owns (LV figure-server design G1): the `{namespace}` of the full name `{namespace}/{type}`. */\nexport interface FigureNamespace {\n id: string\n name: string\n description?: string | null\n state: 'active' | 'suspended'\n isDefault: boolean\n}\n\nconst FIGURE_NAMESPACE_FIELDS = 'id name description state isDefault'\n\n/** The categories the server allows, or null when a category is free text (LV figure-server design G5). */\nexport async function fetchFigureCategories(): Promise<string[] | null> {\n const response = await client.query({\n query: gql`\n query {\n figureCategories\n }\n `,\n fetchPolicy: 'network-only'\n })\n return unwrap<string[] | null>(response, 'figureCategories') ?? null\n}\n\n/** The domain's namespaces, its default first. */\nexport async function fetchFigureNamespaces(): Promise<FigureNamespace[]> {\n const response = await client.query({\n query: gql`\n query {\n figureNamespaces { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n fetchPolicy: 'network-only'\n })\n return unwrap<FigureNamespace[]>(response, 'figureNamespaces') ?? []\n}\n\n/** Adds a namespace to the domain; the server says why when it refuses (not a namespace, another domain's). */\nexport async function createFigureNamespace(name: string, isDefault = false): Promise<FigureNamespace> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($name: String!, $isDefault: Boolean) {\n createFigureNamespace(name: $name, isDefault: $isDefault) { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n variables: { name, isDefault }\n })\n return unwrap<FigureNamespace>(response, 'createFigureNamespace')\n}\n\n/** Makes one of the domain's active namespaces its default. */\nexport async function setDefaultFigureNamespace(name: string): Promise<FigureNamespace> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($name: String!) {\n setDefaultFigureNamespace(name: $name) { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n variables: { name }\n })\n return unwrap<FigureNamespace>(response, 'setDefaultFigureNamespace')\n}\n\n/** Suspends a namespace or makes it active again. */\nexport async function setFigureNamespaceState(name: string, state: 'active' | 'suspended'): Promise<FigureNamespace> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($name: String!, $state: String!) {\n setFigureNamespaceState(name: $name, state: $state) { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n variables: { name, state }\n })\n return unwrap<FigureNamespace>(response, 'setFigureNamespaceState')\n}\n\n/** A draft a revert kept when it wrote over it (ADR-0097 §5.6). Not a released version. */\nexport interface KeptDraft {\n id: string\n kind: 'revert' | 'undo'\n state?: string\n version?: number\n revertedToVersion?: number\n restoredAt?: string\n createdAt?: string\n creator?: { name?: string }\n}\n\nconst KEPT_FIELDS = `id kind state version revertedToVersion restoredAt createdAt creator { name }`\n\n/**\n * 옛 버전을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 덮이는 초안은 같은 트랜잭션에서 서버가\n * 보관한다. `expectedSource` 는 서버가 들고 있다고 확인된 정본 -- 그 사이 바뀌었으면 거절된다. 답을 못 받았으면\n * (REVERT_UNREACHABLE) 같은 `requestId` 로 다시 묻는다: 서버는 같은 되돌리기로 답하고 두 번 하지 않는다.\n */\nexport async function revertFigureVersion(\n id: string,\n version: number,\n requestId: string,\n expectedSource: string\n): Promise<SaveResult & { kept: KeptDraft }> {\n const response = await reached(\n client.mutate({\n mutation: gql`\n mutation RevertFigureVersion($id: String!, $version: Float!, $requestId: String!, $expectedSource: String!) {\n revertFigureVersion(id: $id, version: $version, requestId: $requestId, expectedSource: $expectedSource) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n kept { ${KEPT_FIELDS} }\n }\n }\n `,\n variables: { id, version, requestId, expectedSource }\n }),\n 'REVERT_UNREACHABLE'\n )\n refusedIn(response)\n return unwrap<SaveResult & { kept: KeptDraft }>(response, 'revertFigureVersion')\n}\n\n/** 되돌리기 취소 -- 보관한 초안을 되살린다. 되돌린 뒤 모델이 바뀌었으면 서버가 거절하고 보관본은 남는다. */\nexport async function undoFigureRevert(keptId: string, requestId: string): Promise<SaveResult & { kept: KeptDraft }> {\n const response = await reached(\n client.mutate({\n mutation: gql`\n mutation UndoFigureRevert($keptId: String!, $requestId: String!) {\n undoFigureRevert(keptId: $keptId, requestId: $requestId) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n kept { ${KEPT_FIELDS} }\n }\n }\n `,\n variables: { keptId, requestId }\n }),\n 'REVERT_UNREACHABLE'\n )\n refusedIn(response)\n return unwrap<SaveResult & { kept: KeptDraft }>(response, 'undoFigureRevert')\n}\n\n/** A figure an AI proposal was saved into (ADR-0097 §5.6 item 5). */\nexport interface ProposalOutcome {\n figureId: string\n type?: string\n name?: string\n recordedAt?: string\n}\n\n/**\n * 제안이 무엇이 됐나 -- 그 제안을 담은 저장이 들어간 피규어들. 비었으면 저장되지 않았다(적용만 하고 저장 전이거나, 되돌렸거나,\n * 적용하지 않았다). 도크의 「결과 확인」이 이것을 묻는다: 도크의 기억이 아니라 서버의 기록이다.\n */\nexport async function fetchProposalOutcome(proposalKey: string): Promise<ProposalOutcome[]> {\n const response = await client.query({\n query: gql`\n query FigureProposalOutcome($proposalKey: String!) {\n figureProposalOutcome(proposalKey: $proposalKey) { figureId type name recordedAt }\n }\n `,\n variables: { proposalKey },\n fetchPolicy: 'network-only'\n })\n return unwrap<ProposalOutcome[]>(response, 'figureProposalOutcome') ?? []\n}\n\n/** 되돌리기가 보관한 초안들, 새것부터 -- 다시 접속해도 여기서 찾는다. */\nexport async function fetchReplacedDrafts(id: string): Promise<KeptDraft[]> {\n const response = await client.query({\n query: gql`\n query FigureReplacedDrafts($id: String!) {\n figureReplacedDrafts(id: $id) { ${KEPT_FIELDS} }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n return unwrap<KeptDraft[]>(response, 'figureReplacedDrafts')\n}\n\n/**\n * 발행해도 되나 — **누르기 전에** 묻는다.\n *\n * 막는 것은 서버의 `releaseFigure` 이고 이것은 이유를 미리 보여 주기 위한 것이다. 허가증이\n * 아니므로 이 답이 통과라 해도 발행은 다시 판정을 받는다.\n */\nexport async function inspectFigure(id: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigure($id: String!) {\n inspectFigure(id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n blocking\n }\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigure')\n}\n\n/**\n * 편집 중인 원본의 발행 판정 — 저장하지 않은 것을 잰다.\n *\n * 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는\n * 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —\n * 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행 버전을 읽는다.\n */\nexport async function inspectFigureDraft(source: string, id?: string): Promise<FigureInspection> {\n const response = await client.query({\n query: gql`\n query InspectFigureDraft($source: String!, $id: String) {\n inspectFigureDraft(source: $source, id: $id) {\n blocked\n findings {\n code\n message\n why\n how\n at\n blocking\n }\n }\n }\n `,\n variables: { source, id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureInspection>(response, 'inspectFigureDraft')\n}\n\n/** 발행된 버전들 — 최근 것부터 열 개. */\n/**\n * 직전 발행 버전의 정본. 한 번도 발행 안 했으면 `undefined`.\n *\n * 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장\n * 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.\n */\nexport async function fetchReleasedSource(id: string): Promise<string | undefined> {\n return (await fetchReleasedFigure(id))?.source\n}\n\n/** Read-only catalogue preview: source and version come from the same released snapshot. */\nexport async function fetchReleasedFigure(id: string): Promise<{ version: number; source: string } | undefined> {\n const response = await client.query({\n query: gql`\n query FigureReleased($id: String!) {\n figureReleased(id: $id) {\n version\n source\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<{ version: number; source: string } | undefined>(response, 'figureReleased') ?? undefined\n}\n\nexport async function fetchFigureVersions(id: string): Promise<FigureVersion[]> {\n const response = await client.query({\n query: gql`\n query FigureVersions($id: String!) {\n figureVersions(id: $id) {\n version\n comment\n state\n updatedAt\n updater { id name }\n score\n triangles\n groups\n }\n }\n `,\n variables: { id },\n fetchPolicy: 'network-only'\n })\n\n return unwrap<FigureVersion[]>(response, 'figureVersions') ?? []\n}\n\n/** A recipe template as the server gives it (ADR-0095, amended): words in the language asked, the recipe as JSON. */\nexport interface RecipeTemplateRow {\n name: string\n category: string\n tags: string[]\n target: 'part' | 'figure'\n title: string\n description: string\n source: string\n hasThumbnail: boolean\n}\n\nexport async function fetchRecipeTemplates(lang: string): Promise<RecipeTemplateRow[]> {\n const response = await client.query({\n query: gql`\n query ($lang: String) {\n figureRecipeTemplates(lang: $lang) {\n name\n category\n tags\n target\n title\n description\n source\n hasThumbnail\n }\n }\n `,\n variables: { lang }\n })\n return unwrap<RecipeTemplateRow[] | null>(response, 'figureRecipeTemplates') ?? []\n}\n\n/** One template's guide in the language asked, or '' while none is written. */\nexport async function fetchRecipeGuide(name: string, lang: string): Promise<string> {\n const response = await client.query({\n query: gql`\n query ($name: String!, $lang: String) {\n figureRecipeTemplate(name: $name, lang: $lang) {\n guide\n }\n }\n `,\n variables: { name, lang }\n })\n return unwrap<{ guide?: string } | null>(response, 'figureRecipeTemplate')?.guide ?? ''\n}\n\n/**\n * A GLB for a mesh part (ADR-0129): the server reads it, keeps it by its hash and answers what it holds. A file a board\n * cannot draw is refused with the reason in the screen's words.\n */\nexport async function uploadFigureMesh(file: File, onProgress: UploadProgress): Promise<import('../modeller/mesh-import.js').MeshUpload> {\n const response = await client.mutate({\n mutation: gql`\n mutation UploadFigureMesh($file: Upload!) {\n uploadFigureMesh(file: $file) {\n hash\n summary\n }\n }\n `,\n variables: { file },\n context: uploadContext(onProgress)\n })\n return unwrap(response, 'uploadFigureMesh')\n}\n"]}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import type { FigureAsset } from '@hatiolab/figure-model';
|
|
2
2
|
import { type FigureCapabilityDescriptor, type FigureCapabilitySetting } from '@hatiolab/things-scene';
|
|
3
|
+
import { type Rung } from '@things-factory/figure-viewer/dist-client/capability-rung.js';
|
|
3
4
|
/** The products' capabilities this modeller knows: registered descriptors with a namespaced name. */
|
|
4
5
|
export declare const consumerDescriptors: () => FigureCapabilityDescriptor[];
|
|
5
6
|
/** The products' capabilities the figure declares that this modeller has no descriptor for: kept, said, removable. */
|
|
6
7
|
export declare function unknownConsumerNames(asset: FigureAsset): string[];
|
|
7
8
|
/** What a capability still needs on this figure: a shared or a product's name not declared, or the item mark. */
|
|
8
9
|
export declare function needsMissing(descriptor: FigureCapabilityDescriptor, asset: FigureAsset): string[];
|
|
10
|
+
/**
|
|
11
|
+
* What a product's capability needs that the figure lacks, as the tab offers it: the item tag, and the rungs that would
|
|
12
|
+
* give the names it needs (Holdable | FlowNode and Capacity | FlowNode: hold or flow). Its own names are not shown to the
|
|
13
|
+
* author (sweep 2026-10-27 U19); `names` keeps a need no rung gives.
|
|
14
|
+
*/
|
|
15
|
+
export declare function needsOffered(descriptor: FigureCapabilityDescriptor, asset: FigureAsset): {
|
|
16
|
+
item: boolean;
|
|
17
|
+
rungs: Rung[];
|
|
18
|
+
names: string[];
|
|
19
|
+
};
|
|
9
20
|
/** The names with a product's capability put on or taken off; the shared ones as they are. */
|
|
10
21
|
export declare function namesWith(asset: FigureAsset, name: string, on: boolean): string[];
|
|
11
22
|
/** A capability's settings on the asset, or an empty object. */
|