@things-factory/figure-ui 10.1.90 → 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 +3 -3
- 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
|
@@ -305,7 +305,7 @@ export class FigureEditor extends LitElement {
|
|
|
305
305
|
[off] {
|
|
306
306
|
display: none !important;
|
|
307
307
|
}
|
|
308
|
-
/* Which part the panel speaks of
|
|
308
|
+
/* Which part the panel speaks of. */
|
|
309
309
|
div[subject] {
|
|
310
310
|
position: sticky;
|
|
311
311
|
top: 0;
|
|
@@ -316,41 +316,6 @@ export class FigureEditor extends LitElement {
|
|
|
316
316
|
background: var(--md-sys-color-surface-container-lowest);
|
|
317
317
|
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
318
318
|
}
|
|
319
|
-
nav[path] {
|
|
320
|
-
display: flex;
|
|
321
|
-
align-items: center;
|
|
322
|
-
gap: 6px;
|
|
323
|
-
min-width: 0;
|
|
324
|
-
}
|
|
325
|
-
nav[path] button[model-crumb] {
|
|
326
|
-
flex: none;
|
|
327
|
-
max-width: 55%;
|
|
328
|
-
height: 28px;
|
|
329
|
-
padding: 0 8px;
|
|
330
|
-
overflow: hidden;
|
|
331
|
-
white-space: nowrap;
|
|
332
|
-
text-overflow: ellipsis;
|
|
333
|
-
border: 1px solid var(--md-sys-color-outline-variant);
|
|
334
|
-
border-radius: 999em;
|
|
335
|
-
background: none;
|
|
336
|
-
color: var(--md-sys-color-primary);
|
|
337
|
-
cursor: pointer;
|
|
338
|
-
}
|
|
339
|
-
nav[path] button[model-crumb][on] {
|
|
340
|
-
border-color: var(--md-sys-color-primary);
|
|
341
|
-
background: var(--md-sys-color-primary-container);
|
|
342
|
-
}
|
|
343
|
-
nav[path] button[model-crumb]:focus-visible {
|
|
344
|
-
outline: 2px solid var(--md-sys-color-primary);
|
|
345
|
-
outline-offset: 1px;
|
|
346
|
-
}
|
|
347
|
-
nav[path] span[current] {
|
|
348
|
-
min-width: 0;
|
|
349
|
-
overflow: hidden;
|
|
350
|
-
white-space: nowrap;
|
|
351
|
-
text-overflow: ellipsis;
|
|
352
|
-
font-weight: 600;
|
|
353
|
-
}
|
|
354
319
|
div[subject-summary] {
|
|
355
320
|
display: grid;
|
|
356
321
|
gap: 1px;
|
|
@@ -651,7 +616,26 @@ export class FigureEditor extends LitElement {
|
|
|
651
616
|
* The section editor is of one part: when the selection moves off it by any way -- another part picked, the scene's
|
|
652
617
|
* own Esc, the part removed, another figure opened -- it closes and lets go of the preview it held.
|
|
653
618
|
*/
|
|
654
|
-
if (this.sectionEditing && this.sectionEditing !== this.selected) this.
|
|
619
|
+
if (this.sectionEditing && this.sectionEditing !== this.selected) this.leaveSectionEditor(this.selected)
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* The selection moved off the part being edited. With nothing changed there the editor closes; with changes, the part
|
|
623
|
+
* stays selected while the editor asks (apply, let go, keep editing), and the move goes on only once it closed. A part
|
|
624
|
+
* gone or another figure opened has nothing to go back to: the editor closes.
|
|
625
|
+
*/
|
|
626
|
+
private leaveSectionEditor(next: string) {
|
|
627
|
+
const editing = this.sectionEditing!
|
|
628
|
+
const editor = this.renderRoot.querySelector('figure-section-editor') as (HTMLElement & { dirty: boolean; requestClose(): Promise<boolean> }) | null
|
|
629
|
+
const view = this.session?.read()
|
|
630
|
+
const stillThere = !!view?.geometry.some(g => ownerOf(g.id) === editing)
|
|
631
|
+
if (!editor?.dirty || !stillThere) {
|
|
632
|
+
this.closeSectionEditor()
|
|
633
|
+
return
|
|
634
|
+
}
|
|
635
|
+
this.selected = editing
|
|
636
|
+
editor.requestClose().then(closed => {
|
|
637
|
+
if (closed && this.selected === editing) this.selected = next
|
|
638
|
+
})
|
|
655
639
|
}
|
|
656
640
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
657
641
|
private useGizmo(mode: 'translate' | 'rotate' | 'scale') {
|
|
@@ -1003,14 +987,11 @@ export class FigureEditor extends LitElement {
|
|
|
1003
987
|
}
|
|
1004
988
|
private renderPlaneAlign(asset: FigureAsset, unavailable: boolean) {
|
|
1005
989
|
const placement = asset.placement ?? 'floor'
|
|
1006
|
-
const icon = { floor: 'vertical_align_bottom', ceiling: 'vertical_align_top', wall: 'flip_to_back', center: 'vertical_align_center' }[placement]
|
|
1007
990
|
/* 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). */
|
|
1008
991
|
const contact = this.planeContactOf(this.session!.source(), this.selected)
|
|
1009
992
|
const told = contact === 'tilted' || contact === 'extreme' ? `-${contact}` : ''
|
|
1010
|
-
return html`<button plane-align=${placement} ?disabled=${unavailable}
|
|
1011
|
-
|
|
1012
|
-
<md-icon>${icon}</md-icon>
|
|
1013
|
-
</button>`
|
|
993
|
+
return html`<button plane-align=${placement} ?disabled=${unavailable} title=${i18next.t(`figure.text.align-to-${placement}${told}`)}
|
|
994
|
+
@click=${() => this.alignToPlane()}>${i18next.t(`figure.button.align-to-${placement}`)}</button>`
|
|
1014
995
|
}
|
|
1015
996
|
private alignToPlane() {
|
|
1016
997
|
const asset = parseFigureAsset(this.session!.source())
|
|
@@ -1031,27 +1012,33 @@ export class FigureEditor extends LitElement {
|
|
|
1031
1012
|
} catch {
|
|
1032
1013
|
return null
|
|
1033
1014
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
</
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1015
|
+
/*
|
|
1016
|
+
* In words, two rows by what they do (user 2026-10-01: 「무엇을 하는 것인지 모르겠다」): moving the part to the figure's
|
|
1017
|
+
* centre lines, onto its plane or onto the grid; and making a mirrored copy, which makes a part and moves nothing.
|
|
1018
|
+
*/
|
|
1019
|
+
const tool = (name: AlignTool, label: string, title: string) =>
|
|
1020
|
+
html`<button ?disabled=${unavailable} title=${i18next.t(title)} @click=${() => this.align(name)}>${i18next.t(label)}</button>`
|
|
1021
|
+
const mirrors = (['x', 'z'] as const).map(axis => ({ axis, able: canMirror(asset as never, this.selected, axis, reach) }))
|
|
1022
|
+
return html`<div vector-row>
|
|
1023
|
+
<label>${i18next.t('figure.label.tools')}</label>
|
|
1024
|
+
<div tools>
|
|
1025
|
+
${tool('centre-x', 'figure.button.center-x', 'figure.text.center-on-x')}
|
|
1026
|
+
${tool('centre-z', 'figure.button.center-z', 'figure.text.center-on-z')}
|
|
1027
|
+
${this.renderPlaneAlign(asset as FigureAsset, unavailable)}
|
|
1028
|
+
${tool('snap', 'figure.button.snap', 'figure.text.snap-explained')}
|
|
1029
|
+
</div>
|
|
1030
|
+
</div>
|
|
1031
|
+
<div vector-row>
|
|
1032
|
+
<label>${i18next.t('figure.label.mirror-copy')}</label>
|
|
1033
|
+
<div tools>
|
|
1034
|
+
${mirrors.map(
|
|
1035
|
+
({ axis, able }) => html`<button ?disabled=${unavailable || !able} title=${i18next.t('figure.text.mirror-explained')} @click=${() => this.mirror(axis)}>
|
|
1036
|
+
${i18next.t('figure.button.mirror-' + axis)}
|
|
1037
|
+
</button>`
|
|
1038
|
+
)}
|
|
1039
|
+
</div>
|
|
1040
|
+
</div>
|
|
1041
|
+
${mirrors.every(m => !m.able) ? html`<p hint>${i18next.t('figure.text.mirror-would-overlap')}</p>` : nothing}`
|
|
1055
1042
|
}
|
|
1056
1043
|
/**
|
|
1057
1044
|
* A part's name. It renames every value the part owns, and what refers to them follows
|
|
@@ -1872,36 +1859,27 @@ export class FigureEditor extends LitElement {
|
|
|
1872
1859
|
</div>`
|
|
1873
1860
|
}
|
|
1874
1861
|
/**
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1877
|
-
*
|
|
1862
|
+
* The part the panel speaks of, held at its top: its kind, how its size answers, what holds it (designer 2026-09-27,
|
|
1863
|
+
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
1864
|
+
* list's root is the way back to the model's settings.
|
|
1878
1865
|
*/
|
|
1879
1866
|
private renderSubject() {
|
|
1880
|
-
const name = this.session?.read()?.name || i18next.t('figure.text.new-figure-name')
|
|
1881
1867
|
let summary: { kind: string; size: string; relation: string } | undefined
|
|
1882
1868
|
try {
|
|
1883
1869
|
summary = this.selected ? partSummaryOf(parseFigureAsset(this.session!.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params)) : undefined
|
|
1884
1870
|
} catch {
|
|
1885
1871
|
summary = undefined
|
|
1886
1872
|
}
|
|
1873
|
+
if (!summary) return nothing
|
|
1887
1874
|
return html`<div subject>
|
|
1888
|
-
<
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
? html`<span aria-hidden="true">›</span><span current aria-current="location" title=${partLabelOf(this.selected)}>${partLabelOf(this.selected)}</span>`
|
|
1893
|
-
: html`<span aria-hidden="true">·</span><span current aria-current="location">${i18next.t('figure.label.model-settings')}</span>`}
|
|
1894
|
-
</nav>
|
|
1895
|
-
${summary
|
|
1896
|
-
? html`<div subject-summary>
|
|
1897
|
-
${summary.kind ? html`<b>${summary.kind}</b>` : nothing}
|
|
1898
|
-
<small>${summary.size}</small><small>${summary.relation}</small>
|
|
1899
|
-
</div>`
|
|
1900
|
-
: nothing}
|
|
1875
|
+
<div subject-summary>
|
|
1876
|
+
${summary.kind ? html`<b>${summary.kind}</b>` : nothing}
|
|
1877
|
+
<small>${summary.size}</small><small>${summary.relation}</small>
|
|
1878
|
+
</div>
|
|
1901
1879
|
</div>`
|
|
1902
1880
|
}
|
|
1903
1881
|
/**
|
|
1904
|
-
* The model's settings
|
|
1882
|
+
* The model's settings, from the parts list's root (B.1). It shows them on
|
|
1905
1883
|
* the properties tab whatever tab was open; it changes no data, no camera, no history and no AI conversation.
|
|
1906
1884
|
*/
|
|
1907
1885
|
private showModelSettings() {
|
|
@@ -2144,7 +2122,7 @@ export class FigureEditor extends LitElement {
|
|
|
2144
2122
|
? html`<section>
|
|
2145
2123
|
<h3>${i18next.t(vectorTitle)}</h3>
|
|
2146
2124
|
${groups.vectors.map(g => this.renderVector(g, unavailable, held))}
|
|
2147
|
-
${tools
|
|
2125
|
+
${tools ?? nothing}
|
|
2148
2126
|
</section>`
|
|
2149
2127
|
: nothing}
|
|
2150
2128
|
${groups.singles.length
|
|
@@ -2255,14 +2233,7 @@ export class FigureEditor extends LitElement {
|
|
|
2255
2233
|
if (agreed) this.linkCommand(refit.step)
|
|
2256
2234
|
}
|
|
2257
2235
|
private renderSingle(name: string, field: FieldLike, unavailable: boolean) {
|
|
2258
|
-
|
|
2259
|
-
const point = /^point\.p(\d+)\.([xyz])$/.exec(name)
|
|
2260
|
-
const apex = /^apex\.([xyz])$/.exec(name)
|
|
2261
|
-
const words = point
|
|
2262
|
-
? [i18next.t('figure.label.section-editor-coordinate', { point: point[1]!, axis: point[2]!.toUpperCase() })]
|
|
2263
|
-
: apex
|
|
2264
|
-
? [`${i18next.t('figure.label.section-editor-apex-mark')} ${apex[1]!.toUpperCase()}`]
|
|
2265
|
-
: name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }))
|
|
2236
|
+
const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }))
|
|
2266
2237
|
return html`<span ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(field as never, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
2267
2238
|
><input
|
|
2268
2239
|
aria-label=${field.id}
|
|
@@ -46,7 +46,14 @@ export function groupFields(fields: readonly FieldLike[], part?: string): { vect
|
|
|
46
46
|
AXES.forEach((axis, i) => byId.has(ids[i]!) && (cells[axis] = byId.get(ids[i]!)!))
|
|
47
47
|
vectors.push({ kind: 'vector', name, cells })
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
/*
|
|
50
|
+
A drawn section's points and a pyramid's apex are edited in the section editor, where a move is checked as it is made
|
|
51
|
+
and the points stand in the outline's order (user 2026-10-01): not here, one field each, in the order they were made.
|
|
52
|
+
*/
|
|
53
|
+
const drawn = (id: string) => /^(point\.[^.]+|apex)\.[xyz]$/.test(id.slice(prefix.length))
|
|
54
|
+
const singles: FieldGroup[] = own
|
|
55
|
+
.filter(f => !used.has(f.id) && !drawn(f.id))
|
|
56
|
+
.map(field => ({ kind: 'single', name: nameOf(field.id.slice(prefix.length)), field }))
|
|
50
57
|
return { vectors, singles }
|
|
51
58
|
}
|
|
52
59
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LitElement, html, css, nothing } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { i18next } from '@operato/i18n'
|
|
4
|
+
import { closePopup } from '@operato/popup'
|
|
5
|
+
import '@operato/popup/ox-popup-actions.js'
|
|
6
|
+
import { CommonPopupStyles } from '@operato/styles'
|
|
7
|
+
import '@material/web/button/filled-button.js'
|
|
8
|
+
import '@material/web/button/text-button.js'
|
|
9
|
+
|
|
10
|
+
/** What to do with a section edit not applied yet, when its editor is about to close (user 2026-10-01). */
|
|
11
|
+
export type SectionCloseChoice = 'apply' | 'discard'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Closing the section editor with changes not applied: apply them, let them go, or keep editing. Closes with the choice,
|
|
15
|
+
* or with nothing -- then the editor stays open as it was. Apply is not offered while the working shape would be refused
|
|
16
|
+
* or the figure changed since the editor opened; the words say which.
|
|
17
|
+
*/
|
|
18
|
+
@customElement('figure-section-close-choice')
|
|
19
|
+
export class FigureSectionCloseChoice extends LitElement {
|
|
20
|
+
static styles = [
|
|
21
|
+
CommonPopupStyles,
|
|
22
|
+
css`
|
|
23
|
+
:host {
|
|
24
|
+
overflow-y: auto;
|
|
25
|
+
}
|
|
26
|
+
p {
|
|
27
|
+
margin: 0 0 12px;
|
|
28
|
+
}
|
|
29
|
+
`
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
/** Why the working shape cannot be applied now, or empty when it can. */
|
|
33
|
+
@property({ type: String }) held = ''
|
|
34
|
+
|
|
35
|
+
render() {
|
|
36
|
+
return html`
|
|
37
|
+
<p>${i18next.t('figure.text.section-editor-close-changed')}</p>
|
|
38
|
+
${this.held ? html`<p>${this.held}</p>` : nothing}
|
|
39
|
+
<ox-popup-actions>
|
|
40
|
+
<md-text-button slot="cancel" @click=${() => closePopup(this)}>${i18next.t('figure.button.keep-editing')}</md-text-button>
|
|
41
|
+
<md-text-button slot="start" destructive @click=${() => closePopup(this, 'discard' satisfies SectionCloseChoice)}
|
|
42
|
+
>${i18next.t('figure.button.section-editor-discard')}</md-text-button
|
|
43
|
+
>
|
|
44
|
+
${this.held
|
|
45
|
+
? nothing
|
|
46
|
+
: html`<md-filled-button slot="confirm" @click=${() => closePopup(this, 'apply' satisfies SectionCloseChoice)}
|
|
47
|
+
>${i18next.t('figure.button.section-editor-apply-and-close')}</md-filled-button
|
|
48
|
+
>`}
|
|
49
|
+
</ox-popup-actions>
|
|
50
|
+
`
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -8,6 +8,9 @@ import type { FigureAuthoringAction, FigureEvaluatedShape, FigureSection, Figure
|
|
|
8
8
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
9
9
|
import { apexOver, drawnOf, insertAfter, markedBy, movePoint, pointLabel, removePoint, sectionOf, workingIssuesOf, workingOf, type SectionWorking } from './section-drawing.js'
|
|
10
10
|
import { END_FACE } from './section-controls.js'
|
|
11
|
+
import { openPopup } from '@operato/layout'
|
|
12
|
+
import './figure-section-close-choice.js'
|
|
13
|
+
import type { SectionCloseChoice } from './figure-section-close-choice.js'
|
|
11
14
|
|
|
12
15
|
type Axis = 'x' | 'y' | 'z'
|
|
13
16
|
|
|
@@ -34,7 +37,8 @@ export const NARROW_BELOW = 900
|
|
|
34
37
|
*
|
|
35
38
|
* What the kernel would refuse is marked on the outline and said in the list; apply is held back, and the solid shows
|
|
36
39
|
* the last valid state, which the caption names. Apply sends one `set-section` command (one step of the history); cancel
|
|
37
|
-
* or Esc sends nothing and lets the preview go
|
|
40
|
+
* or Esc sends nothing and lets the preview go -- after asking, when something was changed: apply it, let it go, or keep
|
|
41
|
+
* editing (`requestClose`). The undo here is the editor's own. If the figure changes while the editor
|
|
38
42
|
* is open, apply is refused until it is opened again.
|
|
39
43
|
*/
|
|
40
44
|
@customElement('figure-section-editor')
|
|
@@ -306,10 +310,12 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
306
310
|
*/
|
|
307
311
|
private keys = (e: KeyboardEvent) => {
|
|
308
312
|
const inField = (e.composedPath()[0] as HTMLElement)?.tagName === 'INPUT'
|
|
313
|
+
/* While the close is being asked, the question has the keys: Esc there is 계속 편집. */
|
|
314
|
+
if (this.asking) return
|
|
309
315
|
const take = () => (e.preventDefault(), e.stopImmediatePropagation())
|
|
310
316
|
if (e.key === 'Escape') {
|
|
311
317
|
take()
|
|
312
|
-
this.
|
|
318
|
+
this.requestClose()
|
|
313
319
|
} else if ((e.key === 'Delete' || e.key === 'Backspace') && !inField && this.selected) {
|
|
314
320
|
/* In a field the key deletes a character; outside, the chosen point. */
|
|
315
321
|
take()
|
|
@@ -390,6 +396,55 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
390
396
|
this.close()
|
|
391
397
|
}
|
|
392
398
|
|
|
399
|
+
/** Whether anything was changed since the editor opened. */
|
|
400
|
+
get dirty(): boolean {
|
|
401
|
+
const key = (w: SectionWorking) =>
|
|
402
|
+
JSON.stringify([
|
|
403
|
+
w.points.map(p => [p.id, p.x ?? 0, p.y ?? 0, p.z ?? 0]),
|
|
404
|
+
Object.entries(w.across).sort(),
|
|
405
|
+
w.length,
|
|
406
|
+
w.apex ? Object.entries(w.apex).sort() : null
|
|
407
|
+
])
|
|
408
|
+
return !!this.opened && key(this.working) !== key(this.opened)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private asking = false
|
|
412
|
+
/**
|
|
413
|
+
* Close, asking first when something was changed (user 2026-10-01): apply it, let it go, or keep editing. True when the
|
|
414
|
+
* editor closed; false when the person keeps editing.
|
|
415
|
+
*/
|
|
416
|
+
async requestClose(): Promise<boolean> {
|
|
417
|
+
if (!this.dirty) {
|
|
418
|
+
this.cancel()
|
|
419
|
+
return true
|
|
420
|
+
}
|
|
421
|
+
if (this.asking) return false
|
|
422
|
+
const held = this.stale
|
|
423
|
+
? i18next.t('figure.text.section-editor-stale')
|
|
424
|
+
: this.valid(this.working)
|
|
425
|
+
? ''
|
|
426
|
+
: i18next.t('figure.text.section-editor-close-invalid')
|
|
427
|
+
this.asking = true
|
|
428
|
+
try {
|
|
429
|
+
const choice = await openPopup<SectionCloseChoice>(html`<figure-section-close-choice .held=${held}></figure-section-close-choice>`, {
|
|
430
|
+
backdrop: true,
|
|
431
|
+
size: 'small',
|
|
432
|
+
title: i18next.t('figure.label.section-editor')
|
|
433
|
+
}).result
|
|
434
|
+
if (choice === 'apply' && !held) {
|
|
435
|
+
this.apply()
|
|
436
|
+
return true
|
|
437
|
+
}
|
|
438
|
+
if (choice === 'discard') {
|
|
439
|
+
this.cancel()
|
|
440
|
+
return true
|
|
441
|
+
}
|
|
442
|
+
return false
|
|
443
|
+
} finally {
|
|
444
|
+
this.asking = false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
393
448
|
private cancel() {
|
|
394
449
|
this.dispatchEvent(new CustomEvent('command-live', { detail: { action: null }, bubbles: true, composed: true }))
|
|
395
450
|
this.close()
|
|
@@ -441,7 +496,7 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
441
496
|
? i18next.t('figure.text.section-editor-stale')
|
|
442
497
|
: i18next.t(this.kind === 'polygon' ? 'figure.text.section-editor-hint' : 'figure.text.section-editor-hint-sized')}
|
|
443
498
|
</span>
|
|
444
|
-
<button @click=${() => this.
|
|
499
|
+
<button @click=${() => this.requestClose()}>${i18next.t('figure.button.section-editor-cancel')}</button>
|
|
445
500
|
<button primary ?disabled=${this.stale || words.length > 0} @click=${() => this.apply()}>${i18next.t('figure.button.section-editor-apply')}</button>
|
|
446
501
|
</footer>
|
|
447
502
|
`
|
|
@@ -106,6 +106,9 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
106
106
|
<button act ?disabled=${this.locked} @click=${() => this.dispatchEvent(new CustomEvent('section-editor-open', { bubbles: true, composed: true }))}>
|
|
107
107
|
${i18next.t('figure.button.section-editor-open')}
|
|
108
108
|
</button>
|
|
109
|
+
${e.section.kind === 'polygon'
|
|
110
|
+
? html`<small>${i18next.t(e.apex ? 'figure.text.section-points-apex' : 'figure.text.section-points', { points: String(e.section.points.length) })}</small>`
|
|
111
|
+
: nothing}
|
|
109
112
|
</div>`
|
|
110
113
|
: nothing}
|
|
111
114
|
${kind === 'rectangle' ? this.renderCorner(body) : nothing}
|
|
@@ -101,19 +101,17 @@ export const SidePanelStyles = css`
|
|
|
101
101
|
div[vector-row] + div[inline-field] {
|
|
102
102
|
margin-top: 5px;
|
|
103
103
|
}
|
|
104
|
-
/* A row of
|
|
104
|
+
/* A row of worded tools (align, snap; mirror copies). */
|
|
105
105
|
div[tools] {
|
|
106
106
|
display: flex;
|
|
107
107
|
flex-wrap: wrap;
|
|
108
108
|
gap: 4px;
|
|
109
109
|
}
|
|
110
110
|
div[tools] button {
|
|
111
|
-
display: inline-flex;
|
|
112
|
-
align-items: center;
|
|
113
|
-
justify-content: center;
|
|
114
|
-
width: 30px;
|
|
115
111
|
height: 28px;
|
|
116
|
-
padding: 0;
|
|
112
|
+
padding: 0 10px;
|
|
113
|
+
font: inherit;
|
|
114
|
+
font-size: 0.78rem;
|
|
117
115
|
color: var(--md-sys-color-on-surface);
|
|
118
116
|
background: var(--md-sys-color-surface-container-lowest);
|
|
119
117
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
@@ -127,11 +125,6 @@ export const SidePanelStyles = css`
|
|
|
127
125
|
opacity: 0.45;
|
|
128
126
|
cursor: default;
|
|
129
127
|
}
|
|
130
|
-
div[tools] md-icon {
|
|
131
|
-
font-size: 17px;
|
|
132
|
-
width: 17px;
|
|
133
|
-
height: 17px;
|
|
134
|
-
}
|
|
135
128
|
div[pair] {
|
|
136
129
|
display: flex;
|
|
137
130
|
gap: 4px;
|
|
@@ -114,6 +114,12 @@ export declare class FigureEditor extends LitElement {
|
|
|
114
114
|
private propertiesPanel;
|
|
115
115
|
private foldsOf;
|
|
116
116
|
protected willUpdate(_changed: PropertyValues): void;
|
|
117
|
+
/**
|
|
118
|
+
* The selection moved off the part being edited. With nothing changed there the editor closes; with changes, the part
|
|
119
|
+
* stays selected while the editor asks (apply, let go, keep editing), and the move goes on only once it closed. A part
|
|
120
|
+
* gone or another figure opened has nothing to go back to: the editor closes.
|
|
121
|
+
*/
|
|
122
|
+
private leaveSectionEditor;
|
|
117
123
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
118
124
|
private useGizmo;
|
|
119
125
|
/**
|
|
@@ -303,13 +309,13 @@ export declare class FigureEditor extends LitElement {
|
|
|
303
309
|
private pointBlockedOf;
|
|
304
310
|
private renderStart;
|
|
305
311
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
312
|
+
* The part the panel speaks of, held at its top: its kind, how its size answers, what holds it (designer 2026-09-27,
|
|
313
|
+
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
314
|
+
* list's root is the way back to the model's settings.
|
|
309
315
|
*/
|
|
310
316
|
private renderSubject;
|
|
311
317
|
/**
|
|
312
|
-
* The model's settings
|
|
318
|
+
* The model's settings, from the parts list's root (B.1). It shows them on
|
|
313
319
|
* the properties tab whatever tab was open; it changes no data, no camera, no history and no AI conversation.
|
|
314
320
|
*/
|
|
315
321
|
private showModelSettings;
|