@things-factory/figure-ui 10.1.34 → 10.1.36
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 +13 -0
- package/client/graphql/v3-store.ts +30 -0
- package/client/modeller/figure-history-panel.ts +29 -2
- package/client/modeller/v3-canvas.ts +145 -0
- package/client/modeller/v3-editor.ts +317 -0
- package/client/modeller/v3-product-session.ts +141 -0
- package/client/modeller/v3-renderer.ts +61 -0
- package/client/pages/figure-modeller-page.ts +50 -3
- package/client/pages/figure-v3-modeller-page.ts +117 -0
- package/client/types.ts +8 -0
- package/dist-client/graphql/index.js +13 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/graphql/v3-store.d.ts +3 -0
- package/dist-client/graphql/v3-store.js +32 -0
- package/dist-client/graphql/v3-store.js.map +1 -0
- package/dist-client/modeller/figure-history-panel.d.ts +2 -0
- package/dist-client/modeller/figure-history-panel.js +28 -1
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/v3-canvas.d.ts +28 -0
- package/dist-client/modeller/v3-canvas.js +157 -0
- package/dist-client/modeller/v3-canvas.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +19 -0
- package/dist-client/modeller/v3-editor.js +332 -0
- package/dist-client/modeller/v3-editor.js.map +1 -0
- package/dist-client/modeller/v3-product-session.d.ts +366 -0
- package/dist-client/modeller/v3-product-session.js +130 -0
- package/dist-client/modeller/v3-product-session.js.map +1 -0
- package/dist-client/modeller/v3-renderer.d.ts +31 -0
- package/dist-client/modeller/v3-renderer.js +81 -0
- package/dist-client/modeller/v3-renderer.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.d.ts +7 -0
- package/dist-client/pages/figure-modeller-page.js +55 -2
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +26 -0
- package/dist-client/pages/figure-v3-modeller-page.js +140 -0
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +7 -0
- package/dist-client/types.js.map +1 -1
- package/docs/v3-product-cutover.md +35 -0
- package/package.json +4 -4
- package/test/v3-product-session.test.ts +133 -0
- package/test/v3-product-write.test.ts +48 -0
- package/test/v3-renderer.test.ts +24 -0
- package/translations/en.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { LitElement, html, css } from 'lit';
|
|
3
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import './v3-canvas.js';
|
|
5
|
+
const names = {
|
|
6
|
+
position: '위치',
|
|
7
|
+
rotation: '회전',
|
|
8
|
+
size: '크기',
|
|
9
|
+
dimension: '치수',
|
|
10
|
+
width: '너비',
|
|
11
|
+
height: '높이',
|
|
12
|
+
depth: '깊이',
|
|
13
|
+
radius: '반지름',
|
|
14
|
+
length: '길이',
|
|
15
|
+
round: '모서리 반경',
|
|
16
|
+
left: '왼쪽',
|
|
17
|
+
right: '오른쪽',
|
|
18
|
+
bottom: '아랫면',
|
|
19
|
+
top: '윗면',
|
|
20
|
+
openingLeft: '개구부 왼쪽',
|
|
21
|
+
openingRight: '개구부 오른쪽',
|
|
22
|
+
openingTop: '개구부 윗면'
|
|
23
|
+
};
|
|
24
|
+
function fieldLabel(id) {
|
|
25
|
+
return id
|
|
26
|
+
.split('.')
|
|
27
|
+
.map(p => names[p] ?? p.toUpperCase())
|
|
28
|
+
.join(' · ');
|
|
29
|
+
}
|
|
30
|
+
/** Product editor surface. The injected session is shared by page persistence and AI commands. */
|
|
31
|
+
let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
32
|
+
constructor() {
|
|
33
|
+
super(...arguments);
|
|
34
|
+
this.revision = 0;
|
|
35
|
+
this.selected = '';
|
|
36
|
+
this.error = '';
|
|
37
|
+
this.lastContext = null;
|
|
38
|
+
}
|
|
39
|
+
static { this.styles = css `
|
|
40
|
+
:host {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
min-height: 0;
|
|
44
|
+
height: 100%;
|
|
45
|
+
font: 13px sans-serif;
|
|
46
|
+
color: var(--md-sys-color-on-surface, #22343f);
|
|
47
|
+
}
|
|
48
|
+
header {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 8px;
|
|
52
|
+
padding: 10px;
|
|
53
|
+
border-bottom: 1px solid #d4dde2;
|
|
54
|
+
flex-wrap: wrap;
|
|
55
|
+
}
|
|
56
|
+
main {
|
|
57
|
+
flex: 1;
|
|
58
|
+
min-height: 0;
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-columns: 180px minmax(180px, 1fr) 300px;
|
|
61
|
+
}
|
|
62
|
+
aside {
|
|
63
|
+
padding: 12px;
|
|
64
|
+
overflow: auto;
|
|
65
|
+
background: var(--md-sys-color-surface, #fff);
|
|
66
|
+
}
|
|
67
|
+
aside:first-child {
|
|
68
|
+
border-right: 1px solid #d4dde2;
|
|
69
|
+
}
|
|
70
|
+
aside:last-child {
|
|
71
|
+
border-left: 1px solid #d4dde2;
|
|
72
|
+
}
|
|
73
|
+
h3 {
|
|
74
|
+
font-size: var(--ui-t-body, 13px);
|
|
75
|
+
margin: 22px 0 10px;
|
|
76
|
+
}
|
|
77
|
+
h3:first-child {
|
|
78
|
+
margin-top: 0;
|
|
79
|
+
}
|
|
80
|
+
button,
|
|
81
|
+
input,
|
|
82
|
+
select {
|
|
83
|
+
font: inherit;
|
|
84
|
+
box-sizing: border-box;
|
|
85
|
+
height: 34px;
|
|
86
|
+
border: 1px solid #cbd5dc;
|
|
87
|
+
border-radius: 6px;
|
|
88
|
+
background: var(--md-sys-color-surface, #fff);
|
|
89
|
+
color: inherit;
|
|
90
|
+
padding: 0 8px;
|
|
91
|
+
min-width: 0;
|
|
92
|
+
}
|
|
93
|
+
button {
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
}
|
|
96
|
+
button:disabled {
|
|
97
|
+
opacity: 0.45;
|
|
98
|
+
cursor: default;
|
|
99
|
+
}
|
|
100
|
+
button[aria-pressed='true'] {
|
|
101
|
+
background: #d6edf0;
|
|
102
|
+
border-color: #337783;
|
|
103
|
+
}
|
|
104
|
+
.parts {
|
|
105
|
+
display: grid;
|
|
106
|
+
gap: 6px;
|
|
107
|
+
}
|
|
108
|
+
.recipes {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: 1fr 1fr;
|
|
111
|
+
gap: 6px;
|
|
112
|
+
}
|
|
113
|
+
label {
|
|
114
|
+
display: grid;
|
|
115
|
+
grid-template-columns: 112px minmax(0, 1fr);
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: 8px;
|
|
118
|
+
margin: 8px 0;
|
|
119
|
+
}
|
|
120
|
+
label span {
|
|
121
|
+
overflow-wrap: anywhere;
|
|
122
|
+
}
|
|
123
|
+
.error {
|
|
124
|
+
padding: 10px;
|
|
125
|
+
background: #fff0eb;
|
|
126
|
+
color: #922716;
|
|
127
|
+
white-space: pre-wrap;
|
|
128
|
+
}
|
|
129
|
+
.status {
|
|
130
|
+
margin-left: auto;
|
|
131
|
+
}
|
|
132
|
+
.hint {
|
|
133
|
+
color: #637783;
|
|
134
|
+
line-height: 1.5;
|
|
135
|
+
}
|
|
136
|
+
@media (max-width: 800px) {
|
|
137
|
+
main {
|
|
138
|
+
grid-template-columns: 140px minmax(160px, 1fr);
|
|
139
|
+
}
|
|
140
|
+
aside:last-child {
|
|
141
|
+
grid-column: 1/-1;
|
|
142
|
+
max-height: 260px;
|
|
143
|
+
border-top: 1px solid #d4dde2;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
`; }
|
|
147
|
+
willUpdate(_changed) {
|
|
148
|
+
const view = this.session?.read();
|
|
149
|
+
if (view?.context !== this.lastContext) {
|
|
150
|
+
this.lastContext = view?.context ?? null;
|
|
151
|
+
this.selected = '';
|
|
152
|
+
this.error = '';
|
|
153
|
+
}
|
|
154
|
+
if (view && !view.geometry.some(g => g.id === this.selected))
|
|
155
|
+
this.selected = '';
|
|
156
|
+
}
|
|
157
|
+
run(action) {
|
|
158
|
+
try {
|
|
159
|
+
action();
|
|
160
|
+
this.error = '';
|
|
161
|
+
this.changed();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
changed() {
|
|
168
|
+
this.requestUpdate();
|
|
169
|
+
this.dispatchEvent(new CustomEvent('v3-changed', { bubbles: true, composed: true }));
|
|
170
|
+
}
|
|
171
|
+
async save() {
|
|
172
|
+
if (!this.session)
|
|
173
|
+
return;
|
|
174
|
+
try {
|
|
175
|
+
const promise = this.session.save();
|
|
176
|
+
this.changed();
|
|
177
|
+
await promise;
|
|
178
|
+
this.error = '';
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
this.changed();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
add(kind) {
|
|
188
|
+
if (!this.session)
|
|
189
|
+
return;
|
|
190
|
+
this.run(() => {
|
|
191
|
+
const ids = new Set(this.session.read().geometry.map(g => g.id));
|
|
192
|
+
let n = 1;
|
|
193
|
+
while (ids.has(`${kind}-${n}`))
|
|
194
|
+
n++;
|
|
195
|
+
const common = { id: `${kind}-${n}`, position: { x: 0, y: 50, z: 0 } };
|
|
196
|
+
const part = kind === 'box'
|
|
197
|
+
? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100 } }
|
|
198
|
+
: kind === 'cylinder'
|
|
199
|
+
? { ...common, kind, dimensions: { radius: 50, length: 100 } }
|
|
200
|
+
: kind === 'rounded-box'
|
|
201
|
+
? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } }
|
|
202
|
+
: {
|
|
203
|
+
...common,
|
|
204
|
+
position: { x: 0, y: 0, z: 0 },
|
|
205
|
+
kind: 'portal',
|
|
206
|
+
dimensions: {
|
|
207
|
+
left: -100,
|
|
208
|
+
right: 100,
|
|
209
|
+
bottom: 0,
|
|
210
|
+
top: 200,
|
|
211
|
+
openingLeft: -70,
|
|
212
|
+
openingRight: 70,
|
|
213
|
+
openingTop: 170,
|
|
214
|
+
depth: 30
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
this.session.add(part);
|
|
218
|
+
this.selected = part.id;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
changeField(field, event) {
|
|
222
|
+
const input = event.target, value = input.valueAsNumber;
|
|
223
|
+
this.run(() => this.session.apply([
|
|
224
|
+
{ kind: field.role === 'state' ? 'set-state' : 'set-design', input: field.id, unit: field.unit, value }
|
|
225
|
+
]));
|
|
226
|
+
// Rejected edits must also restore the displayed numeric value.
|
|
227
|
+
input.value = String(this.session.read().fields.find(i => i.id === field.id).value);
|
|
228
|
+
}
|
|
229
|
+
render() {
|
|
230
|
+
const view = this.session?.read();
|
|
231
|
+
if (!view)
|
|
232
|
+
return html `<p>모델을 여는 중입니다.</p>`;
|
|
233
|
+
const unavailable = view.loading || !!view.error || view.saving || !view.context;
|
|
234
|
+
const part = view.geometry.find(g => g.id === this.selected);
|
|
235
|
+
// Shared/root parameters remain available even when a part is selected.
|
|
236
|
+
const own = view.fields.filter(i => this.selected && i.id.startsWith(this.selected + '.'));
|
|
237
|
+
const common = view.fields.filter(i => !view.geometry.some(g => i.id.startsWith(g.id + '.')));
|
|
238
|
+
const fields = this.selected ? [...own, ...common] : view.fields;
|
|
239
|
+
return html ` <header>
|
|
240
|
+
<strong>V3 모델링</strong>
|
|
241
|
+
<button ?disabled=${unavailable || !view.canUndo} @click=${() => this.run(() => this.session.undo())}>
|
|
242
|
+
실행 취소
|
|
243
|
+
</button>
|
|
244
|
+
<button ?disabled=${unavailable || !view.canRedo} @click=${() => this.run(() => this.session.redo())}>
|
|
245
|
+
다시 실행
|
|
246
|
+
</button>
|
|
247
|
+
<button @click=${() => this.renderRoot.querySelector('figure-v3-canvas')?.fit()}>
|
|
248
|
+
전체 보기
|
|
249
|
+
</button>
|
|
250
|
+
<span class="status" role="status"
|
|
251
|
+
>${view.loading ? '불러오는 중' : view.saving ? '저장 중' : view.dirty || !view.id ? '저장 안 됨' : '저장됨'}</span
|
|
252
|
+
>
|
|
253
|
+
<button ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>저장</button>
|
|
254
|
+
</header>
|
|
255
|
+
${this.error || view.error ? html `<div role="alert" class="error">${this.error || view.error}</div>` : null}
|
|
256
|
+
<main>
|
|
257
|
+
<aside>
|
|
258
|
+
<h3>도형 추가</h3>
|
|
259
|
+
<div class="recipes">
|
|
260
|
+
${['box', 'cylinder', 'rounded-box', 'portal'].map((kind, i) => html `<button ?disabled=${unavailable} @click=${() => this.add(kind)}>${['상자', '원기둥', '둥근 상자', '문틀'][i]}</button>`)}
|
|
261
|
+
</div>
|
|
262
|
+
<h3>부품 ${view.geometry.length}</h3>
|
|
263
|
+
<div class="parts">
|
|
264
|
+
${view.geometry.map(g => html `<button
|
|
265
|
+
aria-pressed=${g.id === this.selected}
|
|
266
|
+
@click=${() => {
|
|
267
|
+
this.selected = g.id;
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
${g.id}
|
|
271
|
+
</button>`)}
|
|
272
|
+
</div>
|
|
273
|
+
</aside>
|
|
274
|
+
<figure-v3-canvas
|
|
275
|
+
.parts=${view.geometry}
|
|
276
|
+
.palette=${view.palette ?? {}}
|
|
277
|
+
.context=${view.context ?? ''}
|
|
278
|
+
@part-selected=${(e) => {
|
|
279
|
+
this.selected = e.detail;
|
|
280
|
+
}}
|
|
281
|
+
@render-error=${(e) => {
|
|
282
|
+
this.error = e.detail.message;
|
|
283
|
+
}}
|
|
284
|
+
></figure-v3-canvas>
|
|
285
|
+
<aside>
|
|
286
|
+
<h3>${this.selected || '모델 입력'}</h3>
|
|
287
|
+
${fields.map(field => html `<label><span>${fieldLabel(field.id)} (${field.unit})${field.role === 'state' ? ' · 동작' : ''}</span><input aria-label=${field.id} type="number" step="any" .value=${String(field.value)} ?disabled=${unavailable} @change=${(e) => this.changeField(field, e)} /></label>`)}
|
|
288
|
+
${part
|
|
289
|
+
? html `<h3>재질</h3>
|
|
290
|
+
<label
|
|
291
|
+
><span>색 역할</span
|
|
292
|
+
><select
|
|
293
|
+
.value=${part.material.token}
|
|
294
|
+
?disabled=${unavailable}
|
|
295
|
+
@change=${(e) => this.run(() => this.session.edit({ kind: 'set-material', target: this.selected, material: { ...part.material, token: e.target.value } }))}
|
|
296
|
+
>
|
|
297
|
+
${Object.keys(view.palette ?? {}).map(token => html `<option value=${token}>${token === 'palette.primary' ? '기본 몸체' : token}</option>`)}
|
|
298
|
+
</select></label
|
|
299
|
+
>
|
|
300
|
+
<p class="hint">
|
|
301
|
+
도형과 위치를 결정하는 입력을 편집합니다. 다른 부품과 공유하는 입력은 함께 영향을 줄 수 있습니다.
|
|
302
|
+
</p>`
|
|
303
|
+
: null}
|
|
304
|
+
${view.bindings.length
|
|
305
|
+
? html `<h3>배치 연결점</h3>
|
|
306
|
+
${view.bindings.map(b => html `<p>${b.label} · ${b.role}</p>`)}`
|
|
307
|
+
: null}
|
|
308
|
+
</aside>
|
|
309
|
+
</main>`;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
__decorate([
|
|
313
|
+
property({ attribute: false }),
|
|
314
|
+
__metadata("design:type", Object)
|
|
315
|
+
], FigureV3Editor.prototype, "session", void 0);
|
|
316
|
+
__decorate([
|
|
317
|
+
property({ type: Number }),
|
|
318
|
+
__metadata("design:type", Object)
|
|
319
|
+
], FigureV3Editor.prototype, "revision", void 0);
|
|
320
|
+
__decorate([
|
|
321
|
+
state(),
|
|
322
|
+
__metadata("design:type", Object)
|
|
323
|
+
], FigureV3Editor.prototype, "selected", void 0);
|
|
324
|
+
__decorate([
|
|
325
|
+
state(),
|
|
326
|
+
__metadata("design:type", Object)
|
|
327
|
+
], FigureV3Editor.prototype, "error", void 0);
|
|
328
|
+
FigureV3Editor = __decorate([
|
|
329
|
+
customElement('figure-v3-editor')
|
|
330
|
+
], FigureV3Editor);
|
|
331
|
+
export { FigureV3Editor };
|
|
332
|
+
//# sourceMappingURL=v3-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-editor.js","sourceRoot":"","sources":["../../client/modeller/v3-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, type PropertyValues } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport type { V3InputChange, V3PrimitiveCreation } from '@hatiolab/figure-model'\nimport type { V3ProductSession } from './v3-product-session.js'\nimport './v3-canvas.js'\nimport type { FigureV3Canvas } from './v3-canvas.js'\n\nconst names: Record<string, string> = {\n position: '위치',\n rotation: '회전',\n size: '크기',\n dimension: '치수',\n width: '너비',\n height: '높이',\n depth: '깊이',\n radius: '반지름',\n length: '길이',\n round: '모서리 반경',\n left: '왼쪽',\n right: '오른쪽',\n bottom: '아랫면',\n top: '윗면',\n openingLeft: '개구부 왼쪽',\n openingRight: '개구부 오른쪽',\n openingTop: '개구부 윗면'\n}\nfunction fieldLabel(id: string) {\n return id\n .split('.')\n .map(p => names[p] ?? p.toUpperCase())\n .join(' · ')\n}\n\n/** Product editor surface. The injected session is shared by page persistence and AI commands. */\n@customElement('figure-v3-editor')\nexport class FigureV3Editor extends LitElement {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n font: 13px sans-serif;\n color: var(--md-sys-color-on-surface, #22343f);\n }\n header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px;\n border-bottom: 1px solid #d4dde2;\n flex-wrap: wrap;\n }\n main {\n flex: 1;\n min-height: 0;\n display: grid;\n grid-template-columns: 180px minmax(180px, 1fr) 300px;\n }\n aside {\n padding: 12px;\n overflow: auto;\n background: var(--md-sys-color-surface, #fff);\n }\n aside:first-child {\n border-right: 1px solid #d4dde2;\n }\n aside:last-child {\n border-left: 1px solid #d4dde2;\n }\n h3 {\n font-size: var(--ui-t-body, 13px);\n margin: 22px 0 10px;\n }\n h3:first-child {\n margin-top: 0;\n }\n button,\n input,\n select {\n font: inherit;\n box-sizing: border-box;\n height: 34px;\n border: 1px solid #cbd5dc;\n border-radius: 6px;\n background: var(--md-sys-color-surface, #fff);\n color: inherit;\n padding: 0 8px;\n min-width: 0;\n }\n button {\n cursor: pointer;\n }\n button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n button[aria-pressed='true'] {\n background: #d6edf0;\n border-color: #337783;\n }\n .parts {\n display: grid;\n gap: 6px;\n }\n .recipes {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 6px;\n }\n label {\n display: grid;\n grid-template-columns: 112px minmax(0, 1fr);\n align-items: center;\n gap: 8px;\n margin: 8px 0;\n }\n label span {\n overflow-wrap: anywhere;\n }\n .error {\n padding: 10px;\n background: #fff0eb;\n color: #922716;\n white-space: pre-wrap;\n }\n .status {\n margin-left: auto;\n }\n .hint {\n color: #637783;\n line-height: 1.5;\n }\n @media (max-width: 800px) {\n main {\n grid-template-columns: 140px minmax(160px, 1fr);\n }\n aside:last-child {\n grid-column: 1/-1;\n max-height: 260px;\n border-top: 1px solid #d4dde2;\n }\n }\n `\n @property({ attribute: false }) session?: V3ProductSession\n @property({ type: Number }) revision = 0\n @state() private selected = ''\n @state() private error = ''\n private lastContext: string | null = null\n protected willUpdate(_changed: PropertyValues) {\n const view = this.session?.read()\n if (view?.context !== this.lastContext) {\n this.lastContext = view?.context ?? null\n this.selected = ''\n this.error = ''\n }\n if (view && !view.geometry.some(g => g.id === this.selected)) this.selected = ''\n }\n private run(action: () => unknown) {\n try {\n action()\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n private changed() {\n this.requestUpdate()\n this.dispatchEvent(new CustomEvent('v3-changed', { bubbles: true, composed: true }))\n }\n private async save() {\n if (!this.session) return\n try {\n const promise = this.session.save()\n this.changed()\n await promise\n this.error = ''\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n } finally {\n this.changed()\n }\n }\n private add(kind: V3PrimitiveCreation['kind']) {\n if (!this.session) return\n this.run(() => {\n const ids = new Set(this.session!.read().geometry.map(g => g.id))\n let n = 1\n while (ids.has(`${kind}-${n}`)) n++\n const common = { id: `${kind}-${n}`, position: { x: 0, y: 50, z: 0 } }\n const part: V3PrimitiveCreation =\n kind === 'box'\n ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100 } }\n : kind === 'cylinder'\n ? { ...common, kind, dimensions: { radius: 50, length: 100 } }\n : kind === 'rounded-box'\n ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } }\n : {\n ...common,\n position: { x: 0, y: 0, z: 0 },\n kind: 'portal',\n dimensions: {\n left: -100,\n right: 100,\n bottom: 0,\n top: 200,\n openingLeft: -70,\n openingRight: 70,\n openingTop: 170,\n depth: 30\n }\n }\n this.session!.add(part)\n this.selected = part.id\n })\n }\n private changeField(field: { id: string; unit: V3InputChange['unit']; role?: string }, event: Event) {\n const input = event.target as HTMLInputElement,\n value = input.valueAsNumber\n this.run(() =>\n this.session!.apply([\n { kind: field.role === 'state' ? 'set-state' : 'set-design', input: field.id, unit: field.unit, value }\n ])\n )\n // Rejected edits must also restore the displayed numeric value.\n input.value = String(this.session!.read().fields.find(i => i.id === field.id)!.value)\n }\n render() {\n const view = this.session?.read()\n if (!view) return html`<p>모델을 여는 중입니다.</p>`\n const unavailable = view.loading || !!view.error || view.saving || !view.context\n const part = view.geometry.find(g => g.id === this.selected)\n // Shared/root parameters remain available even when a part is selected.\n const own = view.fields.filter(i => this.selected && i.id.startsWith(this.selected + '.'))\n const common = view.fields.filter(i => !view.geometry.some(g => i.id.startsWith(g.id + '.')))\n const fields = this.selected ? [...own, ...common] : view.fields\n return html` <header>\n <strong>V3 모델링</strong>\n <button ?disabled=${unavailable || !view.canUndo} @click=${() => this.run(() => this.session!.undo())}>\n 실행 취소\n </button>\n <button ?disabled=${unavailable || !view.canRedo} @click=${() => this.run(() => this.session!.redo())}>\n 다시 실행\n </button>\n <button @click=${() => this.renderRoot.querySelector<FigureV3Canvas>('figure-v3-canvas')?.fit()}>\n 전체 보기\n </button>\n <span class=\"status\" role=\"status\"\n >${view.loading ? '불러오는 중' : view.saving ? '저장 중' : view.dirty || !view.id ? '저장 안 됨' : '저장됨'}</span\n >\n <button ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>저장</button>\n </header>\n ${this.error || view.error ? html`<div role=\"alert\" class=\"error\">${this.error || view.error}</div>` : null}\n <main>\n <aside>\n <h3>도형 추가</h3>\n <div class=\"recipes\">\n ${(['box', 'cylinder', 'rounded-box', 'portal'] as const).map((kind, i) => html`<button ?disabled=${unavailable} @click=${() => this.add(kind)}>${['상자', '원기둥', '둥근 상자', '문틀'][i]}</button>`)}\n </div>\n <h3>부품 ${view.geometry.length}</h3>\n <div class=\"parts\">\n ${view.geometry.map(\n g =>\n html`<button\n aria-pressed=${g.id === this.selected}\n @click=${() => {\n this.selected = g.id\n }}\n >\n ${g.id}\n </button>`\n )}\n </div>\n </aside>\n <figure-v3-canvas\n .parts=${view.geometry}\n .palette=${view.palette ?? {}}\n .context=${view.context ?? ''}\n @part-selected=${(e: CustomEvent<string>) => {\n this.selected = e.detail\n }}\n @render-error=${(e: CustomEvent<Error>) => {\n this.error = e.detail.message\n }}\n ></figure-v3-canvas>\n <aside>\n <h3>${this.selected || '모델 입력'}</h3>\n ${fields.map(field => html`<label><span>${fieldLabel(field.id)} (${field.unit})${field.role === 'state' ? ' · 동작' : ''}</span><input aria-label=${field.id} type=\"number\" step=\"any\" .value=${String(field.value)} ?disabled=${unavailable} @change=${(e: Event) => this.changeField(field, e)} /></label>`)}\n ${\n part\n ? html`<h3>재질</h3>\n <label\n ><span>색 역할</span\n ><select\n .value=${part.material.token}\n ?disabled=${unavailable}\n @change=${(e: Event) => this.run(() => this.session!.edit({ kind: 'set-material', target: this.selected, material: { ...part.material, token: (e.target as HTMLSelectElement).value } }))}\n >\n ${Object.keys(view.palette ?? {}).map(token => html`<option value=${token}>${token === 'palette.primary' ? '기본 몸체' : token}</option>`)}\n </select></label\n >\n <p class=\"hint\">\n 도형과 위치를 결정하는 입력을 편집합니다. 다른 부품과 공유하는 입력은 함께 영향을 줄 수 있습니다.\n </p>`\n : null\n }\n ${\n view.bindings.length\n ? html`<h3>배치 연결점</h3>\n ${view.bindings.map(b => html`<p>${b.label} · ${b.role}</p>`)}`\n : null\n }\n </aside>\n </main>`\n }\n}\n"]}
|