@things-factory/figure-ui 10.1.159 → 10.1.161
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 +38 -0
- package/client/index.ts +1 -0
- package/client/modeller/figure-links.ts +21 -6
- package/client/modeller/links.ts +13 -0
- package/client/pages/figure-list-page.ts +5 -72
- package/client/setting-let/figure-namespace-setting-let.ts +85 -0
- package/client/viewparts/figure-thumb.ts +10 -37
- package/dist-client/graphql/index.d.ts +13 -0
- package/dist-client/graphql/index.js +24 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +1 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/modeller/figure-links.js +21 -7
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/links.d.ts +13 -0
- package/dist-client/modeller/links.js +11 -0
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +5 -72
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/setting-let/figure-namespace-setting-let.d.ts +18 -0
- package/dist-client/setting-let/figure-namespace-setting-let.js +94 -0
- package/dist-client/setting-let/figure-namespace-setting-let.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/figure-thumb.d.ts +3 -1
- package/dist-client/viewparts/figure-thumb.js +10 -37
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +5 -4
- package/test/links.test.ts +29 -0
- package/translations/en.json +8 -1
- package/translations/es.json +8 -1
- package/translations/ja.json +8 -1
- package/translations/ko.json +8 -1
- package/translations/ms.json +8 -1
- package/translations/zh.json +8 -1
package/client/graphql/index.ts
CHANGED
|
@@ -211,6 +211,44 @@ export async function releaseFigure(id: string, comment?: string): Promise<Figur
|
|
|
211
211
|
return unwrap<Figure>(response, 'releaseFigure')
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/** A domain's figure namespace as the setting screen shows it (ADR-0114). */
|
|
215
|
+
export interface FigureNamespaceState {
|
|
216
|
+
/** What new figures get; null when neither the setting nor the subdomain is a namespace. */
|
|
217
|
+
namespace: string | null
|
|
218
|
+
/** The setting as written; empty when the subdomain is used. */
|
|
219
|
+
written: string
|
|
220
|
+
subdomain: string
|
|
221
|
+
/** The domain's figures carry it: it cannot be set any more. */
|
|
222
|
+
fixed: boolean
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const FIGURE_NAMESPACE_FIELDS = 'namespace written subdomain fixed'
|
|
226
|
+
|
|
227
|
+
export async function fetchFigureNamespace(): Promise<FigureNamespaceState> {
|
|
228
|
+
const response = await client.query({
|
|
229
|
+
query: gql`
|
|
230
|
+
query {
|
|
231
|
+
figureNamespace { ${FIGURE_NAMESPACE_FIELDS} }
|
|
232
|
+
}
|
|
233
|
+
`,
|
|
234
|
+
fetchPolicy: 'network-only'
|
|
235
|
+
})
|
|
236
|
+
return unwrap<FigureNamespaceState>(response, 'figureNamespace')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Writes the namespace; empty takes the subdomain. The server says why when it refuses. */
|
|
240
|
+
export async function setFigureNamespace(namespace: string): Promise<FigureNamespaceState> {
|
|
241
|
+
const response = await client.mutate({
|
|
242
|
+
mutation: gql`
|
|
243
|
+
mutation ($namespace: String!) {
|
|
244
|
+
setFigureNamespace(namespace: $namespace) { ${FIGURE_NAMESPACE_FIELDS} }
|
|
245
|
+
}
|
|
246
|
+
`,
|
|
247
|
+
variables: { namespace }
|
|
248
|
+
})
|
|
249
|
+
return unwrap<FigureNamespaceState>(response, 'setFigureNamespace')
|
|
250
|
+
}
|
|
251
|
+
|
|
214
252
|
/** A draft a revert kept when it wrote over it (ADR-0097 §5.6). Not a released version. */
|
|
215
253
|
export interface KeptDraft {
|
|
216
254
|
id: string
|
package/client/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
visibilityControlsOf,
|
|
29
29
|
placeKeptOf,
|
|
30
30
|
fasteningOf,
|
|
31
|
+
fastenGapOf,
|
|
31
32
|
hasInstanceSize,
|
|
32
33
|
holdsThings,
|
|
33
34
|
leadersFor,
|
|
@@ -73,6 +74,7 @@ const freshFasten = () => ({
|
|
|
73
74
|
targetFace: 'top' as FigureFace,
|
|
74
75
|
facing: 'meet' as 'meet' | 'flush',
|
|
75
76
|
gap: 0,
|
|
77
|
+
gapKept: undefined as unknown,
|
|
76
78
|
twist: 0,
|
|
77
79
|
align: {} as Partial<Record<Axis, Alignment>>,
|
|
78
80
|
alignMm: {} as Partial<Record<Axis, number>>,
|
|
@@ -482,11 +484,14 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
482
484
|
private heldWords(asset: FigureAsset) {
|
|
483
485
|
const joint = (asset.joints ?? []).find(j => j.body1.part === this.partId)
|
|
484
486
|
const face = joint ? i18next.t(endPlaceKeyOf(joint.body1)) : ''
|
|
485
|
-
const gap =
|
|
487
|
+
const gap = joint?.origin?.gap ?? 0
|
|
488
|
+
const place = i18next.t(`figure.label.resize-place-${asset.placement ?? 'floor'}`)
|
|
486
489
|
const where = !joint
|
|
487
490
|
? i18next.t('figure.text.held-by-rule')
|
|
488
491
|
: 'plane' in joint.body0
|
|
489
|
-
?
|
|
492
|
+
? typeof gap === 'number'
|
|
493
|
+
? i18next.t('figure.text.held-on-plane', { face, place, gap: String(gap) })
|
|
494
|
+
: i18next.t('figure.text.held-on-plane-worked-out', { face, place })
|
|
490
495
|
: i18next.t('figure.text.held-on-part', { face, other: joint.body0.part, otherFace: i18next.t(endPlaceKeyOf(joint.body0)) })
|
|
491
496
|
return where
|
|
492
497
|
}
|
|
@@ -573,7 +578,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
573
578
|
face: myFace,
|
|
574
579
|
to: f.toPlane ? { plane: 'mounting-plane' } : { part: f.target, face: targetFace },
|
|
575
580
|
...(f.toPlane ? {} : { facing: f.facing }),
|
|
576
|
-
...(f
|
|
581
|
+
...fastenGapOf(f),
|
|
577
582
|
twist: f.twist,
|
|
578
583
|
...(align ? { align } : {}),
|
|
579
584
|
replace: !!held
|
|
@@ -586,7 +591,11 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
586
591
|
for the default.
|
|
587
592
|
*/
|
|
588
593
|
const detail = [
|
|
589
|
-
...(f.
|
|
594
|
+
...(f.gapKept !== undefined
|
|
595
|
+
? [i18next.t('figure.text.fasten-summary-gap-worked-out')]
|
|
596
|
+
: f.gap
|
|
597
|
+
? [i18next.t('figure.text.fasten-summary-gap', { mm: String(f.gap) })]
|
|
598
|
+
: []),
|
|
590
599
|
...(f.twist ? [i18next.t('figure.text.fasten-summary-twist', { degrees: String(f.twist) })] : [])
|
|
591
600
|
]
|
|
592
601
|
/* Across the face, a row only where the drawing cannot set it: no drawing of the face, or a share or a field to follow. */
|
|
@@ -672,7 +681,13 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
672
681
|
${alignRows.length ? this.renderAlignment(alignRows, settle) : nothing}
|
|
673
682
|
<div form>
|
|
674
683
|
<span>${i18next.t('figure.label.gap')}</span>
|
|
675
|
-
<input
|
|
684
|
+
<input
|
|
685
|
+
type="number"
|
|
686
|
+
step="any"
|
|
687
|
+
.value=${f.gapKept !== undefined ? '' : String(f.gap)}
|
|
688
|
+
placeholder=${f.gapKept !== undefined ? i18next.t('figure.label.follows-expression') : ''}
|
|
689
|
+
@change=${(e: Event) => ((this.fasten = { ...f, gap: Number((e.target as HTMLInputElement).value) || 0, gapKept: undefined }), settle())}
|
|
690
|
+
/>
|
|
676
691
|
<span>${i18next.t('figure.label.twist')}</span>
|
|
677
692
|
<input type="number" step="any" .value=${String(f.twist)} @change=${(e: Event) => ((this.fasten = { ...f, twist: Number((e.target as HTMLInputElement).value) || 0 }), settle())} />
|
|
678
693
|
</div>
|
|
@@ -696,7 +711,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
696
711
|
const f = this.fasten
|
|
697
712
|
if ('targetFace' in c) this.aimAt(asset, { ...f, targetFace: c.targetFace as FigureFace })
|
|
698
713
|
else if ('mode' in c) this.fasten = { ...f, face: c.mode === 'centre' ? 'centre' : f.face === 'centre' ? 'bottom' : f.face, facing: c.mode === 'flush' ? 'flush' : 'meet' }
|
|
699
|
-
else if ('gap' in c) this.fasten = { ...f, gap: c.gap }
|
|
714
|
+
else if ('gap' in c) this.fasten = { ...f, gap: c.gap, gapKept: undefined }
|
|
700
715
|
else
|
|
701
716
|
this.fasten = {
|
|
702
717
|
...f,
|
package/client/modeller/links.ts
CHANGED
|
@@ -387,6 +387,8 @@ export function fasteningOf(
|
|
|
387
387
|
targetFace: string
|
|
388
388
|
facing: 'meet' | 'flush'
|
|
389
389
|
gap: number
|
|
390
|
+
/** The record's gap when it is worked out (a length that follows something): kept as it is until a number is typed. */
|
|
391
|
+
gapKept?: unknown
|
|
390
392
|
twist: number
|
|
391
393
|
align: Partial<Record<Axis, Alignment>>
|
|
392
394
|
alignMm: Partial<Record<Axis, number>>
|
|
@@ -424,12 +426,23 @@ export function fasteningOf(
|
|
|
424
426
|
targetFace,
|
|
425
427
|
facing: record.origin?.facing ?? 'meet',
|
|
426
428
|
gap: typeof gap === 'number' ? gap : 0,
|
|
429
|
+
...(gap !== undefined && typeof gap !== 'number' ? { gapKept: gap } : {}),
|
|
427
430
|
twist,
|
|
428
431
|
...kept,
|
|
429
432
|
...(atOrigin ? { atOrigin: true as const } : {})
|
|
430
433
|
}
|
|
431
434
|
}
|
|
432
435
|
|
|
436
|
+
/**
|
|
437
|
+
* The `gap` an attach command writes from the form: a worked-out gap as it was read, else the number, else none. Read as
|
|
438
|
+
* a number, a gap that follows the legs or the instance height became 0, and opening the form on the part dropped it
|
|
439
|
+
* to the plane (2026-10-13, the robot dog and the forklift).
|
|
440
|
+
*/
|
|
441
|
+
export function fastenGapOf(form: { gap: number; gapKept?: unknown }): { gap?: unknown } {
|
|
442
|
+
if (form.gapKept !== undefined) return { gap: form.gapKept }
|
|
443
|
+
return form.gap ? { gap: form.gap } : {}
|
|
444
|
+
}
|
|
445
|
+
|
|
433
446
|
/**
|
|
434
447
|
* The alignment that keeps the part where it stands when it is fastened: on each axis across the face, its distance
|
|
435
448
|
* from the target's centre (the plane's is the origin). Fastening used to centre it -- a door set 100 mm in front of
|
|
@@ -10,7 +10,7 @@ import { i18next, localize } from '@operato/i18n'
|
|
|
10
10
|
import { p13n } from '@operato/p13n'
|
|
11
11
|
import { auth } from '@things-factory/auth-base/dist-client'
|
|
12
12
|
import { navigate, PageView } from '@operato/shell'
|
|
13
|
-
import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'
|
|
13
|
+
import { CardCatalogStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'
|
|
14
14
|
|
|
15
15
|
import { capacityFrom } from '../modeller/scene-budget.js'
|
|
16
16
|
import { deleteFigure, fetchFigureList } from '../graphql/index.js'
|
|
@@ -84,88 +84,21 @@ export class FigureListPage extends FigureListPageBase {
|
|
|
84
84
|
ScrollbarStyles,
|
|
85
85
|
CommonGristStyles,
|
|
86
86
|
CommonHeaderStyles,
|
|
87
|
+
CardCatalogStyles,
|
|
87
88
|
css`
|
|
88
89
|
:host {
|
|
89
90
|
background-color: var(--md-sys-color-surface);
|
|
90
91
|
color: var(--md-sys-color-on-surface);
|
|
91
|
-
/* 떠 있는 단추의 기준 상자 */
|
|
92
|
-
position: relative;
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
/*
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
글자를 함께 둔다(extended FAB). 아이콘만 두면 처음 온 사람은 + 표가 무엇을 만드는지
|
|
100
|
-
모른다 — 이 화면에서 만들 수 있는 것이 무엇인지가 그 글자다.
|
|
101
|
-
*/
|
|
102
|
-
button[fab] {
|
|
103
|
-
position: absolute;
|
|
104
|
-
right: var(--spacing-large, 12px);
|
|
105
|
-
bottom: var(--spacing-large, 12px);
|
|
106
|
-
z-index: 10;
|
|
107
|
-
|
|
108
|
-
display: flex;
|
|
109
|
-
align-items: center;
|
|
110
|
-
gap: 6px;
|
|
111
|
-
padding: 10px 18px 10px 14px;
|
|
112
|
-
font: var(--label-font, inherit);
|
|
113
|
-
font-size: 0.82rem;
|
|
114
|
-
font-weight: 600;
|
|
115
|
-
color: var(--md-sys-color-on-primary-container);
|
|
116
|
-
background-color: var(--md-sys-color-primary-container);
|
|
117
|
-
border: none;
|
|
118
|
-
/* 떠 있는 것의 모서리는 알약이다 — 담는 면(6px)을 따르지 않는다 */
|
|
119
|
-
border-radius: var(--md-sys-shape-corner-full, 999px);
|
|
120
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
|
|
121
|
-
cursor: pointer;
|
|
122
|
-
}
|
|
123
|
-
button[fab]:hover {
|
|
124
|
-
color: var(--md-sys-color-on-primary, #fff);
|
|
125
|
-
background-color: var(--md-sys-color-primary);
|
|
126
|
-
}
|
|
127
|
-
button[fab] md-icon {
|
|
128
|
-
--md-icon-size: 20px;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/* 격자가 바깥에 요구하는 것은 확정된 높이 하나뿐이다 — 스크롤은 격자가 안에서 한다 */
|
|
132
|
-
/*
|
|
133
|
-
── 그림이 주인공인 카탈로그 ────────────────────────────────────────
|
|
134
|
-
카드 기본값은 글이 많은 목록(직원·결재함)에 맞춰져 있다. 여기서는 사람이 먼저 보는 것이
|
|
135
|
-
형상 그림이므로, 그림 칸을 비율로 잡아 카드 폭이 달라져도 그림이 나란히 읽히게 한다.
|
|
136
|
-
|
|
137
|
-
그림은 카드 끝까지 닿는다 — 위쪽 모서리만 카드와 같은 6px 로 깎아 한 장처럼 보이게 하고,
|
|
138
|
-
그림 안에 얹히는 초안 표시·삭제는 figure-thumb 이 그린다.
|
|
95
|
+
카드 · 그림 칸 · 만들기 FAB 는 모든 카드 카탈로그와 같다(@operato/styles CardCatalogStyles,
|
|
96
|
+
ADR-0117 개정 2026-10-13). 여기에는 이 화면에만 있는 것만 둔다.
|
|
139
97
|
*/
|
|
140
98
|
ox-grist {
|
|
141
|
-
--data-card-min-width: 240px;
|
|
142
|
-
--data-card-record-card-border-radius: 6px;
|
|
143
|
-
--data-card-record-card-padding: 0;
|
|
144
|
-
--data-card-thumbnail-height: auto;
|
|
145
|
-
--data-card-thumbnail-aspect: 4 / 3;
|
|
146
|
-
--data-card-thumbnail-padding: 0;
|
|
147
|
-
--data-card-thumbnail-radius: 6px 6px 0 0;
|
|
148
|
-
--data-card-thumbnail-background: var(--md-sys-color-surface-container);
|
|
149
|
-
|
|
150
|
-
/*
|
|
151
|
-
카드 기본 글꼴은 굵다(제목 bold 1.1em). 글이 정보를 나르는 목록에서는 맞지만, 여기서
|
|
152
|
-
정보를 나르는 것은 그림이다. 굵은 제목이 카드마다 서면 그림보다 글이 먼저 읽힌다 —
|
|
153
|
-
이름은 알아볼 정도로만, 타입은 곁글로 둔다.
|
|
154
|
-
*/
|
|
155
|
-
--data-card-item-name-font: 500 0.9rem/1.35 var(--theme-font, 'Roboto');
|
|
156
|
-
--data-card-item-name-color: var(--md-sys-color-on-surface);
|
|
157
|
-
--data-card-item-disc-font: normal 0.72rem/1.2 var(--theme-font, 'Roboto');
|
|
158
|
-
--data-card-item-disc-color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
159
|
-
--data-card-item-etc-font: normal 0.72rem/1.2 var(--theme-font, 'Roboto');
|
|
160
|
-
|
|
161
99
|
/* 상태와 성숙도는 짧은 표식이다 — 두 줄로 세우지 않고 한 줄에 뉜다 */
|
|
162
100
|
--data-card-detail-direction: row;
|
|
163
|
-
|
|
164
|
-
/*
|
|
165
|
-
맨 위로 가는 화살표를 만들기 단추 **위로** 올린다. 둘 다 오른쪽 아래에 떠 있으므로
|
|
166
|
-
그냥 두면 겹친다 — 단추(36px + 아래 여백 12px) 위에 12px 을 띄운 자리다.
|
|
167
|
-
*/
|
|
168
|
-
--data-card-fab-position-vertical: 64px;
|
|
101
|
+
/* 표 모드의 맨 위로 단추도 만들기 단추 위로 비킨다 */
|
|
169
102
|
--data-list-fab-position-vertical: 64px;
|
|
170
103
|
}
|
|
171
104
|
`
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import '@material/web/textfield/filled-text-field.js'
|
|
2
|
+
import '@things-factory/setting-base'
|
|
3
|
+
|
|
4
|
+
import { css, html, LitElement } from 'lit'
|
|
5
|
+
import { customElement, state } from 'lit/decorators.js'
|
|
6
|
+
|
|
7
|
+
import { i18next, localize } from '@operato/i18n'
|
|
8
|
+
|
|
9
|
+
import { fetchFigureNamespace, setFigureNamespace, type FigureNamespaceState } from '../graphql/index.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The domain's figure namespace (ADR-0114): the `{namespace}` of every full name `{namespace}/{type}` a board stores.
|
|
13
|
+
* Empty takes the subdomain. It is set once: when the domain's figures carry it, it is shown and no longer edited --
|
|
14
|
+
* the server refuses a change either way, and says why.
|
|
15
|
+
*/
|
|
16
|
+
@customElement('figure-namespace-setting-let')
|
|
17
|
+
export class FigureNamespaceSettingLet extends localize(i18next)(LitElement) {
|
|
18
|
+
static styles = [
|
|
19
|
+
css`
|
|
20
|
+
/* A namespace is lower-case by rule: the card's capitalised words must not reach it, nor what is typed. */
|
|
21
|
+
div[slot='content'] {
|
|
22
|
+
display: grid;
|
|
23
|
+
gap: var(--spacing-small, 4px);
|
|
24
|
+
text-transform: none;
|
|
25
|
+
}
|
|
26
|
+
md-filled-text-field {
|
|
27
|
+
width: 100%;
|
|
28
|
+
}
|
|
29
|
+
strong {
|
|
30
|
+
font: var(--label-font);
|
|
31
|
+
color: var(--md-sys-color-on-surface);
|
|
32
|
+
}
|
|
33
|
+
p {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-size: 0.8rem;
|
|
36
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
37
|
+
}
|
|
38
|
+
`
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
@state() private now?: FigureNamespaceState
|
|
42
|
+
@state() private refused = ''
|
|
43
|
+
|
|
44
|
+
render() {
|
|
45
|
+
const now = this.now
|
|
46
|
+
return html`
|
|
47
|
+
<setting-let>
|
|
48
|
+
<span slot="title">${i18next.t('figure.title.namespace-setting')}</span>
|
|
49
|
+
<div slot="content">
|
|
50
|
+
${!now
|
|
51
|
+
? html``
|
|
52
|
+
: now.fixed
|
|
53
|
+
? html`<strong>${now.namespace}</strong>
|
|
54
|
+
<p>${i18next.t('figure.text.namespace-fixed')}</p>`
|
|
55
|
+
: html`<md-filled-text-field
|
|
56
|
+
.label=${i18next.t('figure.label.namespace')}
|
|
57
|
+
.value=${now.written}
|
|
58
|
+
placeholder=${now.subdomain}
|
|
59
|
+
?error=${!!this.refused || !now.namespace}
|
|
60
|
+
error-text=${this.refused || i18next.t('figure.text.namespace-subdomain-unusable', { subdomain: now.subdomain })}
|
|
61
|
+
supporting-text=${i18next.t('figure.text.namespace-empty-takes-subdomain', { subdomain: now.subdomain })}
|
|
62
|
+
@change=${(e: Event) => this.save((e.target as HTMLInputElement).value)}
|
|
63
|
+
></md-filled-text-field>`}
|
|
64
|
+
</div>
|
|
65
|
+
</setting-let>
|
|
66
|
+
`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async firstUpdated() {
|
|
70
|
+
try {
|
|
71
|
+
this.now = await fetchFigureNamespace()
|
|
72
|
+
} catch (e) {
|
|
73
|
+
this.refused = (e as Error).message
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async save(value: string) {
|
|
78
|
+
try {
|
|
79
|
+
this.now = await setFigureNamespace(value)
|
|
80
|
+
this.refused = ''
|
|
81
|
+
} catch (e) {
|
|
82
|
+
this.refused = (e as Error).message
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement } from 'lit'
|
|
|
4
4
|
import { customElement, property } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import '@operato/data-grist/ox-card-thumb.js'
|
|
7
8
|
import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js'
|
|
8
9
|
import { getPathInfo } from '@operato/utils'
|
|
9
10
|
|
|
@@ -31,7 +32,8 @@ function thumbnailSrc(figure: Figure): string | undefined {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
|
-
* 카드의 그림 칸.
|
|
35
|
+
* 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가
|
|
36
|
+
* 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).
|
|
35
37
|
*
|
|
36
38
|
* ## 왜 컴포넌트인가
|
|
37
39
|
*
|
|
@@ -53,39 +55,9 @@ function thumbnailSrc(figure: Figure): string | undefined {
|
|
|
53
55
|
export class FigureThumb extends localize(i18next)(LitElement) {
|
|
54
56
|
static styles = css`
|
|
55
57
|
:host {
|
|
56
|
-
position: relative;
|
|
57
58
|
display: block;
|
|
58
59
|
width: 100%;
|
|
59
60
|
height: 100%;
|
|
60
|
-
overflow: hidden;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
img {
|
|
64
|
-
display: block;
|
|
65
|
-
width: 100%;
|
|
66
|
-
height: 100%;
|
|
67
|
-
/* 형상을 자르지 않는다 — 긴 것(컨베이어)과 높은 것(시그널타워)이 한 줄에 선다 */
|
|
68
|
-
object-fit: contain;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/*
|
|
72
|
-
초안 표시.
|
|
73
|
-
|
|
74
|
-
발행된 것에는 아무 말도 하지 않는다 — 카탈로그에 있는 것은 대개 발행된 것이라, 카드마다
|
|
75
|
-
「발행됨」이 붙으면 아무것도 가르지 않고 자리만 먹는다. 초안만 말하면 이 표시가 있는 카드가
|
|
76
|
-
곧 「아직 쓰면 안 되는 것」이 된다.
|
|
77
|
-
*/
|
|
78
|
-
span[draft] {
|
|
79
|
-
position: absolute;
|
|
80
|
-
top: 6px;
|
|
81
|
-
left: 6px;
|
|
82
|
-
padding: 1px 7px;
|
|
83
|
-
font: var(--label-font, inherit);
|
|
84
|
-
font-size: 0.68rem;
|
|
85
|
-
line-height: 1.5;
|
|
86
|
-
color: var(--md-sys-color-on-secondary-container);
|
|
87
|
-
background-color: var(--md-sys-color-secondary-container);
|
|
88
|
-
border-radius: 4px;
|
|
89
61
|
}
|
|
90
62
|
|
|
91
63
|
/*
|
|
@@ -188,12 +160,12 @@ export class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
188
160
|
const src = thumbnailSrc(figure)
|
|
189
161
|
|
|
190
162
|
return html`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
163
|
+
<ox-card-thumb
|
|
164
|
+
src=${src || ''}
|
|
165
|
+
alt=${figure.name || ''}
|
|
166
|
+
badge=${figure.state === 'released' ? '' : i18next.t('figure.label.draft')}
|
|
167
|
+
>
|
|
168
|
+
<figure-thumb-mark slot="empty" .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>
|
|
197
169
|
${figure.tags?.length
|
|
198
170
|
? html`<div tags>
|
|
199
171
|
${figure.tags.map(
|
|
@@ -225,6 +197,7 @@ export class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
225
197
|
>
|
|
226
198
|
<md-icon>delete</md-icon>
|
|
227
199
|
</button>
|
|
200
|
+
</ox-card-thumb>
|
|
228
201
|
`
|
|
229
202
|
}
|
|
230
203
|
}
|
|
@@ -39,6 +39,19 @@ export declare function deleteFigure(id: string): Promise<boolean>;
|
|
|
39
39
|
* 발행을 푼다(초안으로 돌아온다).
|
|
40
40
|
*/
|
|
41
41
|
export declare function releaseFigure(id: string, comment?: string): Promise<Figure>;
|
|
42
|
+
/** A domain's figure namespace as the setting screen shows it (ADR-0114). */
|
|
43
|
+
export interface FigureNamespaceState {
|
|
44
|
+
/** What new figures get; null when neither the setting nor the subdomain is a namespace. */
|
|
45
|
+
namespace: string | null;
|
|
46
|
+
/** The setting as written; empty when the subdomain is used. */
|
|
47
|
+
written: string;
|
|
48
|
+
subdomain: string;
|
|
49
|
+
/** The domain's figures carry it: it cannot be set any more. */
|
|
50
|
+
fixed: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function fetchFigureNamespace(): Promise<FigureNamespaceState>;
|
|
53
|
+
/** Writes the namespace; empty takes the subdomain. The server says why when it refuses. */
|
|
54
|
+
export declare function setFigureNamespace(namespace: string): Promise<FigureNamespaceState>;
|
|
42
55
|
/** A draft a revert kept when it wrote over it (ADR-0097 §5.6). Not a released version. */
|
|
43
56
|
export interface KeptDraft {
|
|
44
57
|
id: string;
|
|
@@ -170,6 +170,30 @@ export async function releaseFigure(id, comment) {
|
|
|
170
170
|
});
|
|
171
171
|
return unwrap(response, 'releaseFigure');
|
|
172
172
|
}
|
|
173
|
+
const FIGURE_NAMESPACE_FIELDS = 'namespace written subdomain fixed';
|
|
174
|
+
export async function fetchFigureNamespace() {
|
|
175
|
+
const response = await client.query({
|
|
176
|
+
query: gql `
|
|
177
|
+
query {
|
|
178
|
+
figureNamespace { ${FIGURE_NAMESPACE_FIELDS} }
|
|
179
|
+
}
|
|
180
|
+
`,
|
|
181
|
+
fetchPolicy: 'network-only'
|
|
182
|
+
});
|
|
183
|
+
return unwrap(response, 'figureNamespace');
|
|
184
|
+
}
|
|
185
|
+
/** Writes the namespace; empty takes the subdomain. The server says why when it refuses. */
|
|
186
|
+
export async function setFigureNamespace(namespace) {
|
|
187
|
+
const response = await client.mutate({
|
|
188
|
+
mutation: gql `
|
|
189
|
+
mutation ($namespace: String!) {
|
|
190
|
+
setFigureNamespace(namespace: $namespace) { ${FIGURE_NAMESPACE_FIELDS} }
|
|
191
|
+
}
|
|
192
|
+
`,
|
|
193
|
+
variables: { namespace }
|
|
194
|
+
});
|
|
195
|
+
return unwrap(response, 'setFigureNamespace');
|
|
196
|
+
}
|
|
173
197
|
const KEPT_FIELDS = `id kind state version revertedToVersion restoredAt createdAt creator { name }`;
|
|
174
198
|
/**
|
|
175
199
|
* 옛 판을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 덮이는 초안은 같은 트랜잭션에서 서버가
|
|
@@ -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 { 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 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 * 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은\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 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): 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: { hasUpload: true }\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 } 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 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 * 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은\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 domain's figure namespace as the setting screen shows it (ADR-0114). */\nexport interface FigureNamespaceState {\n /** What new figures get; null when neither the setting nor the subdomain is a namespace. */\n namespace: string | null\n /** The setting as written; empty when the subdomain is used. */\n written: string\n subdomain: string\n /** The domain's figures carry it: it cannot be set any more. */\n fixed: boolean\n}\n\nconst FIGURE_NAMESPACE_FIELDS = 'namespace written subdomain fixed'\n\nexport async function fetchFigureNamespace(): Promise<FigureNamespaceState> {\n const response = await client.query({\n query: gql`\n query {\n figureNamespace { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n fetchPolicy: 'network-only'\n })\n return unwrap<FigureNamespaceState>(response, 'figureNamespace')\n}\n\n/** Writes the namespace; empty takes the subdomain. The server says why when it refuses. */\nexport async function setFigureNamespace(namespace: string): Promise<FigureNamespaceState> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($namespace: String!) {\n setFigureNamespace(namespace: $namespace) { ${FIGURE_NAMESPACE_FIELDS} }\n }\n `,\n variables: { namespace }\n })\n return unwrap<FigureNamespaceState>(response, 'setFigureNamespace')\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): 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: { hasUpload: true }\n })\n return unwrap(response, 'uploadFigureMesh')\n}\n"]}
|
package/dist-client/index.d.ts
CHANGED
package/dist-client/index.js
CHANGED
package/dist-client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA","sourcesContent":["export * from './types.js'\nexport * from './graphql/index.js'\nexport * from './figure-assistant.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+CAA+C,CAAA","sourcesContent":["export * from './types.js'\nexport * from './graphql/index.js'\nexport * from './figure-assistant.js'\nexport * from './setting-let/figure-namespace-setting-let.js'\n"]}
|