@things-factory/figure-ui 10.1.91 → 10.1.92
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/editor.ts +61 -90
- package/client/modeller/fields.ts +8 -1
- package/client/modeller/figure-section-close-choice.ts +52 -0
- package/client/modeller/figure-section-editor.ts +58 -3
- package/client/modeller/figure-section-panel.ts +3 -0
- package/client/modeller/side-panel-styles.ts +4 -11
- package/dist-client/modeller/editor.d.ts +10 -4
- package/dist-client/modeller/editor.js +59 -88
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fields.js +8 -1
- package/dist-client/modeller/fields.js.map +1 -1
- package/dist-client/modeller/figure-section-close-choice.d.ts +17 -0
- package/dist-client/modeller/figure-section-close-choice.js +58 -0
- package/dist-client/modeller/figure-section-close-choice.js.map +1 -0
- package/dist-client/modeller/figure-section-editor.d.ts +11 -1
- package/dist-client/modeller/figure-section-editor.js +57 -3
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.js +3 -0
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/side-panel-styles.js +4 -11
- package/dist-client/modeller/side-panel-styles.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/test/layout.test.ts +19 -4
- package/test/section-editor.test.ts +31 -8
- package/translations/en.json +19 -15
- package/translations/ja.json +15 -11
- package/translations/ko.json +19 -15
- package/translations/ms.json +15 -11
- package/translations/zh.json +15 -11
|
@@ -379,7 +379,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
379
379
|
[off] {
|
|
380
380
|
display: none !important;
|
|
381
381
|
}
|
|
382
|
-
/* Which part the panel speaks of
|
|
382
|
+
/* Which part the panel speaks of. */
|
|
383
383
|
div[subject] {
|
|
384
384
|
position: sticky;
|
|
385
385
|
top: 0;
|
|
@@ -390,41 +390,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
390
390
|
background: var(--md-sys-color-surface-container-lowest);
|
|
391
391
|
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
392
392
|
}
|
|
393
|
-
nav[path] {
|
|
394
|
-
display: flex;
|
|
395
|
-
align-items: center;
|
|
396
|
-
gap: 6px;
|
|
397
|
-
min-width: 0;
|
|
398
|
-
}
|
|
399
|
-
nav[path] button[model-crumb] {
|
|
400
|
-
flex: none;
|
|
401
|
-
max-width: 55%;
|
|
402
|
-
height: 28px;
|
|
403
|
-
padding: 0 8px;
|
|
404
|
-
overflow: hidden;
|
|
405
|
-
white-space: nowrap;
|
|
406
|
-
text-overflow: ellipsis;
|
|
407
|
-
border: 1px solid var(--md-sys-color-outline-variant);
|
|
408
|
-
border-radius: 999em;
|
|
409
|
-
background: none;
|
|
410
|
-
color: var(--md-sys-color-primary);
|
|
411
|
-
cursor: pointer;
|
|
412
|
-
}
|
|
413
|
-
nav[path] button[model-crumb][on] {
|
|
414
|
-
border-color: var(--md-sys-color-primary);
|
|
415
|
-
background: var(--md-sys-color-primary-container);
|
|
416
|
-
}
|
|
417
|
-
nav[path] button[model-crumb]:focus-visible {
|
|
418
|
-
outline: 2px solid var(--md-sys-color-primary);
|
|
419
|
-
outline-offset: 1px;
|
|
420
|
-
}
|
|
421
|
-
nav[path] span[current] {
|
|
422
|
-
min-width: 0;
|
|
423
|
-
overflow: hidden;
|
|
424
|
-
white-space: nowrap;
|
|
425
|
-
text-overflow: ellipsis;
|
|
426
|
-
font-weight: 600;
|
|
427
|
-
}
|
|
428
393
|
div[subject-summary] {
|
|
429
394
|
display: grid;
|
|
430
395
|
gap: 1px;
|
|
@@ -638,7 +603,27 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
638
603
|
* own Esc, the part removed, another figure opened -- it closes and lets go of the preview it held.
|
|
639
604
|
*/
|
|
640
605
|
if (this.sectionEditing && this.sectionEditing !== this.selected)
|
|
606
|
+
this.leaveSectionEditor(this.selected);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* The selection moved off the part being edited. With nothing changed there the editor closes; with changes, the part
|
|
610
|
+
* stays selected while the editor asks (apply, let go, keep editing), and the move goes on only once it closed. A part
|
|
611
|
+
* gone or another figure opened has nothing to go back to: the editor closes.
|
|
612
|
+
*/
|
|
613
|
+
leaveSectionEditor(next) {
|
|
614
|
+
const editing = this.sectionEditing;
|
|
615
|
+
const editor = this.renderRoot.querySelector('figure-section-editor');
|
|
616
|
+
const view = this.session?.read();
|
|
617
|
+
const stillThere = !!view?.geometry.some(g => ownerOf(g.id) === editing);
|
|
618
|
+
if (!editor?.dirty || !stillThere) {
|
|
641
619
|
this.closeSectionEditor();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
this.selected = editing;
|
|
623
|
+
editor.requestClose().then(closed => {
|
|
624
|
+
if (closed && this.selected === editing)
|
|
625
|
+
this.selected = next;
|
|
626
|
+
});
|
|
642
627
|
}
|
|
643
628
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
644
629
|
useGizmo(mode) {
|
|
@@ -1019,14 +1004,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1019
1004
|
}
|
|
1020
1005
|
renderPlaneAlign(asset, unavailable) {
|
|
1021
1006
|
const placement = asset.placement ?? 'floor';
|
|
1022
|
-
const icon = { floor: 'vertical_align_bottom', ceiling: 'vertical_align_top', wall: 'flip_to_back', center: 'vertical_align_center' }[placement];
|
|
1023
1007
|
/* A tilted part touches the plane at a point or an edge, a ball at its lowest; the tooltip says so rather than promise a face (designer 2026-09-27). */
|
|
1024
1008
|
const contact = this.planeContactOf(this.session.source(), this.selected);
|
|
1025
1009
|
const told = contact === 'tilted' || contact === 'extreme' ? `-${contact}` : '';
|
|
1026
|
-
return html `<button plane-align=${placement} ?disabled=${unavailable}
|
|
1027
|
-
|
|
1028
|
-
<md-icon>${icon}</md-icon>
|
|
1029
|
-
</button>`;
|
|
1010
|
+
return html `<button plane-align=${placement} ?disabled=${unavailable} title=${i18next.t(`figure.text.align-to-${placement}${told}`)}
|
|
1011
|
+
@click=${() => this.alignToPlane()}>${i18next.t(`figure.button.align-to-${placement}`)}</button>`;
|
|
1030
1012
|
}
|
|
1031
1013
|
alignToPlane() {
|
|
1032
1014
|
const asset = parseFigureAsset(this.session.source());
|
|
@@ -1051,26 +1033,30 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1051
1033
|
catch {
|
|
1052
1034
|
return null;
|
|
1053
1035
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
</
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1036
|
+
/*
|
|
1037
|
+
* In words, two rows by what they do (user 2026-10-01: 「무엇을 하는 것인지 모르겠다」): moving the part to the figure's
|
|
1038
|
+
* centre lines, onto its plane or onto the grid; and making a mirrored copy, which makes a part and moves nothing.
|
|
1039
|
+
*/
|
|
1040
|
+
const tool = (name, label, title) => html `<button ?disabled=${unavailable} title=${i18next.t(title)} @click=${() => this.align(name)}>${i18next.t(label)}</button>`;
|
|
1041
|
+
const mirrors = ['x', 'z'].map(axis => ({ axis, able: canMirror(asset, this.selected, axis, reach) }));
|
|
1042
|
+
return html `<div vector-row>
|
|
1043
|
+
<label>${i18next.t('figure.label.tools')}</label>
|
|
1044
|
+
<div tools>
|
|
1045
|
+
${tool('centre-x', 'figure.button.center-x', 'figure.text.center-on-x')}
|
|
1046
|
+
${tool('centre-z', 'figure.button.center-z', 'figure.text.center-on-z')}
|
|
1047
|
+
${this.renderPlaneAlign(asset, unavailable)}
|
|
1048
|
+
${tool('snap', 'figure.button.snap', 'figure.text.snap-explained')}
|
|
1049
|
+
</div>
|
|
1050
|
+
</div>
|
|
1051
|
+
<div vector-row>
|
|
1052
|
+
<label>${i18next.t('figure.label.mirror-copy')}</label>
|
|
1053
|
+
<div tools>
|
|
1054
|
+
${mirrors.map(({ axis, able }) => html `<button ?disabled=${unavailable || !able} title=${i18next.t('figure.text.mirror-explained')} @click=${() => this.mirror(axis)}>
|
|
1055
|
+
${i18next.t('figure.button.mirror-' + axis)}
|
|
1056
|
+
</button>`)}
|
|
1057
|
+
</div>
|
|
1058
|
+
</div>
|
|
1059
|
+
${mirrors.every(m => !m.able) ? html `<p hint>${i18next.t('figure.text.mirror-would-overlap')}</p>` : nothing}`;
|
|
1074
1060
|
}
|
|
1075
1061
|
/**
|
|
1076
1062
|
* A part's name. It renames every value the part owns, and what refers to them follows
|
|
@@ -1906,12 +1892,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1906
1892
|
</div>`;
|
|
1907
1893
|
}
|
|
1908
1894
|
/**
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1895
|
+
* The part the panel speaks of, held at its top: its kind, how its size answers, what holds it (designer 2026-09-27,
|
|
1896
|
+
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
1897
|
+
* list's root is the way back to the model's settings.
|
|
1912
1898
|
*/
|
|
1913
1899
|
renderSubject() {
|
|
1914
|
-
const name = this.session?.read()?.name || i18next.t('figure.text.new-figure-name');
|
|
1915
1900
|
let summary;
|
|
1916
1901
|
try {
|
|
1917
1902
|
summary = this.selected ? partSummaryOf(parseFigureAsset(this.session.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params)) : undefined;
|
|
@@ -1919,24 +1904,17 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1919
1904
|
catch {
|
|
1920
1905
|
summary = undefined;
|
|
1921
1906
|
}
|
|
1907
|
+
if (!summary)
|
|
1908
|
+
return nothing;
|
|
1922
1909
|
return html `<div subject>
|
|
1923
|
-
<
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
? html `<span aria-hidden="true">›</span><span current aria-current="location" title=${partLabelOf(this.selected)}>${partLabelOf(this.selected)}</span>`
|
|
1928
|
-
: html `<span aria-hidden="true">·</span><span current aria-current="location">${i18next.t('figure.label.model-settings')}</span>`}
|
|
1929
|
-
</nav>
|
|
1930
|
-
${summary
|
|
1931
|
-
? html `<div subject-summary>
|
|
1932
|
-
${summary.kind ? html `<b>${summary.kind}</b>` : nothing}
|
|
1933
|
-
<small>${summary.size}</small><small>${summary.relation}</small>
|
|
1934
|
-
</div>`
|
|
1935
|
-
: nothing}
|
|
1910
|
+
<div subject-summary>
|
|
1911
|
+
${summary.kind ? html `<b>${summary.kind}</b>` : nothing}
|
|
1912
|
+
<small>${summary.size}</small><small>${summary.relation}</small>
|
|
1913
|
+
</div>
|
|
1936
1914
|
</div>`;
|
|
1937
1915
|
}
|
|
1938
1916
|
/**
|
|
1939
|
-
* The model's settings
|
|
1917
|
+
* The model's settings, from the parts list's root (B.1). It shows them on
|
|
1940
1918
|
* the properties tab whatever tab was open; it changes no data, no camera, no history and no AI conversation.
|
|
1941
1919
|
*/
|
|
1942
1920
|
showModelSettings() {
|
|
@@ -2172,7 +2150,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2172
2150
|
? html `<section>
|
|
2173
2151
|
<h3>${i18next.t(vectorTitle)}</h3>
|
|
2174
2152
|
${groups.vectors.map(g => this.renderVector(g, unavailable, held))}
|
|
2175
|
-
${tools
|
|
2153
|
+
${tools ?? nothing}
|
|
2176
2154
|
</section>`
|
|
2177
2155
|
: nothing}
|
|
2178
2156
|
${groups.singles.length
|
|
@@ -2282,14 +2260,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2282
2260
|
this.linkCommand(refit.step);
|
|
2283
2261
|
}
|
|
2284
2262
|
renderSingle(name, field, unavailable) {
|
|
2285
|
-
|
|
2286
|
-
const point = /^point\.p(\d+)\.([xyz])$/.exec(name);
|
|
2287
|
-
const apex = /^apex\.([xyz])$/.exec(name);
|
|
2288
|
-
const words = point
|
|
2289
|
-
? [i18next.t('figure.label.section-editor-coordinate', { point: point[1], axis: point[2].toUpperCase() })]
|
|
2290
|
-
: apex
|
|
2291
|
-
? [`${i18next.t('figure.label.section-editor-apex-mark')} ${apex[1].toUpperCase()}`]
|
|
2292
|
-
: name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }));
|
|
2263
|
+
const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }));
|
|
2293
2264
|
return html `<span ?scrub=${!unavailable} @pointerdown=${(e) => this.scrub(field, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
2294
2265
|
><input
|
|
2295
2266
|
aria-label=${field.id}
|