@things-factory/figure-ui 10.1.35 → 10.1.37

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.
@@ -0,0 +1,366 @@
1
+ import { type V3InputChange, type V3PrimitiveCreation, type V3PartEdit } from '@hatiolab/figure-model';
2
+ export interface V3ProductRecord {
3
+ id: string;
4
+ source: string;
5
+ }
6
+ /** Persistence is injected; neither the model engine nor the renderer owns the product ID. */
7
+ export interface V3ProductStore {
8
+ load(id: string): Promise<V3ProductRecord>;
9
+ save(request: {
10
+ id?: string;
11
+ source: string;
12
+ expectedSource?: string;
13
+ creationId?: string;
14
+ }): Promise<V3ProductRecord>;
15
+ }
16
+ /** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
17
+ export declare function createV3ProductSession(store: V3ProductStore): {
18
+ read: () => {
19
+ id: string | undefined;
20
+ loading: boolean;
21
+ saving: boolean;
22
+ error: string | undefined;
23
+ context: null;
24
+ name: null;
25
+ fields: never[];
26
+ geometry: never[];
27
+ bindings: never[];
28
+ dirty: boolean;
29
+ revision: null;
30
+ canUndo?: undefined;
31
+ canRedo?: undefined;
32
+ palette?: undefined;
33
+ } | {
34
+ id: string | undefined;
35
+ loading: boolean;
36
+ saving: boolean;
37
+ error: string | undefined;
38
+ context: string;
39
+ name: string;
40
+ revision: number;
41
+ dirty: boolean;
42
+ canUndo: boolean;
43
+ canRedo: boolean;
44
+ fields: {
45
+ value: number;
46
+ id: string;
47
+ unit: import("@hatiolab/figure-model").V3Unit;
48
+ min: number;
49
+ max: number;
50
+ role?: "design" | "state";
51
+ }[];
52
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
53
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
54
+ palette: Record<string, string>;
55
+ };
56
+ startNew({ discard }?: {
57
+ discard?: boolean | undefined;
58
+ }): {
59
+ id: string | undefined;
60
+ loading: boolean;
61
+ saving: boolean;
62
+ error: string | undefined;
63
+ context: null;
64
+ name: null;
65
+ fields: never[];
66
+ geometry: never[];
67
+ bindings: never[];
68
+ dirty: boolean;
69
+ revision: null;
70
+ canUndo?: undefined;
71
+ canRedo?: undefined;
72
+ palette?: undefined;
73
+ } | {
74
+ id: string | undefined;
75
+ loading: boolean;
76
+ saving: boolean;
77
+ error: string | undefined;
78
+ context: string;
79
+ name: string;
80
+ revision: number;
81
+ dirty: boolean;
82
+ canUndo: boolean;
83
+ canRedo: boolean;
84
+ fields: {
85
+ value: number;
86
+ id: string;
87
+ unit: import("@hatiolab/figure-model").V3Unit;
88
+ min: number;
89
+ max: number;
90
+ role?: "design" | "state";
91
+ }[];
92
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
93
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
94
+ palette: Record<string, string>;
95
+ };
96
+ open(nextId: string, { discard }?: {
97
+ discard?: boolean | undefined;
98
+ }): Promise<{
99
+ id: string | undefined;
100
+ loading: boolean;
101
+ saving: boolean;
102
+ error: string | undefined;
103
+ context: null;
104
+ name: null;
105
+ fields: never[];
106
+ geometry: never[];
107
+ bindings: never[];
108
+ dirty: boolean;
109
+ revision: null;
110
+ canUndo?: undefined;
111
+ canRedo?: undefined;
112
+ palette?: undefined;
113
+ } | {
114
+ id: string | undefined;
115
+ loading: boolean;
116
+ saving: boolean;
117
+ error: string | undefined;
118
+ context: string;
119
+ name: string;
120
+ revision: number;
121
+ dirty: boolean;
122
+ canUndo: boolean;
123
+ canRedo: boolean;
124
+ fields: {
125
+ value: number;
126
+ id: string;
127
+ unit: import("@hatiolab/figure-model").V3Unit;
128
+ min: number;
129
+ max: number;
130
+ role?: "design" | "state";
131
+ }[];
132
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
133
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
134
+ palette: Record<string, string>;
135
+ }>;
136
+ close({ discard }?: {
137
+ discard?: boolean | undefined;
138
+ }): {
139
+ id: string | undefined;
140
+ loading: boolean;
141
+ saving: boolean;
142
+ error: string | undefined;
143
+ context: null;
144
+ name: null;
145
+ fields: never[];
146
+ geometry: never[];
147
+ bindings: never[];
148
+ dirty: boolean;
149
+ revision: null;
150
+ canUndo?: undefined;
151
+ canRedo?: undefined;
152
+ palette?: undefined;
153
+ } | {
154
+ id: string | undefined;
155
+ loading: boolean;
156
+ saving: boolean;
157
+ error: string | undefined;
158
+ context: string;
159
+ name: string;
160
+ revision: number;
161
+ dirty: boolean;
162
+ canUndo: boolean;
163
+ canRedo: boolean;
164
+ fields: {
165
+ value: number;
166
+ id: string;
167
+ unit: import("@hatiolab/figure-model").V3Unit;
168
+ min: number;
169
+ max: number;
170
+ role?: "design" | "state";
171
+ }[];
172
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
173
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
174
+ palette: Record<string, string>;
175
+ };
176
+ apply(changes: V3InputChange[], origin?: "ui" | "ai", expectedRevision?: number, context?: string): {
177
+ context: null;
178
+ name: null;
179
+ fields: never[];
180
+ geometry: never[];
181
+ bindings: never[];
182
+ dirty: boolean;
183
+ revision: null;
184
+ canUndo?: undefined;
185
+ canRedo?: undefined;
186
+ palette?: undefined;
187
+ } | {
188
+ context: string;
189
+ name: string;
190
+ revision: number;
191
+ dirty: boolean;
192
+ canUndo: boolean;
193
+ canRedo: boolean;
194
+ fields: {
195
+ value: number;
196
+ id: string;
197
+ unit: import("@hatiolab/figure-model").V3Unit;
198
+ min: number;
199
+ max: number;
200
+ role?: "design" | "state";
201
+ }[];
202
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
203
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
204
+ palette: Record<string, string>;
205
+ };
206
+ add(part: V3PrimitiveCreation): {
207
+ context: null;
208
+ name: null;
209
+ fields: never[];
210
+ geometry: never[];
211
+ bindings: never[];
212
+ dirty: boolean;
213
+ revision: null;
214
+ canUndo?: undefined;
215
+ canRedo?: undefined;
216
+ palette?: undefined;
217
+ } | {
218
+ context: string;
219
+ name: string;
220
+ revision: number;
221
+ dirty: boolean;
222
+ canUndo: boolean;
223
+ canRedo: boolean;
224
+ fields: {
225
+ value: number;
226
+ id: string;
227
+ unit: import("@hatiolab/figure-model").V3Unit;
228
+ min: number;
229
+ max: number;
230
+ role?: "design" | "state";
231
+ }[];
232
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
233
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
234
+ palette: Record<string, string>;
235
+ };
236
+ edit(edit: V3PartEdit): {
237
+ context: null;
238
+ name: null;
239
+ fields: never[];
240
+ geometry: never[];
241
+ bindings: never[];
242
+ dirty: boolean;
243
+ revision: null;
244
+ canUndo?: undefined;
245
+ canRedo?: undefined;
246
+ palette?: undefined;
247
+ } | {
248
+ context: string;
249
+ name: string;
250
+ revision: number;
251
+ dirty: boolean;
252
+ canUndo: boolean;
253
+ canRedo: boolean;
254
+ fields: {
255
+ value: number;
256
+ id: string;
257
+ unit: import("@hatiolab/figure-model").V3Unit;
258
+ min: number;
259
+ max: number;
260
+ role?: "design" | "state";
261
+ }[];
262
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
263
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
264
+ palette: Record<string, string>;
265
+ };
266
+ undo(): {
267
+ context: null;
268
+ name: null;
269
+ fields: never[];
270
+ geometry: never[];
271
+ bindings: never[];
272
+ dirty: boolean;
273
+ revision: null;
274
+ canUndo?: undefined;
275
+ canRedo?: undefined;
276
+ palette?: undefined;
277
+ } | {
278
+ context: string;
279
+ name: string;
280
+ revision: number;
281
+ dirty: boolean;
282
+ canUndo: boolean;
283
+ canRedo: boolean;
284
+ fields: {
285
+ value: number;
286
+ id: string;
287
+ unit: import("@hatiolab/figure-model").V3Unit;
288
+ min: number;
289
+ max: number;
290
+ role?: "design" | "state";
291
+ }[];
292
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
293
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
294
+ palette: Record<string, string>;
295
+ };
296
+ redo(): {
297
+ context: null;
298
+ name: null;
299
+ fields: never[];
300
+ geometry: never[];
301
+ bindings: never[];
302
+ dirty: boolean;
303
+ revision: null;
304
+ canUndo?: undefined;
305
+ canRedo?: undefined;
306
+ palette?: undefined;
307
+ } | {
308
+ context: string;
309
+ name: string;
310
+ revision: number;
311
+ dirty: boolean;
312
+ canUndo: boolean;
313
+ canRedo: boolean;
314
+ fields: {
315
+ value: number;
316
+ id: string;
317
+ unit: import("@hatiolab/figure-model").V3Unit;
318
+ min: number;
319
+ max: number;
320
+ role?: "design" | "state";
321
+ }[];
322
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
323
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
324
+ palette: Record<string, string>;
325
+ };
326
+ source(): string;
327
+ save(): Promise<{
328
+ id: string | undefined;
329
+ loading: boolean;
330
+ saving: boolean;
331
+ error: string | undefined;
332
+ context: null;
333
+ name: null;
334
+ fields: never[];
335
+ geometry: never[];
336
+ bindings: never[];
337
+ dirty: boolean;
338
+ revision: null;
339
+ canUndo?: undefined;
340
+ canRedo?: undefined;
341
+ palette?: undefined;
342
+ } | {
343
+ id: string | undefined;
344
+ loading: boolean;
345
+ saving: boolean;
346
+ error: string | undefined;
347
+ context: string;
348
+ name: string;
349
+ revision: number;
350
+ dirty: boolean;
351
+ canUndo: boolean;
352
+ canRedo: boolean;
353
+ fields: {
354
+ value: number;
355
+ id: string;
356
+ unit: import("@hatiolab/figure-model").V3Unit;
357
+ min: number;
358
+ max: number;
359
+ role?: "design" | "state";
360
+ }[];
361
+ geometry: import("@hatiolab/figure-model").V3AssetGeometry[];
362
+ bindings: import("@hatiolab/figure-model").V3EvaluatedBinding[];
363
+ palette: Record<string, string>;
364
+ }>;
365
+ };
366
+ export type V3ProductSession = ReturnType<typeof createV3ProductSession>;
@@ -0,0 +1,130 @@
1
+ import { createV3Asset, createV3EditorWorkspace, serializeV3Asset, parseV3Asset } from '@hatiolab/figure-model';
2
+ /** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
3
+ export function createV3ProductSession(store) {
4
+ const workspace = createV3EditorWorkspace();
5
+ let epoch = 0, id, baseline;
6
+ let loading = false, saving = false, error;
7
+ let creationId;
8
+ function read() {
9
+ return { ...workspace.read(), id, loading, saving, error };
10
+ }
11
+ function writable() {
12
+ const view = workspace.read();
13
+ if (loading || error || !view.context || view.revision === null)
14
+ throw Error('먼저 모델을 정상적으로 열어 주세요.');
15
+ // Serializing writes also makes create retries safe: no second create while the first is pending.
16
+ if (saving)
17
+ throw Error('저장이 끝난 뒤 편집해 주세요.');
18
+ return { context: view.context, expectedRevision: view.revision, origin: 'ui' };
19
+ }
20
+ function clear() {
21
+ workspace.close({ discardDesignChanges: true });
22
+ id = undefined;
23
+ baseline = undefined;
24
+ error = undefined;
25
+ saving = false;
26
+ creationId = undefined;
27
+ }
28
+ function canLeave(discard) {
29
+ if (saving)
30
+ throw Error('저장이 끝난 뒤 모델을 전환해 주세요.');
31
+ if (workspace.read().dirty && !discard)
32
+ throw Error('저장하지 않은 변경이 있습니다.');
33
+ }
34
+ return {
35
+ read,
36
+ startNew({ discard = false } = {}) {
37
+ canLeave(discard);
38
+ ++epoch;
39
+ clear();
40
+ loading = false;
41
+ workspace.open(serializeV3Asset(createV3Asset('새 Figure')));
42
+ creationId = crypto.randomUUID();
43
+ return read();
44
+ },
45
+ async open(nextId, { discard = false } = {}) {
46
+ canLeave(discard);
47
+ const requestEpoch = ++epoch;
48
+ clear();
49
+ loading = true;
50
+ try {
51
+ const record = await store.load(nextId);
52
+ if (requestEpoch !== epoch)
53
+ return read();
54
+ if (record.id !== nextId)
55
+ throw Error('요청한 Figure와 응답의 대상이 다릅니다.');
56
+ workspace.open(record.source);
57
+ id = nextId;
58
+ baseline = record.source;
59
+ }
60
+ catch (cause) {
61
+ if (requestEpoch === epoch)
62
+ error = cause instanceof Error ? cause.message : String(cause);
63
+ }
64
+ finally {
65
+ if (requestEpoch === epoch)
66
+ loading = false;
67
+ }
68
+ return read();
69
+ },
70
+ close({ discard = false } = {}) {
71
+ canLeave(discard);
72
+ ++epoch;
73
+ clear();
74
+ loading = false;
75
+ return read();
76
+ },
77
+ apply(changes, origin = 'ui', expectedRevision, context) {
78
+ const control = writable();
79
+ // AI must supply the context AND revision captured when it started. Never substitute current values.
80
+ if (origin === 'ai' && (context === undefined || expectedRevision === undefined))
81
+ throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.');
82
+ return workspace.apply({
83
+ ...control,
84
+ origin,
85
+ context: context ?? control.context,
86
+ expectedRevision: expectedRevision ?? control.expectedRevision,
87
+ changes
88
+ });
89
+ },
90
+ add(part) {
91
+ return workspace.addPrimitive({ ...writable(), part });
92
+ },
93
+ edit(edit) {
94
+ return workspace.editPart({ ...writable(), edit });
95
+ },
96
+ undo() {
97
+ return workspace.undo(writable());
98
+ },
99
+ redo() {
100
+ return workspace.redo(writable());
101
+ },
102
+ source() {
103
+ const { context } = writable();
104
+ return workspace.export({ context }).text;
105
+ },
106
+ async save() {
107
+ const control = writable(), requestEpoch = epoch;
108
+ const exported = workspace.export(control);
109
+ saving = true;
110
+ try {
111
+ const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId });
112
+ if (requestEpoch !== epoch)
113
+ return read();
114
+ if (!record.id || (id !== undefined && record.id !== id))
115
+ throw Error('저장 응답의 Figure 대상이 다릅니다.');
116
+ if (serializeV3Asset(parseV3Asset(record.source)) !== exported.text)
117
+ throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.');
118
+ workspace.acknowledgeSaved(exported);
119
+ id = record.id;
120
+ baseline = record.source;
121
+ }
122
+ finally {
123
+ if (requestEpoch === epoch)
124
+ saving = false;
125
+ }
126
+ return read();
127
+ }
128
+ };
129
+ }
130
+ //# sourceMappingURL=v3-product-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3-product-session.js","sourceRoot":"","sources":["../../client/modeller/v3-product-session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createV3Asset,\n createV3EditorWorkspace,\n serializeV3Asset,\n parseV3Asset,\n type V3InputChange,\n type V3PrimitiveCreation,\n type V3PartEdit\n} from '@hatiolab/figure-model'\n\nexport interface V3ProductRecord {\n id: string\n source: string\n}\n\n/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */\nexport interface V3ProductStore {\n load(id: string): Promise<V3ProductRecord>\n save(request: { id?: string; source: string; expectedSource?: string; creationId?: string }): Promise<V3ProductRecord>\n}\n\n/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */\nexport function createV3ProductSession(store: V3ProductStore) {\n const workspace = createV3EditorWorkspace()\n let epoch = 0,\n id: string | undefined,\n baseline: string | undefined\n let loading = false,\n saving = false,\n error: string | undefined\n let creationId: string | undefined\n function read() {\n return { ...workspace.read(), id, loading, saving, error }\n }\n function writable() {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n // Serializing writes also makes create retries safe: no second create while the first is pending.\n if (saving) throw Error('저장이 끝난 뒤 편집해 주세요.')\n return { context: view.context, expectedRevision: view.revision, origin: 'ui' as const }\n }\n function clear() {\n workspace.close({ discardDesignChanges: true })\n id = undefined\n baseline = undefined\n error = undefined\n saving = false\n creationId = undefined\n }\n function canLeave(discard: boolean) {\n if (saving) throw Error('저장이 끝난 뒤 모델을 전환해 주세요.')\n if (workspace.read().dirty && !discard) throw Error('저장하지 않은 변경이 있습니다.')\n }\n return {\n read,\n startNew({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n workspace.open(serializeV3Asset(createV3Asset('새 Figure')))\n creationId = crypto.randomUUID()\n return read()\n },\n async open(nextId: string, { discard = false } = {}) {\n canLeave(discard)\n const requestEpoch = ++epoch\n clear()\n loading = true\n try {\n const record = await store.load(nextId)\n if (requestEpoch !== epoch) return read()\n if (record.id !== nextId) throw Error('요청한 Figure와 응답의 대상이 다릅니다.')\n workspace.open(record.source)\n id = nextId\n baseline = record.source\n } catch (cause) {\n if (requestEpoch === epoch) error = cause instanceof Error ? cause.message : String(cause)\n } finally {\n if (requestEpoch === epoch) loading = false\n }\n return read()\n },\n close({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n return read()\n },\n apply(changes: V3InputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {\n const control = writable()\n // AI must supply the context AND revision captured when it started. Never substitute current values.\n if (origin === 'ai' && (context === undefined || expectedRevision === undefined))\n throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.')\n return workspace.apply({\n ...control,\n origin,\n context: context ?? control.context,\n expectedRevision: expectedRevision ?? control.expectedRevision,\n changes\n })\n },\n add(part: V3PrimitiveCreation) {\n return workspace.addPrimitive({ ...writable(), part })\n },\n edit(edit: V3PartEdit) {\n return workspace.editPart({ ...writable(), edit })\n },\n undo() {\n return workspace.undo(writable())\n },\n redo() {\n return workspace.redo(writable())\n },\n source() {\n const { context } = writable()\n return workspace.export({ context }).text\n },\n async save() {\n const control = writable(),\n requestEpoch = epoch\n const exported = workspace.export(control)\n saving = true\n try {\n const record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId })\n if (requestEpoch !== epoch) return read()\n if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')\n if (serializeV3Asset(parseV3Asset(record.source)) !== exported.text)\n throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')\n workspace.acknowledgeSaved(exported)\n id = record.id\n baseline = record.source\n } finally {\n if (requestEpoch === epoch) saving = false\n }\n return read()\n }\n }\n}\nexport type V3ProductSession = ReturnType<typeof createV3ProductSession>\n"]}
@@ -0,0 +1,26 @@
1
+ import { PageView } from '@operato/shell';
2
+ import '../modeller/v3-editor.js';
3
+ /** The V3 product page has exactly one authoritative workspace for UI and AI input transactions. */
4
+ export declare class FigureV3ModellerPage extends PageView {
5
+ static styles: import("lit").CSSResult;
6
+ private readonly session;
7
+ private routeId;
8
+ private discardApproved;
9
+ private revision;
10
+ private error;
11
+ private announce;
12
+ private changed;
13
+ /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */
14
+ private applyAI;
15
+ connectedCallback(): void;
16
+ disconnectedCallback(): void;
17
+ canDeactivate(): Promise<boolean>;
18
+ pageUpdated(changes: Record<string, unknown>, lifecycle: {
19
+ resourceId?: string;
20
+ }): Promise<void>;
21
+ get context(): {
22
+ title: string;
23
+ actions: never[];
24
+ };
25
+ render(): import("lit-html").TemplateResult<1>;
26
+ }
@@ -0,0 +1,140 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { html, css } from 'lit';
3
+ import { customElement, state } from 'lit/decorators.js';
4
+ import { PageView } from '@operato/shell';
5
+ import { createV3ProductSession } from '../modeller/v3-product-session.js';
6
+ import { v3ProductStore } from '../graphql/v3-store.js';
7
+ import '../modeller/v3-editor.js';
8
+ /** The V3 product page has exactly one authoritative workspace for UI and AI input transactions. */
9
+ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.session = createV3ProductSession(v3ProductStore);
13
+ this.discardApproved = false;
14
+ this.revision = 0;
15
+ this.error = '';
16
+ this.announce = () => {
17
+ const view = this.session.read();
18
+ if (!this.active || view.loading || view.saving || view.error || !view.context)
19
+ return;
20
+ window.dispatchEvent(new CustomEvent('figure-ai-context', {
21
+ detail: {
22
+ figureId: view.id,
23
+ source: JSON.parse(this.session.source()),
24
+ context: view.context,
25
+ revision: view.revision,
26
+ format: 'v3'
27
+ }
28
+ }));
29
+ };
30
+ this.changed = () => {
31
+ this.revision++;
32
+ if (!this.session.read().saving)
33
+ this.announce();
34
+ };
35
+ /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */
36
+ this.applyAI = (event) => {
37
+ const detail = event.detail;
38
+ if (!this.active || !detail)
39
+ return;
40
+ try {
41
+ this.session.apply(detail.changes, 'ai', detail.revision, detail.context);
42
+ detail.applied = true;
43
+ this.changed();
44
+ }
45
+ catch (error) {
46
+ detail.applied = false;
47
+ detail.error = error instanceof Error ? error.message : String(error);
48
+ }
49
+ };
50
+ }
51
+ static { this.styles = css `
52
+ :host {
53
+ display: flex;
54
+ flex-direction: column;
55
+ height: 100%;
56
+ min-height: 0;
57
+ }
58
+ figure-v3-editor {
59
+ flex: 1;
60
+ min-height: 0;
61
+ }
62
+ .error {
63
+ padding: 16px;
64
+ color: #922716;
65
+ }
66
+ `; }
67
+ connectedCallback() {
68
+ super.connectedCallback();
69
+ window.addEventListener('figure-ai-context-request', this.announce);
70
+ window.addEventListener('figure-v3-apply', this.applyAI);
71
+ }
72
+ disconnectedCallback() {
73
+ window.removeEventListener('figure-ai-context-request', this.announce);
74
+ window.removeEventListener('figure-v3-apply', this.applyAI);
75
+ super.disconnectedCallback();
76
+ }
77
+ async canDeactivate() {
78
+ const view = this.session.read();
79
+ if (view.saving)
80
+ return false;
81
+ if (!view.dirty)
82
+ return true;
83
+ // The shell invokes this while the edited model is still visible, before navigating away.
84
+ this.discardApproved = window.confirm('저장하지 않은 변경을 버리고 이동하시겠습니까?');
85
+ return this.discardApproved;
86
+ }
87
+ async pageUpdated(changes, lifecycle) {
88
+ if (!this.active) {
89
+ if (this.session.read().loading)
90
+ this.session.close();
91
+ window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }));
92
+ return;
93
+ }
94
+ const id = lifecycle?.resourceId;
95
+ if (this.session.read().context && id === this.routeId && !changes.active)
96
+ return;
97
+ try {
98
+ const discard = this.discardApproved;
99
+ this.discardApproved = false;
100
+ if (id) {
101
+ const opening = this.session.open(id, { discard });
102
+ this.changed();
103
+ await opening;
104
+ }
105
+ else
106
+ this.session.startNew({ discard });
107
+ this.routeId = id;
108
+ this.error = '';
109
+ this.changed();
110
+ }
111
+ catch (error) {
112
+ this.error = error instanceof Error ? error.message : String(error);
113
+ }
114
+ }
115
+ get context() {
116
+ return { title: this.session.read().name ?? 'Figure', actions: [] };
117
+ }
118
+ render() {
119
+ return this.error
120
+ ? html `<p role="alert" class="error">${this.error}</p>`
121
+ : html `<figure-v3-editor
122
+ .session=${this.session}
123
+ .revision=${this.revision}
124
+ @v3-changed=${this.changed}
125
+ ></figure-v3-editor>`;
126
+ }
127
+ };
128
+ __decorate([
129
+ state(),
130
+ __metadata("design:type", Object)
131
+ ], FigureV3ModellerPage.prototype, "revision", void 0);
132
+ __decorate([
133
+ state(),
134
+ __metadata("design:type", Object)
135
+ ], FigureV3ModellerPage.prototype, "error", void 0);
136
+ FigureV3ModellerPage = __decorate([
137
+ customElement('figure-modeller-page')
138
+ ], FigureV3ModellerPage);
139
+ export { FigureV3ModellerPage };
140
+ //# sourceMappingURL=figure-v3-modeller-page.js.map