@things-factory/figure-ui 10.1.223 → 10.1.225
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/canvas.ts +2 -2
- package/client/modeller/consumer-capabilities.ts +66 -0
- package/client/modeller/data-interface.ts +230 -80
- package/client/modeller/editor-styles.ts +3 -2
- package/client/modeller/editor.ts +17 -8
- package/client/modeller/figure-capability-panel.ts +151 -1
- package/client/modeller/figure-clips.ts +1 -1
- package/client/modeller/figure-data-panel.ts +509 -199
- package/client/modeller/figure-links.ts +130 -12
- package/client/modeller/figure-preview.ts +65 -8
- package/client/modeller/figure-slot-layout-panel.ts +7 -3
- package/client/modeller/links.ts +58 -0
- package/client/modeller/product-session.ts +3 -2
- package/client/modeller/refusals.ts +12 -4
- package/client/modeller/slot-layout-host.ts +76 -5
- package/client/pages/figure-modeller-page.ts +2 -2
- package/dist-client/modeller/canvas.js +2 -2
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/consumer-capabilities.d.ts +29 -0
- package/dist-client/modeller/consumer-capabilities.js +60 -0
- package/dist-client/modeller/consumer-capabilities.js.map +1 -0
- package/dist-client/modeller/data-interface.d.ts +83 -17
- package/dist-client/modeller/data-interface.js +194 -82
- package/dist-client/modeller/data-interface.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +3 -2
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +5 -2
- package/dist-client/modeller/editor.js +21 -8
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +12 -0
- package/dist-client/modeller/figure-capability-panel.js +129 -1
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +1 -1
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.d.ts +57 -13
- package/dist-client/modeller/figure-data-panel.js +476 -191
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +11 -0
- package/dist-client/modeller/figure-links.js +137 -16
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +4 -0
- package/dist-client/modeller/figure-preview.js +65 -7
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +6 -4
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/links.d.ts +22 -0
- package/dist-client/modeller/links.js +48 -0
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +4 -2
- package/dist-client/modeller/product-session.js +3 -2
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/refusals.js +14 -6
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/modeller/slot-layout-host.d.ts +28 -3
- package/dist-client/modeller/slot-layout-host.js +68 -6
- package/dist-client/modeller/slot-layout-host.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.js +2 -2
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/test/ai-proposal.test.ts +2 -2
- package/test/browser/data-table-page.js +9 -0
- package/test/browser/links.check.mjs +25 -3
- package/test/browser/product-page.check.mjs +25 -0
- package/test/browser/side-panel-sweep.check.mjs +182 -0
- package/test/consumer-capabilities.test.ts +116 -0
- package/test/data-motion.test.ts +94 -27
- package/test/figure-assistant-surface.test.ts +1 -1
- package/test/first-save.test.ts +2 -2
- package/test/links.test.ts +15 -0
- package/test/product-session.test.ts +5 -5
- package/test/proposal-record.test.ts +3 -3
- package/test/revert-keeping.test.ts +1 -1
- package/test/slot-layout-host.test.ts +63 -5
- package/translations/en.json +82 -23
- package/translations/es.json +82 -23
- package/translations/ja.json +82 -23
- package/translations/ko.json +82 -23
- package/translations/ms.json +82 -23
- package/translations/zh.json +82 -23
|
@@ -8,6 +8,20 @@ import type { FigureAsset } from '@hatiolab/figure-model'
|
|
|
8
8
|
import { RUNGS, rungOf, type Rung } from '@things-factory/figure-viewer/dist-client/capability-rung.js'
|
|
9
9
|
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
|
|
10
10
|
import { placeTerms } from '@things-factory/figure-viewer/dist-client/detail-facts.js'
|
|
11
|
+
import { onFigureCapabilitiesChanged, type FigureCapabilityDescriptor, type FigureCapabilitySetting } from '@hatiolab/things-scene'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
consumerDescriptors,
|
|
15
|
+
inputTableAt,
|
|
16
|
+
namesWith,
|
|
17
|
+
needsMissing,
|
|
18
|
+
partsAt,
|
|
19
|
+
settingsIssues,
|
|
20
|
+
settingsOf,
|
|
21
|
+
unknownConsumerNames,
|
|
22
|
+
withInputTable,
|
|
23
|
+
withParts
|
|
24
|
+
} from './consumer-capabilities.js'
|
|
11
25
|
|
|
12
26
|
/**
|
|
13
27
|
* 자리·능력 — what the figure does on a board: the ladder from drawing only, to
|
|
@@ -15,6 +29,10 @@ import { placeTerms } from '@things-factory/figure-viewer/dist-client/detail-fac
|
|
|
15
29
|
* so the list is the only way to find one; choosing a row selects its part, where the place is edited. Whether the
|
|
16
30
|
* figure is the look of an item a holder carries is said here too (ADR-0155 amendment 2): a tag, apart from the ladder,
|
|
17
31
|
* since an item may hold things as well.
|
|
32
|
+
*
|
|
33
|
+
* Below the ladder, the products' capabilities this modeller has descriptors for (ADR-0167 9, 12): each put on or off,
|
|
34
|
+
* what it still needs, and its settings as its descriptor draws them -- parts picked (the part chosen here by one
|
|
35
|
+
* button), an input and a value per fixed key, never a row added or taken away -- with what its check says.
|
|
18
36
|
*/
|
|
19
37
|
@customElement('figure-capability-panel')
|
|
20
38
|
export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
@@ -32,6 +50,12 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
32
50
|
border-radius: 8px;
|
|
33
51
|
cursor: pointer;
|
|
34
52
|
}
|
|
53
|
+
/* The box keeps its size beside a long note: squeezed by the flex row it read as a dot (sweep 2026-10-27 U1). */
|
|
54
|
+
label[rung] > input,
|
|
55
|
+
label[item] > input {
|
|
56
|
+
flex: none;
|
|
57
|
+
margin: 2px 0 0;
|
|
58
|
+
}
|
|
35
59
|
label[rung][on],
|
|
36
60
|
label[item][on] {
|
|
37
61
|
border-color: var(--md-sys-color-primary);
|
|
@@ -70,8 +94,42 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
70
94
|
font-size: var(--ui-t-small, 11.5px);
|
|
71
95
|
color: var(--md-sys-color-on-surface-variant);
|
|
72
96
|
}
|
|
97
|
+
div[chips] {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
gap: 4px 10px;
|
|
101
|
+
align-items: center;
|
|
102
|
+
}
|
|
103
|
+
div[chips] label {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
gap: 4px;
|
|
106
|
+
align-items: center;
|
|
107
|
+
}
|
|
108
|
+
div[table] {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
111
|
+
gap: 4px 6px;
|
|
112
|
+
align-items: center;
|
|
113
|
+
}
|
|
114
|
+
p[warn] {
|
|
115
|
+
margin: 0;
|
|
116
|
+
color: var(--md-sys-color-error);
|
|
117
|
+
}
|
|
73
118
|
`]
|
|
74
119
|
|
|
120
|
+
private unlisten?: () => void
|
|
121
|
+
|
|
122
|
+
connectedCallback() {
|
|
123
|
+
super.connectedCallback()
|
|
124
|
+
/* A product's descriptors may be registered after the panel stands. */
|
|
125
|
+
this.unlisten = onFigureCapabilitiesChanged(() => this.requestUpdate())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
disconnectedCallback() {
|
|
129
|
+
super.disconnectedCallback()
|
|
130
|
+
this.unlisten?.()
|
|
131
|
+
}
|
|
132
|
+
|
|
75
133
|
@property({ attribute: false }) asset?: FigureAsset
|
|
76
134
|
/** The selected part, whose places are marked. */
|
|
77
135
|
@property() selected = ''
|
|
@@ -97,6 +155,7 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
97
155
|
${rung === null ? html`<p hint>${i18next.t('figure.text.capability-hand-written')}</p>` : nothing}
|
|
98
156
|
${names.length ? html`<code>${names.join(' · ')}</code>` : nothing}
|
|
99
157
|
</section>
|
|
158
|
+
${this.renderConsumers(asset)}
|
|
100
159
|
<section>
|
|
101
160
|
<h3>${i18next.t('figure.label.places')}</h3>
|
|
102
161
|
${places.length
|
|
@@ -125,8 +184,99 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
125
184
|
`
|
|
126
185
|
}
|
|
127
186
|
|
|
187
|
+
/** The products' capabilities: those this modeller knows, each on or off with its settings, and those it does not know. */
|
|
188
|
+
private renderConsumers(asset: FigureAsset) {
|
|
189
|
+
const known = consumerDescriptors()
|
|
190
|
+
const unknown = unknownConsumerNames(asset)
|
|
191
|
+
if (!known.length && !unknown.length) return nothing
|
|
192
|
+
const names = asset.document.capabilities.names ?? []
|
|
193
|
+
return html`<section>
|
|
194
|
+
<h3>${i18next.t('figure.label.product-capabilities')}</h3>
|
|
195
|
+
${known.map(d => {
|
|
196
|
+
const on = names.includes(d.name)
|
|
197
|
+
const missing = needsMissing(d, asset)
|
|
198
|
+
return html`<label rung ?on=${on}>
|
|
199
|
+
<input type="checkbox" .checked=${live(on)} ?disabled=${this.locked || (!on && missing.length > 0)} @change=${(e: Event) => this.setConsumer(asset, d.name, (e.target as HTMLInputElement).checked)} />
|
|
200
|
+
<span>
|
|
201
|
+
<strong>${d.label}</strong>
|
|
202
|
+
<em>${d.note}</em>
|
|
203
|
+
<code>${d.name} · ${d.version}</code>
|
|
204
|
+
</span>
|
|
205
|
+
</label>
|
|
206
|
+
${missing.length ? html`<p hint>${i18next.t('figure.text.product-capability-needs', { needs: missing.map(m => (m === 'item' ? i18next.t('figure.label.is-item') : m)).join(', ') })}</p>` : nothing}
|
|
207
|
+
${on ? this.renderSettings(asset, d) : nothing}`
|
|
208
|
+
})}
|
|
209
|
+
${unknown.map(
|
|
210
|
+
name => html`<p hint>
|
|
211
|
+
${i18next.t('figure.text.product-capability-unknown', { name })}
|
|
212
|
+
<button quiet ?disabled=${this.locked} @click=${() => this.setConsumer(asset, name, false)}>${i18next.t('figure.button.product-capability-remove')}</button>
|
|
213
|
+
</p>`
|
|
214
|
+
)}
|
|
215
|
+
</section>`
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** A capability's settings as its descriptor draws them, and what its check says of them. */
|
|
219
|
+
private renderSettings(asset: FigureAsset, d: FigureCapabilityDescriptor) {
|
|
220
|
+
const settings = settingsOf(asset, d.name)
|
|
221
|
+
const write = (next: Record<string, unknown>) =>
|
|
222
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit: { kind: 'set-capability-settings', name: d.name, settings: Object.keys(next).length ? next : null } }, bubbles: true, composed: true }))
|
|
223
|
+
const parts = [...new Set(asset.appearance.map(a => a.target))]
|
|
224
|
+
const inputs = asset.document.model.inputs.filter(i => i.role === 'state')
|
|
225
|
+
const issues = settingsIssues(d, asset)
|
|
226
|
+
return html`<div form>
|
|
227
|
+
${(d.settings ?? []).map((setting: FigureCapabilitySetting) => {
|
|
228
|
+
if (setting.kind === 'parts') {
|
|
229
|
+
const chosen = partsAt(settings, setting.key)
|
|
230
|
+
const toggle = (part: string, on: boolean) => write(withParts(settings, setting.key, on ? [...chosen, part] : chosen.filter(p => p !== part)))
|
|
231
|
+
return html`<span>${setting.label}</span>
|
|
232
|
+
<div chips>
|
|
233
|
+
${parts.map(p => html`<label><input type="checkbox" .checked=${live(chosen.includes(p))} ?disabled=${this.locked} @change=${(e: Event) => toggle(p, (e.target as HTMLInputElement).checked)} />${p}</label>`)}
|
|
234
|
+
${this.selected && parts.includes(this.selected) && !chosen.includes(this.selected)
|
|
235
|
+
? html`<button quiet ?disabled=${this.locked} @click=${() => toggle(this.selected, true)}>${i18next.t('figure.button.data-add-selected-part', { part: this.selected })}</button>`
|
|
236
|
+
: nothing}
|
|
237
|
+
</div>`
|
|
238
|
+
}
|
|
239
|
+
const table = inputTableAt(settings, setting)
|
|
240
|
+
const input = inputs.find(i => i.id === table.input)
|
|
241
|
+
return html`<span>${setting.label}</span>
|
|
242
|
+
<select ?disabled=${this.locked} @change=${(e: Event) => write(withInputTable(settings, setting, (e.target as HTMLSelectElement).value || undefined, table.values))}>
|
|
243
|
+
<option value="" ?selected=${!table.input}>${i18next.t('figure.label.data-none')}</option>
|
|
244
|
+
${inputs.map(i => html`<option value=${i.id} ?selected=${i.id === table.input}>${asset.stateInputs?.[i.id]?.label ?? i.id}${i.unit ? ` (${i.unit} ${i.min ?? ''}~${i.max ?? ''})` : ''}</option>`)}
|
|
245
|
+
</select>
|
|
246
|
+
${table.input
|
|
247
|
+
? html`<span></span>
|
|
248
|
+
<div table>
|
|
249
|
+
${setting.keys.map(
|
|
250
|
+
key => html`<span>${key}</span>
|
|
251
|
+
<input
|
|
252
|
+
type="number"
|
|
253
|
+
step="any"
|
|
254
|
+
min=${input?.min ?? ''}
|
|
255
|
+
max=${input?.max ?? ''}
|
|
256
|
+
.value=${String(table.values[key])}
|
|
257
|
+
?disabled=${this.locked}
|
|
258
|
+
@change=${(e: Event) => {
|
|
259
|
+
const n = (e.target as HTMLInputElement).valueAsNumber
|
|
260
|
+
if (!Number.isFinite(n)) return this.requestUpdate()
|
|
261
|
+
write(withInputTable(settings, setting, table.input, { ...table.values, [key]: n }))
|
|
262
|
+
}}
|
|
263
|
+
/>`
|
|
264
|
+
)}
|
|
265
|
+
</div>`
|
|
266
|
+
: nothing}`
|
|
267
|
+
})}
|
|
268
|
+
</div>
|
|
269
|
+
${issues.map(reason => html`<p warn>${reason}</p>`)}`
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private setConsumer(asset: FigureAsset, name: string, on: boolean) {
|
|
273
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit: { kind: 'set-capabilities', names: namesWith(asset, name, on) } }, bubbles: true, composed: true }))
|
|
274
|
+
}
|
|
275
|
+
|
|
128
276
|
private setRung(which: Rung) {
|
|
129
|
-
|
|
277
|
+
/* The ladder sets the shared names; a product's capabilities stay as they are. */
|
|
278
|
+
const consumers = (this.asset?.document.capabilities.names ?? []).filter(n => n.includes('.'))
|
|
279
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit: { kind: 'set-capabilities', names: [...RUNGS[which], ...consumers] } }, bubbles: true, composed: true }))
|
|
130
280
|
}
|
|
131
281
|
|
|
132
282
|
private setItem(item: boolean) {
|
|
@@ -118,7 +118,7 @@ export class FigureClips extends localize(i18next)(LitElement) {
|
|
|
118
118
|
<h3>${i18next.t('figure.label.clips')}${this.help.button('figure.text.clips-explained')}</h3>
|
|
119
119
|
${this.help.hint('figure.text.clips-explained')}
|
|
120
120
|
${clips.map(clip => this.renderClip(clip))}
|
|
121
|
-
${empty ? html`<p hint>${i18next.t('figure.text.no-
|
|
121
|
+
${empty ? html`<p hint>${i18next.t('figure.text.no-clips')}</p>` : nothing}
|
|
122
122
|
</section>
|
|
123
123
|
${empty ? nothing : this.renderAdd(asset, clips)} ${this.renderGroups(asset)}
|
|
124
124
|
`
|