@things-factory/figure-ui 10.1.224 → 10.1.226
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/consumer-capabilities.ts +15 -0
- package/client/modeller/editor-styles.ts +21 -3
- package/client/modeller/editor.ts +41 -27
- package/client/modeller/figure-capability-panel.ts +37 -10
- package/client/modeller/figure-clips.ts +3 -2
- package/client/modeller/figure-controls.ts +4 -3
- package/client/modeller/figure-data-panel.ts +41 -21
- package/client/modeller/figure-links.ts +5 -5
- package/client/modeller/figure-material-panel.ts +2 -0
- package/client/modeller/figure-preview.ts +115 -8
- package/client/modeller/figure-recipe-catalog.ts +4 -0
- package/client/modeller/figure-slot-layout-panel.ts +25 -8
- package/client/modeller/figure-verdict.ts +4 -1
- package/client/modeller/product-session.ts +3 -2
- package/client/modeller/unit-words.ts +11 -0
- package/client/pages/figure-modeller-page.ts +5 -4
- package/dist-client/modeller/consumer-capabilities.d.ts +11 -0
- package/dist-client/modeller/consumer-capabilities.js +14 -0
- package/dist-client/modeller/consumer-capabilities.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +21 -3
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +43 -29
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +3 -0
- package/dist-client/modeller/figure-capability-panel.js +35 -11
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +3 -2
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/figure-controls.js +4 -3
- package/dist-client/modeller/figure-controls.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.js +42 -21
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.js +5 -5
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +2 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +3 -0
- package/dist-client/modeller/figure-preview.js +124 -8
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-recipe-catalog.js +4 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.d.ts +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +25 -8
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/figure-verdict.js +4 -1
- package/dist-client/modeller/figure-verdict.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/unit-words.d.ts +5 -0
- package/dist-client/modeller/unit-words.js +12 -0
- package/dist-client/modeller/unit-words.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.js +5 -4
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/test/ai-proposal.test.ts +2 -2
- package/test/browser/editing-page.js +3 -3
- package/test/browser/editing.check.mjs +5 -5
- package/test/browser/links-page.js +1 -1
- package/test/browser/links.check.mjs +2 -3
- package/test/browser/modeller-page.js +4 -4
- package/test/browser/modeller-revision.check.mjs +5 -5
- package/test/browser/modeller.check.mjs +5 -5
- package/test/browser/parts.check.mjs +2 -2
- package/test/browser/product-page.check.mjs +31 -0
- package/test/browser/props.check.mjs +3 -4
- package/test/browser/row-preview.check.mjs +3 -3
- package/test/browser/screen.check.mjs +13 -11
- package/test/browser/side-panel-sweep.check.mjs +182 -0
- package/test/consumer-capabilities.test.ts +7 -0
- package/test/figure-assistant-surface.test.ts +1 -1
- package/test/first-save.test.ts +2 -2
- package/test/links.test.ts +1 -1
- package/test/product-session.test.ts +5 -5
- package/test/proposal-record.test.ts +3 -3
- package/test/refusals.test.ts +1 -1
- package/test/revert-keeping.test.ts +1 -1
- package/translations/en.json +24 -12
- package/translations/es.json +24 -12
- package/translations/ja.json +25 -13
- package/translations/ko.json +83 -71
- package/translations/ms.json +24 -12
- package/translations/zh.json +24 -12
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { isFigureConsumerCapability } from '@hatiolab/figure-model'
|
|
8
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
9
9
|
import { figureCapabilityDescriptors, type FigureCapabilityDescriptor, type FigureCapabilitySetting } from '@hatiolab/things-scene'
|
|
10
|
+
import { RUNGS, type Rung } from '@things-factory/figure-viewer/dist-client/capability-rung.js'
|
|
10
11
|
|
|
11
12
|
/** The products' capabilities this modeller knows: registered descriptors with a namespaced name. */
|
|
12
13
|
export const consumerDescriptors = (): FigureCapabilityDescriptor[] => figureCapabilityDescriptors().filter(d => isFigureConsumerCapability(d.name))
|
|
@@ -24,6 +25,20 @@ export function needsMissing(descriptor: FigureCapabilityDescriptor, asset: Figu
|
|
|
24
25
|
return descriptor.needs.filter(need => (Array.isArray(need) ? !need.some(has) : !has(need as string))).map(need => (Array.isArray(need) ? need.join(' | ') : (need as string)))
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* What a product's capability needs that the figure lacks, as the tab offers it: the item tag, and the rungs that would
|
|
30
|
+
* give the names it needs (Holdable | FlowNode and Capacity | FlowNode: hold or flow). Its own names are not shown to the
|
|
31
|
+
* author (sweep 2026-10-27 U19); `names` keeps a need no rung gives.
|
|
32
|
+
*/
|
|
33
|
+
export function needsOffered(descriptor: FigureCapabilityDescriptor, asset: FigureAsset): { item: boolean; rungs: Rung[]; names: string[] } {
|
|
34
|
+
const missing = needsMissing(descriptor, asset)
|
|
35
|
+
const groups = descriptor.needs.filter(need => need !== 'item').map(need => (Array.isArray(need) ? need : [need as string]))
|
|
36
|
+
const gives = (rung: Rung) => groups.every(group => group.some(name => (RUNGS[rung] as readonly string[]).includes(name)))
|
|
37
|
+
const lacking = missing.filter(m => m !== 'item')
|
|
38
|
+
const rungs = lacking.length ? (['hold', 'flow'] as const).filter(gives) : []
|
|
39
|
+
return { item: missing.includes('item'), rungs, names: lacking.length && !rungs.length ? lacking : [] }
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
/** The names with a product's capability put on or taken off; the shared ones as they are. */
|
|
28
43
|
export function namesWith(asset: FigureAsset, name: string, on: boolean): string[] {
|
|
29
44
|
const names = asset.document.capabilities.names ?? []
|
|
@@ -19,7 +19,8 @@ export const EditorStyles = css`
|
|
|
19
19
|
/* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and
|
|
20
20
|
fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */
|
|
21
21
|
ox-canvas-bar button[mode] {
|
|
22
|
-
height
|
|
22
|
+
/* The bar's height, beside its 32px icons and the save (sweep 2026-10-27 U25). */
|
|
23
|
+
height: 32px;
|
|
23
24
|
border: 1px solid transparent;
|
|
24
25
|
padding: 0 10px;
|
|
25
26
|
border-radius: 999em;
|
|
@@ -207,6 +208,22 @@ export const EditorStyles = css`
|
|
|
207
208
|
margin-top: 4px;
|
|
208
209
|
color: var(--md-sys-color-on-surface-variant);
|
|
209
210
|
}
|
|
211
|
+
div[start] button[start-more] {
|
|
212
|
+
display: block;
|
|
213
|
+
text-align: left;
|
|
214
|
+
margin: 4px 0 0;
|
|
215
|
+
padding: 0;
|
|
216
|
+
font: inherit;
|
|
217
|
+
font-size: var(--ui-t-small);
|
|
218
|
+
color: var(--md-sys-color-primary);
|
|
219
|
+
background: none;
|
|
220
|
+
border: none;
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
}
|
|
223
|
+
/* No rule under the last way to start: it closed nothing (sweep 2026-10-27 U23). */
|
|
224
|
+
div[start-paths] > section:last-of-type {
|
|
225
|
+
border-bottom: none;
|
|
226
|
+
}
|
|
210
227
|
/* The side: tabs over one panel at a time, each using the whole height. */
|
|
211
228
|
/*
|
|
212
229
|
The side's words at the panel's size (side-panel-styles: titles body, the rest small). The editor's host is at body
|
|
@@ -339,11 +356,12 @@ export const EditorStyles = css`
|
|
|
339
356
|
min-height: 0;
|
|
340
357
|
overflow: auto;
|
|
341
358
|
}
|
|
342
|
-
/* Which part the panel speaks of.
|
|
359
|
+
/* Which part the panel speaks of. Above z-index 1: a chip's label (md-filter-chip) sits at 1 and, coming later, was
|
|
360
|
+
drawn over it when the panel scrolled (sweep 2026-10-27 U2). */
|
|
343
361
|
div[subject] {
|
|
344
362
|
position: sticky;
|
|
345
363
|
top: 0;
|
|
346
|
-
z-index:
|
|
364
|
+
z-index: 2;
|
|
347
365
|
display: grid;
|
|
348
366
|
gap: 4px;
|
|
349
367
|
padding: 10px 12px;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { LitElement, html, nothing, type PropertyValues } from 'lit'
|
|
2
2
|
import { i18next } from '@operato/i18n'
|
|
3
|
+
import { isMacOS } from '@operato/utils'
|
|
4
|
+
import { unitWord } from './unit-words.js'
|
|
3
5
|
import type { OxThreePane } from '@operato/layout'
|
|
4
6
|
import '@operato/layout'
|
|
5
7
|
import { openPopup } from '@operato/layout'
|
|
@@ -887,6 +889,14 @@ export class FigureEditor extends LitElement {
|
|
|
887
889
|
<summary><h3>${i18next.t('figure.label.group-more')}</h3></summary>
|
|
888
890
|
<div more-body>
|
|
889
891
|
${tools ? html`<div group-fields>${tools}</div>` : nothing}
|
|
892
|
+
<figure-material-panel
|
|
893
|
+
show="more"
|
|
894
|
+
.body=${{ ...part, id: this.selected }}
|
|
895
|
+
.palette=${palette}
|
|
896
|
+
?locked=${unavailable}
|
|
897
|
+
@part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
|
|
898
|
+
@part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
|
|
899
|
+
></figure-material-panel>
|
|
890
900
|
<figure-section-panel
|
|
891
901
|
embedded
|
|
892
902
|
.body=${{ ...part, id: this.selected }}
|
|
@@ -898,14 +908,6 @@ export class FigureEditor extends LitElement {
|
|
|
898
908
|
@command-live=${(e: CustomEvent) => this.holdAction(e.detail.action)}
|
|
899
909
|
@section-editor-open=${() => (this.sectionEditing = this.selected)}
|
|
900
910
|
></figure-section-panel>
|
|
901
|
-
<figure-material-panel
|
|
902
|
-
show="more"
|
|
903
|
-
.body=${{ ...part, id: this.selected }}
|
|
904
|
-
.palette=${palette}
|
|
905
|
-
?locked=${unavailable}
|
|
906
|
-
@part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
|
|
907
|
-
@part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
|
|
908
|
-
></figure-material-panel>
|
|
909
911
|
</div>
|
|
910
912
|
</details>
|
|
911
913
|
</section>`
|
|
@@ -1143,7 +1145,7 @@ export class FigureEditor extends LitElement {
|
|
|
1143
1145
|
${title('figure.text.board-size-range-explained')}
|
|
1144
1146
|
${(['x', 'y', 'z'] as const).map(
|
|
1145
1147
|
axis => html`<div inline-field>
|
|
1146
|
-
<label>${i18next.t('figure.label.resize-' + axis)} <span unit>mm</span></label>
|
|
1148
|
+
<label>${i18next.t('figure.label.resize-' + axis)} <span unit>(mm)</span></label>
|
|
1147
1149
|
<div pair range>
|
|
1148
1150
|
${(['min', 'max'] as const).map(
|
|
1149
1151
|
(bound, i) => html`${i ? html`<span dash aria-hidden="true">–</span>` : nothing}<input
|
|
@@ -1279,7 +1281,7 @@ export class FigureEditor extends LitElement {
|
|
|
1279
1281
|
<div inline-field><label>${i18next.t('figure.label.repeat-axis')}</label>${axes.length ? html`<select aria-label=${i18next.t('figure.label.repeat-axis')} ?disabled=${unavailable} .value=${live(axis)} @change=${(e: Event) => update({ axis: (e.target as HTMLSelectElement).value.toUpperCase() as 'X' | 'Y' | 'Z' })}>${axes.map(a => html`<option value=${a} ?selected=${a === axis}>${a.toUpperCase()}</option>`)}</select>` : html`<span value>${axis.toUpperCase()}</span>`}</div>
|
|
1280
1282
|
<div inline-field><label>${i18next.t('figure.label.repeat-count')}</label>${!fits && record ? html`<input type="number" min="1" step="1" aria-label=${i18next.t('figure.label.repeat-count')} .value=${live(String(repeat.count))} ?disabled=${unavailable} @change=${(e: Event) => update({ count: (e.target as HTMLInputElement).valueAsNumber })}>` : html`<span value>${repeat.count}</span>`}</div>
|
|
1281
1283
|
<div inline-field>
|
|
1282
|
-
<label>${i18next.t('figure.label.repeat-gap')} <span unit>mm</span></label>
|
|
1284
|
+
<label>${i18next.t('figure.label.repeat-gap')} <span unit>(mm)</span></label>
|
|
1283
1285
|
${fits ? html`<input
|
|
1284
1286
|
type="number"
|
|
1285
1287
|
step="any"
|
|
@@ -1371,8 +1373,8 @@ export class FigureEditor extends LitElement {
|
|
|
1371
1373
|
</label>
|
|
1372
1374
|
${open
|
|
1373
1375
|
? html`<div inline-field><label>${i18next.t('figure.label.board-name')}</label><input aria-label="figure.label.board-name" .value=${form!.label} @change=${edit('label')} /></div>
|
|
1374
|
-
<div inline-field><label>${i18next.t('figure.label.least')} <span unit>mm</span></label><input type="number" step="any" aria-label="figure.label.least" .value=${String(form!.min)} @change=${edit('min')} /></div>
|
|
1375
|
-
<div inline-field><label>${i18next.t('figure.label.most')} <span unit>mm</span></label><input type="number" step="any" aria-label="figure.label.most" .value=${String(form!.max)} @change=${edit('max')} /></div>
|
|
1376
|
+
<div inline-field><label>${i18next.t('figure.label.least')} <span unit>(mm)</span></label><input type="number" step="any" aria-label="figure.label.least" .value=${String(form!.min)} @change=${edit('min')} /></div>
|
|
1377
|
+
<div inline-field><label>${i18next.t('figure.label.most')} <span unit>(mm)</span></label><input type="number" step="any" aria-label="figure.label.most" .value=${String(form!.max)} @change=${edit('max')} /></div>
|
|
1376
1378
|
<button act publish-pitch ?disabled=${unavailable || !form!.label.trim()} @click=${() => run(steps())}>
|
|
1377
1379
|
<md-icon>${listed ? 'check' : 'ios_share'}</md-icon>${i18next.t(listed ? 'figure.button.apply' : 'figure.button.publish-input')}
|
|
1378
1380
|
</button>`
|
|
@@ -1601,9 +1603,12 @@ export class FigureEditor extends LitElement {
|
|
|
1601
1603
|
}
|
|
1602
1604
|
/** A side tab says what it is in words under its icon: the icons alone had to be learnt (designer 2026-09-27, U05). */
|
|
1603
1605
|
private renderTab(tab: SideTab, icon: string, label: string) {
|
|
1604
|
-
/*
|
|
1605
|
-
|
|
1606
|
-
|
|
1606
|
+
/*
|
|
1607
|
+
Its accessible name is the name it shows (designer 2026-09-27); the longer title stays as its tooltip. One name
|
|
1608
|
+
whatever is picked: the size tab was 「반응형」 with nothing picked and 「배치」 with a part (sweep 2026-10-27 J2).
|
|
1609
|
+
*/
|
|
1610
|
+
const name = i18next.t('figure.label.tab-short-' + tab)
|
|
1611
|
+
return html`<button tab ?on=${this.tab === tab} title=${label} aria-label=${name} @click=${() => (this.tab = tab)}>
|
|
1607
1612
|
<md-icon>${icon}</md-icon><span tab-label aria-hidden="true">${name}</span>
|
|
1608
1613
|
</button>`
|
|
1609
1614
|
}
|
|
@@ -1700,7 +1705,13 @@ export class FigureEditor extends LitElement {
|
|
|
1700
1705
|
</button>`
|
|
1701
1706
|
)}
|
|
1702
1707
|
</div>
|
|
1703
|
-
|
|
1708
|
+
${this.panes?.foldStart
|
|
1709
|
+
? /* The list it points to is folded at this width: a way to it, not words about a list not there (sweep 2026-10-27 U23). */
|
|
1710
|
+
html`<button start-more @click=${() => {
|
|
1711
|
+
this.panes!.foldStart = false
|
|
1712
|
+
this.requestUpdate()
|
|
1713
|
+
}}>${i18next.t('figure.button.start-shape-more')}</button>`
|
|
1714
|
+
: html`<small>${i18next.t('figure.text.start-shape-more')}</small>`}
|
|
1704
1715
|
</section>
|
|
1705
1716
|
${this.aiDraft
|
|
1706
1717
|
? html`<section>
|
|
@@ -1708,7 +1719,6 @@ export class FigureEditor extends LitElement {
|
|
|
1708
1719
|
<button start-button start-ai @click=${() => this.dispatchEvent(new CustomEvent('ask-ai-draft', { bubbles: true, composed: true }))}>
|
|
1709
1720
|
${i18next.t('figure.button.start-ai')}
|
|
1710
1721
|
</button>
|
|
1711
|
-
<small>${i18next.t('figure.text.start-ai-optional')}</small>
|
|
1712
1722
|
</section>`
|
|
1713
1723
|
: nothing}
|
|
1714
1724
|
</div>
|
|
@@ -1789,8 +1799,8 @@ export class FigureEditor extends LitElement {
|
|
|
1789
1799
|
return html`<ox-canvas-bar
|
|
1790
1800
|
history
|
|
1791
1801
|
fit
|
|
1792
|
-
undo-shortcut
|
|
1793
|
-
redo-shortcut
|
|
1802
|
+
undo-shortcut=${isMacOS() ? '⌘Z' : 'Ctrl+Z'}
|
|
1803
|
+
redo-shortcut=${isMacOS() ? '⇧⌘Z' : 'Ctrl+Y'}
|
|
1794
1804
|
?can-undo=${!unavailable && view.canUndo}
|
|
1795
1805
|
?can-redo=${!unavailable && view.canRedo}
|
|
1796
1806
|
.pane=${this.panes}
|
|
@@ -1822,7 +1832,7 @@ export class FigureEditor extends LitElement {
|
|
|
1822
1832
|
>
|
|
1823
1833
|
<md-icon>publish</md-icon>${i18next.t('figure.button.release')}
|
|
1824
1834
|
</button>
|
|
1825
|
-
<button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}><md-icon>save</md-icon>${i18next.t('figure.button.save')}</button>
|
|
1835
|
+
<button slot="commit" save title=${!view.id && !view.geometry.length ? i18next.t('figure.text.save-needs-a-part') : ''} ?disabled=${unavailable || (!view.dirty && !!view.id) || (!view.id && !view.geometry.length)} @click=${this.save}><md-icon>save</md-icon>${i18next.t('figure.button.save')}</button>
|
|
1826
1836
|
</ox-canvas-bar>
|
|
1827
1837
|
${this.blockersOpen && blockers.length ? this.renderReleaseBlockers(blockers, asset) : nothing}
|
|
1828
1838
|
${this.catalogOpen
|
|
@@ -1914,7 +1924,7 @@ export class FigureEditor extends LitElement {
|
|
|
1914
1924
|
${this.renderTab('motion', 'animation', i18next.t('figure.label.motion'))}
|
|
1915
1925
|
${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}
|
|
1916
1926
|
${this.renderTab('data', 'sensors', i18next.t('figure.label.tab-data'))}
|
|
1917
|
-
${this.renderTab('verdict', 'insights', i18next.t('figure.label.
|
|
1927
|
+
${this.renderTab('verdict', 'insights', i18next.t('figure.label.tab-verdict'))}
|
|
1918
1928
|
</div>
|
|
1919
1929
|
<slot name="notice"></slot>
|
|
1920
1930
|
<div panel ?off=${tab !== 'properties'}>
|
|
@@ -1943,7 +1953,9 @@ export class FigureEditor extends LitElement {
|
|
|
1943
1953
|
@part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
|
|
1944
1954
|
></figure-material-panel>
|
|
1945
1955
|
${this.renderMore(part, view.palette ?? {}, unavailable)}`
|
|
1946
|
-
: view.
|
|
1956
|
+
: view.loading || !view.context
|
|
1957
|
+
? nothing
|
|
1958
|
+
: view.geometry.length === 0
|
|
1947
1959
|
? html`${this.renderSubject()}<section empty-figure>
|
|
1948
1960
|
<p>${i18next.t('figure.text.empty-figure-next')}</p>
|
|
1949
1961
|
<details>
|
|
@@ -2039,18 +2051,19 @@ export class FigureEditor extends LitElement {
|
|
|
2039
2051
|
return v === undefined || !Number.isFinite(v) ? '—' : String(Math.round(v * 10) / 10)
|
|
2040
2052
|
}
|
|
2041
2053
|
return html`<div vector-row>
|
|
2042
|
-
<label>${i18next.t('figure.label.group-' + group.name)}${group.name === 'rotation' ? '' : html` <span unit
|
|
2054
|
+
<label>${i18next.t('figure.label.group-' + group.name)}${group.name === 'rotation' ? '' : html` <span unit>(${unit})</span>`}</label>
|
|
2043
2055
|
<div triple>
|
|
2044
2056
|
${(['x', 'y', 'z'] as const).map(
|
|
2045
2057
|
axis => !group.cells[axis]
|
|
2046
2058
|
? html`<div cell held title=${i18next.t('figure.text.axis-held-by-rule')}>
|
|
2047
2059
|
<span axis>${axis.toUpperCase()}</span>
|
|
2048
|
-
<input type="text" readonly .value=${given(axis)} aria-label=${`${group.name}
|
|
2060
|
+
<input type="text" readonly .value=${given(axis)} data-field=${`${group.name}.${axis}`} aria-label=${`${i18next.t('figure.label.group-' + group.name)} ${axis.toUpperCase()} · ${i18next.t('figure.text.axis-held-by-rule')}`} />
|
|
2049
2061
|
</div>`
|
|
2050
2062
|
: html`<div cell>
|
|
2051
2063
|
<span axis ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(group.cells[axis] as never, e, unavailable)}>${axis.toUpperCase()}</span>
|
|
2052
2064
|
<input
|
|
2053
|
-
|
|
2065
|
+
data-field=${group.cells[axis]!.id}
|
|
2066
|
+
aria-label=${`${i18next.t('figure.label.group-' + group.name)} ${axis.toUpperCase()}`}
|
|
2054
2067
|
type="number"
|
|
2055
2068
|
step="any"
|
|
2056
2069
|
.value=${String(group.cells[axis]!.value)}
|
|
@@ -2085,9 +2098,10 @@ export class FigureEditor extends LitElement {
|
|
|
2085
2098
|
}
|
|
2086
2099
|
private renderSingle(name: string, field: FieldLike, unavailable: boolean) {
|
|
2087
2100
|
const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }))
|
|
2088
|
-
return html`<span ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(field as never, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
2101
|
+
return html`<span ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(field as never, e, unavailable)}>${words.join(' · ')} (${unitWord(field.unit)})</span
|
|
2089
2102
|
><input
|
|
2090
|
-
|
|
2103
|
+
data-field=${field.id}
|
|
2104
|
+
aria-label=${words.join(' · ')}
|
|
2091
2105
|
type="number"
|
|
2092
2106
|
step="any"
|
|
2093
2107
|
.value=${String(field.value)}
|
|
@@ -3,6 +3,7 @@ import { customElement, property } from 'lit/decorators.js'
|
|
|
3
3
|
import { live } from 'lit/directives/live.js'
|
|
4
4
|
|
|
5
5
|
import { i18next, localize } from '@operato/i18n'
|
|
6
|
+
import { unitWord } from './unit-words.js'
|
|
6
7
|
import { figureBindingsOf } from '@hatiolab/figure-model'
|
|
7
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
9
|
import { RUNGS, rungOf, type Rung } from '@things-factory/figure-viewer/dist-client/capability-rung.js'
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
inputTableAt,
|
|
16
17
|
namesWith,
|
|
17
18
|
needsMissing,
|
|
19
|
+
needsOffered,
|
|
18
20
|
partsAt,
|
|
19
21
|
settingsIssues,
|
|
20
22
|
settingsOf,
|
|
@@ -50,6 +52,12 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
50
52
|
border-radius: 8px;
|
|
51
53
|
cursor: pointer;
|
|
52
54
|
}
|
|
55
|
+
/* The box keeps its size beside a long note: squeezed by the flex row it read as a dot (sweep 2026-10-27 U1). */
|
|
56
|
+
label[rung] > input,
|
|
57
|
+
label[item] > input {
|
|
58
|
+
flex: none;
|
|
59
|
+
margin: 2px 0 0;
|
|
60
|
+
}
|
|
53
61
|
label[rung][on],
|
|
54
62
|
label[item][on] {
|
|
55
63
|
border-color: var(--md-sys-color-primary);
|
|
@@ -135,7 +143,8 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
135
143
|
const names = asset.document.capabilities.names ?? []
|
|
136
144
|
const rung = rungOf(names)
|
|
137
145
|
const places = figureBindingsOf(asset)
|
|
138
|
-
return html
|
|
146
|
+
return html`${this.renderItem(asset)}
|
|
147
|
+
<section>
|
|
139
148
|
<h3>${i18next.t('figure.label.what-this-does')}</h3>
|
|
140
149
|
${(['none', 'hold', 'flow'] as const).map(
|
|
141
150
|
which => html`<label rung ?on=${rung === which}>
|
|
@@ -147,10 +156,18 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
147
156
|
</label>`
|
|
148
157
|
)}
|
|
149
158
|
${rung === null ? html`<p hint>${i18next.t('figure.text.capability-hand-written')}</p>` : nothing}
|
|
150
|
-
${names.length ? html`<code>${names.join(' · ')}</code>` : nothing}
|
|
151
159
|
</section>
|
|
152
160
|
${this.renderConsumers(asset)}
|
|
153
|
-
|
|
161
|
+
${places.length || !this.selected ? this.renderPlaces(places, rung) : nothing}
|
|
162
|
+
`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
The figure's places. None yet, with a part picked: nothing here -- the part's own place form stands above, and 「pick a
|
|
167
|
+
part and add one」 under it told the author what they were doing (sweep 2026-10-27 U20).
|
|
168
|
+
*/
|
|
169
|
+
private renderPlaces(places: ReturnType<typeof figureBindingsOf>, rung: Rung | null) {
|
|
170
|
+
return html`<section>
|
|
154
171
|
<h3>${i18next.t('figure.label.places')}</h3>
|
|
155
172
|
${places.length
|
|
156
173
|
? places.map(
|
|
@@ -164,8 +181,12 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
164
181
|
)
|
|
165
182
|
: html`<p hint>${i18next.t('figure.text.no-places')}</p>`}
|
|
166
183
|
${places.length && rung === 'none' ? html`<p hint>${i18next.t('figure.text.place-needs-holding')}</p>` : nothing}
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
</section>`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Whether the figure is an item: what a product capability may need first, so it stands first (sweep 2026-10-27 J4). */
|
|
188
|
+
private renderItem(asset: FigureAsset) {
|
|
189
|
+
return html`<section>
|
|
169
190
|
<h3>${i18next.t('figure.label.item')}</h3>
|
|
170
191
|
<label item ?on=${!!asset.item}>
|
|
171
192
|
<input type="checkbox" .checked=${live(!!asset.item)} ?disabled=${this.locked} @change=${(e: Event) => this.setItem((e.target as HTMLInputElement).checked)} />
|
|
@@ -174,8 +195,7 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
174
195
|
<em>${i18next.t('figure.text.is-item-note')}</em>
|
|
175
196
|
</span>
|
|
176
197
|
</label>
|
|
177
|
-
|
|
178
|
-
`
|
|
198
|
+
</section>`
|
|
179
199
|
}
|
|
180
200
|
|
|
181
201
|
/** The products' capabilities: those this modeller knows, each on or off with its settings, and those it does not know. */
|
|
@@ -189,15 +209,22 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
189
209
|
${known.map(d => {
|
|
190
210
|
const on = names.includes(d.name)
|
|
191
211
|
const missing = needsMissing(d, asset)
|
|
212
|
+
const offered = needsOffered(d, asset)
|
|
213
|
+
const needs = [
|
|
214
|
+
...(offered.item ? [i18next.t('figure.label.is-item')] : []),
|
|
215
|
+
...(offered.rungs.length
|
|
216
|
+
? [i18next.t('figure.text.product-capability-needs-rungs', { rungs: offered.rungs.map(r => i18next.t(`figure.label.rung-${r}`)).join(i18next.t('figure.label.or-between')) })]
|
|
217
|
+
: []),
|
|
218
|
+
...offered.names
|
|
219
|
+
]
|
|
192
220
|
return html`<label rung ?on=${on}>
|
|
193
221
|
<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)} />
|
|
194
222
|
<span>
|
|
195
223
|
<strong>${d.label}</strong>
|
|
196
224
|
<em>${d.note}</em>
|
|
197
|
-
|
|
225
|
+
${needs.length ? html`<small needs>${i18next.t('figure.text.product-capability-needs', { needs: needs.join(', ') })}</small>` : nothing}
|
|
198
226
|
</span>
|
|
199
227
|
</label>
|
|
200
|
-
${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}
|
|
201
228
|
${on ? this.renderSettings(asset, d) : nothing}`
|
|
202
229
|
})}
|
|
203
230
|
${unknown.map(
|
|
@@ -235,7 +262,7 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
235
262
|
return html`<span>${setting.label}</span>
|
|
236
263
|
<select ?disabled=${this.locked} @change=${(e: Event) => write(withInputTable(settings, setting, (e.target as HTMLSelectElement).value || undefined, table.values))}>
|
|
237
264
|
<option value="" ?selected=${!table.input}>${i18next.t('figure.label.data-none')}</option>
|
|
238
|
-
${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>`)}
|
|
265
|
+
${inputs.map(i => html`<option value=${i.id} ?selected=${i.id === table.input}>${asset.stateInputs?.[i.id]?.label ?? i.id}${i.unit ? ` (${unitWord(i.unit)} ${i.min ?? ''}~${i.max ?? ''})` : ''}</option>`)}
|
|
239
266
|
</select>
|
|
240
267
|
${table.input
|
|
241
268
|
? html`<span></span>
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import { unitWord } from './unit-words.js'
|
|
7
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
9
|
|
|
9
10
|
import { clipGroupsOf, clipsOf, freeGroupIdOf, groupEdit, poseAt, trackEdit, undrivenOf, withKey, withoutKey, type Clip, type ClipGroup, type ClipTrack } from './clips.js'
|
|
@@ -118,7 +119,7 @@ export class FigureClips extends localize(i18next)(LitElement) {
|
|
|
118
119
|
<h3>${i18next.t('figure.label.clips')}${this.help.button('figure.text.clips-explained')}</h3>
|
|
119
120
|
${this.help.hint('figure.text.clips-explained')}
|
|
120
121
|
${clips.map(clip => this.renderClip(clip))}
|
|
121
|
-
${empty ? html`<p hint>${i18next.t('figure.text.no-
|
|
122
|
+
${empty ? html`<p hint>${i18next.t('figure.text.no-clips')}</p>` : nothing}
|
|
122
123
|
</section>
|
|
123
124
|
${empty ? nothing : this.renderAdd(asset, clips)} ${this.renderGroups(asset)}
|
|
124
125
|
`
|
|
@@ -156,7 +157,7 @@ export class FigureClips extends localize(i18next)(LitElement) {
|
|
|
156
157
|
const current = this.values[track.state]
|
|
157
158
|
return html`<div track>
|
|
158
159
|
<div track-head>
|
|
159
|
-
<strong>${track.label} <small>(${track.unit})</small></strong>
|
|
160
|
+
<strong>${track.label} <small>(${unitWord(track.unit)})</small></strong>
|
|
160
161
|
<select
|
|
161
162
|
aria-label=${i18next.t('figure.label.clip-loop')}
|
|
162
163
|
?disabled=${this.locked}
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import { unitWord } from './unit-words.js'
|
|
7
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
9
|
|
|
9
10
|
import { controlsOf, poseAsDefault, poseDiffers, type Control } from '@things-factory/figure-viewer/dist-client/controls.js'
|
|
@@ -125,7 +126,7 @@ export class FigureControls extends localize(i18next)(LitElement) {
|
|
|
125
126
|
const c = this.live[control.id] === undefined ? control : { ...control, value: this.live[control.id]! }
|
|
126
127
|
const open = this.open === c.id
|
|
127
128
|
return html`<div control>
|
|
128
|
-
<span name title=${c.id}>${c.label} <em>${c.unit}</em></span>
|
|
129
|
+
<span name title=${c.id}>${c.label} <em>${unitWord(c.unit)}</em></span>
|
|
129
130
|
${c.unit === 'deg' ? this.renderDial(c) : html`<span></span>`}
|
|
130
131
|
<input
|
|
131
132
|
type="range"
|
|
@@ -177,7 +178,7 @@ export class FigureControls extends localize(i18next)(LitElement) {
|
|
|
177
178
|
<div form>
|
|
178
179
|
<span>${i18next.t('figure.label.control-name')}</span>
|
|
179
180
|
<input .value=${c.named ? c.label : ''} placeholder=${c.id} ?disabled=${this.locked} @change=${(e: Event) => policy({ label: (e.target as HTMLInputElement).value })} />
|
|
180
|
-
<span>${i18next.t('figure.label.control-range')} (${c.unit})</span>
|
|
181
|
+
<span>${i18next.t('figure.label.control-range')} (${unitWord(c.unit)})</span>
|
|
181
182
|
<div pair>
|
|
182
183
|
<input type="number" step="any" .value=${String(c.min)} ?disabled=${this.locked} @change=${(e: Event) => policy({ min: number(e) })} />
|
|
183
184
|
<input type="number" step="any" .value=${String(c.max)} ?disabled=${this.locked} @change=${(e: Event) => policy({ max: number(e) })} />
|
|
@@ -185,7 +186,7 @@ export class FigureControls extends localize(i18next)(LitElement) {
|
|
|
185
186
|
<span>${i18next.t('figure.label.control-sweep')}</span>
|
|
186
187
|
<input type="number" min="0" step="any" .value=${String(c.sweep)} ?disabled=${this.locked} @change=${(e: Event) => policy({ sweep: number(e) })} />
|
|
187
188
|
</div>
|
|
188
|
-
<small>${i18next.t('figure.text.control-starts-at', { value: c.start, unit: c.unit })}</small>
|
|
189
|
+
<small>${i18next.t('figure.text.control-starts-at', { value: c.start, unit: unitWord(c.unit) })}</small>
|
|
189
190
|
</div>`
|
|
190
191
|
}
|
|
191
192
|
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import { unitWord } from './unit-words.js'
|
|
7
8
|
import { FIGURE_UNITS, figureDataExample, inferFigureDataSchema } from '@hatiolab/figure-model'
|
|
8
9
|
import type {
|
|
9
10
|
FigureAsset,
|
|
@@ -137,6 +138,17 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
137
138
|
overflow-wrap: anywhere;
|
|
138
139
|
cursor: pointer;
|
|
139
140
|
}
|
|
141
|
+
/* What it reads and where on the first line, its method under it: a method wrapped alone read 「· 배율」 (sweep 2026-10-27 U11). */
|
|
142
|
+
div[head] button[line] small {
|
|
143
|
+
display: block;
|
|
144
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
145
|
+
}
|
|
146
|
+
/* A binding a product capability takes over is marked where the list is read, folded or not (sweep 2026-10-27 U18). */
|
|
147
|
+
div[head] button[line] span[rests] {
|
|
148
|
+
padding: 0 6px;
|
|
149
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
150
|
+
border-radius: 999px;
|
|
151
|
+
}
|
|
140
152
|
div[head] button[act] {
|
|
141
153
|
width: 30px;
|
|
142
154
|
min-height: var(--figure-control-height);
|
|
@@ -307,7 +319,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
307
319
|
const kind = i18next.t('figure.label.data-kind-' + node.kind)
|
|
308
320
|
if (node.kind === 'number') {
|
|
309
321
|
const range = node.min !== undefined || node.max !== undefined ? ` ${node.min ?? ''}~${node.max ?? ''}` : ''
|
|
310
|
-
return `${kind}${node.unit ? ` ${node.unit}` : ''}${range}`
|
|
322
|
+
return `${kind}${node.unit ? ` ${unitWord(node.unit)}` : ''}${range}`
|
|
311
323
|
}
|
|
312
324
|
if (node.kind === 'text' && node.values) return `${kind} (${node.values.join(', ')})`
|
|
313
325
|
if (node.kind === 'list') return `${kind} · ${i18next.t('figure.label.data-kind-' + node.item.kind)}`
|
|
@@ -346,7 +358,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
346
358
|
this.edit({ kind: 'set-data-schema', schema: schemaWithoutField(schema, row.path) })
|
|
347
359
|
}}
|
|
348
360
|
>
|
|
349
|
-
<md-icon>
|
|
361
|
+
<md-icon>delete</md-icon>
|
|
350
362
|
</button>
|
|
351
363
|
</div>
|
|
352
364
|
<div form>
|
|
@@ -361,8 +373,8 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
361
373
|
${field.kind === 'number'
|
|
362
374
|
? html`<span>${i18next.t('figure.label.data-unit')}</span>
|
|
363
375
|
<select ?disabled=${this.locked} @change=${(e: Event) => set({ unit: value(e) || undefined })}>
|
|
364
|
-
<option value="" ?selected=${!field.unit}
|
|
365
|
-
${FIGURE_UNITS.map(u => html`<option value=${u} ?selected=${u === field.unit}>${u}</option>`)}
|
|
376
|
+
<option value="" ?selected=${!field.unit}>${i18next.t('figure.label.unit-none')}</option>
|
|
377
|
+
${FIGURE_UNITS.map(u => html`<option value=${u} ?selected=${u === field.unit}>${unitWord(u)}</option>`)}
|
|
366
378
|
</select>
|
|
367
379
|
<span>${i18next.t('figure.label.data-min')}</span>
|
|
368
380
|
<input type="number" step="any" .value=${field.min === undefined ? '' : String(field.min)} ?disabled=${this.locked} @change=${(e: Event) => set({ min: number(e) })} />
|
|
@@ -412,22 +424,25 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
412
424
|
private renderAddField(schema: readonly FigureDataField[], under: string) {
|
|
413
425
|
const a = this.adding
|
|
414
426
|
const named = a.name.trim()
|
|
427
|
+
const add = () => {
|
|
428
|
+
if (this.locked || !named) return
|
|
429
|
+
this.edit({ kind: 'set-data-schema', schema: schemaWithField(schema, under, { name: named, ...nodeOfKind(a.kind) } as FigureDataField) })
|
|
430
|
+
this.adding = { ...a, name: '' }
|
|
431
|
+
}
|
|
415
432
|
return html`<div form>
|
|
416
433
|
<span>${i18next.t('figure.label.data-name')}</span>
|
|
417
|
-
<input
|
|
434
|
+
<input
|
|
435
|
+
aria-label=${i18next.t('figure.label.data-name')}
|
|
436
|
+
.value=${a.name}
|
|
437
|
+
@input=${(e: Event) => (this.adding = { ...a, name: (e.target as HTMLInputElement).value })}
|
|
438
|
+
@keydown=${(e: KeyboardEvent) => e.key === 'Enter' && !e.isComposing && add()}
|
|
439
|
+
/>
|
|
418
440
|
<span>${i18next.t('figure.label.data-kind')}</span>
|
|
419
441
|
<select @change=${(e: Event) => (this.adding = { ...a, kind: (e.target as HTMLSelectElement).value as FigureDataNode['kind'] })}>
|
|
420
442
|
${KINDS.map(k => html`<option value=${k} ?selected=${k === a.kind}>${i18next.t('figure.label.data-kind-' + k)}</option>`)}
|
|
421
443
|
</select>
|
|
422
444
|
</div>
|
|
423
|
-
<button
|
|
424
|
-
act
|
|
425
|
-
?disabled=${this.locked || !named}
|
|
426
|
-
@click=${() => {
|
|
427
|
-
this.edit({ kind: 'set-data-schema', schema: schemaWithField(schema, under, { name: named, ...nodeOfKind(a.kind) } as FigureDataField) })
|
|
428
|
-
this.adding = { ...a, name: '' }
|
|
429
|
-
}}
|
|
430
|
-
>
|
|
445
|
+
<button act ?disabled=${this.locked || !named} @click=${add}>
|
|
431
446
|
<md-icon>add</md-icon>${i18next.t(under ? 'figure.button.add-data-child' : 'figure.button.add-data-field', { parent: under })}
|
|
432
447
|
</button>`
|
|
433
448
|
}
|
|
@@ -499,13 +514,15 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
499
514
|
const open = this.opened === b.id
|
|
500
515
|
const set = (next: FigureDataBinding) => this.edit({ kind: 'set-data-binding', binding: next })
|
|
501
516
|
const what = b.rule === 'table' ? this.targetsName(asset, b) : this.behaviorName(asset, choices, b.behavior)
|
|
517
|
+
const rests = resting.some(r => r.id === b.id)
|
|
502
518
|
return html`<section item>
|
|
503
519
|
<div head>
|
|
504
520
|
<button line aria-expanded=${open ? 'true' : 'false'} @click=${() => (this.opened = open ? '' : b.id)}>
|
|
505
|
-
<strong>${b.label ?? b.accessor}</strong> → ${what}
|
|
521
|
+
<strong>${b.label ?? b.accessor}</strong> → ${what}
|
|
522
|
+
<small>${i18next.t('figure.label.data-method-' + methodOf(b))}${rests ? html` <span rests>${i18next.t('figure.label.data-binding-resting')}</span>` : nothing}</small>
|
|
506
523
|
</button>
|
|
507
524
|
<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-binding')} @click=${() => this.edit({ kind: 'remove-data-binding', id: b.id })}>
|
|
508
|
-
<md-icon>
|
|
525
|
+
<md-icon>delete</md-icon>
|
|
509
526
|
</button>
|
|
510
527
|
</div>
|
|
511
528
|
${resting.filter(r => r.id === b.id).map(r => this.renderResting(r))}
|
|
@@ -700,7 +717,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
700
717
|
return html`<div looks style="grid-template-columns: ${gridOf(keyCells, columns.length)}">
|
|
701
718
|
<span style="grid-column: span ${keyCells}">${i18next.t('figure.label.data-otherwise')}</span>
|
|
702
719
|
${this.lookCells(asset, columns, look, l => set(tableWithLook(b, 'default', l)))}
|
|
703
|
-
${needed ? html`<span></span>` : html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => set({ ...b, table: rest })}><md-icon>
|
|
720
|
+
${needed ? html`<span></span>` : html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => set({ ...b, table: rest })}><md-icon>delete</md-icon></button>`}
|
|
704
721
|
</div>`
|
|
705
722
|
}
|
|
706
723
|
|
|
@@ -736,7 +753,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
736
753
|
${this.lookCells(asset, columns, look, l => set(tableWithLook(b, k, l)))}
|
|
737
754
|
${boolean || rows.length < 2 && domain.length > 0
|
|
738
755
|
? html`<span></span>`
|
|
739
|
-
: html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => rekeyed(rows.filter((_, j) => j !== i))}><md-icon>
|
|
756
|
+
: html`<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => rekeyed(rows.filter((_, j) => j !== i))}><md-icon>delete</md-icon></button>`}`
|
|
740
757
|
)}
|
|
741
758
|
</div>
|
|
742
759
|
${this.renderOtherwise(asset, b, columns, domain.length > 0 && !covered, set, 1)}
|
|
@@ -779,7 +796,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
779
796
|
return html`<input type="number" step="any" .value=${e.from} ?disabled=${this.locked} @change=${(ev: Event) => rekey(i, 'from', (ev.target as HTMLInputElement).value)} />
|
|
780
797
|
<input type="number" step="any" .value=${e.to} ?disabled=${this.locked} @change=${(ev: Event) => rekey(i, 'to', (ev.target as HTMLInputElement).value)} />
|
|
781
798
|
${this.lookCells(asset, columns, look, l => set(tableWithLook(b, k, l)))}
|
|
782
|
-
<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-range')} @click=${() => set(tableWithKeys(b, rows.filter((_, j) => j !== i)))}><md-icon>
|
|
799
|
+
<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-range')} @click=${() => set(tableWithKeys(b, rows.filter((_, j) => j !== i)))}><md-icon>delete</md-icon></button>`
|
|
783
800
|
})}
|
|
784
801
|
</div>
|
|
785
802
|
${this.renderOtherwise(asset, b, columns, false, set, 2)}
|
|
@@ -868,9 +885,12 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
868
885
|
<input .value=${a.name} @input=${(e: Event) => set({ name: typed(e) })} />`
|
|
869
886
|
: nothing}
|
|
870
887
|
<span>${i18next.t('figure.label.data-method')}</span>
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
888
|
+
${methods.length === 1
|
|
889
|
+
? /* One way only (a text is read by a code table): said, not offered as a choice of one (sweep 2026-10-27 U10). */
|
|
890
|
+
html`<span method>${i18next.t('figure.label.data-method-' + methods[0])}</span>`
|
|
891
|
+
: html`<select ?disabled=${!methods.length} @change=${(e: Event) => set({ method: typed(e) as DataMethod })}>
|
|
892
|
+
${methods.map(m => html`<option value=${m} ?selected=${m === method}>${i18next.t('figure.label.data-method-' + m)}</option>`)}
|
|
893
|
+
</select>`}
|
|
874
894
|
${!table && fitting.length
|
|
875
895
|
? html`<span>${i18next.t('figure.label.data-behavior')}</span>
|
|
876
896
|
<select @change=${(e: Event) => set({ behavior: typed(e) })}>
|
|
@@ -7,6 +7,7 @@ import './figure-size-editor.js'
|
|
|
7
7
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
8
8
|
|
|
9
9
|
import { i18next, localize } from '@operato/i18n'
|
|
10
|
+
import { unitWord } from './unit-words.js'
|
|
10
11
|
import { figureFaceOverhangOf, figureMotionsOf, motionIdOf } from '@hatiolab/figure-model'
|
|
11
12
|
import type { FigureAsset, FigureAuthoringAction, FigureBindingPlace, FigureFace } from '@hatiolab/figure-model'
|
|
12
13
|
|
|
@@ -624,7 +625,6 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
624
625
|
const axes = followableAxes(asset, part)
|
|
625
626
|
if (!axes.length) return nothing
|
|
626
627
|
const word = (axis: Axis) => `${axis.toUpperCase()}${measureFactor(asset, part, axis) === 2 ? ' · ' + i18next.t('figure.label.size-diameter') : ''}`
|
|
627
|
-
const unitWord = (unit: string) => i18next.t(`figure.label.state-unit-${unit}`)
|
|
628
628
|
const sized = axes.flatMap(axis => {
|
|
629
629
|
const f = followOf(asset, part, axis)
|
|
630
630
|
return f.of === 'state' ? [{ axis, f }] : []
|
|
@@ -687,9 +687,9 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
687
687
|
<input type="number" step="any" .value=${String(form.min)} ?disabled=${this.locked} @change=${number('min')} />
|
|
688
688
|
<input type="number" step="any" .value=${String(form.max)} ?disabled=${this.locked} @change=${number('max')} />
|
|
689
689
|
</div>
|
|
690
|
-
<span>${i18next.t('figure.label.state-size-at-min')} (mm)</span>
|
|
690
|
+
<span>${i18next.t('figure.label.state-size-at-min', { at: String(form.min) })} (mm)</span>
|
|
691
691
|
<input type="number" step="any" min="0" state-size-at-min .value=${Number.isFinite(form.atMin) ? String(form.atMin) : ''} ?disabled=${this.locked} @change=${number('atMin')} />
|
|
692
|
-
<span>${i18next.t('figure.label.state-size-at-max')} (mm)</span>
|
|
692
|
+
<span>${i18next.t('figure.label.state-size-at-max', { at: String(form.max) })} (mm)</span>
|
|
693
693
|
<input type="number" step="any" min="0" state-size-at-max .value=${Number.isFinite(atMax) ? String(atMax) : ''} ?disabled=${this.locked} @change=${number('atMax')} />
|
|
694
694
|
</div>
|
|
695
695
|
<button act add-state-size ?disabled=${this.locked || !command} @click=${() => {
|
|
@@ -768,7 +768,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
768
768
|
: it.follows
|
|
769
769
|
? html`<small>${i18next.t('figure.label.motion-follow')}: ${it.follows.sum.map(t => `${t.joint} × ${t.multiplier}`).join(' + ')} + ${it.follows.offset}</small>`
|
|
770
770
|
: it.control
|
|
771
|
-
? html`<small>${it.control.id} (${it.control.unit}) ${it.control.min} … ${it.control.max}</small>`
|
|
771
|
+
? html`<small>${it.control.id} (${unitWord(it.control.unit)}) ${it.control.min} … ${it.control.max}</small>`
|
|
772
772
|
: nothing}
|
|
773
773
|
</span>
|
|
774
774
|
${count > 1
|
|
@@ -792,7 +792,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
792
792
|
})}
|
|
793
793
|
</p>
|
|
794
794
|
${ends
|
|
795
|
-
? html`<p hint motion-ends>${i18next.t('figure.text.motion-ends-shown', { min: String(ends.min), max: String(ends.max), unit: ends.unit })}</p>`
|
|
795
|
+
? html`<p hint motion-ends>${i18next.t('figure.text.motion-ends-shown', { min: String(ends.min), max: String(ends.max), unit: unitWord(ends.unit) })}</p>`
|
|
796
796
|
: nothing}
|
|
797
797
|
${Array.from({ length: count }, (_, k) => row(k))}
|
|
798
798
|
<div pair>
|