@things-factory/figure-ui 10.1.38 → 10.1.40
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 +53 -0
- package/client/modeller/figure-history-panel.ts +11 -85
- package/client/modeller/figure-v3-properties.ts +254 -0
- package/client/modeller/figure-v3-verdict.ts +247 -0
- package/client/modeller/gate-findings.ts +105 -0
- package/client/modeller/v3-canvas.ts +27 -3
- package/client/modeller/v3-editor.ts +39 -11
- package/client/modeller/v3-figure-facts.ts +53 -0
- package/client/modeller/v3-occupancy.ts +6 -2
- package/client/pages/figure-v3-modeller-page.ts +250 -17
- package/dist-client/graphql/index.d.ts +8 -0
- package/dist-client/graphql/index.js +51 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +6 -0
- package/dist-client/modeller/figure-history-panel.js +9 -85
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-v3-properties.d.ts +46 -0
- package/dist-client/modeller/figure-v3-properties.js +259 -0
- package/dist-client/modeller/figure-v3-properties.js.map +1 -0
- package/dist-client/modeller/figure-v3-verdict.d.ts +50 -0
- package/dist-client/modeller/figure-v3-verdict.js +268 -0
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -0
- package/dist-client/modeller/gate-findings.d.ts +12 -0
- package/dist-client/modeller/gate-findings.js +102 -0
- package/dist-client/modeller/gate-findings.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +6 -0
- package/dist-client/modeller/v3-canvas.js +27 -3
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-editor.js +39 -11
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-figure-facts.d.ts +26 -0
- package/dist-client/modeller/v3-figure-facts.js +34 -0
- package/dist-client/modeller/v3-figure-facts.js.map +1 -0
- package/dist-client/modeller/v3-occupancy.d.ts +5 -1
- package/dist-client/modeller/v3-occupancy.js +4 -2
- package/dist-client/modeller/v3-occupancy.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +40 -0
- package/dist-client/pages/figure-v3-modeller-page.js +264 -18
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/v3-properties.test.ts +93 -0
- package/test/v3-theme.test.ts +67 -0
- package/test/v3-verdict.test.ts +82 -0
- package/translations/en.json +12 -0
- package/translations/ja.json +12 -0
- package/translations/ko.json +12 -0
- package/translations/ms.json +12 -0
- package/translations/zh.json +12 -0
|
@@ -3,10 +3,15 @@ import { customElement, state } from 'lit/decorators.js'
|
|
|
3
3
|
import { PageView } from '@operato/shell'
|
|
4
4
|
import { createV3ProductSession } from '../modeller/v3-product-session.js'
|
|
5
5
|
import { v3ProductStore } from '../graphql/v3-store.js'
|
|
6
|
-
import {
|
|
6
|
+
import { i18next } from '@operato/i18n'
|
|
7
|
+
import { fetchFigure, updateFigure } from '../graphql/index.js'
|
|
7
8
|
import { occupancyFromParts } from '../modeller/v3-occupancy.js'
|
|
9
|
+
import type { V3AuthoringAction } from '@hatiolab/figure-model'
|
|
8
10
|
import '../modeller/v3-editor.js'
|
|
11
|
+
import '@material/web/icon/icon.js'
|
|
9
12
|
import '../modeller/figure-history-panel.js'
|
|
13
|
+
import '../modeller/figure-v3-verdict.js'
|
|
14
|
+
import '../modeller/figure-v3-properties.js'
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
|
|
@@ -37,13 +42,92 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
37
42
|
min-width: 0;
|
|
38
43
|
min-height: 0;
|
|
39
44
|
}
|
|
40
|
-
|
|
45
|
+
aside {
|
|
41
46
|
width: 320px;
|
|
42
47
|
flex: none;
|
|
43
48
|
min-height: 0;
|
|
44
49
|
overflow: auto;
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
45
52
|
border-left: 1px solid var(--md-sys-color-outline-variant);
|
|
46
53
|
}
|
|
54
|
+
label[type-field] {
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: 4px;
|
|
57
|
+
padding: 12px 12px 8px;
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
60
|
+
}
|
|
61
|
+
label[type-field] input,
|
|
62
|
+
label[type-field] code {
|
|
63
|
+
font: inherit;
|
|
64
|
+
font-family: monospace;
|
|
65
|
+
padding: 6px 8px;
|
|
66
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
background: var(--md-sys-color-surface);
|
|
69
|
+
color: var(--md-sys-color-on-surface);
|
|
70
|
+
}
|
|
71
|
+
label[type-field] small {
|
|
72
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
73
|
+
}
|
|
74
|
+
p[fit-error] {
|
|
75
|
+
margin: 0;
|
|
76
|
+
padding: 8px 12px;
|
|
77
|
+
font-size: 12px;
|
|
78
|
+
color: var(--md-sys-color-error);
|
|
79
|
+
}
|
|
80
|
+
label[type-field] small[role='alert'] {
|
|
81
|
+
color: var(--md-sys-color-error);
|
|
82
|
+
}
|
|
83
|
+
/* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */
|
|
84
|
+
div[tabs] {
|
|
85
|
+
display: flex;
|
|
86
|
+
flex: none;
|
|
87
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
88
|
+
}
|
|
89
|
+
button[tab] {
|
|
90
|
+
flex: 1;
|
|
91
|
+
position: relative;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
padding: 7px 6px;
|
|
96
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
97
|
+
background: none;
|
|
98
|
+
border: none;
|
|
99
|
+
border-bottom: 2px solid transparent;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
}
|
|
102
|
+
button[tab]:hover:not([on]) {
|
|
103
|
+
background-color: var(--md-sys-color-surface-container);
|
|
104
|
+
}
|
|
105
|
+
button[tab][on] {
|
|
106
|
+
color: var(--md-sys-color-primary);
|
|
107
|
+
border-bottom-color: var(--md-sys-color-primary);
|
|
108
|
+
}
|
|
109
|
+
button[tab] md-icon {
|
|
110
|
+
font-size: 20px;
|
|
111
|
+
width: 20px;
|
|
112
|
+
height: 20px;
|
|
113
|
+
}
|
|
114
|
+
span[badge] {
|
|
115
|
+
position: absolute;
|
|
116
|
+
top: 5px;
|
|
117
|
+
right: 2px;
|
|
118
|
+
padding: 0 5px;
|
|
119
|
+
font-size: 0.62rem;
|
|
120
|
+
font-weight: 700;
|
|
121
|
+
line-height: 1.5;
|
|
122
|
+
color: var(--md-sys-color-on-primary-container);
|
|
123
|
+
background-color: var(--md-sys-color-primary-container);
|
|
124
|
+
border-radius: 999em;
|
|
125
|
+
pointer-events: none;
|
|
126
|
+
}
|
|
127
|
+
/* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */
|
|
128
|
+
[off] {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
47
131
|
.error {
|
|
48
132
|
padding: 16px;
|
|
49
133
|
color: #922716;
|
|
@@ -62,8 +146,33 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
62
146
|
* 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로
|
|
63
147
|
* V2 화면의 패널을 그대로 건다.
|
|
64
148
|
*/
|
|
65
|
-
@state() private row: { state?: 'draft' | 'released'; version: number
|
|
149
|
+
@state() private row: { state?: 'draft' | 'released'; version: number; type?: string; category?: string; tags?: string[] } = {
|
|
150
|
+
version: 0
|
|
151
|
+
}
|
|
152
|
+
@state() private typeError = ''
|
|
153
|
+
/** Why a command from the side did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
|
|
154
|
+
@state() private commandError = ''
|
|
155
|
+
/**
|
|
156
|
+
* Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on
|
|
157
|
+
* its tab stays in sight while the history is open — as on the V2 page.
|
|
158
|
+
*/
|
|
159
|
+
@state() private tab: 'properties' | 'verdict' | 'history' = 'verdict'
|
|
160
|
+
@state() private instances?: number
|
|
161
|
+
/**
|
|
162
|
+
* The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is
|
|
163
|
+
* kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.
|
|
164
|
+
*/
|
|
165
|
+
private draft?: string
|
|
166
|
+
private draftSource(): string | undefined {
|
|
167
|
+
try {
|
|
168
|
+
this.draft = this.session.source()
|
|
169
|
+
} catch {
|
|
170
|
+
/* loading or saving — keep the last source */
|
|
171
|
+
}
|
|
172
|
+
return this.draft
|
|
173
|
+
}
|
|
66
174
|
private wasDirty = false
|
|
175
|
+
private titled?: string | null
|
|
67
176
|
private announce = () => {
|
|
68
177
|
const view = this.session.read()
|
|
69
178
|
if (!this.active || view.loading || view.saving || view.error || !view.context) return
|
|
@@ -81,7 +190,14 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
81
190
|
}
|
|
82
191
|
private changed = () => {
|
|
83
192
|
this.revision++
|
|
193
|
+
/* Any edit after a failed command makes the message stale — the author has done something about it. */
|
|
194
|
+
this.commandError = ''
|
|
84
195
|
const view = this.session.read()
|
|
196
|
+
/* The title bar reads the name through `context`, which the shell asks for only when told — a rename left it stale. */
|
|
197
|
+
if (view.name !== this.titled) {
|
|
198
|
+
this.titled = view.name
|
|
199
|
+
this.updateContext()
|
|
200
|
+
}
|
|
85
201
|
if (!view.saving) this.announce()
|
|
86
202
|
/* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */
|
|
87
203
|
if (this.wasDirty && !view.dirty && !view.saving && view.id) this.readRow(view.id)
|
|
@@ -90,23 +206,91 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
90
206
|
private async readRow(id: string) {
|
|
91
207
|
try {
|
|
92
208
|
const figure = await fetchFigure(id)
|
|
93
|
-
if (this.session.read().id === id)
|
|
209
|
+
if (this.session.read().id === id)
|
|
210
|
+
this.row = {
|
|
211
|
+
state: figure.state as 'draft' | 'released',
|
|
212
|
+
version: figure.version ?? 0,
|
|
213
|
+
type: figure.type,
|
|
214
|
+
category: figure.category ?? undefined,
|
|
215
|
+
tags: figure.tags ?? undefined
|
|
216
|
+
}
|
|
94
217
|
} catch {
|
|
95
218
|
/* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */
|
|
96
219
|
}
|
|
97
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).
|
|
223
|
+
*
|
|
224
|
+
* 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.
|
|
225
|
+
* 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.
|
|
226
|
+
* 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.
|
|
227
|
+
*/
|
|
228
|
+
private renameType = async (event: Event) => {
|
|
229
|
+
const input = event.target as HTMLInputElement
|
|
230
|
+
const id = this.session.read().id
|
|
231
|
+
const wanted = input.value.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()
|
|
232
|
+
input.value = wanted
|
|
233
|
+
if (!id || !wanted || wanted === this.row.type) return
|
|
234
|
+
try {
|
|
235
|
+
const result = await updateFigure(id, { type: wanted })
|
|
236
|
+
this.typeError = ''
|
|
237
|
+
this.row = { ...this.row, type: result.figure.type }
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.typeError = error instanceof Error ? error.message : String(error)
|
|
240
|
+
input.value = this.row.type ?? ''
|
|
241
|
+
}
|
|
242
|
+
}
|
|
98
243
|
/** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */
|
|
99
244
|
private fitToParts = () => {
|
|
245
|
+
this.runCommand(() => occupancyFromParts(this.session.source()))
|
|
246
|
+
}
|
|
247
|
+
/** A command from the properties tab — rename, declare the room, the mounting face. */
|
|
248
|
+
private onCommand = (event: Event) => {
|
|
249
|
+
const action = (event as CustomEvent).detail?.action as V3AuthoringAction | undefined
|
|
250
|
+
if (action) this.runCommand(() => action)
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* One step of the session's history, whichever part of the side asked for it — so undo takes it back like any
|
|
254
|
+
* other edit, and it is saved with the figure.
|
|
255
|
+
*/
|
|
256
|
+
private runCommand(actionOf: () => V3AuthoringAction) {
|
|
100
257
|
try {
|
|
101
|
-
this.session.command(
|
|
258
|
+
this.session.command(actionOf())
|
|
259
|
+
this.commandError = ''
|
|
102
260
|
this.changed()
|
|
103
261
|
} catch (error) {
|
|
104
|
-
|
|
262
|
+
/*
|
|
263
|
+
This used to set the page error, which replaces the whole editor with one raw line — for a figure with no
|
|
264
|
+
parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).
|
|
265
|
+
*/
|
|
266
|
+
this.commandError =
|
|
267
|
+
(error as { code?: string })?.code === 'OCCUPANCY_NO_PARTS'
|
|
268
|
+
? i18next.t('figure.text.fit-needs-parts')
|
|
269
|
+
: error instanceof Error
|
|
270
|
+
? error.message
|
|
271
|
+
: String(error)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Category and tags — the catalogue's, kept on the row, not in the asset. Written at once, as the V2 page does;
|
|
276
|
+
* the server's refusal is shown as it gave it.
|
|
277
|
+
*/
|
|
278
|
+
private onCatalog = async (event: Event) => {
|
|
279
|
+
const patch = (event as CustomEvent).detail as { category?: string; tags?: string[] }
|
|
280
|
+
const id = this.session.read().id
|
|
281
|
+
if (!id || !patch) return
|
|
282
|
+
try {
|
|
283
|
+
const result = await updateFigure(id, patch)
|
|
284
|
+
this.commandError = ''
|
|
285
|
+
this.row = { ...this.row, category: result.figure.category ?? undefined, tags: result.figure.tags ?? undefined }
|
|
286
|
+
} catch (error) {
|
|
287
|
+
this.commandError = error instanceof Error ? error.message : String(error)
|
|
105
288
|
}
|
|
106
289
|
}
|
|
107
290
|
private released = (event: Event) => {
|
|
108
291
|
const figure = (event as CustomEvent).detail?.figure
|
|
109
|
-
|
|
292
|
+
/* Keep the rest of the row: rebuilding it from these two dropped the type, and the locked type field went blank. */
|
|
293
|
+
if (figure?.id && figure.id === this.session.read().id) this.row = { ...this.row, state: figure.state, version: figure.version ?? 0 }
|
|
110
294
|
}
|
|
111
295
|
/** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */
|
|
112
296
|
private reverted = async () => {
|
|
@@ -159,13 +343,16 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
159
343
|
const discard = this.discardApproved
|
|
160
344
|
this.discardApproved = false
|
|
161
345
|
if (id) {
|
|
346
|
+
this.draft = undefined
|
|
162
347
|
const opening = this.session.open(id, { discard })
|
|
163
348
|
this.changed()
|
|
164
349
|
await opening
|
|
165
350
|
this.readRow(id)
|
|
166
351
|
} else {
|
|
167
352
|
this.session.startNew({ discard })
|
|
353
|
+
this.draft = undefined
|
|
168
354
|
this.row = { version: 0 }
|
|
355
|
+
this.typeError = ''
|
|
169
356
|
}
|
|
170
357
|
this.routeId = id
|
|
171
358
|
this.error = ''
|
|
@@ -185,15 +372,61 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
185
372
|
.revision=${this.revision}
|
|
186
373
|
@v3-changed=${this.changed}
|
|
187
374
|
></figure-v3-editor>
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
375
|
+
<aside>
|
|
376
|
+
<label type-field>
|
|
377
|
+
<span>${i18next.t('figure.label.figure-type')}</span>
|
|
378
|
+
${this.row.version > 0
|
|
379
|
+
? html`<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`
|
|
380
|
+
: html`<input
|
|
381
|
+
.value=${this.row.type ?? ''}
|
|
382
|
+
placeholder=${i18next.t('figure.text.figure-type-placeholder')}
|
|
383
|
+
?disabled=${!view.id}
|
|
384
|
+
@change=${this.renameType}
|
|
385
|
+
/>`}
|
|
386
|
+
${this.typeError ? html`<small role="alert">${this.typeError}</small>` : ''}
|
|
387
|
+
</label>
|
|
388
|
+
${this.commandError ? html`<p fit-error role="alert">${this.commandError}</p>` : ''}
|
|
389
|
+
<div tabs>
|
|
390
|
+
${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
|
|
391
|
+
${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
|
|
392
|
+
${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
|
|
393
|
+
</div>
|
|
394
|
+
<figure-v3-properties
|
|
395
|
+
?off=${this.tab !== 'properties'}
|
|
396
|
+
.source=${this.draftSource()}
|
|
397
|
+
.figureId=${view.id}
|
|
398
|
+
.figureCategory=${this.row.category}
|
|
399
|
+
.figureTags=${this.row.tags}
|
|
400
|
+
@v3-command=${this.onCommand}
|
|
401
|
+
@v3-catalog=${this.onCatalog}
|
|
402
|
+
></figure-v3-properties>
|
|
403
|
+
<figure-v3-verdict
|
|
404
|
+
?off=${this.tab !== 'verdict'}
|
|
405
|
+
.source=${this.draftSource()}
|
|
406
|
+
.figureId=${view.id}
|
|
407
|
+
.figureType=${this.row.type}
|
|
408
|
+
@capacity-changed=${(event: CustomEvent) => (this.instances = event.detail.instances)}
|
|
409
|
+
@fit-base-to-parts=${this.fitToParts}
|
|
410
|
+
></figure-v3-verdict>
|
|
411
|
+
<figure-history-panel
|
|
412
|
+
?off=${this.tab !== 'history'}
|
|
413
|
+
format="v3"
|
|
414
|
+
.figureId=${view.id}
|
|
415
|
+
.figureState=${this.row.state}
|
|
416
|
+
.figureVersion=${this.row.version}
|
|
417
|
+
.figureType=${this.row.type}
|
|
418
|
+
.dirty=${!!view.dirty}
|
|
419
|
+
@fit-base-to-parts=${this.fitToParts}
|
|
420
|
+
@figure-released=${this.released}
|
|
421
|
+
@figure-reverted=${this.reverted}
|
|
422
|
+
></figure-history-panel>
|
|
423
|
+
</aside>`
|
|
424
|
+
}
|
|
425
|
+
private renderTab(tab: 'properties' | 'verdict' | 'history', icon: string, label: string, badge?: number) {
|
|
426
|
+
return html`
|
|
427
|
+
<button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>
|
|
428
|
+
<md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}
|
|
429
|
+
</button>
|
|
430
|
+
`
|
|
198
431
|
}
|
|
199
432
|
}
|
|
@@ -66,6 +66,14 @@ export declare function revertFigureVersion(id: string, version: number): Promis
|
|
|
66
66
|
* 아니므로 이 답이 통과라 해도 발행은 다시 판정을 받는다.
|
|
67
67
|
*/
|
|
68
68
|
export declare function inspectFigure(id: string): Promise<FigureInspection>;
|
|
69
|
+
/**
|
|
70
|
+
* 편집 중인 원본의 발행 판정 — 저장하지 않은 것을 잰다.
|
|
71
|
+
*
|
|
72
|
+
* 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는
|
|
73
|
+
* 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —
|
|
74
|
+
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행판을 읽는다.
|
|
75
|
+
*/
|
|
76
|
+
export declare function inspectFigureDraft(source: string, id?: string): Promise<FigureInspection>;
|
|
69
77
|
/** 발행된 판들 — 최근 것부터 열 개. */
|
|
70
78
|
/**
|
|
71
79
|
* 직전 발행판의 정본. 한 번도 발행 안 했으면 `undefined`.
|
|
@@ -279,6 +279,57 @@ export async function inspectFigure(id) {
|
|
|
279
279
|
});
|
|
280
280
|
return unwrap(response, 'inspectFigure');
|
|
281
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* 편집 중인 원본의 발행 판정 — 저장하지 않은 것을 잰다.
|
|
284
|
+
*
|
|
285
|
+
* 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는
|
|
286
|
+
* 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —
|
|
287
|
+
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행판을 읽는다.
|
|
288
|
+
*/
|
|
289
|
+
export async function inspectFigureDraft(source, id) {
|
|
290
|
+
const response = await client.query({
|
|
291
|
+
query: gql `
|
|
292
|
+
query InspectFigureDraft($source: String!, $id: String) {
|
|
293
|
+
inspectFigureDraft(source: $source, id: $id) {
|
|
294
|
+
blocked
|
|
295
|
+
findings {
|
|
296
|
+
code
|
|
297
|
+
message
|
|
298
|
+
why
|
|
299
|
+
how
|
|
300
|
+
at
|
|
301
|
+
fixes {
|
|
302
|
+
findingWay
|
|
303
|
+
part
|
|
304
|
+
axis
|
|
305
|
+
from
|
|
306
|
+
to
|
|
307
|
+
resolved
|
|
308
|
+
safe
|
|
309
|
+
}
|
|
310
|
+
fixPlan {
|
|
311
|
+
resolved
|
|
312
|
+
safe
|
|
313
|
+
fixes {
|
|
314
|
+
findingWay
|
|
315
|
+
part
|
|
316
|
+
axis
|
|
317
|
+
from
|
|
318
|
+
to
|
|
319
|
+
resolved
|
|
320
|
+
safe
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
blocking
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
`,
|
|
328
|
+
variables: { source, id },
|
|
329
|
+
fetchPolicy: 'network-only'
|
|
330
|
+
});
|
|
331
|
+
return unwrap(response, 'inspectFigureDraft');
|
|
332
|
+
}
|
|
282
333
|
/** 발행된 판들 — 최근 것부터 열 개. */
|
|
283
334
|
/**
|
|
284
335
|
* 직전 발행판의 정본. 한 번도 발행 안 했으면 `undefined`.
|
|
@@ -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 } from '@operato/graphql'\n\nimport type {\n Figure,\n FigureFinding,\n FigureInspection,\n FigureListResult,\n FigurePatch,\n FigureProposal,\n ProposalFeedback,\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 type\n name\n description\n category\n tags\n state\n version\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}): Promise<FigureListResult> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n figures(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items { ${FIGURE_LIST_FIELDS} }\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, 'figures')\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\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 * 팔레트를 **클라이언트가 보낸다.** 서버는 팔레트를 모른다 — things-scene 이 갖고\n * 있는데 그 패키지의 node 조건은 팔레트가 없는 번들로 간다. 그리는 쪽이 어떤 토큰을\n * 풀 수 있는지 아는 유일한 자리이므로 그쪽이 보낸다.\n *\n * 돌아오는 것은 **후보**다. 저장되지 않았다 — 저작자가 받아야 정본이 된다.\n */\nexport async function proposeFigure(request: {\n prompt: string\n base?: string\n type?: string\n palette: string[]\n refine?: boolean\n feedback?: ProposalFeedback[]\n image?: File\n}): Promise<FigureProposal> {\n const response = await client.mutate({\n mutation: gql`\n mutation ProposeFigure($request: ProposeRequest!) {\n proposeFigure(request: $request) {\n source\n score\n grade\n triangles\n groups\n quality {\n status\n summary\n findings {\n dimension\n code\n message\n }\n visualRegions\n visualCoverage\n visualReadability\n }\n attempts\n violations {\n code\n message\n }\n }\n }\n `,\n variables: { request },\n // @operato/graphql 는 이 신호가 있을 때만 Upload가 포함된 변수를 multipart로 인코딩한다.\n // 없으면 브라우저 File은 JSON에서 {}가 되어 서버 Upload scalar가 요청 자체를 거절한다.\n context: { hasUpload: !!request.image }\n })\n\n return unwrap<FigureProposal>(response, 'proposeFigure')\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/** 옛 판을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. */\nexport async function revertFigureVersion(id: string, version: number): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation RevertFigureVersion($id: String!, $version: Float!) {\n revertFigureVersion(id: $id, version: $version) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, version }\n })\n\n return unwrap<SaveResult>(response, 'revertFigureVersion')\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 fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n fixPlan {\n resolved\n safe\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n }\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 * 직전 발행판의 정본. 한 번도 발행 안 했으면 `undefined`.\n *\n * 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장\n * 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.\n */\nexport async function fetchReleasedSource(id: string): Promise<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<{ source?: string }>(response, 'figureReleased')?.source ?? 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"]}
|
|
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 } from '@operato/graphql'\n\nimport type {\n Figure,\n FigureFinding,\n FigureInspection,\n FigureListResult,\n FigurePatch,\n FigureProposal,\n ProposalFeedback,\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 type\n name\n description\n category\n tags\n state\n version\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}): Promise<FigureListResult> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n figures(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items { ${FIGURE_LIST_FIELDS} }\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, 'figures')\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\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 * 팔레트를 **클라이언트가 보낸다.** 서버는 팔레트를 모른다 — things-scene 이 갖고\n * 있는데 그 패키지의 node 조건은 팔레트가 없는 번들로 간다. 그리는 쪽이 어떤 토큰을\n * 풀 수 있는지 아는 유일한 자리이므로 그쪽이 보낸다.\n *\n * 돌아오는 것은 **후보**다. 저장되지 않았다 — 저작자가 받아야 정본이 된다.\n */\nexport async function proposeFigure(request: {\n prompt: string\n base?: string\n type?: string\n palette: string[]\n refine?: boolean\n feedback?: ProposalFeedback[]\n image?: File\n}): Promise<FigureProposal> {\n const response = await client.mutate({\n mutation: gql`\n mutation ProposeFigure($request: ProposeRequest!) {\n proposeFigure(request: $request) {\n source\n score\n grade\n triangles\n groups\n quality {\n status\n summary\n findings {\n dimension\n code\n message\n }\n visualRegions\n visualCoverage\n visualReadability\n }\n attempts\n violations {\n code\n message\n }\n }\n }\n `,\n variables: { request },\n // @operato/graphql 는 이 신호가 있을 때만 Upload가 포함된 변수를 multipart로 인코딩한다.\n // 없으면 브라우저 File은 JSON에서 {}가 되어 서버 Upload scalar가 요청 자체를 거절한다.\n context: { hasUpload: !!request.image }\n })\n\n return unwrap<FigureProposal>(response, 'proposeFigure')\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/** 옛 판을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. */\nexport async function revertFigureVersion(id: string, version: number): Promise<SaveResult> {\n const response = await client.mutate({\n mutation: gql`\n mutation RevertFigureVersion($id: String!, $version: Float!) {\n revertFigureVersion(id: $id, version: $version) {\n figure { ${FIGURE_LIST_FIELDS} source }\n violations { code message at }\n }\n }\n `,\n variables: { id, version }\n })\n\n return unwrap<SaveResult>(response, 'revertFigureVersion')\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 fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n fixPlan {\n resolved\n safe\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n }\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 fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n fixPlan {\n resolved\n safe\n fixes {\n findingWay\n part\n axis\n from\n to\n resolved\n safe\n }\n }\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 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<{ source?: string }>(response, 'figureReleased')?.source ?? 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"]}
|
|
@@ -34,6 +34,12 @@ export declare class FigureHistoryPanel extends FigureHistoryPanel_base {
|
|
|
34
34
|
* 화면이 없는 단추를 그리면 눌러도 아무 일이 없다.
|
|
35
35
|
*/
|
|
36
36
|
format: 'v2' | 'v3';
|
|
37
|
+
/**
|
|
38
|
+
* 행의 타입. 판정 하나(`type-is-placeholder`)가 이것을 본다 — V3 자산은 타입을 원본에 적지 않아서, 타입을 바꾸면
|
|
39
|
+
* 원본도 dirty 도 그대로다. 그래서 이 값이 바뀌면 판정을 다시 묻는다. 안 그러면 타입을 정한 뒤에도 「임시 타입」
|
|
40
|
+
* 판정이 남아 발행 단추가 잠긴 채다.
|
|
41
|
+
*/
|
|
42
|
+
figureType?: string;
|
|
37
43
|
/** 직전 발행판의 기준 상자. 외형 변화 안내에서 X·Z 복원 선택지에만 쓴다. */
|
|
38
44
|
releasedBase?: {
|
|
39
45
|
x: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import '@material/web/icon/icon.js';
|
|
3
|
+
import { gateFindingStyles, renderGateFinding } from './gate-findings.js';
|
|
3
4
|
import { css, html, LitElement, nothing } from 'lit';
|
|
4
5
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
5
6
|
import { i18next, localize } from '@operato/i18n';
|
|
@@ -42,6 +43,7 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
|
|
|
42
43
|
this.keepChangedAppearance = false;
|
|
43
44
|
}
|
|
44
45
|
static { this.styles = [
|
|
46
|
+
gateFindingStyles,
|
|
45
47
|
ScrollbarStyles,
|
|
46
48
|
css `
|
|
47
49
|
:host {
|
|
@@ -202,77 +204,6 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
|
|
|
202
204
|
게이트 사유. 막는 것은 색으로도 말하고, 알리는 것은 조용히 둔다 — 둘을 같은 톤으로 두면
|
|
203
205
|
「이 중 어느 것이 발행을 막았나」를 사람이 다시 세어야 한다.
|
|
204
206
|
*/
|
|
205
|
-
ul[gate] {
|
|
206
|
-
margin: var(--spacing-small, 6px) 0 0;
|
|
207
|
-
padding: 0;
|
|
208
|
-
list-style: none;
|
|
209
|
-
display: flex;
|
|
210
|
-
flex-direction: column;
|
|
211
|
-
gap: 3px;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
li[head] {
|
|
215
|
-
font: var(--label-font, inherit);
|
|
216
|
-
font-size: 0.72rem;
|
|
217
|
-
color: var(--md-sys-color-error);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
li[head][quiet] {
|
|
221
|
-
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
li[finding] {
|
|
225
|
-
display: flex;
|
|
226
|
-
flex-direction: column;
|
|
227
|
-
align-items: flex-start;
|
|
228
|
-
gap: 3px;
|
|
229
|
-
padding: 3px 6px;
|
|
230
|
-
font-size: 0.72rem;
|
|
231
|
-
line-height: 1.5;
|
|
232
|
-
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
233
|
-
background-color: var(--md-sys-color-surface-container);
|
|
234
|
-
border-radius: 4px;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
li[finding][blocking] {
|
|
238
|
-
color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));
|
|
239
|
-
background-color: var(--md-sys-color-error-container);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
li[finding] code[where] {
|
|
243
|
-
font-family: var(--mono-font, monospace);
|
|
244
|
-
font-size: 0.68rem;
|
|
245
|
-
opacity: 0.85;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
li[finding] details[affected] {
|
|
249
|
-
width: 100%;
|
|
250
|
-
color: inherit;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
li[finding] details[affected] summary {
|
|
254
|
-
cursor: pointer;
|
|
255
|
-
font-weight: 600;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/* 왜 나쁜지는 곁글로 — 읽어야 하지만 잰 것보다 먼저 눈에 들 필요는 없다 */
|
|
259
|
-
li[finding] span[why-line] {
|
|
260
|
-
opacity: 0.8;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/* 무엇을 바꾸면 되나 — 이 줄이 실제로 사람이 할 일이다. 연장 표를 달아 눈에 걸리게 한다 */
|
|
264
|
-
li[finding] span[how-line] {
|
|
265
|
-
display: flex;
|
|
266
|
-
align-items: flex-start;
|
|
267
|
-
gap: 4px;
|
|
268
|
-
padding-top: 2px;
|
|
269
|
-
font-weight: 600;
|
|
270
|
-
}
|
|
271
|
-
li[finding] span[how-line] md-icon {
|
|
272
|
-
--md-icon-size: 13px;
|
|
273
|
-
flex: none;
|
|
274
|
-
margin-top: 1px;
|
|
275
|
-
}
|
|
276
207
|
|
|
277
208
|
/* 설명으로 끝내지 않는다. 이 단추는 원본을 바꾸지 않고 모델러에 후보만 연다. */
|
|
278
209
|
li[finding] button[preview-fix] {
|
|
@@ -338,7 +269,7 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
|
|
|
338
269
|
저장이 끝난 순간(dirty 가 풀림) 정본이 바뀌었으니 판정을 다시 묻는다. 저작 중(dirty)에는
|
|
339
270
|
묻지 않는다 — 서버에 있는 것은 저장 전 정본이라, 그 답은 화면에 있는 것과 다른 것을 말한다.
|
|
340
271
|
*/
|
|
341
|
-
if (changes.has('figureId') || changes.has('dirty')) {
|
|
272
|
+
if (changes.has('figureId') || changes.has('dirty') || changes.has('figureType')) {
|
|
342
273
|
void this.inspect();
|
|
343
274
|
}
|
|
344
275
|
}
|
|
@@ -450,17 +381,7 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
|
|
|
450
381
|
* 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.
|
|
451
382
|
*/
|
|
452
383
|
renderFinding(finding, blocking) {
|
|
453
|
-
return html `
|
|
454
|
-
<li finding ?blocking=${blocking}>
|
|
455
|
-
<span msg>${finding.message}</span>
|
|
456
|
-
${finding.at ? html `
|
|
457
|
-
<details affected>
|
|
458
|
-
<summary>${i18next.t('figure.text.show-affected-parts')}</summary>
|
|
459
|
-
<code where>${finding.at}</code>
|
|
460
|
-
</details>
|
|
461
|
-
` : nothing}
|
|
462
|
-
${finding.why ? html `<span why-line>${finding.why}</span>` : nothing}
|
|
463
|
-
${finding.how ? html `<span how-line><md-icon>build</md-icon>${finding.how}</span>` : nothing}
|
|
384
|
+
return renderGateFinding(finding, blocking, html `
|
|
464
385
|
${this.offersFit(finding.code)
|
|
465
386
|
? html `<button fit-base @click=${() => this.fitBase()}>
|
|
466
387
|
${i18next.t('figure.button.fit-base-to-parts')}
|
|
@@ -498,8 +419,7 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
|
|
|
498
419
|
${i18next.t('figure.button.apply-recommended-fix')}
|
|
499
420
|
</button>
|
|
500
421
|
` : nothing}
|
|
501
|
-
|
|
502
|
-
`;
|
|
422
|
+
`);
|
|
503
423
|
}
|
|
504
424
|
/**
|
|
505
425
|
* 게이트는 수정을 저장하지 않는다. 부모 모델러가 현재 정본과 이 한 수정안을 나란히 렌더하고,
|
|
@@ -645,6 +565,10 @@ __decorate([
|
|
|
645
565
|
property({ type: String }),
|
|
646
566
|
__metadata("design:type", String)
|
|
647
567
|
], FigureHistoryPanel.prototype, "format", void 0);
|
|
568
|
+
__decorate([
|
|
569
|
+
property({ type: String }),
|
|
570
|
+
__metadata("design:type", String)
|
|
571
|
+
], FigureHistoryPanel.prototype, "figureType", void 0);
|
|
648
572
|
__decorate([
|
|
649
573
|
property({ attribute: false }),
|
|
650
574
|
__metadata("design:type", Object)
|