@things-factory/figure-ui 10.1.48 → 10.1.49
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/modeller/figure-v3-parts.ts +85 -10
- package/client/modeller/side-panel-styles.ts +8 -0
- package/client/modeller/v3-canvas.ts +25 -5
- package/client/modeller/v3-editor.ts +159 -22
- package/client/modeller/v3-fields.ts +15 -1
- package/client/modeller/v3-stage-model.ts +24 -0
- package/dist-client/modeller/figure-v3-parts.d.ts +13 -0
- package/dist-client/modeller/figure-v3-parts.js +80 -10
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/side-panel-styles.js +8 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -1
- package/dist-client/modeller/v3-canvas.d.ts +2 -0
- package/dist-client/modeller/v3-canvas.js +27 -5
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +1 -1
- package/dist-client/modeller/v3-editor.d.ts +19 -0
- package/dist-client/modeller/v3-editor.js +160 -20
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +6 -0
- package/dist-client/modeller/v3-fields.js +14 -2
- package/dist-client/modeller/v3-fields.js.map +1 -1
- package/dist-client/modeller/v3-stage-model.d.ts +8 -0
- package/dist-client/modeller/v3-stage-model.js +26 -0
- package/dist-client/modeller/v3-stage-model.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/test/v3-fields.test.ts +37 -1
- package/test/v3-gizmo.test.ts +3 -1
- package/test/v3-maturity.test.ts +1 -1
- package/translations/en.json +11 -1
- package/translations/ja.json +11 -1
- package/translations/ko.json +11 -1
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
|
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from 'lit';
|
|
|
4
4
|
import { customElement, property } from 'lit/decorators.js';
|
|
5
5
|
import { i18next, localize } from '@operato/i18n';
|
|
6
6
|
import { ScrollbarStyles } from '@operato/styles';
|
|
7
|
+
import { PART_LIMIT } from '@hatiolab/figure-model';
|
|
7
8
|
import { partLabelOf } from './v3-fields.js';
|
|
8
9
|
/** 제공자마다의 아이콘. Material Symbols 이름이다. */
|
|
9
10
|
const PROVIDER_ICON = {
|
|
@@ -121,25 +122,61 @@ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
121
122
|
font-style: normal;
|
|
122
123
|
color: var(--md-sys-color-error);
|
|
123
124
|
}
|
|
125
|
+
span[room][full] {
|
|
126
|
+
color: var(--md-sys-color-error);
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
}
|
|
129
|
+
p[full] {
|
|
130
|
+
margin: 4px 0 0;
|
|
131
|
+
font-size: 0.68rem;
|
|
132
|
+
color: var(--md-sys-color-error);
|
|
133
|
+
}
|
|
134
|
+
div[level] {
|
|
135
|
+
display: grid;
|
|
136
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
137
|
+
align-items: center;
|
|
138
|
+
gap: 6px;
|
|
139
|
+
margin: 0 0 6px;
|
|
140
|
+
font-size: 0.68rem;
|
|
141
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
142
|
+
}
|
|
143
|
+
div[level] select {
|
|
144
|
+
min-width: 0;
|
|
145
|
+
padding: 2px 4px;
|
|
146
|
+
font: inherit;
|
|
147
|
+
font-size: 0.7rem;
|
|
148
|
+
color: var(--md-sys-color-on-surface);
|
|
149
|
+
background: var(--md-sys-color-surface-container-lowest);
|
|
150
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
151
|
+
border-radius: 6px;
|
|
152
|
+
}
|
|
153
|
+
small[copies] {
|
|
154
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
155
|
+
}
|
|
124
156
|
span[room] {
|
|
125
157
|
font-size: 0.7rem;
|
|
126
158
|
font-weight: 400;
|
|
127
159
|
letter-spacing: 0;
|
|
128
160
|
text-transform: none;
|
|
129
161
|
}
|
|
162
|
+
/* Three to a row, small (user, 2026-09-24): the palette is reached for, not read -- the list below is what grows. */
|
|
130
163
|
div[palette] {
|
|
131
164
|
display: grid;
|
|
132
|
-
grid-template-columns: repeat(
|
|
133
|
-
gap:
|
|
165
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
166
|
+
gap: 3px;
|
|
134
167
|
}
|
|
135
168
|
button[shape] {
|
|
136
169
|
display: flex;
|
|
137
170
|
flex-direction: column;
|
|
138
171
|
align-items: center;
|
|
139
|
-
gap:
|
|
140
|
-
|
|
172
|
+
gap: 1px;
|
|
173
|
+
min-width: 0;
|
|
174
|
+
padding: 5px 1px 4px;
|
|
141
175
|
font: var(--label-font, inherit);
|
|
142
|
-
font-size: 0.
|
|
176
|
+
font-size: 0.6rem;
|
|
177
|
+
line-height: 1.2;
|
|
178
|
+
text-align: center;
|
|
179
|
+
overflow-wrap: anywhere;
|
|
143
180
|
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
144
181
|
background: none;
|
|
145
182
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
@@ -156,7 +193,7 @@ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
156
193
|
cursor: default;
|
|
157
194
|
}
|
|
158
195
|
button[shape] md-icon {
|
|
159
|
-
--md-icon-size:
|
|
196
|
+
--md-icon-size: 18px;
|
|
160
197
|
}
|
|
161
198
|
ol {
|
|
162
199
|
margin: 0;
|
|
@@ -287,6 +324,8 @@ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
287
324
|
`
|
|
288
325
|
]; }
|
|
289
326
|
render() {
|
|
327
|
+
const b = this.partBudget;
|
|
328
|
+
const full = !!b && b.limit !== null && b.used >= b.limit;
|
|
290
329
|
return html `
|
|
291
330
|
<section>
|
|
292
331
|
<h3>${i18next.t('figure.label.shapes')}</h3>
|
|
@@ -294,8 +333,8 @@ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
294
333
|
${RECIPES.map(recipe => html `
|
|
295
334
|
<button
|
|
296
335
|
shape
|
|
297
|
-
?disabled=${this.locked}
|
|
298
|
-
title=${i18next.t(recipe.label)}
|
|
336
|
+
?disabled=${this.locked || full}
|
|
337
|
+
title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t(recipe.label)}
|
|
299
338
|
@click=${() => this.ask('add-part', { kind: recipe.kind })}
|
|
300
339
|
>
|
|
301
340
|
<md-icon>${recipe.icon}</md-icon>
|
|
@@ -303,14 +342,18 @@ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
303
342
|
</button>
|
|
304
343
|
`)}
|
|
305
344
|
</div>
|
|
345
|
+
${full ? html `<p full>${i18next.t('figure.text.part-limit-reached-short', { level: b.level, limit: b.limit })}</p>` : nothing}
|
|
306
346
|
</section>
|
|
307
347
|
|
|
308
348
|
<section list>
|
|
309
349
|
<h3>
|
|
310
350
|
<span>${i18next.t('figure.label.parts')}</span>
|
|
311
|
-
<span room
|
|
351
|
+
<span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}
|
|
352
|
+
>${b && b.limit !== null ? i18next.t('figure.text.n-of-limit', { used: b.used, limit: b.limit }) : (b?.used ?? this.parts.length)}</span
|
|
353
|
+
>
|
|
312
354
|
${this.renderShowAll()}
|
|
313
355
|
</h3>
|
|
356
|
+
${this.renderLevel()}
|
|
314
357
|
${this.renderBudget()}
|
|
315
358
|
${this.parts.length === 0
|
|
316
359
|
? html `<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`
|
|
@@ -320,6 +363,29 @@ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
320
363
|
</section>
|
|
321
364
|
`;
|
|
322
365
|
}
|
|
366
|
+
/** 디테일 등급 — 고르면 한도가 바뀐다. V2 의 자리 그대로, 목록 머리 아래. */
|
|
367
|
+
renderLevel() {
|
|
368
|
+
const b = this.partBudget;
|
|
369
|
+
if (!b)
|
|
370
|
+
return nothing;
|
|
371
|
+
return html `<div level>
|
|
372
|
+
<label for="detail-level">${i18next.t('figure.label.detail-level')}</label>
|
|
373
|
+
<select
|
|
374
|
+
id="detail-level"
|
|
375
|
+
title=${i18next.t('figure.text.detail-level-explained')}
|
|
376
|
+
?disabled=${this.locked}
|
|
377
|
+
@change=${(e) => {
|
|
378
|
+
const value = e.target.value;
|
|
379
|
+
this.dispatchEvent(new CustomEvent('set-detail-level', { detail: { level: value || null }, bubbles: true, composed: true }));
|
|
380
|
+
}}
|
|
381
|
+
>
|
|
382
|
+
<option value="" ?selected=${!b.level}>${i18next.t('figure.label.detail-none')}</option>
|
|
383
|
+
${['S', 'M', 'L'].map(one => html `<option value=${one} ?selected=${one === b.level}>
|
|
384
|
+
${one} ${i18next.t('figure.label.detail-' + one)} · ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}
|
|
385
|
+
</option>`)}
|
|
386
|
+
</select>
|
|
387
|
+
</div>`;
|
|
388
|
+
}
|
|
323
389
|
renderBudget() {
|
|
324
390
|
const b = this.budget;
|
|
325
391
|
if (!b || !this.parts.length)
|
|
@@ -393,7 +459,7 @@ let FigureV3Parts = class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
393
459
|
</button>
|
|
394
460
|
<md-icon>${PROVIDER_ICON[part.provider] ?? 'deployed_code'}</md-icon>
|
|
395
461
|
<span label title=${part.id}
|
|
396
|
-
>${partLabelOf(part.id)}${part.invisible ? html `<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}</span
|
|
462
|
+
>${partLabelOf(part.id)}${part.copies ? html ` <small copies>× ${part.copies}</small>` : nothing}${part.invisible ? html `<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}</span
|
|
397
463
|
>
|
|
398
464
|
${selected ? this.renderOps(part.id, index) : nothing}
|
|
399
465
|
</li>
|
|
@@ -445,6 +511,10 @@ __decorate([
|
|
|
445
511
|
property({ attribute: false }),
|
|
446
512
|
__metadata("design:type", Object)
|
|
447
513
|
], FigureV3Parts.prototype, "budget", void 0);
|
|
514
|
+
__decorate([
|
|
515
|
+
property({ attribute: false }),
|
|
516
|
+
__metadata("design:type", Object)
|
|
517
|
+
], FigureV3Parts.prototype, "partBudget", void 0);
|
|
448
518
|
FigureV3Parts = __decorate([
|
|
449
519
|
customElement('figure-v3-parts')
|
|
450
520
|
], FigureV3Parts);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-v3-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-v3-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport type { V3PrimitiveCreation } from '@hatiolab/figure-model'\nimport type { V3BudgetLine } from './v3-maturity.js'\nimport { partLabelOf } from './v3-fields.js'\n\n/**\n * 도형 팔레트와 부품 목록 — V3 자산의 것.\n *\n * `figure-parts`(V2) 와 **같은 자리, 같은 조작, 같은 낱말**이다. 저작자가 V2 에서 익힌 손이 그대로\n * 가야 한다: 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.\n *\n * 다른 것은 읽는 대상뿐이다. V2 는 부품의 `primitive` 를 읽었고 여기서는 V3 평가가 준 `provider` 를\n * 읽는다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라\n * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — V2 와 같은 규칙, 같은 사건 이름.\n */\n\n/** 한 줄 — V3 평가에서 이 패널이 읽는 것만. */\nexport interface V3PartRow {\n id: string\n provider: string\n /** 작업대에서 잠깐 가린 것. 저장되지 않는다. */\n hidden?: boolean\n /** 자산이 지금 상태에서 보이지 않는다고 한 것. 저장되는 것이고, 여기서는 표시만 한다. */\n invisible?: boolean\n}\n\n/** 제공자마다의 아이콘. Material Symbols 이름이다. */\nconst PROVIDER_ICON: Record<string, string> = {\n box: 'deployed_code',\n cylinder: 'database',\n ellipticCylinder: 'database',\n frustum: 'change_history',\n sphere: 'circle',\n roundedBox: 'crop_square',\n roundedBoxXZ: 'crop_square',\n hollowBox: 'check_box_outline_blank',\n hollowBoxXZ: 'check_box_outline_blank',\n polygon: 'pentagon',\n portal: 'door_front'\n}\n\n/** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */\n/*\n The palette: V2's shapes. A plate and a wall are boxes of a shape a person reaches for (V2 drew both as boxes), so\n they are starting sizes, not kinds of their own.\n*/\nexport type V3PaletteKind = V3PrimitiveCreation['kind'] | 'plate' | 'wall'\nconst RECIPES: Array<{ kind: V3PaletteKind; icon: string; label: string }> = [\n { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },\n { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },\n { kind: 'rounded-box', icon: 'crop_square', label: 'figure.label.primitive-rounded-box' },\n { kind: 'sphere', icon: 'circle', label: 'figure.label.primitive-sphere' },\n { kind: 'frustum', icon: 'change_history', label: 'figure.label.primitive-frustum' },\n { kind: 'polygon', icon: 'hexagon', label: 'figure.label.primitive-polygon' },\n { kind: 'plate', icon: 'check_box_outline_blank', label: 'figure.label.primitive-plate' },\n { kind: 'wall', icon: 'crop_portrait', label: 'figure.label.primitive-wall' },\n { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }\n]\n\n@customElement('figure-v3-parts')\nexport class FigureV3Parts extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n /* The divider beside it is the editor's frame's (ox-three-pane). */\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n /* Folds by its own width: the editor gives this column 140 px when it has two columns (UX, 2026-09-24). */\n container-type: inline-size;\n }\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section[list] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n h3 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 6px;\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n /* The cost line: the grade's letter in its colour, then what the figure costs. */\n button[budget] {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n width: 100%;\n margin: 0 0 6px;\n padding: 4px 6px;\n font: inherit;\n font-size: 0.68rem;\n line-height: 1.5;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[budget]:hover {\n background: var(--md-sys-color-surface-container);\n }\n button[budget] b {\n flex: none;\n min-width: 16px;\n text-align: center;\n border-radius: 4px;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n }\n button[budget][grade='C'] b {\n color: var(--md-sys-color-on-tertiary-container);\n background: var(--md-sys-color-tertiary-container);\n }\n button[budget][grade='D'] {\n border-color: var(--md-sys-color-error);\n }\n button[budget][grade='D'] b {\n color: var(--md-sys-color-on-error);\n background: var(--md-sys-color-error);\n }\n button[budget] em {\n display: block;\n font-style: normal;\n color: var(--md-sys-color-error);\n }\n span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n div[palette] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 4px;\n }\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding: 7px 2px 5px 2px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[shape]:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[shape][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n button[shape] md-icon {\n --md-icon-size: 20px;\n }\n ol {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n }\n li:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n li[selected] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n li md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[eye][all] {\n margin-left: auto;\n opacity: 0.75;\n }\n button[eye][all]:hover {\n opacity: 1;\n }\n button[eye] {\n display: flex;\n flex: none;\n padding: 1px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n }\n li:hover button[eye],\n li[selected] button[eye],\n button[eye][off] {\n opacity: 1;\n }\n button[eye][off] {\n color: var(--md-sys-color-tertiary);\n }\n button[eye] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n li[off] span[label],\n li[off] > md-icon,\n li[invisible] span[label],\n li[invisible] > md-icon {\n opacity: 0.45;\n }\n li[selected] md-icon {\n color: inherit;\n }\n span[label] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.78rem;\n }\n span[label] small {\n margin-left: 4px;\n font-size: 0.66rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[ops] {\n display: none;\n gap: 1px;\n }\n li[selected] div[ops] {\n display: flex;\n }\n div[ops] button {\n display: flex;\n padding: 2px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n div[ops] button:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[ops] button[disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[ops] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n /*\n Under 200 px the selected row's four buttons pushed its name out and the last one past the edge (seen at 140,\n 2026-09-24). They go to a line of their own under the name.\n */\n @container (max-width: 199px) {\n li[selected] {\n flex-wrap: wrap;\n }\n li[selected] div[ops] {\n flex-basis: 100%;\n justify-content: flex-end;\n }\n }\n p[quiet] {\n margin: 0;\n font-size: 0.76rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n @property({ type: Array }) parts: V3PartRow[] = []\n /** 고른 부품의 id. 자리(index)가 아니다 — V3 부품은 id 로 안정하게 가리킨다. */\n @property({ type: String }) selected = ''\n /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */\n @property({ type: Boolean }) locked = false\n /**\n * 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의\n * 기준이 저작을 이끈다(V2 모델러의 정체성). 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.\n */\n @property({ attribute: false }) budget?: V3BudgetLine\n\n render() {\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${RECIPES.map(\n recipe => html`\n <button\n shape\n ?disabled=${this.locked}\n title=${i18next.t(recipe.label)}\n @click=${() => this.ask('add-part', { kind: recipe.kind })}\n >\n <md-icon>${recipe.icon}</md-icon>\n <span>${i18next.t(recipe.label)}</span>\n </button>\n `\n )}\n </div>\n </section>\n\n <section list>\n <h3>\n <span>${i18next.t('figure.label.parts')}</span>\n <span room>${this.parts.length}</span>\n ${this.renderShowAll()}\n </h3>\n ${this.renderBudget()}\n ${this.parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`\n : html`<ol>\n ${this.parts.map((part, index) => this.renderRow(part, index))}\n </ol>`}\n </section>\n `\n }\n\n private renderBudget() {\n const b = this.budget\n if (!b || !this.parts.length) return nothing\n const over = b.grade === 'D'\n return html`<button\n budget\n grade=${b.grade}\n title=${i18next.t('figure.text.parts-budget-open')}\n @click=${() => this.dispatchEvent(new CustomEvent('show-maturity', { bubbles: true, composed: true }))}\n >\n <b>${b.grade}</b>\n <span>\n ${i18next.t('figure.text.parts-budget', {\n triangles: b.triangles.toLocaleString(),\n groups: b.groups.toLocaleString(),\n instances: b.instances.toLocaleString()\n })}\n ${over\n ? html`<em\n >${i18next.t('figure.text.parts-budget-over', {\n budget: i18next.t('figure.label.budget-' + b.tightest.key),\n used: b.tightest.used.toLocaleString(),\n limit: b.tightest.limit.toLocaleString()\n })}</em\n >`\n : nothing}\n </span>\n </button>`\n }\n\n /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */\n private renderShowAll() {\n if (this.parts.length === 0) return nothing\n const anyHidden = this.parts.some(part => part.hidden)\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('set-all-parts-hidden', { hidden: !anyHidden })\n }}\n >\n <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n `\n }\n\n private renderRow(part: V3PartRow, index: number) {\n const selected = part.id === this.selected\n const off = !!part.hidden\n return html`\n <li\n ?selected=${selected}\n ?off=${off}\n ?invisible=${!!part.invisible}\n @click=${() => this.ask('select-part', { id: part.id })}\n >\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('toggle-part-hidden', { id: part.id })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${PROVIDER_ICON[part.provider] ?? 'deployed_code'}</md-icon>\n <span label title=${part.id}\n >${partLabelOf(part.id)}${part.invisible ? html`<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}</span\n >\n ${selected ? this.renderOps(part.id, index) : nothing}\n </li>\n `\n }\n\n /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */\n private renderOps(id: string, index: number) {\n const count = this.parts.length\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled || this.locked}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled && !this.locked) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}\n </div>\n `\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-v3-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-v3-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { PART_LIMIT } from '@hatiolab/figure-model'\nimport type { V3PrimitiveCreation } from '@hatiolab/figure-model'\nimport type { V3BudgetLine } from './v3-maturity.js'\nimport { partLabelOf } from './v3-fields.js'\n\n/**\n * 도형 팔레트와 부품 목록 — V3 자산의 것.\n *\n * `figure-parts`(V2) 와 **같은 자리, 같은 조작, 같은 낱말**이다. 저작자가 V2 에서 익힌 손이 그대로\n * 가야 한다: 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.\n *\n * 다른 것은 읽는 대상뿐이다. V2 는 부품의 `primitive` 를 읽었고 여기서는 V3 평가가 준 `provider` 를\n * 읽는다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라\n * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다 — V2 와 같은 규칙, 같은 사건 이름.\n */\n\n/** 한 줄 — V3 평가에서 이 패널이 읽는 것만. */\nexport interface V3PartRow {\n id: string\n provider: string\n /** 작업대에서 잠깐 가린 것. 저장되지 않는다. */\n hidden?: boolean\n /** 자산이 지금 상태에서 보이지 않는다고 한 것. 저장되는 것이고, 여기서는 표시만 한다. */\n invisible?: boolean\n /** 되풀이한 부품이면 지금 몇 개로 그려지나. 되풀이는 한 부품이다 — 한 줄로 보인다. */\n copies?: number\n}\n\n/** 제공자마다의 아이콘. Material Symbols 이름이다. */\nconst PROVIDER_ICON: Record<string, string> = {\n box: 'deployed_code',\n cylinder: 'database',\n ellipticCylinder: 'database',\n frustum: 'change_history',\n sphere: 'circle',\n roundedBox: 'crop_square',\n roundedBoxXZ: 'crop_square',\n hollowBox: 'check_box_outline_blank',\n hollowBoxXZ: 'check_box_outline_blank',\n polygon: 'pentagon',\n portal: 'door_front'\n}\n\n/** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */\n/*\n The palette: V2's shapes. A plate and a wall are boxes of a shape a person reaches for (V2 drew both as boxes), so\n they are starting sizes, not kinds of their own.\n*/\nexport type V3PaletteKind = V3PrimitiveCreation['kind'] | 'plate' | 'wall'\nconst RECIPES: Array<{ kind: V3PaletteKind; icon: string; label: string }> = [\n { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },\n { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },\n { kind: 'rounded-box', icon: 'crop_square', label: 'figure.label.primitive-rounded-box' },\n { kind: 'sphere', icon: 'circle', label: 'figure.label.primitive-sphere' },\n { kind: 'frustum', icon: 'change_history', label: 'figure.label.primitive-frustum' },\n { kind: 'polygon', icon: 'hexagon', label: 'figure.label.primitive-polygon' },\n { kind: 'plate', icon: 'check_box_outline_blank', label: 'figure.label.primitive-plate' },\n { kind: 'wall', icon: 'crop_portrait', label: 'figure.label.primitive-wall' },\n { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }\n]\n\n@customElement('figure-v3-parts')\nexport class FigureV3Parts extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n /* The divider beside it is the editor's frame's (ox-three-pane). */\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n /* Folds by its own width: the editor gives this column 140 px when it has two columns (UX, 2026-09-24). */\n container-type: inline-size;\n }\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section[list] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n h3 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 6px;\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n /* The cost line: the grade's letter in its colour, then what the figure costs. */\n button[budget] {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n width: 100%;\n margin: 0 0 6px;\n padding: 4px 6px;\n font: inherit;\n font-size: 0.68rem;\n line-height: 1.5;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[budget]:hover {\n background: var(--md-sys-color-surface-container);\n }\n button[budget] b {\n flex: none;\n min-width: 16px;\n text-align: center;\n border-radius: 4px;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n }\n button[budget][grade='C'] b {\n color: var(--md-sys-color-on-tertiary-container);\n background: var(--md-sys-color-tertiary-container);\n }\n button[budget][grade='D'] {\n border-color: var(--md-sys-color-error);\n }\n button[budget][grade='D'] b {\n color: var(--md-sys-color-on-error);\n background: var(--md-sys-color-error);\n }\n button[budget] em {\n display: block;\n font-style: normal;\n color: var(--md-sys-color-error);\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n font-weight: 600;\n }\n p[full] {\n margin: 4px 0 0;\n font-size: 0.68rem;\n color: var(--md-sys-color-error);\n }\n div[level] {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: 6px;\n margin: 0 0 6px;\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[level] select {\n min-width: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n small[copies] {\n color: var(--md-sys-color-on-surface-variant);\n }\n span[room] {\n font-size: 0.7rem;\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n /* Three to a row, small (user, 2026-09-24): the palette is reached for, not read -- the list below is what grows. */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 3px;\n }\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n min-width: 0;\n padding: 5px 1px 4px;\n font: var(--label-font, inherit);\n font-size: 0.6rem;\n line-height: 1.2;\n text-align: center;\n overflow-wrap: anywhere;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[shape]:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[shape][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n button[shape] md-icon {\n --md-icon-size: 18px;\n }\n ol {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n }\n li:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n li[selected] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n li md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[eye][all] {\n margin-left: auto;\n opacity: 0.75;\n }\n button[eye][all]:hover {\n opacity: 1;\n }\n button[eye] {\n display: flex;\n flex: none;\n padding: 1px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n }\n li:hover button[eye],\n li[selected] button[eye],\n button[eye][off] {\n opacity: 1;\n }\n button[eye][off] {\n color: var(--md-sys-color-tertiary);\n }\n button[eye] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n li[off] span[label],\n li[off] > md-icon,\n li[invisible] span[label],\n li[invisible] > md-icon {\n opacity: 0.45;\n }\n li[selected] md-icon {\n color: inherit;\n }\n span[label] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.78rem;\n }\n span[label] small {\n margin-left: 4px;\n font-size: 0.66rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[ops] {\n display: none;\n gap: 1px;\n }\n li[selected] div[ops] {\n display: flex;\n }\n div[ops] button {\n display: flex;\n padding: 2px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n div[ops] button:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[ops] button[disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[ops] md-icon {\n --md-icon-size: 15px;\n color: inherit;\n }\n /*\n Under 200 px the selected row's four buttons pushed its name out and the last one past the edge (seen at 140,\n 2026-09-24). They go to a line of their own under the name.\n */\n @container (max-width: 199px) {\n li[selected] {\n flex-wrap: wrap;\n }\n li[selected] div[ops] {\n flex-basis: 100%;\n justify-content: flex-end;\n }\n }\n p[quiet] {\n margin: 0;\n font-size: 0.76rem;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n @property({ type: Array }) parts: V3PartRow[] = []\n /** 고른 부품의 id. 자리(index)가 아니다 — V3 부품은 id 로 안정하게 가리킨다. */\n @property({ type: String }) selected = ''\n /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */\n @property({ type: Boolean }) locked = false\n /**\n * 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의\n * 기준이 저작을 이끈다(V2 모델러의 정체성). 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.\n */\n @property({ attribute: false }) budget?: V3BudgetLine\n /**\n * 디테일 등급의 부품 한도 — V2 의 S/M/L(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가\n * 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.\n */\n @property({ attribute: false }) partBudget?: { level: 'S' | 'M' | 'L' | null; used: number; limit: number | null }\n\n render() {\n const b = this.partBudget\n const full = !!b && b.limit !== null && b.used >= b.limit\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${RECIPES.map(\n recipe => html`\n <button\n shape\n ?disabled=${this.locked || full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t(recipe.label)}\n @click=${() => this.ask('add-part', { kind: recipe.kind })}\n >\n <md-icon>${recipe.icon}</md-icon>\n <span>${i18next.t(recipe.label)}</span>\n </button>\n `\n )}\n </div>\n ${full ? html`<p full>${i18next.t('figure.text.part-limit-reached-short', { level: b!.level, limit: b!.limit })}</p>` : nothing}\n </section>\n\n <section list>\n <h3>\n <span>${i18next.t('figure.label.parts')}</span>\n <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}\n >${b && b.limit !== null ? i18next.t('figure.text.n-of-limit', { used: b.used, limit: b.limit }) : (b?.used ?? this.parts.length)}</span\n >\n ${this.renderShowAll()}\n </h3>\n ${this.renderLevel()}\n ${this.renderBudget()}\n ${this.parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`\n : html`<ol>\n ${this.parts.map((part, index) => this.renderRow(part, index))}\n </ol>`}\n </section>\n `\n }\n\n /** 디테일 등급 — 고르면 한도가 바뀐다. V2 의 자리 그대로, 목록 머리 아래. */\n private renderLevel() {\n const b = this.partBudget\n if (!b) return nothing\n return html`<div level>\n <label for=\"detail-level\">${i18next.t('figure.label.detail-level')}</label>\n <select\n id=\"detail-level\"\n title=${i18next.t('figure.text.detail-level-explained')}\n ?disabled=${this.locked}\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.dispatchEvent(new CustomEvent('set-detail-level', { detail: { level: value || null }, bubbles: true, composed: true }))\n }}\n >\n <option value=\"\" ?selected=${!b.level}>${i18next.t('figure.label.detail-none')}</option>\n ${(['S', 'M', 'L'] as const).map(\n one => html`<option value=${one} ?selected=${one === b.level}>\n ${one} ${i18next.t('figure.label.detail-' + one)} · ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>`\n )}\n </select>\n </div>`\n }\n\n private renderBudget() {\n const b = this.budget\n if (!b || !this.parts.length) return nothing\n const over = b.grade === 'D'\n return html`<button\n budget\n grade=${b.grade}\n title=${i18next.t('figure.text.parts-budget-open')}\n @click=${() => this.dispatchEvent(new CustomEvent('show-maturity', { bubbles: true, composed: true }))}\n >\n <b>${b.grade}</b>\n <span>\n ${i18next.t('figure.text.parts-budget', {\n triangles: b.triangles.toLocaleString(),\n groups: b.groups.toLocaleString(),\n instances: b.instances.toLocaleString()\n })}\n ${over\n ? html`<em\n >${i18next.t('figure.text.parts-budget-over', {\n budget: i18next.t('figure.label.budget-' + b.tightest.key),\n used: b.tightest.used.toLocaleString(),\n limit: b.tightest.limit.toLocaleString()\n })}</em\n >`\n : nothing}\n </span>\n </button>`\n }\n\n /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */\n private renderShowAll() {\n if (this.parts.length === 0) return nothing\n const anyHidden = this.parts.some(part => part.hidden)\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('set-all-parts-hidden', { hidden: !anyHidden })\n }}\n >\n <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n `\n }\n\n private renderRow(part: V3PartRow, index: number) {\n const selected = part.id === this.selected\n const off = !!part.hidden\n return html`\n <li\n ?selected=${selected}\n ?off=${off}\n ?invisible=${!!part.invisible}\n @click=${() => this.ask('select-part', { id: part.id })}\n >\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('toggle-part-hidden', { id: part.id })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${PROVIDER_ICON[part.provider] ?? 'deployed_code'}</md-icon>\n <span label title=${part.id}\n >${partLabelOf(part.id)}${part.copies ? html` <small copies>× ${part.copies}</small>` : nothing}${part.invisible ? html`<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}</span\n >\n ${selected ? this.renderOps(part.id, index) : nothing}\n </li>\n `\n }\n\n /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */\n private renderOps(id: string, index: number) {\n const count = this.parts.length\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled || this.locked}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled && !this.locked) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}\n </div>\n `\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -87,6 +87,14 @@ export const SidePanelStyles = css `
|
|
|
87
87
|
overflow-wrap: anywhere;
|
|
88
88
|
white-space: normal;
|
|
89
89
|
}
|
|
90
|
+
/* A value read, not edited, beside its label: the field's size and colour, not the page's. */
|
|
91
|
+
div[inline-field] > span[value] {
|
|
92
|
+
font-size: 0.76rem;
|
|
93
|
+
color: var(--md-sys-color-on-surface);
|
|
94
|
+
}
|
|
95
|
+
label span[unit] {
|
|
96
|
+
color: var(--md-sys-color-outline);
|
|
97
|
+
}
|
|
90
98
|
div[inline-field] + div[inline-field],
|
|
91
99
|
div[inline-field] + div[vector-row],
|
|
92
100
|
div[vector-row] + div[inline-field] {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"side-panel-styles.js","sourceRoot":"","sources":["../../client/modeller/side-panel-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"side-panel-styles.js","sourceRoot":"","sources":["../../client/modeller/side-panel-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqTjC,CAAA","sourcesContent":["import { css } from 'lit'\n\n/**\n * The side panel's one look -- V2's inspector, for every element that draws in the V3 modeller's side.\n *\n * The side is drawn by several elements (the type field, the figure's properties, a part's values, fastening, places,\n * motion, material, controls, clips, the environment) and each had styled its own fields: three label styles and\n * three field heights in one column (user, 2026-09-24: \"why are the styles and layout all different?\"). They share\n * this sheet now and keep only what is theirs.\n *\n * The vocabulary, V2's:\n * section one titled group; the side is a stack of them\n * h3 a group's title -- small capitals\n * div[inline-field] a short label and its one field on one line\n * div[field] a label above a full-width field\n * div[form] short labels beside their fields, the fields starting on one line\n * div[vector-row] a title, then X · Y · Z in one row of cells with the axis inside (`div[triple]` of\n * `div[cell]`) -- three values read together, as vector tools lay them out\n * div[pair] two controls side by side\n * label[toggle] a checkbox and its words\n * p[hint] · small the quiet line under a field\n * button[act] an action under the fields it acts on\n * [scrub] a label dragged sideways to scrub its field\n */\nexport const SidePanelStyles = css`\n :host {\n --figure-control-height: 30px;\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n }\n /* The side is a stack of sections, each a titled group (V2's inspector). */\n section {\n display: grid;\n gap: 6px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section > h3 {\n margin: 0;\n }\n section div[field] + div[field],\n section div[inline-field] + div[inline-field],\n section div[vector-row] + div[vector-row] {\n margin-top: 0;\n }\n h3 {\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 14px 0 6px;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant);\n }\n h3:first-child {\n margin-top: 0;\n }\n label,\n div[form] > span {\n font-size: 0.68rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[field] {\n display: grid;\n gap: 2px;\n }\n div[field] + div[field],\n div[field] + div[form],\n div[form] + div[field],\n div[vector-row] + div[vector-row],\n div[vector-row] + div[field],\n div[field] + div[vector-row] {\n margin-top: 5px;\n }\n div[form],\n div[inline-field],\n div[vector-row] {\n display: grid;\n grid-template-columns: 64px minmax(0, 1fr);\n align-items: center;\n gap: 5px 7px;\n }\n div[form] > span,\n div[inline-field] > label,\n div[vector-row] > label {\n overflow-wrap: anywhere;\n white-space: normal;\n }\n /* A value read, not edited, beside its label: the field's size and colour, not the page's. */\n div[inline-field] > span[value] {\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n }\n label span[unit] {\n color: var(--md-sys-color-outline);\n }\n div[inline-field] + div[inline-field],\n div[inline-field] + div[vector-row],\n div[vector-row] + div[inline-field] {\n margin-top: 5px;\n }\n /* A row of icon tools (align, mirror, snap), as V2's placement section had them. */\n div[tools] {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n div[tools] button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 28px;\n padding: 0;\n color: var(--md-sys-color-on-surface);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n div[tools] button:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container);\n }\n div[tools] button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n div[tools] md-icon {\n font-size: 17px;\n width: 17px;\n height: 17px;\n }\n div[pair] {\n display: flex;\n gap: 4px;\n }\n div[pair] > * {\n flex: 1;\n min-width: 0;\n }\n div[triple] {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 4px;\n }\n input,\n select {\n width: 100%;\n min-width: 0;\n min-height: var(--figure-control-height);\n box-sizing: border-box;\n padding: 4px 7px;\n font: inherit;\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input[type='checkbox'],\n input[type='radio'] {\n width: auto;\n min-height: 0;\n }\n /* A thin track and a small thumb: a slider is a line to drag along, not a block (user, 2026-09-24). */\n input[type='range'] {\n -webkit-appearance: none;\n appearance: none;\n min-height: 16px;\n height: 16px;\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n }\n input[type='range']::-webkit-slider-runnable-track {\n height: 3px;\n border-radius: 2px;\n background: var(--md-sys-color-outline-variant);\n }\n input[type='range']::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 12px;\n height: 12px;\n margin-top: -4.5px;\n border: none;\n border-radius: 50%;\n background: var(--md-sys-color-primary);\n }\n input[type='range']::-moz-range-track {\n height: 3px;\n border-radius: 2px;\n background: var(--md-sys-color-outline-variant);\n }\n input[type='range']::-moz-range-thumb {\n width: 12px;\n height: 12px;\n border: none;\n border-radius: 50%;\n background: var(--md-sys-color-primary);\n }\n input[type='range']:focus {\n outline: none;\n }\n input[type='range']:focus-visible::-webkit-slider-thumb {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n input[type='color'] {\n padding: 2px;\n width: 40px;\n }\n input:focus,\n select:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n input:disabled,\n select:disabled {\n opacity: 0.6;\n }\n /* A number with its axis inside the cell, on the left. */\n div[cell] {\n display: flex;\n box-sizing: border-box;\n height: var(--figure-control-height);\n align-items: center;\n gap: 2px;\n padding-left: 6px;\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n div[cell]:focus-within {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n border-color: transparent;\n }\n /* A label that scrubs its field when dragged sideways (vector tools' way). */\n [scrub] {\n cursor: ew-resize;\n user-select: none;\n touch-action: none;\n }\n div[cell] span[axis] {\n flex: none;\n font-size: 0.64rem;\n letter-spacing: 0.02em;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[cell] input {\n min-height: 0;\n height: calc(var(--figure-control-height) - 2px);\n padding: 4px 6px 4px 2px;\n font-family: var(--mono-font, monospace);\n text-align: right;\n background: none;\n border: none;\n border-radius: 0;\n }\n div[cell] input:focus {\n outline: none;\n }\n label[toggle] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.76rem;\n color: var(--md-sys-color-on-surface);\n }\n /* The 「?」 beside a title: no border, it says there is more rather than asking to be pressed (V2). */\n button[help] {\n display: inline-flex;\n align-items: center;\n padding: 0;\n margin: 0;\n color: var(--md-sys-color-outline);\n background: none;\n border: none;\n cursor: pointer;\n }\n button[help] md-icon {\n font-size: 13px;\n width: 13px;\n height: 13px;\n }\n button[help]:hover,\n button[help][aria-expanded='true'] {\n color: var(--md-sys-color-primary);\n }\n p[hint],\n small {\n display: block;\n margin: 2px 0 4px;\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n p[hint] {\n padding-left: 6px;\n border-left: 2px solid var(--md-sys-color-outline-variant);\n }\n button[act] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n width: 100%;\n margin-top: 4px;\n padding: 3px 8px;\n min-height: 28px;\n font: inherit;\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[act]:hover:not(:disabled) {\n background-color: var(--md-sys-color-surface-container-high);\n }\n button[act]:disabled {\n opacity: 0.5;\n cursor: default;\n }\n button[act] md-icon {\n font-size: 15px;\n width: 15px;\n height: 15px;\n }\n`\n"]}
|
|
@@ -48,6 +48,8 @@ export declare class FigureV3Canvas extends LitElement {
|
|
|
48
48
|
private components;
|
|
49
49
|
/** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */
|
|
50
50
|
private standing;
|
|
51
|
+
/** A larger board under the same scene: its size, and the grid that suits it. Components are re-placed by `sync`. */
|
|
52
|
+
private grow;
|
|
51
53
|
/** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */
|
|
52
54
|
private sync;
|
|
53
55
|
/** Selection and drags, told out by the part's name -- never by its place in the list, which may change meanwhile. */
|
|
@@ -6,6 +6,7 @@ import { create, Model, niceGridStep } from '@hatiolab/things-scene';
|
|
|
6
6
|
import { DEFAULT_VIEW, sceneLook, WORKBENCH } from './figure-view.js';
|
|
7
7
|
import { boardFor, changedKeys, componentOf, gizmoResultOf } from './v3-stage-model.js';
|
|
8
8
|
import { JOINT_FAN } from './v3-joint-fan.js';
|
|
9
|
+
import { ownerOf } from './v3-fields.js';
|
|
9
10
|
/* The fan's colour when the figure's palette names no accent. */
|
|
10
11
|
const FAN_COLOUR = '#ff8a00';
|
|
11
12
|
/* The 2D side of the board, which the author never sees on this page but the scene paints. */
|
|
@@ -114,7 +115,13 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
114
115
|
return;
|
|
115
116
|
const needed = boardFor(this.parts, this.board);
|
|
116
117
|
const outgrown = !this.board || needed.width > this.board.width || needed.height > this.board.height || needed.depth > this.board.depth;
|
|
117
|
-
if (this.scene && this.standingFor === this.context
|
|
118
|
+
if (this.scene && this.standingFor === this.context) {
|
|
119
|
+
/*
|
|
120
|
+
A part dragged past the board's edge grows the board where it stands. Standing it up again lost the selection,
|
|
121
|
+
the gizmo and the camera after every such drag -- the gizmo seemed not to work at all (user, 2026-09-24).
|
|
122
|
+
*/
|
|
123
|
+
if (outgrown)
|
|
124
|
+
this.grow(needed);
|
|
118
125
|
this.sync();
|
|
119
126
|
return;
|
|
120
127
|
}
|
|
@@ -168,6 +175,20 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
168
175
|
}
|
|
169
176
|
return found;
|
|
170
177
|
}
|
|
178
|
+
/** A larger board under the same scene: its size, and the grid that suits it. Components are re-placed by `sync`. */
|
|
179
|
+
grow(needed) {
|
|
180
|
+
const layer = this.scene?.root;
|
|
181
|
+
this.board = needed;
|
|
182
|
+
if (!layer)
|
|
183
|
+
return;
|
|
184
|
+
this.writing = true;
|
|
185
|
+
try {
|
|
186
|
+
layer.set({ width: needed.width, height: needed.height, depth: needed.depth, gridStep: niceGridStep(Math.max(needed.width, needed.height)) });
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
this.writing = false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
171
192
|
/** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */
|
|
172
193
|
sync() {
|
|
173
194
|
const layer = this.scene?.root;
|
|
@@ -210,7 +231,7 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
210
231
|
root.on('selected', (after) => {
|
|
211
232
|
const name = after?.[0]?.state?.name;
|
|
212
233
|
const id = typeof name === 'string' ? name : '';
|
|
213
|
-
if (id !== this.selected)
|
|
234
|
+
if (ownerOf(id) !== this.selected)
|
|
214
235
|
this.dispatchEvent(new CustomEvent('part-selected', { detail: id, bubbles: true, composed: true }));
|
|
215
236
|
});
|
|
216
237
|
/*
|
|
@@ -238,11 +259,12 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
238
259
|
const root = layer?.root;
|
|
239
260
|
if (!layer || !root)
|
|
240
261
|
return;
|
|
241
|
-
|
|
262
|
+
/* A repeat is selected whole: every copy of it (the editor selects the part the author made). */
|
|
263
|
+
const wanted = this.selected ? [...this.standing()].filter(([name]) => ownerOf(name) === this.selected).map(([, component]) => component) : [];
|
|
242
264
|
const now = root.selected ?? [];
|
|
243
|
-
if (
|
|
265
|
+
if (now.length === wanted.length && wanted.every((one, i) => now[i] === one))
|
|
244
266
|
return;
|
|
245
|
-
root.selected =
|
|
267
|
+
root.selected = wanted;
|
|
246
268
|
layer.invalidate?.();
|
|
247
269
|
}
|
|
248
270
|
/** View settings go into the scene as they are: they are the board's own state names. Nothing is stood up again. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3-canvas.js","sourceRoot":"","sources":["../../client/modeller/v3-canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport { create, Model, niceGridStep } from '@hatiolab/things-scene'\n\nimport type { GizmoReach } from './v3-gizmo.js'\nimport type { V3RenderPart } from './v3-renderer.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from './figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, type Board } from './v3-stage-model.js'\nimport { JOINT_FAN } from './v3-joint-fan.js'\n\n/*\n * The V3 modeller's canvas -- a things-scene board in editing mode, as the V2 modeller's was.\n *\n * Each body figure-model evaluates stands on the board as a `figure-v3-part` component, so everything the V2 canvas\n * had comes from the scene, not from code of our own: picking and the orange outline, the gizmo with its snapping and\n * live readout, box and shift selection, the camera (damping, zoom toward the cursor, double-click to fit, the six\n * view presets and their keys), the studio environment, the coordinate ground, lights and shadows, supersampling.\n * An earlier canvas drew the bodies with three.js directly and lost all of that (user, 2026-09-24: \"why nerf it?\").\n *\n * What the gizmo writes back to a component comes out as `part-transformed` in the inputs' terms (`v3-stage-model`),\n * and the editor turns it into one step of the history; the next evaluation puts the component where the inputs say,\n * so a change a part cannot take (a fastened part moved) springs back.\n */\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown>) => void\n dispose?: () => void\n}\ninterface SceneLayer extends SceneComponent {\n components?: SceneComponent[]\n root?: { on?: (event: string, handler: (...args: unknown[]) => void) => void; selected?: SceneComponent[] }\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n addComponent?: (component: SceneComponent) => void\n removeComponent?: (component: SceneComponent) => void\n invalidate?: () => void\n fitCameraToModel?: () => void\n app?: unknown\n}\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: SceneLayer\n}\n\n/* The fan's colour when the figure's palette names no accent. */\nconst FAN_COLOUR = '#ff8a00'\n\n/* The 2D side of the board, which the author never sees on this page but the scene paints. */\nconst PAPER = '#ffffff'\n\n@customElement('figure-v3-canvas')\nexport class FigureV3Canvas extends LitElement {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n min-height: 0;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n /* The scene sets position: relative inline on its target; the size is given so it still fills the host. */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n p {\n position: absolute;\n inset: 45% 1rem auto;\n margin: 0;\n text-align: center;\n pointer-events: none;\n color: #f4f4f4;\n }\n `\n @property({ attribute: false }) parts: V3RenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n /** Which figure is open. Another one stands the board up again. */\n @property() context = ''\n @property() selected = ''\n /** What the gizmo may write for the selected part; a change beyond it is not applied and the part springs back. */\n @property({ attribute: false }) reach: GizmoReach = { translate: false, rotate: false, scale: false }\n @property({ attribute: false }) view: ViewSettings = DEFAULT_VIEW\n /**\n * View only: nothing that happens on the board is told out. The card's picture is taken from a second canvas stood up\n * off screen (`v3-thumbnail.ts`); the scene settles its components as it builds them, and a change told from there\n * would reach the editor as an edit (V2's `viewOnly`, for the same reason).\n */\n @property({ type: Boolean }) viewOnly = false\n /** Where the selected part turns and how far (`v3-joint-fan`): shown, never picked, never saved. */\n @property({ attribute: false }) fan: V3RenderPart | null = null\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n private scene?: SceneHandle\n private board?: Board & { depth: number }\n private standingFor?: string\n private watcher?: ResizeObserver\n /** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */\n private writing = false\n\n render() {\n return html`<div stage></div>${this.parts.length ? nothing : html`<p>${i18next.t('figure.text.add-a-shape-to-start')}</p>`}`\n }\n\n firstUpdated() {\n this.standUp()\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n protected updated(changed: PropertyValues) {\n if (!this.hasUpdated) return\n if (changed.has('parts') || changed.has('palette') || changed.has('context') || changed.has('fan')) this.standUp()\n if (changed.has('view')) this.applyView()\n if (changed.has('selected')) this.showSelected()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * The board as a picture, for the card: the 3D view at this size, or nothing when it cannot be taken. The scene's\n * `toDataURL` renders the 2D board (a white sheet, V2 found), and the 3D renderer keeps no drawing buffer, so the\n * scene's own `snapshot3d` draws and reads in one task. A picture that cannot be taken is not an error and is not\n * faked (V2's `snapshot`, lost when this canvas moved onto the scene -- no card had a picture since, user 2026-09-24).\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n const layer = this.scene?.root as (SceneLayer & { snapshot3d?: (w?: number, h?: number, type?: string, quality?: number) => Promise<string | undefined> }) | undefined\n if (!layer?.snapshot3d) return undefined\n try {\n /* webp: a card needs no alpha, and png is three or four times heavier in every list response. */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n console.warn(`[thumbnail] could not take the picture -- ${(e as Error).message}`)\n return undefined\n }\n }\n\n /** The whole figure in view: the board's fit, then the 3D camera's own, as V2 did. */\n fit() {\n this.scene?.fit?.('ratio')\n this.scene?.root?.fitCameraToModel?.()\n }\n\n /**\n * Stand the board up for this figure, or bring the one standing up to date. A new board only for another figure or\n * one that has outgrown it -- standing up again loses the selection and the camera.\n */\n private standUp() {\n if (!this.stage) return\n const needed = boardFor(this.parts, this.board)\n const outgrown = !this.board || needed.width > this.board.width || needed.height > this.board.height || needed.depth > this.board.depth\n if (this.scene && this.standingFor === this.context && !outgrown) {\n this.sync()\n return\n }\n this.teardown()\n if (!this.parts.length) return\n this.board = needed\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: needed.width,\n height: needed.height,\n depth: needed.depth,\n threed: true,\n /* Two samples a pixel: MSAA alone leaves steps on the silhouettes of one asset shown large (V2). */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n ...sceneLook(this.view ?? DEFAULT_VIEW),\n gridStep: niceGridStep(Math.max(needed.width, needed.height)),\n sky: 'color',\n fillStyle: PAPER,\n components: this.components()\n },\n /* Editing mode: the camera view buttons, gizmo modes, help and keys V2's canvas had. */\n mode: 1\n }) as SceneHandle\n this.standingFor = this.context\n this.watchScene()\n this.scene.resize?.()\n this.fit()\n this.showSelected()\n } catch (error) {\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n\n private components(): Record<string, unknown>[] {\n const parts = this.parts.map(part => componentOf(part, this.palette, this.board!)).filter((c): c is Record<string, unknown> => !!c)\n /* The fan in the accent colour, translucent; `capturable: false` keeps clicks, the marquee and the gizmo off it. */\n const fan = this.fan ? componentOf(this.fan, { ...this.palette, [JOINT_FAN]: this.palette['palette.accent'] ?? FAN_COLOUR }, this.board!) : null\n return fan ? [...parts, { ...fan, capturable: false, locked: true }] : parts\n }\n\n /** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */\n private standing(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */\n private sync() {\n const layer = this.scene?.root\n if (!layer || !this.board) return\n const wanted = new Map(this.components().map(c => [c.name as string, c]))\n const standing = this.standing()\n this.writing = true\n try {\n for (const [name, component] of standing) {\n if (wanted.has(name)) continue\n layer.removeComponent?.(component)\n component.dispose?.()\n }\n for (const [name, state] of wanted) {\n const found = standing.get(name)\n if (found) {\n const diff = changedKeys(found.state, state)\n if (diff) found.set(diff)\n continue\n }\n const made = Model.compile({ ...state }, layer.app as never) as SceneComponent | null\n if (made) layer.addComponent?.(made)\n }\n } finally {\n this.writing = false\n }\n layer.invalidate?.()\n }\n\n /** Selection and drags, told out by the part's name -- never by its place in the list, which may change meanwhile. */\n private watchScene() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root?.on || this.viewOnly) return\n root.on('selected', (after: unknown) => {\n const name = (after as SceneComponent[] | undefined)?.[0]?.state?.name\n const id = typeof name === 'string' ? name : ''\n if (id !== this.selected) this.dispatchEvent(new CustomEvent('part-selected', { detail: id, bubbles: true, composed: true }))\n })\n /*\n A change comes up to the board with the component in the last argument's `origin` (the scene appends\n { origin, name, deliverer } as it bubbles). Reading the first argument as the component does nothing (V2).\n */\n layer.on?.('change', (...args: unknown[]) => {\n if (this.writing) return\n const origin = (args[args.length - 1] as { origin?: SceneComponent } | undefined)?.origin\n const name = origin?.state?.name\n const part = typeof name === 'string' ? this.parts.find(p => p.id === name) : undefined\n if (!part || !this.board) return\n const result = gizmoResultOf(part, origin!.state, this.board)\n if (!result.position && !result.rotation && !result.scale) return\n this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: part.id, result }, bubbles: true, composed: true }))\n /* After the editor has applied (or refused) it: a refused change springs back to where the inputs put it. */\n setTimeout(() => this.sync())\n })\n }\n\n private showSelected() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root) return\n const component = this.selected ? this.standing().get(this.selected) : undefined\n const now = root.selected ?? []\n if ((now[0] ?? undefined) === component && now.length === (component ? 1 : 0)) return\n root.selected = component ? [component] : []\n layer.invalidate?.()\n }\n\n /** View settings go into the scene as they are: they are the board's own state names. Nothing is stood up again. */\n private applyView() {\n const layer = this.scene?.root\n if (!layer) return\n layer.set(sceneLook(this.view ?? DEFAULT_VIEW))\n layer.invalidate?.()\n }\n\n private teardown() {\n this.scene?.dispose?.()\n this.scene = undefined\n this.standingFor = undefined\n this.board = undefined\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"v3-canvas.js","sourceRoot":"","sources":["../../client/modeller/v3-canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing, unsafeCSS, type PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport { create, Model, niceGridStep } from '@hatiolab/things-scene'\n\nimport type { GizmoReach } from './v3-gizmo.js'\nimport type { V3RenderPart } from './v3-renderer.js'\nimport { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from './figure-view.js'\nimport { boardFor, changedKeys, componentOf, gizmoResultOf, type Board } from './v3-stage-model.js'\nimport { JOINT_FAN } from './v3-joint-fan.js'\nimport { ownerOf } from './v3-fields.js'\n\n/*\n * The V3 modeller's canvas -- a things-scene board in editing mode, as the V2 modeller's was.\n *\n * Each body figure-model evaluates stands on the board as a `figure-v3-part` component, so everything the V2 canvas\n * had comes from the scene, not from code of our own: picking and the orange outline, the gizmo with its snapping and\n * live readout, box and shift selection, the camera (damping, zoom toward the cursor, double-click to fit, the six\n * view presets and their keys), the studio environment, the coordinate ground, lights and shadows, supersampling.\n * An earlier canvas drew the bodies with three.js directly and lost all of that (user, 2026-09-24: \"why nerf it?\").\n *\n * What the gizmo writes back to a component comes out as `part-transformed` in the inputs' terms (`v3-stage-model`),\n * and the editor turns it into one step of the history; the next evaluation puts the component where the inputs say,\n * so a change a part cannot take (a fastened part moved) springs back.\n */\n\ninterface SceneComponent {\n state: Record<string, unknown>\n set: (props: Record<string, unknown>) => void\n dispose?: () => void\n}\ninterface SceneLayer extends SceneComponent {\n components?: SceneComponent[]\n root?: { on?: (event: string, handler: (...args: unknown[]) => void) => void; selected?: SceneComponent[] }\n on?: (event: string, handler: (...args: unknown[]) => void) => void\n addComponent?: (component: SceneComponent) => void\n removeComponent?: (component: SceneComponent) => void\n invalidate?: () => void\n fitCameraToModel?: () => void\n app?: unknown\n}\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: SceneLayer\n}\n\n/* The fan's colour when the figure's palette names no accent. */\nconst FAN_COLOUR = '#ff8a00'\n\n/* The 2D side of the board, which the author never sees on this page but the scene paints. */\nconst PAPER = '#ffffff'\n\n@customElement('figure-v3-canvas')\nexport class FigureV3Canvas extends LitElement {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n min-height: 0;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n /* The scene sets position: relative inline on its target; the size is given so it still fills the host. */\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n p {\n position: absolute;\n inset: 45% 1rem auto;\n margin: 0;\n text-align: center;\n pointer-events: none;\n color: #f4f4f4;\n }\n `\n @property({ attribute: false }) parts: V3RenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n /** Which figure is open. Another one stands the board up again. */\n @property() context = ''\n @property() selected = ''\n /** What the gizmo may write for the selected part; a change beyond it is not applied and the part springs back. */\n @property({ attribute: false }) reach: GizmoReach = { translate: false, rotate: false, scale: false }\n @property({ attribute: false }) view: ViewSettings = DEFAULT_VIEW\n /**\n * View only: nothing that happens on the board is told out. The card's picture is taken from a second canvas stood up\n * off screen (`v3-thumbnail.ts`); the scene settles its components as it builds them, and a change told from there\n * would reach the editor as an edit (V2's `viewOnly`, for the same reason).\n */\n @property({ type: Boolean }) viewOnly = false\n /** Where the selected part turns and how far (`v3-joint-fan`): shown, never picked, never saved. */\n @property({ attribute: false }) fan: V3RenderPart | null = null\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n private scene?: SceneHandle\n private board?: Board & { depth: number }\n private standingFor?: string\n private watcher?: ResizeObserver\n /** Set while this canvas writes to components, so the scene's echo of it is not taken for a drag. */\n private writing = false\n\n render() {\n return html`<div stage></div>${this.parts.length ? nothing : html`<p>${i18next.t('figure.text.add-a-shape-to-start')}</p>`}`\n }\n\n firstUpdated() {\n this.standUp()\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n protected updated(changed: PropertyValues) {\n if (!this.hasUpdated) return\n if (changed.has('parts') || changed.has('palette') || changed.has('context') || changed.has('fan')) this.standUp()\n if (changed.has('view')) this.applyView()\n if (changed.has('selected')) this.showSelected()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * The board as a picture, for the card: the 3D view at this size, or nothing when it cannot be taken. The scene's\n * `toDataURL` renders the 2D board (a white sheet, V2 found), and the 3D renderer keeps no drawing buffer, so the\n * scene's own `snapshot3d` draws and reads in one task. A picture that cannot be taken is not an error and is not\n * faked (V2's `snapshot`, lost when this canvas moved onto the scene -- no card had a picture since, user 2026-09-24).\n */\n async snapshot(width = 480, height = 360): Promise<string | undefined> {\n const layer = this.scene?.root as (SceneLayer & { snapshot3d?: (w?: number, h?: number, type?: string, quality?: number) => Promise<string | undefined> }) | undefined\n if (!layer?.snapshot3d) return undefined\n try {\n /* webp: a card needs no alpha, and png is three or four times heavier in every list response. */\n const url = await layer.snapshot3d(width, height, 'image/webp', 0.85)\n return typeof url === 'string' && url.startsWith('data:') ? url : undefined\n } catch (e) {\n console.warn(`[thumbnail] could not take the picture -- ${(e as Error).message}`)\n return undefined\n }\n }\n\n /** The whole figure in view: the board's fit, then the 3D camera's own, as V2 did. */\n fit() {\n this.scene?.fit?.('ratio')\n this.scene?.root?.fitCameraToModel?.()\n }\n\n /**\n * Stand the board up for this figure, or bring the one standing up to date. A new board only for another figure or\n * one that has outgrown it -- standing up again loses the selection and the camera.\n */\n private standUp() {\n if (!this.stage) return\n const needed = boardFor(this.parts, this.board)\n const outgrown = !this.board || needed.width > this.board.width || needed.height > this.board.height || needed.depth > this.board.depth\n if (this.scene && this.standingFor === this.context) {\n /*\n A part dragged past the board's edge grows the board where it stands. Standing it up again lost the selection,\n the gizmo and the camera after every such drag -- the gizmo seemed not to work at all (user, 2026-09-24).\n */\n if (outgrown) this.grow(needed)\n this.sync()\n return\n }\n this.teardown()\n if (!this.parts.length) return\n this.board = needed\n try {\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: needed.width,\n height: needed.height,\n depth: needed.depth,\n threed: true,\n /* Two samples a pixel: MSAA alone leaves steps on the silhouettes of one asset shown large (V2). */\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n ...sceneLook(this.view ?? DEFAULT_VIEW),\n gridStep: niceGridStep(Math.max(needed.width, needed.height)),\n sky: 'color',\n fillStyle: PAPER,\n components: this.components()\n },\n /* Editing mode: the camera view buttons, gizmo modes, help and keys V2's canvas had. */\n mode: 1\n }) as SceneHandle\n this.standingFor = this.context\n this.watchScene()\n this.scene.resize?.()\n this.fit()\n this.showSelected()\n } catch (error) {\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n\n private components(): Record<string, unknown>[] {\n const parts = this.parts.map(part => componentOf(part, this.palette, this.board!)).filter((c): c is Record<string, unknown> => !!c)\n /* The fan in the accent colour, translucent; `capturable: false` keeps clicks, the marquee and the gizmo off it. */\n const fan = this.fan ? componentOf(this.fan, { ...this.palette, [JOINT_FAN]: this.palette['palette.accent'] ?? FAN_COLOUR }, this.board!) : null\n return fan ? [...parts, { ...fan, capturable: false, locked: true }] : parts\n }\n\n /** The components standing now, by name. Not held between calls: the scene stands its board up again at times. */\n private standing(): Map<string, SceneComponent> {\n const found = new Map<string, SceneComponent>()\n for (const component of this.scene?.root?.components ?? []) {\n const name = component.state?.name\n if (typeof name === 'string') found.set(name, component)\n }\n return found\n }\n\n /** A larger board under the same scene: its size, and the grid that suits it. Components are re-placed by `sync`. */\n private grow(needed: Board & { depth: number }) {\n const layer = this.scene?.root\n this.board = needed\n if (!layer) return\n this.writing = true\n try {\n layer.set({ width: needed.width, height: needed.height, depth: needed.depth, gridStep: niceGridStep(Math.max(needed.width, needed.height)) })\n } finally {\n this.writing = false\n }\n }\n\n /** Bring the components to the evaluation: remove the gone, write only what differs, add the new. */\n private sync() {\n const layer = this.scene?.root\n if (!layer || !this.board) return\n const wanted = new Map(this.components().map(c => [c.name as string, c]))\n const standing = this.standing()\n this.writing = true\n try {\n for (const [name, component] of standing) {\n if (wanted.has(name)) continue\n layer.removeComponent?.(component)\n component.dispose?.()\n }\n for (const [name, state] of wanted) {\n const found = standing.get(name)\n if (found) {\n const diff = changedKeys(found.state, state)\n if (diff) found.set(diff)\n continue\n }\n const made = Model.compile({ ...state }, layer.app as never) as SceneComponent | null\n if (made) layer.addComponent?.(made)\n }\n } finally {\n this.writing = false\n }\n layer.invalidate?.()\n }\n\n /** Selection and drags, told out by the part's name -- never by its place in the list, which may change meanwhile. */\n private watchScene() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root?.on || this.viewOnly) return\n root.on('selected', (after: unknown) => {\n const name = (after as SceneComponent[] | undefined)?.[0]?.state?.name\n const id = typeof name === 'string' ? name : ''\n if (ownerOf(id) !== this.selected) this.dispatchEvent(new CustomEvent('part-selected', { detail: id, bubbles: true, composed: true }))\n })\n /*\n A change comes up to the board with the component in the last argument's `origin` (the scene appends\n { origin, name, deliverer } as it bubbles). Reading the first argument as the component does nothing (V2).\n */\n layer.on?.('change', (...args: unknown[]) => {\n if (this.writing) return\n const origin = (args[args.length - 1] as { origin?: SceneComponent } | undefined)?.origin\n const name = origin?.state?.name\n const part = typeof name === 'string' ? this.parts.find(p => p.id === name) : undefined\n if (!part || !this.board) return\n const result = gizmoResultOf(part, origin!.state, this.board)\n if (!result.position && !result.rotation && !result.scale) return\n this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: part.id, result }, bubbles: true, composed: true }))\n /* After the editor has applied (or refused) it: a refused change springs back to where the inputs put it. */\n setTimeout(() => this.sync())\n })\n }\n\n private showSelected() {\n const layer = this.scene?.root\n const root = layer?.root\n if (!layer || !root) return\n /* A repeat is selected whole: every copy of it (the editor selects the part the author made). */\n const wanted = this.selected ? [...this.standing()].filter(([name]) => ownerOf(name) === this.selected).map(([, component]) => component) : []\n const now = root.selected ?? []\n if (now.length === wanted.length && wanted.every((one, i) => now[i] === one)) return\n root.selected = wanted\n layer.invalidate?.()\n }\n\n /** View settings go into the scene as they are: they are the board's own state names. Nothing is stood up again. */\n private applyView() {\n const layer = this.scene?.root\n if (!layer) return\n layer.set(sceneLook(this.view ?? DEFAULT_VIEW))\n layer.invalidate?.()\n }\n\n private teardown() {\n this.scene?.dispose?.()\n this.scene = undefined\n this.standingFor = undefined\n this.board = undefined\n }\n}\n"]}
|
|
@@ -110,6 +110,25 @@ export declare class FigureV3Editor extends LitElement {
|
|
|
110
110
|
* it is offered it -- a mechanism does not shear -- and the author decides (chief architect's ruling 2026-09-24).
|
|
111
111
|
*/
|
|
112
112
|
private renderSizingRule;
|
|
113
|
+
/**
|
|
114
|
+
* The parts list, one row per part the author made: a repeat is one row with how many copies it makes now (V2 listed
|
|
115
|
+
* its parts the same way; user, 2026-09-24: the conveyor's sixteen rollers were sixteen rows).
|
|
116
|
+
*/
|
|
117
|
+
private partRows;
|
|
118
|
+
/** The detail level's part budget for the list's head, from figure-model's count (a repeat once). */
|
|
119
|
+
private partBudgetOf;
|
|
120
|
+
/**
|
|
121
|
+
* A repeat: which way its copies run, how many there are now, and how far apart -- V2's `repeat: { axis, pitch }`.
|
|
122
|
+
* The pitch of a row that fills its length is edited as a number (`set-repeat` makes it the part's own the first
|
|
123
|
+
* time); a row of a fixed count says its pitch cannot be set yet (figure-model refuses it the same way).
|
|
124
|
+
*/
|
|
125
|
+
private renderRepeat;
|
|
126
|
+
/**
|
|
127
|
+
* A pitch always goes through `set-repeat`: the command moves the longest length with it (at most `limit` copies),
|
|
128
|
+
* which a plain input edit would not. Refused only when the authored length would already hold more copies than
|
|
129
|
+
* that -- said with the numbers.
|
|
130
|
+
*/
|
|
131
|
+
private setPitch;
|
|
113
132
|
/** Choose a part, from the list, the canvas or a place row. A figure-level tab gives way to the part's properties. */
|
|
114
133
|
private choose;
|
|
115
134
|
private asset;
|