@things-factory/figure-ui 10.1.95 → 10.1.97
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 +16 -18
- package/client/modeller/figure-links.ts +5 -4
- package/client/modeller/figure-section-panel.ts +1 -5
- package/client/modeller/links.ts +49 -23
- package/dist-client/modeller/editor.d.ts +4 -4
- package/dist-client/modeller/editor.js +17 -19
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-links.js +5 -4
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.js +1 -5
- 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/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/test/command-preview.test.ts +3 -3
- package/test/first-minute.test.ts +13 -6
- 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
|
@@ -1631,14 +1631,13 @@ export class FigureEditor extends LitElement {
|
|
|
1631
1631
|
const key = `${view.context}|${view.revision}|${this.selected}`
|
|
1632
1632
|
if (this.detachSaid?.key === key) return this.detachSaid.words
|
|
1633
1633
|
const say = (k: string, params?: Record<string, string>) => i18next.t(k, params)
|
|
1634
|
-
const from = 'plane' in joint.body0 ? say('figure.label.mounting-plane') : partLabelOf(joint.body0.part)
|
|
1635
1634
|
let words: string[]
|
|
1636
1635
|
try {
|
|
1637
1636
|
const seen = this.session.previewCommand({ kind: 'detach', part: this.selected })
|
|
1638
1637
|
const own = seen.moves.some(m => m.body === this.selected)
|
|
1639
1638
|
words = own
|
|
1640
1639
|
? [say('figure.text.detach-moves'), ...previewWordsOf({ context: seen.context, revision: seen.revision, key, moves: seen.moves }, this.selected, say)]
|
|
1641
|
-
:
|
|
1640
|
+
: /* It stays where it is: as expected, so nothing is said (user 2026-10-01). */ []
|
|
1642
1641
|
const children = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === this.selected).map(j => partLabelOf(j.body1.part))
|
|
1643
1642
|
if (children.length) words.push(say('figure.text.detach-keeps-children', { parts: children.join(', ') }))
|
|
1644
1643
|
if (seen.clipsWouldGo.length) words.push(say('figure.text.detach-clips', { clips: seen.clipsWouldGo.map(c => c.clip).join(', ') }))
|
|
@@ -1864,14 +1863,15 @@ export class FigureEditor extends LitElement {
|
|
|
1864
1863
|
</div>`
|
|
1865
1864
|
}
|
|
1866
1865
|
/**
|
|
1867
|
-
* The part the panel speaks of, held at its top: its kind,
|
|
1868
|
-
* 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
|
|
1869
1867
|
* list's root is the way back to the model's settings.
|
|
1870
1868
|
*/
|
|
1871
1869
|
private renderSubject() {
|
|
1872
|
-
let summary: { kind: string; size: string
|
|
1870
|
+
let summary: { kind: string; size: string } | undefined
|
|
1873
1871
|
try {
|
|
1874
|
-
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
|
|
1875
1875
|
} catch {
|
|
1876
1876
|
summary = undefined
|
|
1877
1877
|
}
|
|
@@ -1879,7 +1879,7 @@ export class FigureEditor extends LitElement {
|
|
|
1879
1879
|
return html`<div subject>
|
|
1880
1880
|
<div subject-summary>
|
|
1881
1881
|
${summary.kind ? html`<b>${summary.kind}</b>` : nothing}
|
|
1882
|
-
|
|
1882
|
+
${summary.size ? html`<small>${summary.size}</small>` : nothing}
|
|
1883
1883
|
</div>
|
|
1884
1884
|
</div>`
|
|
1885
1885
|
}
|
|
@@ -2185,19 +2185,17 @@ export class FigureEditor extends LitElement {
|
|
|
2185
2185
|
`
|
|
2186
2186
|
}
|
|
2187
2187
|
/**
|
|
2188
|
-
*
|
|
2189
|
-
*
|
|
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.
|
|
2190
2191
|
*/
|
|
2191
2192
|
private renderHeldAxes(vectors: FieldGroup[]) {
|
|
2192
|
-
const
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
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>`)
|
|
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>`
|
|
2201
2199
|
}
|
|
2202
2200
|
/**
|
|
2203
2201
|
* A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
|
|
@@ -325,9 +325,10 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
325
325
|
const word = (axis: Axis) => i18next.t(round && axis === 'x' ? 'figure.label.resize-diameter' : `figure.label.resize-${axis}`)
|
|
326
326
|
const others = asset.appearance.map(look => look.target).filter(one => one !== this.partId && canFollow(asset, one))
|
|
327
327
|
const followsAll = editable.every(axis => followOf(asset, this.partId, axis).of === 'instance')
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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 => {
|
|
331
332
|
const now = followOf(asset, this.partId, axis)
|
|
332
333
|
const following = now.of === 'instance' && now.axis === axis
|
|
333
334
|
const custom = now.of !== 'own' && !following
|
|
@@ -343,7 +344,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
343
344
|
}}>${i18next.t('figure.button.resize-unfollow')}</button>`
|
|
344
345
|
: nothing}
|
|
345
346
|
</div>`
|
|
346
|
-
})}</div
|
|
347
|
+
})}</div>`}
|
|
347
348
|
${followsAll
|
|
348
349
|
? html`<p hint>${i18next.t('figure.text.resize-all-following')}</p>`
|
|
349
350
|
: html`<button act follow-size ?disabled=${this.locked || asset.sizing === 'uniform'} @click=${() => this.command({ kind: 'follow-size', part: this.partId })}>
|
|
@@ -114,11 +114,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
114
114
|
</div>`
|
|
115
115
|
: nothing}
|
|
116
116
|
${kind === 'rectangle' ? this.renderCorner(body) : nothing}
|
|
117
|
-
${kind === 'rectangle' || kind === 'ellipse'
|
|
118
|
-
? this.renderCavity(body, e.axis)
|
|
119
|
-
: html`<div field>
|
|
120
|
-
<small>${i18next.t('figure.text.section-cavity-unavailable')}</small>
|
|
121
|
-
</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}
|
|
122
118
|
</section>`
|
|
123
119
|
}
|
|
124
120
|
|
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
|
/**
|
|
@@ -309,8 +309,7 @@ export declare class FigureEditor extends LitElement {
|
|
|
309
309
|
private pointBlockedOf;
|
|
310
310
|
private renderStart;
|
|
311
311
|
/**
|
|
312
|
-
* The part the panel speaks of, held at its top: its kind,
|
|
313
|
-
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
312
|
+
* 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
|
|
314
313
|
* list's root is the way back to the model's settings.
|
|
315
314
|
*/
|
|
316
315
|
private renderSubject;
|
|
@@ -329,8 +328,9 @@ export declare class FigureEditor extends LitElement {
|
|
|
329
328
|
private withSizeRow;
|
|
330
329
|
private renderVector;
|
|
331
330
|
/**
|
|
332
|
-
*
|
|
333
|
-
*
|
|
331
|
+
* Why a row's numbers cannot be typed, said once under the rows by what decides them (user 2026-10-01: "set by a
|
|
332
|
+
* rule" named nothing): a fastened part's place and turn are its fastening's; other numbers are worked out from
|
|
333
|
+
* something else. A size that follows something is said at the panel's head, so not again here.
|
|
334
334
|
*/
|
|
335
335
|
private renderHeldAxes;
|
|
336
336
|
/**
|
|
@@ -1656,14 +1656,13 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1656
1656
|
if (this.detachSaid?.key === key)
|
|
1657
1657
|
return this.detachSaid.words;
|
|
1658
1658
|
const say = (k, params) => i18next.t(k, params);
|
|
1659
|
-
const from = 'plane' in joint.body0 ? say('figure.label.mounting-plane') : partLabelOf(joint.body0.part);
|
|
1660
1659
|
let words;
|
|
1661
1660
|
try {
|
|
1662
1661
|
const seen = this.session.previewCommand({ kind: 'detach', part: this.selected });
|
|
1663
1662
|
const own = seen.moves.some(m => m.body === this.selected);
|
|
1664
1663
|
words = own
|
|
1665
1664
|
? [say('figure.text.detach-moves'), ...previewWordsOf({ context: seen.context, revision: seen.revision, key, moves: seen.moves }, this.selected, say)]
|
|
1666
|
-
:
|
|
1665
|
+
: /* It stays where it is: as expected, so nothing is said (user 2026-10-01). */ [];
|
|
1667
1666
|
const children = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === this.selected).map(j => partLabelOf(j.body1.part));
|
|
1668
1667
|
if (children.length)
|
|
1669
1668
|
words.push(say('figure.text.detach-keeps-children', { parts: children.join(', ') }));
|
|
@@ -1897,14 +1896,15 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1897
1896
|
</div>`;
|
|
1898
1897
|
}
|
|
1899
1898
|
/**
|
|
1900
|
-
* The part the panel speaks of, held at its top: its kind,
|
|
1901
|
-
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
1899
|
+
* 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
|
|
1902
1900
|
* list's root is the way back to the model's settings.
|
|
1903
1901
|
*/
|
|
1904
1902
|
renderSubject() {
|
|
1905
1903
|
let summary;
|
|
1906
1904
|
try {
|
|
1907
|
-
summary = this.selected
|
|
1905
|
+
summary = this.selected
|
|
1906
|
+
? partSummaryOf(parseFigureAsset(this.session.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params), i18next.language || 'ko')
|
|
1907
|
+
: undefined;
|
|
1908
1908
|
}
|
|
1909
1909
|
catch {
|
|
1910
1910
|
summary = undefined;
|
|
@@ -1914,7 +1914,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1914
1914
|
return html `<div subject>
|
|
1915
1915
|
<div subject-summary>
|
|
1916
1916
|
${summary.kind ? html `<b>${summary.kind}</b>` : nothing}
|
|
1917
|
-
|
|
1917
|
+
${summary.size ? html `<small>${summary.size}</small>` : nothing}
|
|
1918
1918
|
</div>
|
|
1919
1919
|
</div>`;
|
|
1920
1920
|
}
|
|
@@ -2214,21 +2214,19 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2214
2214
|
`;
|
|
2215
2215
|
}
|
|
2216
2216
|
/**
|
|
2217
|
-
*
|
|
2218
|
-
*
|
|
2217
|
+
* Why a row's numbers cannot be typed, said once under the rows by what decides them (user 2026-10-01: "set by a
|
|
2218
|
+
* rule" named nothing): a fastened part's place and turn are its fastening's; other numbers are worked out from
|
|
2219
|
+
* something else. A size that follows something is said at the panel's head, so not again here.
|
|
2219
2220
|
*/
|
|
2220
2221
|
renderHeldAxes(vectors) {
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
byAxes.set(axes, [...(byAxes.get(axes) ?? []), i18next.t('figure.label.group-' + g.name)]);
|
|
2230
|
-
}
|
|
2231
|
-
return [...byAxes].map(([axes, groups]) => html `<p hint held-axes>${i18next.t('figure.text.axes-held-by-rule', { group: groups.join(' · '), axes })}</p>`);
|
|
2222
|
+
const fastened = !!this.asset()?.joints?.some(j => j.body1.part === ownerOf(this.selected));
|
|
2223
|
+
const held = vectors.filter(g => g.kind === 'vector' && g.name !== 'size' && ['x', 'y', 'z'].some(axis => !g.cells[axis]));
|
|
2224
|
+
if (!held.length)
|
|
2225
|
+
return nothing;
|
|
2226
|
+
const placed = held.every(g => g.name === 'position' || g.name === 'rotation');
|
|
2227
|
+
if (fastened && placed)
|
|
2228
|
+
return html `<p hint held-axes>${i18next.t('figure.text.held-by-fastening')}</p>`;
|
|
2229
|
+
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>`;
|
|
2232
2230
|
}
|
|
2233
2231
|
/**
|
|
2234
2232
|
* A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
|