@things-factory/figure-ui 10.1.103 → 10.1.105
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 +2 -2
- package/client/modeller/fields.ts +7 -1
- package/client/modeller/figure-cavity-drawing.ts +65 -9
- package/client/modeller/figure-material-panel.ts +0 -1
- package/client/modeller/figure-section-panel.ts +15 -103
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/editor.js +2 -2
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fields.js +7 -1
- package/dist-client/modeller/fields.js.map +1 -1
- package/dist-client/modeller/figure-cavity-drawing.d.ts +6 -0
- package/dist-client/modeller/figure-cavity-drawing.js +68 -9
- package/dist-client/modeller/figure-cavity-drawing.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +0 -1
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.d.ts +0 -1
- package/dist-client/modeller/figure-section-panel.js +14 -73
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/test/cavity-drawing.test.ts +5 -1
- package/test/section-editor.test.ts +3 -1
- package/test/section-panel.test.ts +5 -3
- package/translations/en.json +0 -1
- package/translations/ja.json +0 -1
- package/translations/ko.json +0 -1
- package/translations/ms.json +0 -1
- package/translations/zh.json +0 -1
|
@@ -1037,13 +1037,13 @@ export class FigureEditor extends LitElement {
|
|
|
1037
1037
|
<label>${i18next.t('figure.label.mirror-copy')}</label>
|
|
1038
1038
|
<div tools>
|
|
1039
1039
|
${mirrors.map(
|
|
1040
|
-
({ axis, able }) => html`<button ?disabled=${unavailable || !able} title=${i18next.t('figure.text.mirror-explained')} @click=${() => this.mirror(axis)}>
|
|
1040
|
+
({ axis, able }) => html`<button ?disabled=${unavailable || !able} title=${i18next.t(able ? 'figure.text.mirror-explained' : 'figure.text.mirror-would-overlap')} @click=${() => this.mirror(axis)}>
|
|
1041
1041
|
${i18next.t('figure.button.mirror-' + axis)}
|
|
1042
1042
|
</button>`
|
|
1043
1043
|
)}
|
|
1044
1044
|
</div>
|
|
1045
1045
|
</div>
|
|
1046
|
-
|
|
1046
|
+
`
|
|
1047
1047
|
}
|
|
1048
1048
|
/**
|
|
1049
1049
|
* A part's name. It renames every value the part owns, and what refers to them follows
|
|
@@ -50,7 +50,13 @@ export function groupFields(fields: readonly FieldLike[], part?: string): { vect
|
|
|
50
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
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
52
|
*/
|
|
53
|
-
|
|
53
|
+
/*
|
|
54
|
+
The section's own numbers -- its corners, its inside's walls and closed ends -- are edited in the 단면 group, where
|
|
55
|
+
they are drawn (user 2026-10-01: 「wall (mm)」, 「closed · start (mm)」 stood in the shape's list under their
|
|
56
|
+
inner names).
|
|
57
|
+
*/
|
|
58
|
+
const ofSection = (id: string) => /^(dimension\.)?(round|roundX|roundZ|wall|wallX|wallZ|floor|closed|corner)(\.|$)/.test(id.slice(prefix.length))
|
|
59
|
+
const drawn = (id: string) => /^(point\.[^.]+|apex)\.[xyz]$/.test(id.slice(prefix.length)) || ofSection(id)
|
|
54
60
|
const singles: FieldGroup[] = own
|
|
55
61
|
.filter(f => !used.has(f.id) && !drawn(f.id))
|
|
56
62
|
.map(field => ({ kind: 'single', name: nameOf(field.id.slice(prefix.length)), field }))
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { css, html, LitElement, nothing, svg } from 'lit'
|
|
2
2
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
3
|
+
import { ref } from 'lit/directives/ref.js'
|
|
3
4
|
|
|
4
5
|
import { i18next, localize } from '@operato/i18n'
|
|
5
6
|
import { figureCavitySectionOf } from '@hatiolab/figure-model'
|
|
6
7
|
import type { FigureAuthoringAction, FigureCavityRequest, FigureEvaluatedShape, FigureSection } from '@hatiolab/figure-model'
|
|
7
|
-
import { cavityRefusalOf, cavityWorkingOf, END_FACE, endToggled, mm, SIDE_FACE, wallDragged } from './section-controls.js'
|
|
8
|
+
import { cavityRefusalOf, cavityWorkingOf, END_FACE, endToggled, mm, SIDE_FACE, sideWallLimitOf, wallDragged } from './section-controls.js'
|
|
8
9
|
|
|
9
10
|
type Axis = 'x' | 'y' | 'z'
|
|
11
|
+
|
|
12
|
+
/** A field opened by a press takes the keys at once. */
|
|
13
|
+
const focusOnRender = () => ref(el => el && requestAnimationFrame(() => (el as HTMLInputElement).select()))
|
|
10
14
|
type End = 'start' | 'end'
|
|
11
15
|
type Body = { id: string; shape: FigureEvaluatedShape }
|
|
12
16
|
|
|
@@ -41,6 +45,19 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
41
45
|
small[refusal] {
|
|
42
46
|
color: var(--md-sys-color-error);
|
|
43
47
|
}
|
|
48
|
+
div[pane] {
|
|
49
|
+
position: relative;
|
|
50
|
+
}
|
|
51
|
+
div[pane] input {
|
|
52
|
+
position: absolute;
|
|
53
|
+
width: 64px;
|
|
54
|
+
transform: translate(-50%, -50%);
|
|
55
|
+
font: inherit;
|
|
56
|
+
}
|
|
57
|
+
text[typed] {
|
|
58
|
+
cursor: text;
|
|
59
|
+
text-decoration: underline dotted;
|
|
60
|
+
}
|
|
44
61
|
`
|
|
45
62
|
|
|
46
63
|
@property({ attribute: false }) body?: Body
|
|
@@ -52,6 +69,8 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
52
69
|
@state() private working?: ReturnType<typeof cavityWorkingOf>
|
|
53
70
|
@state() private refusal = ''
|
|
54
71
|
private dragging: { kind: 'wall'; side: string } | { kind: 'end'; end: End } | null = null
|
|
72
|
+
/** A number pressed to be typed: which, and where on its picture (percent of the picture's box). */
|
|
73
|
+
@state() private typing?: ({ kind: 'wall'; side: string } | { kind: 'end'; end: End }) & { x: number; y: number; pane: 'top' | 'side'; value: number }
|
|
55
74
|
private pressedAt = 0
|
|
56
75
|
|
|
57
76
|
private get now() {
|
|
@@ -66,18 +85,22 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
66
85
|
return { kind: 'set-cavity', part: this.body!.id, cavity: this.request(w) }
|
|
67
86
|
}
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
/** What the kernel would refuse, in words; for one side's wall, the most that side can be with the opposite kept. */
|
|
89
|
+
private judge(w: NonNullable<ReturnType<typeof cavityWorkingOf>>, side?: string): string {
|
|
70
90
|
const ask = cavityRefusalOf(this.body!.shape, this.request(w))
|
|
71
91
|
if (!ask) return ''
|
|
72
|
-
if (ask.code === 'wall-too-thick')
|
|
92
|
+
if (ask.code === 'wall-too-thick')
|
|
93
|
+
return i18next.t('figure.text.section-wall-over', {
|
|
94
|
+
max: String(this.perSide && side ? mm(sideWallLimitOf(this.body!.shape, w.wall, side)) : ask.max)
|
|
95
|
+
})
|
|
73
96
|
if (ask.code === 'ends-too-thick') return i18next.t('figure.text.section-ends-over', { max: String(ask.max) })
|
|
74
97
|
return i18next.t('figure.text.section-not-positive')
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
/** The working inside while dragging: drawn, judged, and previewed behind when the kernel would take it. */
|
|
78
|
-
private hold(w: NonNullable<ReturnType<typeof cavityWorkingOf
|
|
101
|
+
private hold(w: NonNullable<ReturnType<typeof cavityWorkingOf>>, side?: string) {
|
|
79
102
|
this.working = w
|
|
80
|
-
this.refusal = this.judge(w)
|
|
103
|
+
this.refusal = this.judge(w, side)
|
|
81
104
|
this.emit('command-live', this.refusal ? null : this.action(w))
|
|
82
105
|
}
|
|
83
106
|
|
|
@@ -109,10 +132,41 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
109
132
|
const e = this.body?.shape.extrusion
|
|
110
133
|
const w = this.now
|
|
111
134
|
if (!e || !w || (e.section.kind !== 'rectangle' && e.section.kind !== 'ellipse')) return nothing
|
|
112
|
-
return html
|
|
135
|
+
return html`<div pane>${this.top(e, w)}${this.typing?.pane === 'top' ? this.renderTyping() : nothing}</div>
|
|
136
|
+
<div pane>${this.side(e, w)}${this.typing?.pane === 'side' ? this.renderTyping() : nothing}</div>
|
|
113
137
|
${this.refusal ? html`<small refusal role="alert">${this.refusal}</small>` : nothing}`
|
|
114
138
|
}
|
|
115
139
|
|
|
140
|
+
/** Press a number, type it, Enter: the same one set-cavity a drag sends; Esc or leaving puts it back. */
|
|
141
|
+
private type(ev: PointerEvent, what: { kind: 'wall'; side: string } | { kind: 'end'; end: End }, pane: 'top' | 'side', value: number) {
|
|
142
|
+
if (this.locked) return
|
|
143
|
+
ev.stopPropagation()
|
|
144
|
+
const svgEl = (ev.target as Element).closest('svg')!
|
|
145
|
+
const box = svgEl.getBoundingClientRect()
|
|
146
|
+
this.refusal = ''
|
|
147
|
+
this.typing = { ...what, pane, value, x: ((ev.clientX - box.left) / box.width) * 100, y: ((ev.clientY - box.top) / box.height) * 100 }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private renderTyping() {
|
|
151
|
+
const t = this.typing!
|
|
152
|
+
return html`<input type="number" step="1" min="1" .value=${String(t.value)} style=${`left:${t.x}%;top:${t.y}%`}
|
|
153
|
+
aria-label=${t.kind === 'wall' ? i18next.t('figure.label.section-wall') : i18next.t('figure.label.face-' + END_FACE(this.body!.shape.extrusion!.axis, t.end))}
|
|
154
|
+
@keydown=${(ev: KeyboardEvent) => {
|
|
155
|
+
ev.stopPropagation()
|
|
156
|
+
if (ev.key === 'Escape') this.typing = undefined
|
|
157
|
+
if (ev.key !== 'Enter') return
|
|
158
|
+
const value = Number((ev.target as HTMLInputElement).value)
|
|
159
|
+
this.typing = undefined
|
|
160
|
+
if (!(Number.isFinite(value) && value > 0)) return
|
|
161
|
+
const now = this.now!
|
|
162
|
+
const next = t.kind === 'wall' ? { ...now, wall: wallDragged(now.wall, t.side, value, this.perSide) } : { ...now, [t.end]: { closed: Math.round(value) } }
|
|
163
|
+
this.refusal = this.judge(next, t.kind === 'wall' ? t.side : undefined)
|
|
164
|
+
if (!this.refusal) this.emit('command', this.action(next))
|
|
165
|
+
}}
|
|
166
|
+
@blur=${() => (this.typing = undefined)}
|
|
167
|
+
${focusOnRender()} />`
|
|
168
|
+
}
|
|
169
|
+
|
|
116
170
|
/** Seen along the push: the section, its inside and a handle on each wall. */
|
|
117
171
|
private top(e: NonNullable<FigureEvaluatedShape['extrusion']>, w: NonNullable<ReturnType<typeof cavityWorkingOf>>) {
|
|
118
172
|
const [h, v] = SCREEN[e.axis]
|
|
@@ -150,7 +204,8 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
150
204
|
return svg`<g wall=${side} style=${`cursor:${a === h ? 'ew-resize' : 'ns-resize'}`}
|
|
151
205
|
@pointerdown=${(ev: PointerEvent) => this.grab(ev, { kind: 'wall', side })}>
|
|
152
206
|
<rect x=${x - 6} y=${y - 6} width="12" height="12" rx="2" style="fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-primary);stroke-width:1.5" />
|
|
153
|
-
<text x=${tx} y=${ty} font-size="11" text-anchor=${a === h ? 'middle' : 'start'} style="fill:var(--md-sys-color-on-surface)"
|
|
207
|
+
<text typed x=${tx} y=${ty} font-size="11" text-anchor=${a === h ? 'middle' : 'start'} style="fill:var(--md-sys-color-on-surface)"
|
|
208
|
+
@pointerdown=${(ev: PointerEvent) => this.type(ev, { kind: 'wall', side }, 'top', w.wall[side]!)}>${mm(w.wall[side]!)}</text>
|
|
154
209
|
<title>${i18next.t('figure.label.face-' + SIDE_FACE[side])} ${i18next.t('figure.label.section-wall')}</title>
|
|
155
210
|
</g>`
|
|
156
211
|
})
|
|
@@ -198,7 +253,8 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
198
253
|
@pointerdown=${(ev: PointerEvent) => this.grab(ev, { kind: 'end', end })}>
|
|
199
254
|
<rect x=${x0 + wl} y=${Math.min(y, inner) - 8} width=${x1 - x0 - wl - wr} height=${Math.abs(t) + 16} style="fill:transparent" />
|
|
200
255
|
${open ? svg`<line x1=${x0 + wl} y1=${y} x2=${x1 - wr} y2=${y} style="stroke:var(--md-sys-color-outline);stroke-dasharray:3 3" />` : nothing}
|
|
201
|
-
<text x=${W / 2} y=${end === 'start' ? yBot + 16 : yTop - 8} font-size="11" text-anchor="middle" style="fill:var(--md-sys-color-primary)"
|
|
256
|
+
<text ?typed=${!open} x=${W / 2} y=${end === 'start' ? yBot + 16 : yTop - 8} font-size="11" text-anchor="middle" style="fill:var(--md-sys-color-primary)"
|
|
257
|
+
@pointerdown=${(ev: PointerEvent) => !open && this.type(ev, { kind: 'end', end }, 'side', (w[end] as { closed: number }).closed)}>${label}</text>
|
|
202
258
|
<title>${label}</title>
|
|
203
259
|
</g>`
|
|
204
260
|
}
|
|
@@ -231,7 +287,7 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
231
287
|
low = d.side[1] === '-'
|
|
232
288
|
const at = a === h ? p.x - ox : p.y - oy
|
|
233
289
|
const value = (half[a] - (low ? -at : at)) / k
|
|
234
|
-
this.hold({ ...this.now!, wall: wallDragged(this.now!.wall, d.side, value, this.perSide) })
|
|
290
|
+
this.hold({ ...this.now!, wall: wallDragged(this.now!.wall, d.side, value, this.perSide) }, d.side)
|
|
235
291
|
}
|
|
236
292
|
|
|
237
293
|
private dragEnd(ev: PointerEvent, k: number, yTop: number, yBot: number) {
|
|
@@ -187,7 +187,6 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
187
187
|
? html`<small>${i18next.t('figure.label.status-colors')}</small>
|
|
188
188
|
<div swatches>${this.swatches(lit, material.token, token => this.put({ token }))}</div>`
|
|
189
189
|
: nothing}
|
|
190
|
-
<small><b>${purposeOf(material.token)}</b> — ${i18next.t('figure.text.palette-purpose-explained')}</small>
|
|
191
190
|
</div>
|
|
192
191
|
<div form>
|
|
193
192
|
<span>${i18next.t('figure.label.preset')}</span>
|
|
@@ -4,30 +4,12 @@ import { customElement, property, state } from 'lit/decorators.js'
|
|
|
4
4
|
import { live } from 'lit/directives/live.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
-
import type { FigureAuthoringAction,
|
|
7
|
+
import type { FigureAuthoringAction, FigureEvaluatedShape } from '@hatiolab/figure-model'
|
|
8
8
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
9
9
|
import { drawnOf } from './section-drawing.js'
|
|
10
10
|
import './figure-cavity-drawing.js'
|
|
11
|
-
import {
|
|
12
|
-
END_FACE,
|
|
13
|
-
SIDE_FACE,
|
|
14
|
-
cavityDepthOf,
|
|
15
|
-
cavityRefusalOf,
|
|
16
|
-
cavityRequestOf,
|
|
17
|
-
cavityResultOf,
|
|
18
|
-
cornerLimitOf,
|
|
19
|
-
cornerRefusalOf,
|
|
20
|
-
defaultWallOf,
|
|
21
|
-
mm,
|
|
22
|
-
sameOf,
|
|
23
|
-
sidesOf,
|
|
24
|
-
sideWallLimitOf,
|
|
25
|
-
wallLimitOf,
|
|
26
|
-
type FigureCavityAsk
|
|
27
|
-
} from './section-controls.js'
|
|
11
|
+
import { cavityDepthOf, cavityRequestOf, cavityResultOf, cornerLimitOf, cornerRefusalOf, mm, sameOf } from './section-controls.js'
|
|
28
12
|
|
|
29
|
-
type Axis = 'x' | 'y' | 'z'
|
|
30
|
-
type End = 'start' | 'end'
|
|
31
13
|
type Body = { id: string; shape: FigureEvaluatedShape }
|
|
32
14
|
|
|
33
15
|
/**
|
|
@@ -114,7 +96,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
114
96
|
</div>`
|
|
115
97
|
: nothing}
|
|
116
98
|
${kind === 'rectangle' ? this.renderCorner(body) : nothing}
|
|
117
|
-
${/* No inside for a drawn section: the control is not there, rather than a line saying so. */ kind === 'rectangle' || kind === 'ellipse' ? this.renderCavity(body
|
|
99
|
+
${/* No inside for a drawn section: the control is not there, rather than a line saying so. */ kind === 'rectangle' || kind === 'ellipse' ? this.renderCavity(body) : nothing}
|
|
118
100
|
</section>`
|
|
119
101
|
}
|
|
120
102
|
|
|
@@ -142,29 +124,13 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
142
124
|
</div>`
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
|
|
127
|
+
/*
|
|
128
|
+
* The inside: made or taken away here, and drawn -- walls dragged or their number pressed and typed, ends pressed
|
|
129
|
+
* open or closed (figure-cavity-drawing). The number fields under the drawing went (user 2026-10-01): they said the
|
|
130
|
+
* drawing again.
|
|
131
|
+
*/
|
|
132
|
+
private renderCavity(body: Body) {
|
|
146
133
|
const cavity = body.shape.cavity
|
|
147
|
-
const request = (patch: Partial<FigureCavityRequest>) => cavityRequestOf(body.shape, patch)
|
|
148
|
-
const said = (ask: FigureCavityAsk | null) =>
|
|
149
|
-
!ask
|
|
150
|
-
? ''
|
|
151
|
-
: ask.code === 'not-positive'
|
|
152
|
-
? i18next.t('figure.text.section-not-positive')
|
|
153
|
-
: i18next.t(`figure.text.section-${ask.code === 'wall-too-thick' ? 'wall' : 'ends'}-over`, { max: String(ask.max) })
|
|
154
|
-
const cavityField = (key: string, now: number | undefined, patch: (value: number) => Partial<FigureCavityRequest>) =>
|
|
155
|
-
this.number(key, now, {
|
|
156
|
-
min: 0,
|
|
157
|
-
ask: value => {
|
|
158
|
-
if (!(value > 0)) return i18next.t('figure.text.section-not-positive')
|
|
159
|
-
const ask = cavityRefusalOf(body.shape, request(patch(value)))
|
|
160
|
-
/* One side too thick: the most it can be is the width across less the wall opposite, not the even limit. */
|
|
161
|
-
const side = key.startsWith('wall.') ? key.slice(5) : undefined
|
|
162
|
-
if (ask?.code === 'wall-too-thick' && side && cavity)
|
|
163
|
-
return said({ code: 'wall-too-thick', max: mm(sideWallLimitOf(body.shape, cavity.wall, side)) })
|
|
164
|
-
return said(ask)
|
|
165
|
-
},
|
|
166
|
-
action: value => ({ kind: 'set-cavity', part: body.id, cavity: request(patch(value)) })
|
|
167
|
-
})
|
|
168
134
|
return html`<div field>
|
|
169
135
|
<label toggle>
|
|
170
136
|
<input
|
|
@@ -174,7 +140,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
174
140
|
@change=${(ev: Event) => {
|
|
175
141
|
const on = (ev.target as HTMLInputElement).checked
|
|
176
142
|
;(ev.target as HTMLInputElement).checked = !!cavity
|
|
177
|
-
this.command({ kind: 'set-cavity', part: body.id, cavity: on ?
|
|
143
|
+
this.command({ kind: 'set-cavity', part: body.id, cavity: on ? cavityRequestOf(body.shape, {}) : null })
|
|
178
144
|
}}
|
|
179
145
|
/>
|
|
180
146
|
${i18next.t('figure.label.section-cavity')}
|
|
@@ -188,69 +154,15 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
188
154
|
</small>`
|
|
189
155
|
: nothing}
|
|
190
156
|
</div>
|
|
191
|
-
${cavity ? html`<figure-cavity-drawing .body=${body} .perSide=${this.perSide} ?locked=${this.locked}></figure-cavity-drawing>` : nothing}
|
|
192
157
|
${cavity
|
|
193
|
-
? html`<
|
|
194
|
-
|
|
195
|
-
<
|
|
196
|
-
${
|
|
197
|
-
|
|
198
|
-
${[...sidesOf(axis)].sort((a, b) => a[0]!.localeCompare(b[0]!) || (a[1] === '-' ? -1 : 1)).map(
|
|
199
|
-
side => html`<label>
|
|
200
|
-
<span>${i18next.t('figure.label.face-' + SIDE_FACE[side])}</span>
|
|
201
|
-
${cavityField(`wall.${side}`, cavity.wall[side], value => ({ wall: { ...cavity.wall, [side]: value } }))}
|
|
202
|
-
</label>`
|
|
203
|
-
)}
|
|
204
|
-
</div>`
|
|
205
|
-
: cavityField('wall', sameOf(Object.values(cavity.wall)), value => ({ wall: value }))}
|
|
206
|
-
${this.perSide || this.refusals.wall
|
|
207
|
-
? nothing
|
|
208
|
-
: html`<small>${i18next.t('figure.text.section-wall-at-most', { max: String(mm(wallLimitOf(body.shape))) })}</small>`}
|
|
209
|
-
<label toggle>
|
|
210
|
-
<input
|
|
211
|
-
type="checkbox"
|
|
212
|
-
.checked=${live(this.perSide)}
|
|
213
|
-
?disabled=${this.locked}
|
|
214
|
-
@change=${(ev: Event) => (this.perSide = (ev.target as HTMLInputElement).checked)}
|
|
215
|
-
/>
|
|
216
|
-
${i18next.t('figure.label.section-wall-per-side')}
|
|
217
|
-
</label>
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
${(['start', 'end'] as const).map(end => this.renderEnd(body, axis, end, cavity[end], request, cavityField))}`
|
|
158
|
+
? html`<figure-cavity-drawing .body=${body} .perSide=${this.perSide} ?locked=${this.locked}></figure-cavity-drawing>
|
|
159
|
+
<label toggle>
|
|
160
|
+
<input type="checkbox" .checked=${live(this.perSide)} ?disabled=${this.locked} @change=${(ev: Event) => (this.perSide = (ev.target as HTMLInputElement).checked)} />
|
|
161
|
+
${i18next.t('figure.label.section-wall-per-side')}
|
|
162
|
+
</label>`
|
|
221
163
|
: nothing}`
|
|
222
164
|
}
|
|
223
165
|
|
|
224
|
-
private renderEnd(
|
|
225
|
-
body: Body,
|
|
226
|
-
axis: Axis,
|
|
227
|
-
end: End,
|
|
228
|
-
now: 'open' | { closed: number },
|
|
229
|
-
request: (patch: Partial<FigureCavityRequest>) => FigureCavityRequest,
|
|
230
|
-
cavityField: (key: string, now: number | undefined, patch: (value: number) => Partial<FigureCavityRequest>) => unknown
|
|
231
|
-
) {
|
|
232
|
-
const closed = now !== 'open'
|
|
233
|
-
return html`<div form>
|
|
234
|
-
<label>${i18next.t('figure.label.face-' + END_FACE(axis, end))}</label>
|
|
235
|
-
<div row>
|
|
236
|
-
<select
|
|
237
|
-
.value=${live(closed ? 'closed' : 'open')}
|
|
238
|
-
?disabled=${this.locked}
|
|
239
|
-
@change=${(ev: Event) => {
|
|
240
|
-
const select = ev.target as HTMLSelectElement
|
|
241
|
-
const want = select.value
|
|
242
|
-
select.value = closed ? 'closed' : 'open'
|
|
243
|
-
this.command({ kind: 'set-cavity', part: body.id, cavity: request({ [end]: want === 'open' ? 'open' : { closed: defaultWallOf(body.shape) } }) })
|
|
244
|
-
}}
|
|
245
|
-
>
|
|
246
|
-
<option value="closed">${i18next.t('figure.label.section-end-closed')}</option>
|
|
247
|
-
<option value="open">${i18next.t('figure.label.section-end-open')}</option>
|
|
248
|
-
</select>
|
|
249
|
-
${closed ? cavityField(`closed.${end}`, now.closed, value => ({ [end]: { closed: value } })) : html`<span></span>`}
|
|
250
|
-
</div>
|
|
251
|
-
</div>`
|
|
252
|
-
}
|
|
253
|
-
|
|
254
166
|
/**
|
|
255
167
|
* A millimetre field. Typing draws the value (or, if the kernel would refuse it, says why under the field and draws the
|
|
256
168
|
* figure as it stands); Enter or leaving sends it once; Esc puts the value back.
|
|
@@ -1056,12 +1056,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1056
1056
|
<div vector-row>
|
|
1057
1057
|
<label>${i18next.t('figure.label.mirror-copy')}</label>
|
|
1058
1058
|
<div tools>
|
|
1059
|
-
${mirrors.map(({ axis, able }) => html `<button ?disabled=${unavailable || !able} title=${i18next.t('figure.text.mirror-explained')} @click=${() => this.mirror(axis)}>
|
|
1059
|
+
${mirrors.map(({ axis, able }) => html `<button ?disabled=${unavailable || !able} title=${i18next.t(able ? 'figure.text.mirror-explained' : 'figure.text.mirror-would-overlap')} @click=${() => this.mirror(axis)}>
|
|
1060
1060
|
${i18next.t('figure.button.mirror-' + axis)}
|
|
1061
1061
|
</button>`)}
|
|
1062
1062
|
</div>
|
|
1063
1063
|
</div>
|
|
1064
|
-
|
|
1064
|
+
`;
|
|
1065
1065
|
}
|
|
1066
1066
|
/**
|
|
1067
1067
|
* A part's name. It renames every value the part owns, and what refers to them follows
|