@things-factory/figure-ui 10.1.94 → 10.1.96
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 +30 -11
- package/client/modeller/figure-fasten-drawing.ts +0 -2
- package/client/modeller/figure-links.ts +22 -25
- package/client/modeller/figure-material-panel.ts +18 -11
- package/client/modeller/figure-properties.ts +2 -2
- package/client/modeller/figure-section-panel.ts +12 -11
- package/client/modeller/links.ts +49 -23
- package/client/modeller/side-panel-styles.ts +67 -32
- package/dist-client/modeller/editor.d.ts +7 -2
- package/dist-client/modeller/editor.js +31 -10
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.js +0 -2
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.js +24 -25
- 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 +12 -11
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/links.d.ts +14 -4
- package/dist-client/modeller/links.js +51 -23
- package/dist-client/modeller/links.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/command-preview.test.ts +3 -3
- package/test/first-minute.test.ts +16 -7
- package/test/section-panel.test.ts +2 -2
- package/translations/en.json +18 -10
- package/translations/ja.json +18 -10
- package/translations/ko.json +18 -10
- package/translations/ms.json +18 -10
- package/translations/zh.json +18 -10
|
@@ -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;
|
|
@@ -1626,14 +1631,13 @@ export class FigureEditor extends LitElement {
|
|
|
1626
1631
|
const key = `${view.context}|${view.revision}|${this.selected}`
|
|
1627
1632
|
if (this.detachSaid?.key === key) return this.detachSaid.words
|
|
1628
1633
|
const say = (k: string, params?: Record<string, string>) => i18next.t(k, params)
|
|
1629
|
-
const from = 'plane' in joint.body0 ? say('figure.label.mounting-plane') : partLabelOf(joint.body0.part)
|
|
1630
1634
|
let words: string[]
|
|
1631
1635
|
try {
|
|
1632
1636
|
const seen = this.session.previewCommand({ kind: 'detach', part: this.selected })
|
|
1633
1637
|
const own = seen.moves.some(m => m.body === this.selected)
|
|
1634
1638
|
words = own
|
|
1635
1639
|
? [say('figure.text.detach-moves'), ...previewWordsOf({ context: seen.context, revision: seen.revision, key, moves: seen.moves }, this.selected, say)]
|
|
1636
|
-
:
|
|
1640
|
+
: /* It stays where it is: as expected, so nothing is said (user 2026-10-01). */ []
|
|
1637
1641
|
const children = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === this.selected).map(j => partLabelOf(j.body1.part))
|
|
1638
1642
|
if (children.length) words.push(say('figure.text.detach-keeps-children', { parts: children.join(', ') }))
|
|
1639
1643
|
if (seen.clipsWouldGo.length) words.push(say('figure.text.detach-clips', { clips: seen.clipsWouldGo.map(c => c.clip).join(', ') }))
|
|
@@ -1859,14 +1863,15 @@ export class FigureEditor extends LitElement {
|
|
|
1859
1863
|
</div>`
|
|
1860
1864
|
}
|
|
1861
1865
|
/**
|
|
1862
|
-
* The part the panel speaks of, held at its top: its kind,
|
|
1863
|
-
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
1866
|
+
* The part the panel speaks of, held at its top: its kind, and what its size follows when it follows something. No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
1864
1867
|
* list's root is the way back to the model's settings.
|
|
1865
1868
|
*/
|
|
1866
1869
|
private renderSubject() {
|
|
1867
|
-
let summary: { kind: string; size: string
|
|
1870
|
+
let summary: { kind: string; size: string } | undefined
|
|
1868
1871
|
try {
|
|
1869
|
-
summary = this.selected
|
|
1872
|
+
summary = this.selected
|
|
1873
|
+
? partSummaryOf(parseFigureAsset(this.session!.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params), i18next.language || 'ko')
|
|
1874
|
+
: undefined
|
|
1870
1875
|
} catch {
|
|
1871
1876
|
summary = undefined
|
|
1872
1877
|
}
|
|
@@ -1874,7 +1879,7 @@ export class FigureEditor extends LitElement {
|
|
|
1874
1879
|
return html`<div subject>
|
|
1875
1880
|
<div subject-summary>
|
|
1876
1881
|
${summary.kind ? html`<b>${summary.kind}</b>` : nothing}
|
|
1877
|
-
|
|
1882
|
+
${summary.size ? html`<small>${summary.size}</small>` : nothing}
|
|
1878
1883
|
</div>
|
|
1879
1884
|
</div>`
|
|
1880
1885
|
}
|
|
@@ -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,20 @@ export class FigureEditor extends LitElement {
|
|
|
2176
2182
|
)}
|
|
2177
2183
|
</div>
|
|
2178
2184
|
</div>
|
|
2179
|
-
|
|
2185
|
+
`
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Why a row's numbers cannot be typed, said once under the rows by what decides them (user 2026-10-01: "set by a
|
|
2189
|
+
* rule" named nothing): a fastened part's place and turn are its fastening's; other numbers are worked out from
|
|
2190
|
+
* something else. A size that follows something is said at the panel's head, so not again here.
|
|
2191
|
+
*/
|
|
2192
|
+
private renderHeldAxes(vectors: FieldGroup[]) {
|
|
2193
|
+
const fastened = !!(this.asset() as FigureAsset | undefined)?.joints?.some(j => j.body1.part === ownerOf(this.selected))
|
|
2194
|
+
const held = vectors.filter(g => g.kind === 'vector' && g.name !== 'size' && (['x', 'y', 'z'] as const).some(axis => !g.cells[axis]))
|
|
2195
|
+
if (!held.length) return nothing
|
|
2196
|
+
const placed = held.every(g => g.name === 'position' || g.name === 'rotation')
|
|
2197
|
+
if (fastened && placed) return html`<p hint held-axes>${i18next.t('figure.text.held-by-fastening')}</p>`
|
|
2198
|
+
return html`<p hint held-axes>${i18next.t('figure.text.held-worked-out', { group: held.map(g => i18next.t('figure.label.group-' + g.name)).join(' · ') })}</p>`
|
|
2180
2199
|
}
|
|
2181
2200
|
/**
|
|
2182
2201
|
* 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 +2210,7 @@ export class FigureEditor extends LitElement {
|
|
|
2191
2210
|
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
2211
|
)}
|
|
2193
2212
|
</div>
|
|
2194
|
-
<button act room-refit ?disabled=${unavailable} @click=${() => this.askRoomRefit()}>${i18next.t('figure.button.room-refit')}</button>
|
|
2213
|
+
<button act room-refit ?disabled=${unavailable} @click=${() => this.askRoomRefit()}><md-icon>fit_screen</md-icon>${i18next.t('figure.button.room-refit')}</button>
|
|
2195
2214
|
</section>`
|
|
2196
2215
|
}
|
|
2197
2216
|
/** The room as declared and as proposed, and what lies outside each as measured, before the author confirms. */
|
|
@@ -76,7 +76,6 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
76
76
|
gap: 2px;
|
|
77
77
|
padding: 2px 4px 4px;
|
|
78
78
|
font: inherit;
|
|
79
|
-
font-size: 0.72rem;
|
|
80
79
|
color: var(--md-sys-color-on-surface);
|
|
81
80
|
background: none;
|
|
82
81
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
@@ -95,7 +94,6 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
95
94
|
position: absolute;
|
|
96
95
|
width: 72px;
|
|
97
96
|
font: inherit;
|
|
98
|
-
font-size: 0.75rem;
|
|
99
97
|
}
|
|
100
98
|
small[warn] {
|
|
101
99
|
color: var(--md-sys-color-tertiary);
|
|
@@ -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] {
|
|
@@ -326,9 +325,10 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
326
325
|
const word = (axis: Axis) => i18next.t(round && axis === 'x' ? 'figure.label.resize-diameter' : `figure.label.resize-${axis}`)
|
|
327
326
|
const others = asset.appearance.map(look => look.target).filter(one => one !== this.partId && canFollow(asset, one))
|
|
328
327
|
const followsAll = editable.every(axis => followOf(asset, this.partId, axis).of === 'instance')
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
328
|
+
/* A size of its own on every axis is the usual: nothing to read, only the way to make it follow (user 2026-10-01). */
|
|
329
|
+
const allOwn = editable.every(axis => followOf(asset, this.partId, axis).of === 'own')
|
|
330
|
+
return html`${asset.sizing === 'uniform' ? html`<p hint>${i18next.t('figure.text.resize-uniform-warning')}</p>` : nothing}
|
|
331
|
+
${allOwn ? nothing : html`<div resize-now>${editable.map(axis => {
|
|
332
332
|
const now = followOf(asset, this.partId, axis)
|
|
333
333
|
const following = now.of === 'instance' && now.axis === axis
|
|
334
334
|
const custom = now.of !== 'own' && !following
|
|
@@ -344,7 +344,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
344
344
|
}}>${i18next.t('figure.button.resize-unfollow')}</button>`
|
|
345
345
|
: nothing}
|
|
346
346
|
</div>`
|
|
347
|
-
})}</div
|
|
347
|
+
})}</div>`}
|
|
348
348
|
${followsAll
|
|
349
349
|
? html`<p hint>${i18next.t('figure.text.resize-all-following')}</p>`
|
|
350
350
|
: html`<button act follow-size ?disabled=${this.locked || asset.sizing === 'uniform'} @click=${() => this.command({ kind: 'follow-size', part: this.partId })}>
|
|
@@ -443,11 +443,11 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
443
443
|
return html`<div resize-held>
|
|
444
444
|
<p><md-icon>link</md-icon> ${where}</p>
|
|
445
445
|
<button act open-fastening @click=${() => this.openFastening(asset)}>
|
|
446
|
-
|
|
446
|
+
<md-icon>link</md-icon>${i18next.t('figure.button.open-fastening')}
|
|
447
447
|
</button>
|
|
448
448
|
${joint
|
|
449
449
|
? html`<button act detach-held ?disabled=${this.locked} @click=${() => this.command({ kind: 'detach', part: this.partId })}>
|
|
450
|
-
|
|
450
|
+
<md-icon>link_off</md-icon>${i18next.t('figure.button.detach-held')}
|
|
451
451
|
</button>
|
|
452
452
|
${this.renderDetachWords()}`
|
|
453
453
|
: nothing}
|
|
@@ -569,17 +569,12 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
569
569
|
...(f.twist ? [i18next.t('figure.text.fasten-summary-twist', { degrees: String(f.twist) })] : [])
|
|
570
570
|
]
|
|
571
571
|
/*
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
572
|
+
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
|
|
573
|
+
said once, by the sentence over the drawing -- the panel's head says it too; a third and a fourth line saying the
|
|
574
|
+
same (「현재 관계 수정」, 「붙은 곳」) went (user 2026-10-01, the panel checked for balance).
|
|
575
575
|
*/
|
|
576
576
|
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')
|
|
577
|
+
const editing = i18next.t('figure.text.fasten-new')
|
|
583
578
|
/* A blank attachment is not a form to decipher. Ask for the thing to attach to first. */
|
|
584
579
|
if (!current && !this.choosingFastening && !f.target)
|
|
585
580
|
return html`
|
|
@@ -589,7 +584,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
589
584
|
</button>
|
|
590
585
|
`
|
|
591
586
|
return html`
|
|
592
|
-
|
|
587
|
+
${current ? nothing : html`<p fasten-mode>${editing}</p>`}
|
|
593
588
|
${this.overhangNote.length ? html`<p hint overhang-note>${this.overhangNote.join(' ')}</p>` : nothing}
|
|
594
589
|
${this.overhangWords.length
|
|
595
590
|
? html`<div overhang role="status">
|
|
@@ -600,13 +595,12 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
600
595
|
</div>`
|
|
601
596
|
: nothing}
|
|
602
597
|
${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>
|
|
598
|
+
? html`<div fastened>
|
|
605
599
|
<button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'detach', part: this.partId })}>
|
|
606
600
|
<md-icon>link_off</md-icon>${i18next.t('figure.button.detach')}
|
|
607
601
|
</button>
|
|
608
|
-
|
|
609
|
-
|
|
602
|
+
${this.renderDetachWords()}
|
|
603
|
+
</div>`
|
|
610
604
|
: nothing}
|
|
611
605
|
<div form>
|
|
612
606
|
<span>${i18next.t('figure.label.target-part')}</span>
|
|
@@ -687,6 +681,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
687
681
|
}
|
|
688
682
|
|
|
689
683
|
private renderDetachWords() {
|
|
684
|
+
/* One thing to say is a line, not a list of one. */
|
|
685
|
+
if (this.detachWords.length === 1) return html`<p hint detach-words>${this.detachWords[0]}</p>`
|
|
690
686
|
return this.detachWords.length ? html`<ul detach-words>${this.detachWords.map(line => html`<li>${line}</li>`)}</ul>` : nothing
|
|
691
687
|
}
|
|
692
688
|
|
|
@@ -729,6 +725,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
729
725
|
* (the editor's) shows it and takes it away.
|
|
730
726
|
*/
|
|
731
727
|
private renderPreviewWords() {
|
|
728
|
+
if (this.previewWords.length === 1) return html`<p preview-words role="status">${this.previewWords[0]}</p>`
|
|
732
729
|
return this.previewWords.length ? html`<ul preview-words role="status">${this.previewWords.map(line => html`<li>${line}</li>`)}</ul>` : nothing
|
|
733
730
|
}
|
|
734
731
|
|
|
@@ -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>`
|
|
@@ -113,11 +114,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
113
114
|
</div>`
|
|
114
115
|
: nothing}
|
|
115
116
|
${kind === 'rectangle' ? this.renderCorner(body) : nothing}
|
|
116
|
-
${kind === 'rectangle' || kind === 'ellipse'
|
|
117
|
-
? this.renderCavity(body, e.axis)
|
|
118
|
-
: html`<div field>
|
|
119
|
-
<small>${i18next.t('figure.text.section-cavity-unavailable')}</small>
|
|
120
|
-
</div>`}
|
|
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, e.axis) : nothing}
|
|
121
118
|
</section>`
|
|
122
119
|
}
|
|
123
120
|
|
|
@@ -128,8 +125,9 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
128
125
|
.map(k => d[k])
|
|
129
126
|
const now = reaches.length ? sameOf(reaches) : 0
|
|
130
127
|
const max = cornerLimitOf(body.shape)
|
|
131
|
-
return html`<div
|
|
128
|
+
return html`<div form>
|
|
132
129
|
<label for="corner">${i18next.t('figure.label.section-corner')}</label>
|
|
130
|
+
<div>
|
|
133
131
|
${this.number('corner', now, {
|
|
134
132
|
min: 0,
|
|
135
133
|
ask: value => {
|
|
@@ -140,6 +138,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
140
138
|
placeholder: now === undefined ? i18next.t('figure.text.section-corner-differs') : ''
|
|
141
139
|
})}
|
|
142
140
|
${this.refusals.corner ? nothing : max !== undefined ? html`<small>${i18next.t('figure.text.section-at-most', { max: String(mm(max)) })}</small>` : nothing}
|
|
141
|
+
</div>
|
|
143
142
|
</div>`
|
|
144
143
|
}
|
|
145
144
|
|
|
@@ -167,7 +166,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
167
166
|
action: value => ({ kind: 'set-cavity', part: body.id, cavity: request(patch(value)) })
|
|
168
167
|
})
|
|
169
168
|
return html`<div field>
|
|
170
|
-
<label>
|
|
169
|
+
<label toggle>
|
|
171
170
|
<input
|
|
172
171
|
type="checkbox"
|
|
173
172
|
.checked=${live(!!cavity)}
|
|
@@ -191,8 +190,9 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
191
190
|
</div>
|
|
192
191
|
${cavity ? html`<figure-cavity-drawing .body=${body} .perSide=${this.perSide} ?locked=${this.locked}></figure-cavity-drawing>` : nothing}
|
|
193
192
|
${cavity
|
|
194
|
-
? html`<div
|
|
193
|
+
? html`<div form>
|
|
195
194
|
<label>${i18next.t('figure.label.section-wall')}</label>
|
|
195
|
+
<div>
|
|
196
196
|
${this.perSide
|
|
197
197
|
? html`<div sides>
|
|
198
198
|
${[...sidesOf(axis)].sort((a, b) => a[0]!.localeCompare(b[0]!) || (a[1] === '-' ? -1 : 1)).map(
|
|
@@ -206,7 +206,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
206
206
|
${this.perSide || this.refusals.wall
|
|
207
207
|
? nothing
|
|
208
208
|
: html`<small>${i18next.t('figure.text.section-wall-at-most', { max: String(mm(wallLimitOf(body.shape))) })}</small>`}
|
|
209
|
-
<label>
|
|
209
|
+
<label toggle>
|
|
210
210
|
<input
|
|
211
211
|
type="checkbox"
|
|
212
212
|
.checked=${live(this.perSide)}
|
|
@@ -215,6 +215,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
215
215
|
/>
|
|
216
216
|
${i18next.t('figure.label.section-wall-per-side')}
|
|
217
217
|
</label>
|
|
218
|
+
</div>
|
|
218
219
|
</div>
|
|
219
220
|
${(['start', 'end'] as const).map(end => this.renderEnd(body, axis, end, cavity[end], request, cavityField))}`
|
|
220
221
|
: nothing}`
|
|
@@ -229,7 +230,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
229
230
|
cavityField: (key: string, now: number | undefined, patch: (value: number) => Partial<FigureCavityRequest>) => unknown
|
|
230
231
|
) {
|
|
231
232
|
const closed = now !== 'open'
|
|
232
|
-
return html`<div
|
|
233
|
+
return html`<div form>
|
|
233
234
|
<label>${i18next.t('figure.label.face-' + END_FACE(axis, end))}</label>
|
|
234
235
|
<div row>
|
|
235
236
|
<select
|
package/client/modeller/links.ts
CHANGED
|
@@ -89,36 +89,62 @@ export function resizeAxesOf(asset: FigureAsset, part: string, pending?: readonl
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
92
|
+
* Korean particles for a word the sentence is built around: the topic (은/는) and "and/with" (과/와), by whether the
|
|
93
|
+
* word's last syllable ends in a consonant. Other languages need none.
|
|
94
94
|
*/
|
|
95
|
-
export function
|
|
95
|
+
export function particlesOf(word: string, lang: string): { topic: string; with: string } {
|
|
96
|
+
if (!lang.startsWith('ko')) return { topic: word, with: word }
|
|
97
|
+
const last = word.trim().slice(-1).charCodeAt(0)
|
|
98
|
+
const closed = last >= 0xac00 && last <= 0xd7a3 && (last - 0xac00) % 28 !== 0
|
|
99
|
+
return { topic: word + (closed ? '은' : '는'), with: word + (closed ? '과' : '와') }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The selected part at the head of its panel: what it is, and -- only when a measure changes by itself -- what it
|
|
104
|
+
* follows, as a sentence a person reads without knowing the model's words: the model's size, a share of it, another
|
|
105
|
+
* part's measure, a field, a calculation (`figureFollowOf`). A size of its own and a part fastened or not say nothing
|
|
106
|
+
* here; the fastening is drawn in its own section (user 2026-10-01: 「규칙을 따름: 높이 (Y)」 -- "who would know what that
|
|
107
|
+
* means?", and "why say anything when all is as usual?").
|
|
108
|
+
*/
|
|
109
|
+
export function partSummaryOf(
|
|
110
|
+
asset: FigureAsset,
|
|
111
|
+
part: string,
|
|
112
|
+
say: (key: string, params?: Record<string, string>) => string,
|
|
113
|
+
lang = 'ko'
|
|
114
|
+
): { kind: string; size: string } {
|
|
96
115
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)
|
|
97
116
|
const named = startingShapeKindOf(shape as never)
|
|
98
117
|
const kind = named ? say(`figure.label.primitive-${named}`) : ''
|
|
99
118
|
const editable = followableAxes(asset, part)
|
|
100
119
|
const round = editable.length === 2
|
|
101
|
-
const word = (axis: Axis) => say(round && axis === 'x' ? 'figure.label.
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
const word = (axis: Axis) => say(round && axis === 'x' ? 'figure.label.measure-diameter' : `figure.label.measure-${axis}`)
|
|
121
|
+
const pct = (times: number) => String(Math.round(times * 1000) / 10)
|
|
122
|
+
/* One sentence per way of following; the measures that follow the same way are said together. */
|
|
123
|
+
const lines = new Map<string, { key: string; params: Record<string, string>; axes: Axis[] }>()
|
|
124
|
+
for (const axis of editable) {
|
|
125
|
+
const f = followOf(asset, part, axis)
|
|
126
|
+
let key: string
|
|
127
|
+
let params: Record<string, string> = {}
|
|
128
|
+
if (f.of === 'own') continue
|
|
129
|
+
if (f.of === 'instance') {
|
|
130
|
+
key = f.times === 1 ? 'figure.text.summary-size-follows' : 'figure.text.summary-size-share'
|
|
131
|
+
params = { pct: pct(f.times), whole: say(`figure.label.measure-${f.axis}`) }
|
|
132
|
+
if (f.plus) (key = key + '-plus'), (params.plus = String(f.plus))
|
|
133
|
+
} else if (f.of === 'part') {
|
|
134
|
+
const other = say(`figure.label.measure-${f.axis}`)
|
|
135
|
+
key = (f.times ?? 1) === 1 ? 'figure.text.summary-size-part' : 'figure.text.summary-size-part-share'
|
|
136
|
+
params = { other: f.part, measure: particlesOf(other, lang).with, measureWord: other, pct: pct(f.times ?? 1) }
|
|
137
|
+
if (f.plus) (key = key + '-plus'), (params.plus = String(f.plus))
|
|
138
|
+
} else if (f.of === 'input') {
|
|
139
|
+
key = 'figure.text.summary-size-input'
|
|
140
|
+
params = { field: fieldWordsOf(f.input, k => say(k)) }
|
|
141
|
+
} else key = 'figure.text.summary-size-worked-out'
|
|
142
|
+
const id = key + JSON.stringify(params)
|
|
143
|
+
lines.set(id, { key, params, axes: [...(lines.get(id)?.axes ?? []), axis] })
|
|
120
144
|
}
|
|
121
|
-
|
|
145
|
+
/* Nothing to say when the size is its own: only a measure that changes by itself is news (user 2026-10-01). */
|
|
146
|
+
const size = [...lines.values()].map(l => say(l.key, { ...l.params, axes: particlesOf(l.axes.map(word).join(' · '), lang).topic })).join(' ')
|
|
147
|
+
return { kind, size }
|
|
122
148
|
}
|
|
123
149
|
|
|
124
150
|
/**
|