@things-factory/figure-ui 10.1.93 → 10.1.95
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 +25 -4
- package/client/modeller/figure-fasten-drawing.ts +19 -15
- package/client/modeller/figure-links.ts +17 -21
- package/client/modeller/figure-material-panel.ts +18 -11
- package/client/modeller/figure-properties.ts +2 -2
- package/client/modeller/figure-section-panel.ts +11 -6
- package/client/modeller/side-panel-styles.ts +67 -32
- package/dist-client/modeller/editor.d.ts +5 -0
- package/dist-client/modeller/editor.js +27 -4
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.js +20 -14
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.js +19 -21
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +18 -11
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-properties.js +2 -2
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.d.ts +1 -0
- package/dist-client/modeller/figure-section-panel.js +11 -6
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/side-panel-styles.d.ts +5 -1
- package/dist-client/modeller/side-panel-styles.js +67 -32
- 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/first-minute.test.ts +3 -1
|
@@ -312,13 +312,18 @@ export class FigureEditor extends LitElement {
|
|
|
312
312
|
z-index: 1;
|
|
313
313
|
display: grid;
|
|
314
314
|
gap: 4px;
|
|
315
|
-
padding:
|
|
315
|
+
padding: 10px 12px;
|
|
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
|
+
/* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */
|
|
319
320
|
div[subject-summary] {
|
|
320
321
|
display: grid;
|
|
321
|
-
gap:
|
|
322
|
+
gap: 2px;
|
|
323
|
+
}
|
|
324
|
+
div[subject-summary] b {
|
|
325
|
+
font-size: var(--ui-t-body, 13px);
|
|
326
|
+
font-weight: 600;
|
|
322
327
|
}
|
|
323
328
|
div[subject-summary] b span {
|
|
324
329
|
font-weight: 400;
|
|
@@ -2122,6 +2127,7 @@ export class FigureEditor extends LitElement {
|
|
|
2122
2127
|
? html`<section>
|
|
2123
2128
|
<h3>${i18next.t(vectorTitle)}</h3>
|
|
2124
2129
|
${groups.vectors.map(g => this.renderVector(g, unavailable, held))}
|
|
2130
|
+
${this.renderHeldAxes(groups.vectors)}
|
|
2125
2131
|
${tools ?? nothing}
|
|
2126
2132
|
</section>`
|
|
2127
2133
|
: nothing}
|
|
@@ -2176,7 +2182,22 @@ export class FigureEditor extends LitElement {
|
|
|
2176
2182
|
)}
|
|
2177
2183
|
</div>
|
|
2178
2184
|
</div>
|
|
2179
|
-
|
|
2185
|
+
`
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Which axes a rule sets, said once under the rows (position and rotation X · Y · Z set by a rule), not a line under each row
|
|
2189
|
+
* saying the same (user 2026-10-01, the panel checked for balance). The cells themselves are dashed and read-only.
|
|
2190
|
+
*/
|
|
2191
|
+
private renderHeldAxes(vectors: FieldGroup[]) {
|
|
2192
|
+
const byAxes = new Map<string, string[]>()
|
|
2193
|
+
for (const g of vectors) {
|
|
2194
|
+
if (g.kind !== 'vector') continue
|
|
2195
|
+
const ruled = (['x', 'y', 'z'] as const).filter(axis => !g.cells[axis])
|
|
2196
|
+
if (!ruled.length) continue
|
|
2197
|
+
const axes = ruled.map(a => a.toUpperCase()).join(' · ')
|
|
2198
|
+
byAxes.set(axes, [...(byAxes.get(axes) ?? []), i18next.t('figure.label.group-' + g.name)])
|
|
2199
|
+
}
|
|
2200
|
+
return [...byAxes].map(([axes, groups]) => html`<p hint held-axes>${i18next.t('figure.text.axes-held-by-rule', { group: groups.join(' · '), axes })}</p>`)
|
|
2180
2201
|
}
|
|
2181
2202
|
/**
|
|
2182
2203
|
* A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
|
|
@@ -2191,7 +2212,7 @@ export class FigureEditor extends LitElement {
|
|
|
2191
2212
|
f => html`<span>${fieldWordsOf(f.id, key => i18next.t(key))} (${f.unit})</span><input aria-label=${f.id} type="number" .value=${String(f.value)} disabled />`
|
|
2192
2213
|
)}
|
|
2193
2214
|
</div>
|
|
2194
|
-
<button act room-refit ?disabled=${unavailable} @click=${() => this.askRoomRefit()}>${i18next.t('figure.button.room-refit')}</button>
|
|
2215
|
+
<button act room-refit ?disabled=${unavailable} @click=${() => this.askRoomRefit()}><md-icon>fit_screen</md-icon>${i18next.t('figure.button.room-refit')}</button>
|
|
2195
2216
|
</section>`
|
|
2196
2217
|
}
|
|
2197
2218
|
/** The room as declared and as proposed, and what lies outside each as measured, before the author confirms. */
|
|
@@ -22,6 +22,7 @@ const SEEN: Array<[FigureFace, string]> = [
|
|
|
22
22
|
['right', 'M160 30 L200 10 L200 80 L160 100 Z']
|
|
23
23
|
]
|
|
24
24
|
/** The faces the drawing cannot show on the box, as tags beside it where they are. */
|
|
25
|
+
/* The box is drawn a little left of its picture's middle, and the viewBox shifts it back to the panel's centre. */
|
|
25
26
|
const HIDDEN: Array<[FigureFace, number, number]> = [
|
|
26
27
|
['bottom', 30, 114],
|
|
27
28
|
['left', 24, 62],
|
|
@@ -64,12 +65,17 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
64
65
|
grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
|
|
65
66
|
gap: 6px;
|
|
66
67
|
}
|
|
68
|
+
div[modes] svg {
|
|
69
|
+
width: 64px;
|
|
70
|
+
height: 44px;
|
|
71
|
+
}
|
|
67
72
|
div[modes] button {
|
|
68
73
|
display: grid;
|
|
69
74
|
justify-items: center;
|
|
75
|
+
align-content: center;
|
|
76
|
+
gap: 2px;
|
|
70
77
|
padding: 2px 4px 4px;
|
|
71
78
|
font: inherit;
|
|
72
|
-
font-size: 0.72rem;
|
|
73
79
|
color: var(--md-sys-color-on-surface);
|
|
74
80
|
background: none;
|
|
75
81
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
@@ -88,7 +94,6 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
88
94
|
position: absolute;
|
|
89
95
|
width: 72px;
|
|
90
96
|
font: inherit;
|
|
91
|
-
font-size: 0.75rem;
|
|
92
97
|
}
|
|
93
98
|
small[warn] {
|
|
94
99
|
color: var(--md-sys-color-tertiary);
|
|
@@ -135,11 +140,8 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
135
140
|
|
|
136
141
|
/** The target box, its faces pressed; or the plane. */
|
|
137
142
|
private renderTarget() {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<path d="M30 80 L110 30 L270 30 L190 80 Z" style="fill:var(--md-sys-color-primary);fill-opacity:.35;stroke:var(--md-sys-color-primary);stroke-width:1.5" />
|
|
141
|
-
<text x="150" y="62" text-anchor="middle" font-size="12" style="fill:var(--md-sys-color-on-primary-container)">${this.planeWord}</text>
|
|
142
|
-
</svg>`
|
|
143
|
+
/* The plane is one face, named in the list and on the face drawn below: nothing to pick, so no picture of it. */
|
|
144
|
+
if (!this.target) return nothing
|
|
143
145
|
const has = (f: string) => this.faces.includes(f)
|
|
144
146
|
/* A press is a whole change, as a drag let go is: the form applies a fastening the part already has. */
|
|
145
147
|
const pick = (f: string) => () => {
|
|
@@ -150,7 +152,7 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
150
152
|
const seen = SEEN.map(([f, d]) => {
|
|
151
153
|
const on = this.face === f
|
|
152
154
|
return svg`<path face=${f} d=${d} @click=${pick(f)}
|
|
153
|
-
style=${`cursor:${has(f) ? 'pointer' : 'default'};fill:${on ? 'var(--md-sys-color-primary)' : 'var(--md-sys-color-
|
|
155
|
+
style=${`cursor:${has(f) ? 'pointer' : 'default'};fill:${on ? 'var(--md-sys-color-primary-container)' : 'var(--md-sys-color-surface-container)'};fill-opacity:${has(f) ? 1 : 0.4};stroke:${on ? 'var(--md-sys-color-primary)' : 'var(--md-sys-color-outline)'};stroke-width:${on ? 2 : 1}`}>
|
|
154
156
|
<title>${i18next.t('figure.label.face-' + f)}</title></path>`
|
|
155
157
|
})
|
|
156
158
|
const hidden = HIDDEN.map(([f, x, y]) => {
|
|
@@ -163,9 +165,9 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
163
165
|
})
|
|
164
166
|
/* A shape's faces beyond its six outer ones -- a door frame's opening -- are tags of their own under the box. */
|
|
165
167
|
const others = this.faces.filter(f => !SEEN.some(([s]) => s === f) && !HIDDEN.some(([h]) => h === f))
|
|
166
|
-
return html`${svg`<svg viewBox="
|
|
168
|
+
return html`${svg`<svg viewBox="-20 0 ${PANEL_W} 126" role="img" aria-label=${this.target}>
|
|
167
169
|
${seen}
|
|
168
|
-
<text x="110" y="70" text-anchor="middle" font-size="12" pointer-events="none" style="fill:var(--md-sys-color-on-
|
|
170
|
+
<text x="110" y="70" text-anchor="middle" font-size="12" pointer-events="none" style="fill:var(--md-sys-color-on-surface)">${this.target}</text>
|
|
169
171
|
${hidden}
|
|
170
172
|
</svg>`}
|
|
171
173
|
${others.length
|
|
@@ -177,17 +179,19 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
177
179
|
|
|
178
180
|
/** How: three side views; the chosen one's gap is dragged. */
|
|
179
181
|
private renderModes() {
|
|
182
|
+
/* One way only (the plane is set on): the sentence says it; a single choice is not offered as a button. */
|
|
183
|
+
if (this.modes.length < 2) return nothing
|
|
180
184
|
const icon = (m: FastenMode, on: boolean) => {
|
|
181
185
|
const g = on && m !== 'centre' ? Math.min(this.gap / 4, 10) : 0
|
|
182
186
|
const base = svg`<rect x="6" y="30" width="52" height="14" style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary)" />`
|
|
183
187
|
const gapDrag = on && !this.locked ? 'ns-resize' : 'pointer'
|
|
184
188
|
if (m === 'meet')
|
|
185
|
-
return svg`<svg
|
|
189
|
+
return svg`<svg viewBox="0 0 64 48">${base}<rect gap x="22" y=${18 - g} width="20" height="12" style=${`fill:var(--md-sys-color-surface);stroke:currentColor;cursor:${gapDrag}`} />
|
|
186
190
|
${g ? svg`<text x="46" y=${26 - g / 2} font-size="10" style="fill:currentColor">${this.gap}</text>` : nothing}</svg>`
|
|
187
191
|
if (m === 'flush')
|
|
188
|
-
return svg`<svg
|
|
192
|
+
return svg`<svg viewBox="0 0 64 48">${base}<rect gap x="22" y=${30 - g} width="20" height="10" style=${`fill:var(--md-sys-color-surface);stroke:currentColor;cursor:${gapDrag}`} />
|
|
189
193
|
<line x1="4" y1="30" x2="60" y2="30" style="stroke:currentColor;stroke-dasharray:2 2" /></svg>`
|
|
190
|
-
return svg`<svg
|
|
194
|
+
return svg`<svg viewBox="0 0 64 48">${base}<circle cx="32" cy="30" r="11" style="fill:var(--md-sys-color-surface);fill-opacity:.85;stroke:currentColor" /><circle cx="32" cy="30" r="2" style="fill:currentColor" /></svg>`
|
|
191
195
|
}
|
|
192
196
|
return html`<div modes>
|
|
193
197
|
${this.modes.map(m => {
|
|
@@ -300,8 +304,8 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
300
304
|
@pointermove=${(ev: PointerEvent) => this.drag(ev, view, cx, cy)}
|
|
301
305
|
@pointerup=${() => this.drop()} @pointercancel=${() => this.drop()}>
|
|
302
306
|
${faceSized
|
|
303
|
-
? svg`<rect x=${fx} y=${fy} width=${fw} height=${fh} rx="
|
|
304
|
-
: svg`<rect x="
|
|
307
|
+
? svg`<rect x=${fx} y=${fy} width=${fw} height=${fh} rx="4" style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:2" />`
|
|
308
|
+
: svg`<rect x="1" y="1" width=${W - 2} height=${H - 2} rx="8" style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:2" />`}
|
|
305
309
|
<text x=${faceSized ? fx + 4 : 10} y=${faceSized ? fy + fh - 6 : 18} font-size="11" pointer-events="none" style="fill:var(--md-sys-color-primary)">${faceName}${sizeWords}</text>
|
|
306
310
|
${guide(h)}${guide(v)}
|
|
307
311
|
<g footprint style=${`cursor:${this.locked ? 'default' : 'grab'}`} @pointerdown=${(ev: PointerEvent) => this.grab(ev, at, caught)}>
|
|
@@ -119,17 +119,16 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
119
119
|
div[resize-held] { display: grid; gap: 8px; margin: 12px 0; padding: 10px 12px; border-radius: 6px; background: var(--md-sys-color-surface-container, #8881); }
|
|
120
120
|
div[resize-held] p { margin: 0; display: flex; align-items: center; gap: 6px; }
|
|
121
121
|
p[fasten-mode] { margin: 0; font-weight: 600; }
|
|
122
|
-
p[fasten-mode][editing] { color: var(--md-sys-color-primary, #477b9e); }
|
|
123
122
|
section[fastening][spotlit] { outline: 2px solid var(--md-sys-color-primary, #477b9e); outline-offset: 4px; border-radius: 6px; transition: outline-color 0.3s; }
|
|
124
|
-
details { margin-top:
|
|
125
|
-
summary { cursor: pointer; padding: 8px 0; }
|
|
123
|
+
details { margin-top: 8px; }
|
|
126
124
|
/* What the command would do, said beside the result drawn on the canvas. */
|
|
127
|
-
ul[preview-words] { margin:
|
|
125
|
+
ul[preview-words] { margin: 8px 0; padding: 8px 12px 8px 26px; border-radius: 6px; background: var(--md-sys-color-surface-container, #8881); display: grid; gap: 4px; }
|
|
126
|
+
p[preview-words] { margin: 8px 0; padding: 8px 12px; border-radius: 6px; background: var(--md-sys-color-surface-container, #8881); }
|
|
128
127
|
div[row-basis] { display: grid; gap: 4px; margin: 8px 0; }
|
|
129
128
|
div[row-basis] label { display: flex; align-items: center; gap: 6px; }
|
|
130
129
|
div[overhang] { display: grid; gap: 6px; margin: 8px 0; padding: 8px 12px; border-radius: 6px; border: 1px solid var(--md-sys-color-outline-variant, #8884); }
|
|
131
130
|
div[overhang] p { margin: 0; }
|
|
132
|
-
ul[detach-words] { margin: 4px 0 10px; padding-left: 20px; display: grid; gap: 2px;
|
|
131
|
+
ul[detach-words] { margin: 4px 0 10px; padding-left: 20px; display: grid; gap: 2px; }
|
|
133
132
|
span[fasten-summary] { opacity: 0.8; }
|
|
134
133
|
/* The difference a following measure keeps: under its choice, on the same line as its words. */
|
|
135
134
|
label[inline-plus] {
|
|
@@ -443,11 +442,11 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
443
442
|
return html`<div resize-held>
|
|
444
443
|
<p><md-icon>link</md-icon> ${where}</p>
|
|
445
444
|
<button act open-fastening @click=${() => this.openFastening(asset)}>
|
|
446
|
-
|
|
445
|
+
<md-icon>link</md-icon>${i18next.t('figure.button.open-fastening')}
|
|
447
446
|
</button>
|
|
448
447
|
${joint
|
|
449
448
|
? html`<button act detach-held ?disabled=${this.locked} @click=${() => this.command({ kind: 'detach', part: this.partId })}>
|
|
450
|
-
|
|
449
|
+
<md-icon>link_off</md-icon>${i18next.t('figure.button.detach-held')}
|
|
451
450
|
</button>
|
|
452
451
|
${this.renderDetachWords()}`
|
|
453
452
|
: nothing}
|
|
@@ -569,17 +568,12 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
569
568
|
...(f.twist ? [i18next.t('figure.text.fasten-summary-twist', { degrees: String(f.twist) })] : [])
|
|
570
569
|
]
|
|
571
570
|
/*
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
571
|
+
Fastening it anew is said, so the form is not taken for the one it has (designer 2026-09-27). The one it has is
|
|
572
|
+
said once, by the sentence over the drawing -- the panel's head says it too; a third and a fourth line saying the
|
|
573
|
+
same (「현재 관계 수정」, 「붙은 곳」) went (user 2026-10-01, the panel checked for balance).
|
|
575
574
|
*/
|
|
576
575
|
const current = asset.joints?.find(j => j.body1.part === this.partId)
|
|
577
|
-
const editing =
|
|
578
|
-
? i18next.t('figure.text.fasten-editing', {
|
|
579
|
-
face: i18next.t(`figure.label.face-${current.body1.face}`),
|
|
580
|
-
target: 'plane' in current.body0 ? i18next.t('figure.label.mounting-plane') : `${current.body0.part} · ${i18next.t(`figure.label.face-${current.body0.face}`)}`
|
|
581
|
-
})
|
|
582
|
-
: i18next.t('figure.text.fasten-new')
|
|
576
|
+
const editing = i18next.t('figure.text.fasten-new')
|
|
583
577
|
/* A blank attachment is not a form to decipher. Ask for the thing to attach to first. */
|
|
584
578
|
if (!current && !this.choosingFastening && !f.target)
|
|
585
579
|
return html`
|
|
@@ -589,7 +583,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
589
583
|
</button>
|
|
590
584
|
`
|
|
591
585
|
return html`
|
|
592
|
-
|
|
586
|
+
${current ? nothing : html`<p fasten-mode>${editing}</p>`}
|
|
593
587
|
${this.overhangNote.length ? html`<p hint overhang-note>${this.overhangNote.join(' ')}</p>` : nothing}
|
|
594
588
|
${this.overhangWords.length
|
|
595
589
|
? html`<div overhang role="status">
|
|
@@ -600,13 +594,12 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
600
594
|
</div>`
|
|
601
595
|
: nothing}
|
|
602
596
|
${held
|
|
603
|
-
? html`<div
|
|
604
|
-
<span>${i18next.t('figure.label.fastened-to')}: <b bound>${'plane' in held ? i18next.t('figure.label.mounting-plane') : held.part}</b></span>
|
|
597
|
+
? html`<div fastened>
|
|
605
598
|
<button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'detach', part: this.partId })}>
|
|
606
599
|
<md-icon>link_off</md-icon>${i18next.t('figure.button.detach')}
|
|
607
600
|
</button>
|
|
608
|
-
|
|
609
|
-
|
|
601
|
+
${this.renderDetachWords()}
|
|
602
|
+
</div>`
|
|
610
603
|
: nothing}
|
|
611
604
|
<div form>
|
|
612
605
|
<span>${i18next.t('figure.label.target-part')}</span>
|
|
@@ -687,6 +680,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
687
680
|
}
|
|
688
681
|
|
|
689
682
|
private renderDetachWords() {
|
|
683
|
+
/* One thing to say is a line, not a list of one. */
|
|
684
|
+
if (this.detachWords.length === 1) return html`<p hint detach-words>${this.detachWords[0]}</p>`
|
|
690
685
|
return this.detachWords.length ? html`<ul detach-words>${this.detachWords.map(line => html`<li>${line}</li>`)}</ul>` : nothing
|
|
691
686
|
}
|
|
692
687
|
|
|
@@ -729,6 +724,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
729
724
|
* (the editor's) shows it and takes it away.
|
|
730
725
|
*/
|
|
731
726
|
private renderPreviewWords() {
|
|
727
|
+
if (this.previewWords.length === 1) return html`<p preview-words role="status">${this.previewWords[0]}</p>`
|
|
732
728
|
return this.previewWords.length ? html`<ul preview-words role="status">${this.previewWords.map(line => html`<li>${line}</li>`)}</ul>` : nothing
|
|
733
729
|
}
|
|
734
730
|
|
|
@@ -49,8 +49,16 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
49
49
|
display: grid;
|
|
50
50
|
gap: 4px;
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
/* A slider and its value on one line, the value at the end where the eye finishes. */
|
|
53
|
+
div[slider] {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: minmax(0, 1fr) 3em;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: 8px;
|
|
58
|
+
}
|
|
59
|
+
div[slider] output {
|
|
60
|
+
text-align: right;
|
|
61
|
+
font-variant-numeric: tabular-nums;
|
|
54
62
|
}
|
|
55
63
|
div[swatches] {
|
|
56
64
|
display: flex;
|
|
@@ -118,16 +126,14 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
118
126
|
fill: var(--md-sys-color-surface-container-lowest);
|
|
119
127
|
}
|
|
120
128
|
button[seg] b {
|
|
121
|
-
font-
|
|
129
|
+
font-weight: 600;
|
|
122
130
|
}
|
|
123
131
|
button[seg] small {
|
|
124
132
|
margin: 0;
|
|
125
|
-
font-size: 0.6rem;
|
|
126
133
|
}
|
|
127
134
|
details[free] summary {
|
|
128
135
|
margin-top: 4px;
|
|
129
136
|
cursor: pointer;
|
|
130
|
-
font-size: 0.68rem;
|
|
131
137
|
color: var(--md-sys-color-on-surface-variant);
|
|
132
138
|
}
|
|
133
139
|
div[presets] {
|
|
@@ -139,7 +145,6 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
139
145
|
flex: 1;
|
|
140
146
|
padding: 3px 0;
|
|
141
147
|
font: inherit;
|
|
142
|
-
font-size: 0.72rem;
|
|
143
148
|
color: var(--md-sys-color-on-surface);
|
|
144
149
|
background: none;
|
|
145
150
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
@@ -184,8 +189,8 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
184
189
|
: nothing}
|
|
185
190
|
<small><b>${purposeOf(material.token)}</b> — ${i18next.t('figure.text.palette-purpose-explained')}</small>
|
|
186
191
|
</div>
|
|
187
|
-
<div
|
|
188
|
-
<
|
|
192
|
+
<div form>
|
|
193
|
+
<span>${i18next.t('figure.label.preset')}</span>
|
|
189
194
|
<select
|
|
190
195
|
?disabled=${this.locked}
|
|
191
196
|
@change=${(e: Event) => this.put({ preset: ((e.target as HTMLSelectElement).value || undefined) as FigureMaterial['preset'] })}
|
|
@@ -205,8 +210,9 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
205
210
|
/>
|
|
206
211
|
${i18next.t('figure.label.transparent')}
|
|
207
212
|
</label>
|
|
208
|
-
<div
|
|
209
|
-
<
|
|
213
|
+
<div form>
|
|
214
|
+
<span>${i18next.t('figure.label.emissive')}</span>
|
|
215
|
+
<div slider>
|
|
210
216
|
<input
|
|
211
217
|
type="range"
|
|
212
218
|
min="0"
|
|
@@ -217,7 +223,8 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
217
223
|
@input=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value), true)}
|
|
218
224
|
@change=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value))}
|
|
219
225
|
/>
|
|
220
|
-
<
|
|
226
|
+
<output>${(this.glowing ?? glow?.intensity ?? 0).toFixed(2)}</output>
|
|
227
|
+
</div>
|
|
221
228
|
</div>
|
|
222
229
|
${glow
|
|
223
230
|
? html`<div field>
|
|
@@ -177,10 +177,10 @@ export class FigureProperties extends localize(i18next)(LitElement) {
|
|
|
177
177
|
</div>
|
|
178
178
|
<div row>
|
|
179
179
|
<button act ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part!, face })}>
|
|
180
|
-
|
|
180
|
+
<md-icon>check</md-icon>${i18next.t('figure.button.set-mounting-face')}
|
|
181
181
|
</button>
|
|
182
182
|
<button act ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>
|
|
183
|
-
|
|
183
|
+
<md-icon>close</md-icon>${i18next.t('figure.button.clear-mounting-face')}
|
|
184
184
|
</button>
|
|
185
185
|
</div>
|
|
186
186
|
<small>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js'
|
|
1
2
|
import { css, html, LitElement, nothing, type PropertyValues } from 'lit'
|
|
2
3
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
3
4
|
import { live } from 'lit/directives/live.js'
|
|
@@ -105,7 +106,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
105
106
|
${body.shape.dimensions.jamb === undefined
|
|
106
107
|
? html`<div field>
|
|
107
108
|
<button act ?disabled=${this.locked} @click=${() => this.dispatchEvent(new CustomEvent('section-editor-open', { bubbles: true, composed: true }))}>
|
|
108
|
-
|
|
109
|
+
<md-icon>edit</md-icon>${i18next.t('figure.button.section-editor-open')}
|
|
109
110
|
</button>
|
|
110
111
|
${e.section.kind === 'polygon'
|
|
111
112
|
? html`<small>${i18next.t(e.apex ? 'figure.text.section-points-apex' : 'figure.text.section-points', { points: String(e.section.points.length) })}</small>`
|
|
@@ -128,8 +129,9 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
128
129
|
.map(k => d[k])
|
|
129
130
|
const now = reaches.length ? sameOf(reaches) : 0
|
|
130
131
|
const max = cornerLimitOf(body.shape)
|
|
131
|
-
return html`<div
|
|
132
|
+
return html`<div form>
|
|
132
133
|
<label for="corner">${i18next.t('figure.label.section-corner')}</label>
|
|
134
|
+
<div>
|
|
133
135
|
${this.number('corner', now, {
|
|
134
136
|
min: 0,
|
|
135
137
|
ask: value => {
|
|
@@ -140,6 +142,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
140
142
|
placeholder: now === undefined ? i18next.t('figure.text.section-corner-differs') : ''
|
|
141
143
|
})}
|
|
142
144
|
${this.refusals.corner ? nothing : max !== undefined ? html`<small>${i18next.t('figure.text.section-at-most', { max: String(mm(max)) })}</small>` : nothing}
|
|
145
|
+
</div>
|
|
143
146
|
</div>`
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -167,7 +170,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
167
170
|
action: value => ({ kind: 'set-cavity', part: body.id, cavity: request(patch(value)) })
|
|
168
171
|
})
|
|
169
172
|
return html`<div field>
|
|
170
|
-
<label>
|
|
173
|
+
<label toggle>
|
|
171
174
|
<input
|
|
172
175
|
type="checkbox"
|
|
173
176
|
.checked=${live(!!cavity)}
|
|
@@ -191,8 +194,9 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
191
194
|
</div>
|
|
192
195
|
${cavity ? html`<figure-cavity-drawing .body=${body} .perSide=${this.perSide} ?locked=${this.locked}></figure-cavity-drawing>` : nothing}
|
|
193
196
|
${cavity
|
|
194
|
-
? html`<div
|
|
197
|
+
? html`<div form>
|
|
195
198
|
<label>${i18next.t('figure.label.section-wall')}</label>
|
|
199
|
+
<div>
|
|
196
200
|
${this.perSide
|
|
197
201
|
? html`<div sides>
|
|
198
202
|
${[...sidesOf(axis)].sort((a, b) => a[0]!.localeCompare(b[0]!) || (a[1] === '-' ? -1 : 1)).map(
|
|
@@ -206,7 +210,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
206
210
|
${this.perSide || this.refusals.wall
|
|
207
211
|
? nothing
|
|
208
212
|
: html`<small>${i18next.t('figure.text.section-wall-at-most', { max: String(mm(wallLimitOf(body.shape))) })}</small>`}
|
|
209
|
-
<label>
|
|
213
|
+
<label toggle>
|
|
210
214
|
<input
|
|
211
215
|
type="checkbox"
|
|
212
216
|
.checked=${live(this.perSide)}
|
|
@@ -215,6 +219,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
215
219
|
/>
|
|
216
220
|
${i18next.t('figure.label.section-wall-per-side')}
|
|
217
221
|
</label>
|
|
222
|
+
</div>
|
|
218
223
|
</div>
|
|
219
224
|
${(['start', 'end'] as const).map(end => this.renderEnd(body, axis, end, cavity[end], request, cavityField))}`
|
|
220
225
|
: nothing}`
|
|
@@ -229,7 +234,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
229
234
|
cavityField: (key: string, now: number | undefined, patch: (value: number) => Partial<FigureCavityRequest>) => unknown
|
|
230
235
|
) {
|
|
231
236
|
const closed = now !== 'open'
|
|
232
|
-
return html`<div
|
|
237
|
+
return html`<div form>
|
|
233
238
|
<label>${i18next.t('figure.label.face-' + END_FACE(axis, end))}</label>
|
|
234
239
|
<div row>
|
|
235
240
|
<select
|
|
@@ -10,7 +10,11 @@ import { css } from 'lit'
|
|
|
10
10
|
*
|
|
11
11
|
* The vocabulary:
|
|
12
12
|
* section one titled group; the side is a stack of them
|
|
13
|
-
* h3 a group's title
|
|
13
|
+
* h3 a group's title
|
|
14
|
+
*
|
|
15
|
+
* Three sizes and two weights (user 2026-10-01, the panel checked for balance: eight sizes, and titles smaller than
|
|
16
|
+
* what they titled): a title at the body token and semibold, everything else at the small token, and a hint at the
|
|
17
|
+
* micro one. Sections are 16 px above their title and 12 px below their last line.
|
|
14
18
|
* div[inline-field] a short label and its one field on one line
|
|
15
19
|
* div[field] a label above a full-width field
|
|
16
20
|
* div[form] short labels beside their fields, the fields starting on one line
|
|
@@ -25,14 +29,14 @@ import { css } from 'lit'
|
|
|
25
29
|
export const SidePanelStyles = css`
|
|
26
30
|
:host {
|
|
27
31
|
--figure-control-height: 30px;
|
|
28
|
-
font-size:
|
|
32
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
29
33
|
color: var(--md-sys-color-on-surface);
|
|
30
34
|
}
|
|
31
35
|
/* The side is a stack of sections, each a titled group. */
|
|
32
36
|
section {
|
|
33
37
|
display: grid;
|
|
34
|
-
gap:
|
|
35
|
-
padding:
|
|
38
|
+
gap: 8px;
|
|
39
|
+
padding: 16px 12px 12px;
|
|
36
40
|
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
37
41
|
}
|
|
38
42
|
section > h3 {
|
|
@@ -48,18 +52,21 @@ export const SidePanelStyles = css`
|
|
|
48
52
|
align-items: center;
|
|
49
53
|
gap: 4px;
|
|
50
54
|
margin: 14px 0 6px;
|
|
51
|
-
font-size: var(--ui-t-
|
|
52
|
-
font-weight:
|
|
53
|
-
|
|
54
|
-
text-transform: uppercase;
|
|
55
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
55
|
+
font-size: var(--ui-t-body, 13px);
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
color: var(--md-sys-color-on-surface);
|
|
56
58
|
}
|
|
57
59
|
h3:first-child {
|
|
58
60
|
margin-top: 0;
|
|
59
61
|
}
|
|
62
|
+
/* Two weights only: a bold word in a line is the title's weight, not the browser's heavier one. */
|
|
63
|
+
b,
|
|
64
|
+
strong {
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
}
|
|
60
67
|
label,
|
|
61
68
|
div[form] > span {
|
|
62
|
-
font-size:
|
|
69
|
+
font-size: inherit;
|
|
63
70
|
color: var(--md-sys-color-on-surface-variant);
|
|
64
71
|
}
|
|
65
72
|
div[field] {
|
|
@@ -90,7 +97,7 @@ export const SidePanelStyles = css`
|
|
|
90
97
|
}
|
|
91
98
|
/* A value read, not edited, beside its label: the field's size and colour, not the page's. */
|
|
92
99
|
div[inline-field] > span[value] {
|
|
93
|
-
font-size:
|
|
100
|
+
font-size: inherit;
|
|
94
101
|
color: var(--md-sys-color-on-surface);
|
|
95
102
|
}
|
|
96
103
|
label span[unit] {
|
|
@@ -108,10 +115,9 @@ export const SidePanelStyles = css`
|
|
|
108
115
|
gap: 4px;
|
|
109
116
|
}
|
|
110
117
|
div[tools] button {
|
|
111
|
-
height:
|
|
118
|
+
height: var(--figure-control-height);
|
|
112
119
|
padding: 0 10px;
|
|
113
120
|
font: inherit;
|
|
114
|
-
font-size: 0.78rem;
|
|
115
121
|
color: var(--md-sys-color-on-surface);
|
|
116
122
|
background: var(--md-sys-color-surface-container-lowest);
|
|
117
123
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
@@ -146,16 +152,45 @@ export const SidePanelStyles = css`
|
|
|
146
152
|
box-sizing: border-box;
|
|
147
153
|
padding: 4px 7px;
|
|
148
154
|
font: inherit;
|
|
149
|
-
font-size: 0.76rem;
|
|
150
155
|
color: var(--md-sys-color-on-surface);
|
|
151
156
|
background-color: var(--md-sys-color-surface-container-lowest);
|
|
152
157
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
153
158
|
border-radius: 6px;
|
|
154
159
|
}
|
|
160
|
+
/* The browser's own box and dot, in the panel's colour. */
|
|
155
161
|
input[type='checkbox'],
|
|
156
162
|
input[type='radio'] {
|
|
157
163
|
width: auto;
|
|
158
164
|
min-height: 0;
|
|
165
|
+
margin: 0;
|
|
166
|
+
accent-color: var(--md-sys-color-primary);
|
|
167
|
+
}
|
|
168
|
+
/* A fold: the panel's chevron, not the browser's triangle. */
|
|
169
|
+
summary {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
gap: 6px;
|
|
173
|
+
padding: 6px 0;
|
|
174
|
+
list-style: none;
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
177
|
+
}
|
|
178
|
+
summary::-webkit-details-marker {
|
|
179
|
+
display: none;
|
|
180
|
+
}
|
|
181
|
+
summary::before {
|
|
182
|
+
content: '';
|
|
183
|
+
flex: none;
|
|
184
|
+
width: 6px;
|
|
185
|
+
height: 6px;
|
|
186
|
+
margin: 0 2px;
|
|
187
|
+
border-right: 1.5px solid currentColor;
|
|
188
|
+
border-bottom: 1.5px solid currentColor;
|
|
189
|
+
transform: rotate(-45deg);
|
|
190
|
+
transition: transform 0.15s;
|
|
191
|
+
}
|
|
192
|
+
details[open] > summary::before {
|
|
193
|
+
transform: rotate(45deg);
|
|
159
194
|
}
|
|
160
195
|
/* A thin track and a small thumb: a slider is a line to drag along, not a block (user, 2026-09-24). */
|
|
161
196
|
input[type='range'] {
|
|
@@ -239,7 +274,7 @@ export const SidePanelStyles = css`
|
|
|
239
274
|
}
|
|
240
275
|
div[cell] span[axis] {
|
|
241
276
|
flex: none;
|
|
242
|
-
font-size:
|
|
277
|
+
font-size: var(--ui-t-micro, 11px);
|
|
243
278
|
letter-spacing: 0.02em;
|
|
244
279
|
color: var(--md-sys-color-on-surface-variant);
|
|
245
280
|
}
|
|
@@ -272,7 +307,6 @@ export const SidePanelStyles = css`
|
|
|
272
307
|
display: flex;
|
|
273
308
|
align-items: center;
|
|
274
309
|
gap: 6px;
|
|
275
|
-
font-size: 0.76rem;
|
|
276
310
|
color: var(--md-sys-color-on-surface);
|
|
277
311
|
}
|
|
278
312
|
/* The 「?」 beside a title: no border, it says there is more rather than asking to be pressed. */
|
|
@@ -299,41 +333,42 @@ export const SidePanelStyles = css`
|
|
|
299
333
|
small {
|
|
300
334
|
display: block;
|
|
301
335
|
margin: 2px 0 4px;
|
|
302
|
-
font-size:
|
|
336
|
+
font-size: var(--ui-t-micro, 11px);
|
|
303
337
|
line-height: 1.5;
|
|
304
338
|
color: var(--md-sys-color-on-surface-variant);
|
|
305
339
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
340
|
+
/*
|
|
341
|
+
* An action under its fields: as wide as its words, not a band across the panel -- four full-width grey bars read as
|
|
342
|
+
* four walls and none of them as the one to press (user 2026-10-01). Every one carries its icon.
|
|
343
|
+
*/
|
|
310
344
|
button[act] {
|
|
311
345
|
display: inline-flex;
|
|
312
346
|
align-items: center;
|
|
313
347
|
justify-content: center;
|
|
314
|
-
|
|
315
|
-
|
|
348
|
+
justify-self: start;
|
|
349
|
+
gap: 6px;
|
|
350
|
+
width: auto;
|
|
351
|
+
max-width: 100%;
|
|
316
352
|
margin-top: 4px;
|
|
317
|
-
padding:
|
|
318
|
-
min-height:
|
|
353
|
+
padding: 0 12px;
|
|
354
|
+
min-height: var(--figure-control-height);
|
|
319
355
|
font: inherit;
|
|
320
|
-
|
|
321
|
-
color: var(--md-sys-color-
|
|
322
|
-
background-color: var(--md-sys-color-surface-container);
|
|
356
|
+
color: var(--md-sys-color-primary);
|
|
357
|
+
background-color: var(--md-sys-color-surface-container-lowest);
|
|
323
358
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
324
359
|
border-radius: 6px;
|
|
325
360
|
cursor: pointer;
|
|
326
361
|
}
|
|
327
362
|
button[act]:hover:not(:disabled) {
|
|
328
|
-
background-color: var(--md-sys-color-
|
|
363
|
+
background-color: var(--md-sys-color-primary-container);
|
|
329
364
|
}
|
|
330
365
|
button[act]:disabled {
|
|
331
366
|
opacity: 0.5;
|
|
332
367
|
cursor: default;
|
|
333
368
|
}
|
|
334
369
|
button[act] md-icon {
|
|
335
|
-
font-size:
|
|
336
|
-
width:
|
|
337
|
-
height:
|
|
370
|
+
font-size: 16px;
|
|
371
|
+
width: 16px;
|
|
372
|
+
height: 16px;
|
|
338
373
|
}
|
|
339
374
|
`
|
|
@@ -328,6 +328,11 @@ export declare class FigureEditor extends LitElement {
|
|
|
328
328
|
*/
|
|
329
329
|
private withSizeRow;
|
|
330
330
|
private renderVector;
|
|
331
|
+
/**
|
|
332
|
+
* Which axes a rule sets, said once under the rows (position and rotation X · Y · Z set by a rule), not a line under each row
|
|
333
|
+
* saying the same (user 2026-10-01, the panel checked for balance). The cells themselves are dashed and read-only.
|
|
334
|
+
*/
|
|
335
|
+
private renderHeldAxes;
|
|
331
336
|
/**
|
|
332
337
|
* A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
|
|
333
338
|
* typed into -- they move nothing. Declaring the room again is offered; nothing is deleted or widened on the way.
|