@things-factory/figure-ui 10.1.208 → 10.1.211
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/data-interface.ts +5 -5
- package/client/modeller/editor.ts +97 -88
- package/client/modeller/fasten-geometry.ts +2 -1
- package/client/modeller/figure-links.ts +87 -157
- package/client/modeller/figure-size-editor.ts +233 -0
- package/client/modeller/joint-drag.ts +25 -21
- package/client/modeller/joint-fan.ts +4 -3
- package/client/modeller/links.ts +87 -36
- package/client/modeller/live.ts +33 -0
- package/client/modeller/motion-ends.ts +10 -6
- package/client/modeller/size-response.ts +95 -0
- package/dist-client/modeller/data-interface.d.ts +1 -1
- package/dist-client/modeller/data-interface.js +5 -5
- package/dist-client/modeller/data-interface.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +14 -1
- package/dist-client/modeller/editor.js +103 -89
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.js +1 -1
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +3 -4
- package/dist-client/modeller/figure-links.js +91 -145
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-size-editor.d.ts +29 -0
- package/dist-client/modeller/figure-size-editor.js +262 -0
- package/dist-client/modeller/figure-size-editor.js.map +1 -0
- package/dist-client/modeller/joint-drag.d.ts +1 -1
- package/dist-client/modeller/joint-drag.js +27 -22
- package/dist-client/modeller/joint-drag.js.map +1 -1
- package/dist-client/modeller/joint-fan.js +4 -3
- package/dist-client/modeller/joint-fan.js.map +1 -1
- package/dist-client/modeller/links.d.ts +21 -9
- package/dist-client/modeller/links.js +85 -35
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/live.d.ts +14 -0
- package/dist-client/modeller/live.js +31 -0
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/modeller/motion-ends.d.ts +4 -1
- package/dist-client/modeller/motion-ends.js +10 -6
- package/dist-client/modeller/motion-ends.js.map +1 -1
- package/dist-client/modeller/size-response.d.ts +29 -0
- package/dist-client/modeller/size-response.js +99 -0
- package/dist-client/modeller/size-response.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/docs/evidence/2026-10-24/current-size-panel.png +0 -0
- package/docs/evidence/2026-10-24/integrated-size.png +0 -0
- package/docs/evidence/2026-10-24/multipart-response.png +0 -0
- package/docs/evidence/2026-10-24/repeat-layout.png +0 -0
- package/docs/evidence/2026-10-24/repeat-shared-style.png +0 -0
- package/docs/evidence/2026-10-24/size-in-layout.png +0 -0
- package/docs/evidence/2026-10-24/size-shared-style.png +0 -0
- package/docs/evidence/2026-10-24/width-preview.png +0 -0
- package/docs/multipart-response-review-2026-10-24.md +77 -0
- package/docs/response-authoring-2026-10-24.md +46 -0
- package/docs/size-and-repeat-ux-2026-10-24.md +67 -0
- package/package.json +7 -7
- package/test/attachment-size-contract.test.ts +47 -0
- package/test/browser/acceptance-page.js +1 -1
- package/test/browser/detail-page.js +1 -1
- package/test/browser/detail-tabs-page.js +1 -1
- package/test/browser/editing-page.js +1 -1
- package/test/browser/editing.check.mjs +66 -14
- package/test/browser/links.check.mjs +41 -22
- package/test/browser/modeller-page.js +1 -1
- package/test/browser/modeller-revision.check.mjs +21 -7
- package/test/browser/modeller.check.mjs +106 -39
- package/test/browser/multipart-response-page.js +8 -0
- package/test/browser/parts.check.mjs +2 -1
- package/test/browser/product-page.js +1 -1
- package/test/browser/props.check.mjs +17 -14
- package/test/browser/response-page.js +12 -0
- package/test/browser/row-preview.check.mjs +3 -3
- package/test/browser/screen.check.mjs +57 -13
- package/test/browser/serve-response.mjs +24 -0
- package/test/clip-ai-proposal.test.ts +1 -1
- package/test/clip-consent.test.ts +2 -2
- package/test/command-preview.test.ts +7 -7
- package/test/joint-drag.test.ts +6 -6
- package/test/joint-frames.test.ts +33 -32
- package/test/joints.ts +1 -6
- package/test/links.test.ts +60 -20
- package/test/multipart-response-fixture.js +17 -0
- package/test/multipart-response.test.ts +37 -0
- package/test/refusals.test.ts +2 -2
- package/test/resize-with-placement.test.ts +23 -1
- package/test/size-response.test.ts +49 -0
- package/test/size-source-pairing.test.ts +66 -0
- package/translations/en.json +47 -10
- package/translations/es.json +47 -10
- package/translations/ja.json +47 -10
- package/translations/ko.json +52 -15
- package/translations/ms.json +47 -10
- package/translations/zh.json +47 -10
|
@@ -3,24 +3,20 @@ import '@material/web/icon/icon.js'
|
|
|
3
3
|
import { css, html, LitElement, nothing } from 'lit'
|
|
4
4
|
import '@operato/input/ox-input-select-buttons.js'
|
|
5
5
|
import './figure-slot-layout-panel.js'
|
|
6
|
+
import './figure-size-editor.js'
|
|
6
7
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
7
8
|
|
|
8
9
|
import { i18next, localize } from '@operato/i18n'
|
|
9
|
-
import { figureFaceOverhangOf } from '@hatiolab/figure-model'
|
|
10
|
-
import type { FigureAsset, FigureAuthoringAction,
|
|
10
|
+
import { figureFaceOverhangOf, figureMotionsOf, motionIdOf } from '@hatiolab/figure-model'
|
|
11
|
+
import type { FigureAsset, FigureAuthoringAction, FigureFace } from '@hatiolab/figure-model'
|
|
11
12
|
|
|
12
13
|
import {
|
|
13
14
|
acceptsOf,
|
|
14
15
|
AXES,
|
|
15
16
|
canFasten,
|
|
16
17
|
canFollow,
|
|
17
|
-
followCommandOf,
|
|
18
|
-
followableAxes,
|
|
19
18
|
positionIsShare,
|
|
20
|
-
choiceOf,
|
|
21
|
-
followWordsOf,
|
|
22
19
|
heldBy,
|
|
23
|
-
followOf,
|
|
24
20
|
shownWhenOf,
|
|
25
21
|
visibilityControlsOf,
|
|
26
22
|
fasteningOf,
|
|
@@ -32,6 +28,8 @@ import {
|
|
|
32
28
|
motionFormRefusalOf,
|
|
33
29
|
endPlaceKeyOf,
|
|
34
30
|
motionJoint,
|
|
31
|
+
controlIdFor,
|
|
32
|
+
withoutAxis,
|
|
35
33
|
placeFacesOf,
|
|
36
34
|
placeIdFor,
|
|
37
35
|
placesOn,
|
|
@@ -40,9 +38,7 @@ import {
|
|
|
40
38
|
fixedJointOf,
|
|
41
39
|
rowOf,
|
|
42
40
|
positionsWorkedOut,
|
|
43
|
-
measureNow,
|
|
44
41
|
type Axis,
|
|
45
|
-
type Follow,
|
|
46
42
|
type MotionForm
|
|
47
43
|
} from './links.js'
|
|
48
44
|
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
|
|
@@ -113,8 +109,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
113
109
|
font-weight: 600;
|
|
114
110
|
}
|
|
115
111
|
/* What each direction does now: read-only rows. */
|
|
116
|
-
div[resize-now] { display: grid; gap: 4px; margin: 12px 0; }
|
|
117
|
-
div[resize-now] div[row] { grid-template-columns:
|
|
112
|
+
div[resize-now] { display: grid; grid-template-columns: 1fr auto auto auto; gap: 4px 8px; margin: 12px 0; }
|
|
113
|
+
div[resize-now] div[row] { grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
|
|
118
114
|
div[resize-now] span[measure] { font-variant-numeric: tabular-nums; }
|
|
119
115
|
/* Size behaviour is a choice about dimensions only. It never implies a placement or a fastening. */
|
|
120
116
|
/* One of the size group's rows: the group's gap spaces it, as it does the rest (a margin on top of it left a hole). */
|
|
@@ -124,13 +120,11 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
124
120
|
details { margin-top: 8px; }
|
|
125
121
|
details[row-fold] { margin: 0; }
|
|
126
122
|
details[row-fold] > summary { padding: 0; color: var(--md-sys-color-on-surface); }
|
|
127
|
-
details[row-fold] > summary h3 { margin: 0; }
|
|
123
|
+
details[row-fold] > summary h3 { margin: 0; flex: 1; }
|
|
128
124
|
details[row-fold][open] > summary { margin-bottom: 8px; }
|
|
129
125
|
/* What the command would do, said beside the result drawn on the canvas. */
|
|
130
126
|
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; }
|
|
131
127
|
p[preview-words] { margin: 8px 0; padding: 8px 12px; border-radius: 6px; background: var(--md-sys-color-surface-container, #8881); }
|
|
132
|
-
div[row-basis] { display: grid; gap: 4px; margin: 8px 0; }
|
|
133
|
-
div[row-basis] label { display: flex; align-items: center; gap: 6px; }
|
|
134
128
|
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); }
|
|
135
129
|
div[overhang] p { margin: 0; }
|
|
136
130
|
ul[detach-words] { margin: 4px 0 10px; padding-left: 20px; display: grid; gap: 2px; }
|
|
@@ -191,6 +185,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
191
185
|
@state() private row = freshRow()
|
|
192
186
|
/** The motion form is open over the part's own motion, to change it where it is. */
|
|
193
187
|
@state() private editingMotion = false
|
|
188
|
+
/** Which of the joint's axes the form is over (ADR-0159); one past the last is a new axis. */
|
|
189
|
+
@state() private motionAxis = 0
|
|
194
190
|
|
|
195
191
|
/*
|
|
196
192
|
The forms describe the next command for the selected part, so another part starts from the defaults: carrying the
|
|
@@ -209,7 +205,10 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
209
205
|
this.motion = freshMotion()
|
|
210
206
|
this.place = freshPlace()
|
|
211
207
|
this.row = freshRow()
|
|
208
|
+
this.rowOpen = false
|
|
209
|
+
this.rowCandidate = undefined
|
|
212
210
|
this.editingMotion = false
|
|
211
|
+
this.motionAxis = 0
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
214
|
|
|
@@ -273,13 +272,14 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
273
272
|
How the part answers a size it is placed at (user 2026-10-17, the 「반응형」 tab): its own measures, where it
|
|
274
273
|
stands, and the row it may make -- what Build Once, Fit Anywhere is authored with, in one place.
|
|
275
274
|
*/
|
|
276
|
-
const positions = positionsWorkedOut(asset, this.partId)
|
|
275
|
+
const positions = held ? [] : positionsWorkedOut(asset, this.partId)
|
|
276
|
+
this.rowCandidate = undefined
|
|
277
277
|
const response = html`
|
|
278
|
-
|
|
278
|
+
${canFollow(asset, this.partId) ? html`<section size>
|
|
279
279
|
<h3>${i18next.t('figure.label.group-size')}</h3>
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
</section
|
|
280
|
+
<figure-size-editor .revision=${this.revision} .partId=${this.partId} ?locked=${this.locked}></figure-size-editor>
|
|
281
|
+
<slot name="size-note"></slot>
|
|
282
|
+
</section>` : nothing}
|
|
283
283
|
${positions.length
|
|
284
284
|
? html`<section position-response>
|
|
285
285
|
<h3>${i18next.t('figure.label.group-position-response')}</h3>
|
|
@@ -292,10 +292,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
292
292
|
return response
|
|
293
293
|
}
|
|
294
294
|
const shown = html`
|
|
295
|
-
<
|
|
296
|
-
<h3>${i18next.t('figure.label.group-size')}</h3>
|
|
297
|
-
<slot name="size"></slot>
|
|
298
|
-
</section>
|
|
295
|
+
<slot name="size"></slot>
|
|
299
296
|
<section fastening>
|
|
300
297
|
<h3>
|
|
301
298
|
${i18next.t('figure.label.group-place')}${this.help.button('figure.text.fastening-explained')}
|
|
@@ -379,80 +376,6 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
379
376
|
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit: { kind: 'set-capabilities', names } }, bubbles: true, composed: true }))
|
|
380
377
|
}
|
|
381
378
|
|
|
382
|
-
private renderDimensions(asset: FigureAsset) {
|
|
383
|
-
const editable = followableAxes(asset, this.partId)
|
|
384
|
-
const round = editable.length === 2
|
|
385
|
-
const word = (axis: Axis) => i18next.t(round && axis === 'x' ? 'figure.label.resize-diameter' : `figure.label.resize-${axis}`)
|
|
386
|
-
const others = asset.appearance.map(look => look.target).filter(one => one !== this.partId && canFollow(asset, one))
|
|
387
|
-
const followsAll = editable.every(axis => followOf(asset, this.partId, axis).of === 'instance')
|
|
388
|
-
/* 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). */
|
|
389
|
-
const allOwn = editable.every(axis => followOf(asset, this.partId, axis).of === 'own')
|
|
390
|
-
return html`${asset.sizing === 'uniform' ? html`<p hint>${i18next.t('figure.text.resize-uniform-warning')}</p>` : nothing}
|
|
391
|
-
${allOwn ? nothing : html`<div resize-now>${editable.map(axis => {
|
|
392
|
-
const now = followOf(asset, this.partId, axis)
|
|
393
|
-
const following = now.of === 'instance' && now.axis === axis
|
|
394
|
-
const custom = now.of !== 'own' && !following
|
|
395
|
-
const mm = measureNow(this.revision!, this.partId, axis)
|
|
396
|
-
return html`<div row>
|
|
397
|
-
<b>${word(axis)}</b>
|
|
398
|
-
<span measure>${mm === undefined ? '—' : `${Math.round(mm)} mm`}</span>
|
|
399
|
-
<span>${custom ? followWordsOf(now, (key, params) => i18next.t(key, params)) : i18next.t(following ? 'figure.label.resize-following' : 'figure.label.follows-nothing')}</span>
|
|
400
|
-
${following
|
|
401
|
-
? html`<button quiet resize-unfollow=${axis} ?disabled=${this.locked}
|
|
402
|
-
@click=${() => {
|
|
403
|
-
this.follow(axis, 'own', now)
|
|
404
|
-
}}>${i18next.t('figure.button.resize-unfollow')}</button>`
|
|
405
|
-
: nothing}
|
|
406
|
-
</div>`
|
|
407
|
-
})}</div>`}
|
|
408
|
-
${followsAll
|
|
409
|
-
? nothing
|
|
410
|
-
: html`<button act follow-size ?disabled=${this.locked || asset.sizing === 'uniform'} @click=${() => this.command({ kind: 'follow-size', part: this.partId })}>
|
|
411
|
-
<md-icon>open_in_full</md-icon>${i18next.t('figure.button.follow-size')}
|
|
412
|
-
</button>`}
|
|
413
|
-
<details resize-choice><summary>${i18next.t('figure.label.resize-advanced')}</summary><div>${followableAxes(asset, this.partId).map(axis => {
|
|
414
|
-
const now = followOf(asset, this.partId, axis)
|
|
415
|
-
const value = choiceOf(now)
|
|
416
|
-
/* A cylinder's two measures are its radius and its length; its X and Y name them. */
|
|
417
|
-
const name = round ? i18next.t(axis === 'x' ? 'figure.label.field-radius' : 'figure.label.field-length') : axis.toUpperCase()
|
|
418
|
-
return html`<div row>
|
|
419
|
-
<b>${name}</b>
|
|
420
|
-
<select ?disabled=${this.locked} @change=${(e: Event) => this.follow(axis, (e.target as HTMLSelectElement).value, now)}>
|
|
421
|
-
<option value="own" ?selected=${value === 'own'}>${i18next.t('figure.label.follows-nothing')}</option>
|
|
422
|
-
${AXES.filter(one => hasInstanceSize(asset, one)).map(
|
|
423
|
-
one => html`<option value=${`share:${one}`} ?selected=${value === `share:${one}`}>
|
|
424
|
-
${i18next.t('figure.label.follows-instance-share', { axis: one.toUpperCase() })}
|
|
425
|
-
</option>
|
|
426
|
-
<option value=${`instance:${one}`} ?selected=${value === `instance:${one}`}>
|
|
427
|
-
${i18next.t('figure.label.follows-instance', { axis: one.toUpperCase() })}
|
|
428
|
-
</option>`
|
|
429
|
-
)}
|
|
430
|
-
${others.flatMap(other =>
|
|
431
|
-
AXES.map(
|
|
432
|
-
one => html`<option value=${`part:${other}:${one}`} ?selected=${value === `part:${other}:${one}`}>${i18next.t('figure.label.follows-part-axis', { part: other, axis: one.toUpperCase() })}</option>`
|
|
433
|
-
)
|
|
434
|
-
)}
|
|
435
|
-
${now.of === 'expression' || now.of === 'input'
|
|
436
|
-
? html`<option value=${value} selected>${now.of === 'input' ? now.input : i18next.t('figure.label.follows-expression')}</option>`
|
|
437
|
-
: nothing}
|
|
438
|
-
</select>
|
|
439
|
-
${now.of === 'instance' || now.of === 'part'
|
|
440
|
-
? html`<span></span>
|
|
441
|
-
<label inline-plus>
|
|
442
|
-
${i18next.t('figure.label.follows-plus')}
|
|
443
|
-
<input
|
|
444
|
-
type="number"
|
|
445
|
-
step="any"
|
|
446
|
-
?disabled=${this.locked}
|
|
447
|
-
.value=${String(now.plus ?? 0)}
|
|
448
|
-
@change=${(e: Event) => this.followPlus(axis, now, Number((e.target as HTMLInputElement).value) || 0)}
|
|
449
|
-
/>
|
|
450
|
-
</label>`
|
|
451
|
-
: nothing}
|
|
452
|
-
</div>`
|
|
453
|
-
})}</div></details>`
|
|
454
|
-
}
|
|
455
|
-
|
|
456
379
|
/**
|
|
457
380
|
* 정렬·간격 고치기: the part moved inside the face it is fastened on, as one step -- the least move on each side it is
|
|
458
381
|
* out, or centred when it is wider than the face. When nothing of it can be moved, why is said under the button, not
|
|
@@ -503,20 +426,6 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
503
426
|
return where
|
|
504
427
|
}
|
|
505
428
|
|
|
506
|
-
/** The same source, the same share, a new difference: a leaf the opening less its gaps (link-dimension's `plus`). */
|
|
507
|
-
private followPlus(axis: Axis, now: Follow, plus: number) {
|
|
508
|
-
if (now.of !== 'instance' && now.of !== 'part') return
|
|
509
|
-
if ((now.plus ?? 0) === plus) return
|
|
510
|
-
const source: FigureDimensionSource = now.of === 'instance' ? { of: 'instance', axis: now.axis } : { of: 'part', part: now.part, axis: now.axis }
|
|
511
|
-
const times = now.of === 'instance' ? now.times : (now.times ?? 1)
|
|
512
|
-
this.command({ kind: 'link-dimension', part: this.partId, axis, source, ...(times !== 1 ? { times } : {}), ...(plus ? { plus } : {}), replace: true })
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
private follow(axis: Axis, choice: string, now: Follow) {
|
|
516
|
-
const action = followCommandOf(this.revision!, this.partId, axis, choice, now)
|
|
517
|
-
if (action) this.command(action)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
429
|
/**
|
|
521
430
|
* Positions worked out from other values, each with the way to
|
|
522
431
|
* keep it where it is. This is the way out the release gate names when a resize leaves a part in the air.
|
|
@@ -617,9 +526,9 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
617
526
|
Folded, open while it is being worked (2026-10-02): a row is made now and then, and most parts stand on the plane,
|
|
618
527
|
where the section said its reason open on every one. Opened, it says why or asks how.
|
|
619
528
|
*/
|
|
620
|
-
const head = html`<summary><h3>${i18next.t('figure.label.repeat')}</h3></summary>`
|
|
529
|
+
const head = html`<summary><h3>${i18next.t('figure.label.repeat')} <span section-state>${i18next.t('figure.label.repeat-inactive')}</span></h3></summary>`
|
|
621
530
|
if (blocked || !axes.length)
|
|
622
|
-
return html`<section row><details row-fold>
|
|
531
|
+
return html`<section row><details row-fold open>
|
|
623
532
|
${head}
|
|
624
533
|
<p hint row-blocked>${i18next.t(`figure.text.row-blocked-${blocked ?? 'unfastened'}`, { part: this.partId })}</p>
|
|
625
534
|
${blocked === 'unfastened'
|
|
@@ -640,27 +549,29 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
640
549
|
const toggled = (e: Event) => (this.rowOpen = (e.target as HTMLDetailsElement).open)
|
|
641
550
|
return html`<section row><details row-fold ?open=${this.rowOpen} @toggle=${toggled}>
|
|
642
551
|
${head}
|
|
552
|
+
<p hint>${i18next.t('figure.text.repeat-intro')}</p>
|
|
643
553
|
<div form>
|
|
644
554
|
<span>${i18next.t('figure.label.repeat-axis')}</span>
|
|
645
|
-
<select @change=${(e: Event) => set({ ...r, axis: (e.target as HTMLSelectElement).value as Axis })}>
|
|
555
|
+
<select aria-label=${i18next.t('figure.label.repeat-axis')} ?disabled=${this.locked} @change=${(e: Event) => set({ ...r, axis: (e.target as HTMLSelectElement).value as Axis })}>
|
|
646
556
|
${axes.map(a => html`<option value=${a} ?selected=${a === axis}>${a.toUpperCase()}</option>`)}
|
|
647
557
|
</select>
|
|
648
558
|
</div>
|
|
649
|
-
<
|
|
650
|
-
|
|
651
|
-
<label><input type="radio" name="row-basis" .checked=${
|
|
559
|
+
<p hint>${i18next.t('figure.text.repeat-direction')}</p>
|
|
560
|
+
<div choice-row role="radiogroup" aria-label=${i18next.t('figure.label.row-basis')}>
|
|
561
|
+
<label toggle><input type="radio" name="row-basis" ?disabled=${this.locked} .checked=${r.fills} @change=${() => set({ ...r, fills: true })} />${i18next.t('figure.label.repeat-fills')}</label>
|
|
562
|
+
<label toggle><input type="radio" name="row-basis" ?disabled=${this.locked} .checked=${!r.fills} @change=${() => set({ ...r, fills: false })} />${i18next.t('figure.label.row-by-count')}</label>
|
|
652
563
|
</div>
|
|
653
564
|
${r.fills
|
|
654
565
|
? html`<div form>
|
|
655
566
|
<span>${i18next.t('figure.label.repeat-pitch')} · mm</span>
|
|
656
|
-
<input type="number" step="any" min="0" .value=${String(r.pitch)} @change=${(e: Event) => set({ ...r, pitch: Number((e.target as HTMLInputElement).value) })} />
|
|
567
|
+
<input aria-label=${i18next.t('figure.label.repeat-pitch')} ?disabled=${this.locked} type="number" step="any" min="0" .value=${String(r.pitch)} @change=${(e: Event) => set({ ...r, pitch: Number((e.target as HTMLInputElement).value) })} />
|
|
657
568
|
</div>`
|
|
658
569
|
: html`<div form>
|
|
659
570
|
<span>${i18next.t('figure.label.repeat-count')}</span>
|
|
660
|
-
<input type="number" step="1" min="1" .value=${String(r.count)} @change=${(e: Event) => set({ ...r, count: Number((e.target as HTMLInputElement).value) })} />
|
|
571
|
+
<input aria-label=${i18next.t('figure.label.repeat-count')} ?disabled=${this.locked} type="number" step="1" min="1" .value=${String(r.count)} @change=${(e: Event) => set({ ...r, count: Number((e.target as HTMLInputElement).value) })} />
|
|
661
572
|
</div>
|
|
662
573
|
<p hint>${i18next.t('figure.text.row-count-spreads')}</p>`}
|
|
663
|
-
${action && this.rowOpen ? this.renderPreviewWords() : nothing}
|
|
574
|
+
${action && this.rowOpen ? html`<p hint>${i18next.t('figure.text.repeat-draft')}</p>${this.renderPreviewWords()}` : nothing}
|
|
664
575
|
<button act ?disabled=${this.locked || !action} @click=${() => action && this.command(action)}>
|
|
665
576
|
<md-icon>view_week</md-icon>${i18next.t('figure.button.make-repeat')}
|
|
666
577
|
</button>
|
|
@@ -717,6 +628,43 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
717
628
|
/* Which part moves, against what, which way, how far -- by name (ADR-0097 §5.5). */
|
|
718
629
|
const against = joint ? ('plane' in joint.body0 ? i18next.t('figure.label.mounting-plane') : joint.body0.part) : ''
|
|
719
630
|
const ends = this.motionEnds
|
|
631
|
+
/* One row an axis (ADR-0159): how it moves and by what, changed or taken away where it is; more added under them. */
|
|
632
|
+
const count = joint ? figureMotionsOf(joint).length : 1
|
|
633
|
+
const edit = (k: number) => {
|
|
634
|
+
if (!joint) return
|
|
635
|
+
this.motionAxis = k
|
|
636
|
+
this.motion = motionFormOf(joint, k)
|
|
637
|
+
this.editingMotion = true
|
|
638
|
+
}
|
|
639
|
+
const row = (k: number) => {
|
|
640
|
+
const it = k === 0 ? now : motionOf(asset, this.partId, k)
|
|
641
|
+
if (!it) return nothing
|
|
642
|
+
return html`<div pair motion-axis=${k}>
|
|
643
|
+
<span>
|
|
644
|
+
${count > 1 ? html`<b>${i18next.t('figure.label.motion-axis-n', { n: k + 1 })}</b> ` : nothing}${i18next.t('figure.label.motion-' + it.kind)} · ${it.vector ? jointAxisWord(it.vector) : String(it.axis).toUpperCase()}
|
|
645
|
+
${it.travel
|
|
646
|
+
? html`<small>${i18next.t('figure.label.motion-travel-is', { part: it.travel.part, axis: it.travel.axis.toUpperCase(), plus: it.travel.plus ?? 0 })}</small>`
|
|
647
|
+
: nothing}
|
|
648
|
+
${it.follows?.sum.length === 1
|
|
649
|
+
? html`<small>${i18next.t('figure.label.motion-follows', { joint: it.follows.sum[0]!.joint, multiplier: it.follows.sum[0]!.multiplier, offset: it.follows.offset })}</small>`
|
|
650
|
+
: it.follows
|
|
651
|
+
? html`<small>${i18next.t('figure.label.motion-follow')}: ${it.follows.sum.map(t => `${t.joint} × ${t.multiplier}`).join(' + ')} + ${it.follows.offset}</small>`
|
|
652
|
+
: it.control
|
|
653
|
+
? html`<small>${it.control.id} (${it.control.unit}) ${it.control.min} … ${it.control.max}</small>`
|
|
654
|
+
: nothing}
|
|
655
|
+
</span>
|
|
656
|
+
${count > 1
|
|
657
|
+
? html`<button act remove-axis ?disabled=${this.locked || !joint} @click=${() => joint && this.command({ kind: 'set-joint', joint: withoutAxis(joint, k) })}>
|
|
658
|
+
<md-icon>close</md-icon>${i18next.t('figure.button.remove-axis')}
|
|
659
|
+
</button>`
|
|
660
|
+
: html`<button act ?disabled=${this.locked || !joint} @click=${() => joint && this.command({ kind: 'set-joint', joint: fixedJointOf(joint) })}>
|
|
661
|
+
<md-icon>close</md-icon>${i18next.t('figure.button.remove-motion')}
|
|
662
|
+
</button>`}
|
|
663
|
+
<button act ?disabled=${this.locked || !joint} @click=${() => edit(k)}>
|
|
664
|
+
<md-icon>edit</md-icon>${i18next.t('figure.button.change-motion')}
|
|
665
|
+
</button>
|
|
666
|
+
</div>`
|
|
667
|
+
}
|
|
720
668
|
return html`<p motion-summary>
|
|
721
669
|
${i18next.t('figure.text.motion-summary', {
|
|
722
670
|
part: this.partId,
|
|
@@ -728,39 +676,17 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
728
676
|
${ends
|
|
729
677
|
? html`<p hint motion-ends>${i18next.t('figure.text.motion-ends-shown', { min: String(ends.min), max: String(ends.max), unit: ends.unit })}</p>`
|
|
730
678
|
: nothing}
|
|
679
|
+
${Array.from({ length: count }, (_, k) => row(k))}
|
|
731
680
|
<div pair>
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
681
|
+
<button act add-axis ?disabled=${this.locked || !joint} @click=${() => edit(count)}>
|
|
682
|
+
<md-icon>add</md-icon>${i18next.t('figure.button.add-axis')}
|
|
683
|
+
</button>
|
|
684
|
+
${count > 1
|
|
685
|
+
? html`<button act ?disabled=${this.locked || !joint} @click=${() => joint && this.command({ kind: 'set-joint', joint: fixedJointOf(joint) })}>
|
|
686
|
+
<md-icon>close</md-icon>${i18next.t('figure.button.remove-motion')}
|
|
687
|
+
</button>`
|
|
736
688
|
: nothing}
|
|
737
|
-
|
|
738
|
-
? html`<small>${i18next.t('figure.label.motion-follows', { joint: now.follows.sum[0]!.joint, multiplier: now.follows.sum[0]!.multiplier, offset: now.follows.offset })}</small>`
|
|
739
|
-
: now.follows
|
|
740
|
-
? html`<small>${i18next.t('figure.label.motion-follow')}: ${now.follows.sum.map(t => `${t.joint} × ${t.multiplier}`).join(' + ')} + ${now.follows.offset}</small>`
|
|
741
|
-
: now.control
|
|
742
|
-
? html`<small>${now.control.id} (${now.control.unit}) ${now.control.min} … ${now.control.max}</small>`
|
|
743
|
-
: nothing}
|
|
744
|
-
</span>
|
|
745
|
-
<button
|
|
746
|
-
act
|
|
747
|
-
?disabled=${this.locked || !joint}
|
|
748
|
-
@click=${() => joint && this.command({ kind: 'set-joint', joint: fixedJointOf(joint) })}
|
|
749
|
-
>
|
|
750
|
-
<md-icon>close</md-icon>${i18next.t('figure.button.remove-motion')}
|
|
751
|
-
</button>
|
|
752
|
-
<button
|
|
753
|
-
act
|
|
754
|
-
?disabled=${this.locked || !joint}
|
|
755
|
-
@click=${() => {
|
|
756
|
-
if (!joint) return
|
|
757
|
-
this.motion = motionFormOf(joint)
|
|
758
|
-
this.editingMotion = true
|
|
759
|
-
}}
|
|
760
|
-
>
|
|
761
|
-
<md-icon>edit</md-icon>${i18next.t('figure.button.change-motion')}
|
|
762
|
-
</button>
|
|
763
|
-
</div>`
|
|
689
|
+
</div>`
|
|
764
690
|
}
|
|
765
691
|
/* A joint needs a parent (URDF): a part fastened to nothing is fastened first. */
|
|
766
692
|
if (!joint)
|
|
@@ -771,10 +697,13 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
771
697
|
const m = this.motion
|
|
772
698
|
const unit = m.kind === 'slide' ? 'mm' : 'deg'
|
|
773
699
|
/* A follower follows a joint that moves the same way; a choice the kind no longer allows is dropped. */
|
|
774
|
-
const
|
|
700
|
+
const k = this.motionAxis
|
|
701
|
+
const leaders = leadersFor(asset, this.partId, m.kind, motionIdOf(joint, k))
|
|
702
|
+
const adding = k > 0 && k >= figureMotionsOf(joint).length
|
|
775
703
|
const follow = leaders.some(j => j.id === m.follow) ? m.follow : ''
|
|
776
704
|
const refusal = motionFormRefusalOf(m, follow)
|
|
777
705
|
return html`
|
|
706
|
+
${k > 0 ? html`<p motion-axis-heading><b>${i18next.t('figure.label.motion-axis-n', { n: k + 1 })}</b></p>` : nothing}
|
|
778
707
|
<div pair>
|
|
779
708
|
<select @change=${(e: Event) => this.setKind((e.target as HTMLSelectElement).value as 'slide' | 'turn' | 'continuous')}>
|
|
780
709
|
<option value="slide" ?selected=${m.kind === 'slide'}>${i18next.t('figure.label.motion-slide')}</option>
|
|
@@ -799,7 +728,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
799
728
|
<span>${i18next.t('figure.label.motion-follow')}</span>
|
|
800
729
|
<select @change=${(e: Event) => (this.motion = { ...m, follow: (e.target as HTMLSelectElement).value })}>
|
|
801
730
|
<option value="" ?selected=${!follow}>${i18next.t('figure.label.motion-own-control')}</option>
|
|
802
|
-
${leaders.map(j => html`<option value=${j.id} ?selected=${j.id === follow}>${j.
|
|
731
|
+
${leaders.map(j => html`<option value=${j.id} ?selected=${j.id === follow}>${j.part}${j.state?.id ? ` · ${j.state.id}` : j.id !== j.part ? ` · ${j.id}` : ''}</option>`)}
|
|
803
732
|
</select>
|
|
804
733
|
</div>`
|
|
805
734
|
: nothing}
|
|
@@ -830,7 +759,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
830
759
|
: nothing}`
|
|
831
760
|
: nothing}
|
|
832
761
|
<span>${i18next.t('figure.label.control-id')}</span>
|
|
833
|
-
<input .value=${m.id} placeholder=${
|
|
762
|
+
<input .value=${m.id} placeholder=${controlIdFor(this.partId, m.kind, k)} @change=${(e: Event) => (this.motion = { ...m, id: (e.target as HTMLInputElement).value.trim() })} />
|
|
834
763
|
${m.kind === 'continuous' || (m.kind === 'slide' && (m.travel || m.travelSum))
|
|
835
764
|
? nothing
|
|
836
765
|
: html`<span>${i18next.t('figure.label.control-range')} (${unit})</span>
|
|
@@ -844,14 +773,15 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
844
773
|
?disabled=${this.locked || !!refusal}
|
|
845
774
|
title=${refusal === 'range' ? i18next.t('figure.text.range-needs-room') : ''}
|
|
846
775
|
@click=${() => {
|
|
847
|
-
this.command({ kind: 'set-joint', joint: motionJoint(joint, { ...m, follow }, this.partId) })
|
|
776
|
+
this.command({ kind: 'set-joint', joint: motionJoint(joint, { ...m, follow }, this.partId, k) })
|
|
848
777
|
this.editingMotion = false
|
|
778
|
+
this.motionAxis = 0
|
|
849
779
|
}}
|
|
850
780
|
>
|
|
851
|
-
<md-icon>animation</md-icon>${i18next.t(this.editingMotion ? 'figure.button.apply-motion-change' : 'figure.button.add-motion')}
|
|
781
|
+
<md-icon>animation</md-icon>${i18next.t(adding ? 'figure.button.add-axis' : this.editingMotion ? 'figure.button.apply-motion-change' : 'figure.button.add-motion')}
|
|
852
782
|
</button>
|
|
853
783
|
${this.editingMotion
|
|
854
|
-
? html`<button act @click=${() => (this.editingMotion = false)}><md-icon>undo</md-icon>${i18next.t('figure.button.cancel-motion-change')}</button>`
|
|
784
|
+
? html`<button act @click=${() => ((this.editingMotion = false), (this.motionAxis = 0))}><md-icon>undo</md-icon>${i18next.t('figure.button.cancel-motion-change')}</button>`
|
|
855
785
|
: nothing}
|
|
856
786
|
`
|
|
857
787
|
}
|