@things-factory/figure-ui 10.1.208 → 10.1.210
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 +74 -57
- package/client/modeller/fasten-geometry.ts +2 -1
- package/client/modeller/figure-links.ts +26 -120
- package/client/modeller/figure-size-editor.ts +196 -0
- package/client/modeller/joint-drag.ts +25 -21
- package/client/modeller/joint-fan.ts +4 -3
- package/client/modeller/links.ts +36 -24
- 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 +9 -1
- package/dist-client/modeller/editor.js +80 -58
- 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 +1 -4
- package/dist-client/modeller/figure-links.js +26 -107
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-size-editor.d.ts +27 -0
- package/dist-client/modeller/figure-size-editor.js +221 -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 +9 -6
- package/dist-client/modeller/links.js +33 -21
- package/dist-client/modeller/links.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/route.d.ts +1 -1
- 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 +28 -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 +51 -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 +21 -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 +44 -10
- package/translations/es.json +44 -10
- package/translations/ja.json +44 -10
- package/translations/ko.json +49 -15
- package/translations/ms.json +44 -10
- package/translations/zh.json +44 -10
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* of data is things-scene's; this only lists what there is to choose, drafts a binding the kernel then checks, and edits
|
|
5
5
|
* the schema as a tree. Kept apart from the element so it is tested without a browser.
|
|
6
6
|
*/
|
|
7
|
-
import { FIGURE_PART_ID, FIGURE_MOTION_SPEED_MAX, FigureContractError, figureDataRows, parseFigureAccessor } from '@hatiolab/figure-model'
|
|
7
|
+
import { FIGURE_PART_ID, FIGURE_MOTION_SPEED_MAX, FigureContractError, figureAxesOf, figureDataRows, motionStateIdOf, parseFigureAccessor } from '@hatiolab/figure-model'
|
|
8
8
|
import type { FigureAsset, FigureDataBehavior, FigureDataBinding, FigureDataField, FigureDataNode, FigureDataRow, FigureDataValue, FigureDataValueIssue, FigureProposalStep, FigureStatusPort } from '@hatiolab/figure-model'
|
|
9
9
|
import { FigureInputs, type FigureDataResult } from '@hatiolab/things-scene'
|
|
10
10
|
|
|
@@ -286,12 +286,12 @@ export interface EndlessJoint {
|
|
|
286
286
|
unit: 'deg' | 'rad'
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
/** The
|
|
289
|
+
/** The axes that turn without end: revolute, with no limits, following no one (ADR-0159: every axis of every joint). */
|
|
290
290
|
export function endlessJointsOf(asset: FigureAsset): EndlessJoint[] {
|
|
291
291
|
const out: EndlessJoint[] = []
|
|
292
|
-
for (const
|
|
293
|
-
if (
|
|
294
|
-
const input =
|
|
292
|
+
for (const { motion: a } of figureAxesOf(asset.joints)) {
|
|
293
|
+
if (a.type !== 'revolute' || a.lowerLimit !== undefined || a.upperLimit !== undefined || a.mimic) continue
|
|
294
|
+
const input = motionStateIdOf(a)
|
|
295
295
|
const unit = asset.document.model.inputs.find(i => i.id === input)?.unit
|
|
296
296
|
if (unit !== 'deg' && unit !== 'rad') continue
|
|
297
297
|
if (!out.some(e => e.input === input)) out.push({ input, label: asset.stateInputs?.[input]?.label ?? input, unit })
|
|
@@ -6,7 +6,7 @@ import { openPopup } from '@operato/layout'
|
|
|
6
6
|
import { OxPrompt } from '@operato/popup'
|
|
7
7
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
8
8
|
import { live } from 'lit/directives/live.js'
|
|
9
|
-
import { figurePartFaceOf, PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditOf, figureSizeEditorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, type FigurePlaneContact, figureRowOf, type FigureRowReading, figureFaceOverhangOf, type FigureFaceOverhang } from '@hatiolab/figure-model'
|
|
9
|
+
import { figureFastenAxesOf, figurePartFaceOf, PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditOf, figureSizeEditorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, type FigurePlaneContact, figureRowOf, type FigureRowReading, figureFaceOverhangOf, type FigureFaceOverhang } from '@hatiolab/figure-model'
|
|
10
10
|
import type { RecipeTemplate } from './recipes.js'
|
|
11
11
|
import './figure-recipe-catalog.js'
|
|
12
12
|
import type { FigureAsset, FigureAuthoringAction, FigureEvaluatedShape, FigureInputChange, FigurePartEdit, FigurePrimitiveCreation, FigureProposalStep, FigureRecipe, FigureRecipeValue, FigurePlacement, FigureFaceMappingNeed, FigureFaceRemap } from '@hatiolab/figure-model'
|
|
@@ -63,7 +63,8 @@ import { editRefusalOf, partNameRefusalOf, sizeRangeFitsOf, partRefusalOf, place
|
|
|
63
63
|
import type { FigureRenderPart } from './renderer.js'
|
|
64
64
|
import type { FigureBudgetLine } from './maturity.js'
|
|
65
65
|
import { HelpToggles } from './help.js'
|
|
66
|
-
import {
|
|
66
|
+
import { sizeInputOf } from './size-response.js'
|
|
67
|
+
import { canFollow, followableAxes, endPlaceKeyOf, fieldWordsOf, followOf, sizeFollowersOf, measureNow, partSummaryOf, instanceRangeOf, instanceSizeToDeclare, proportionToFollow, rangeWith, turnsAboutAJoint, valueNow } from './links.js'
|
|
67
68
|
import { jointFanOf } from './joint-fan.js'
|
|
68
69
|
import { derivedOf, type FigureRevision } from './revision.js'
|
|
69
70
|
import { jointDragOf } from './joint-drag.js'
|
|
@@ -360,10 +361,14 @@ export class FigureEditor extends LitElement {
|
|
|
360
361
|
}
|
|
361
362
|
/**
|
|
362
363
|
* A refusal no particular sentence knows: figure-model's code said as a refusal, never its English; any other error
|
|
363
|
-
* (the network, the server) as it came.
|
|
364
|
+
* (the network, the server) as it came. A loop is said as a loop whichever way it was made: a command, or the size
|
|
365
|
+
* editor's steps (a measure made to follow what follows it, 2026-10-24 -- said as a bare refused step before).
|
|
364
366
|
*/
|
|
365
367
|
private said(error: unknown): string {
|
|
366
|
-
|
|
368
|
+
const code = (error as { code?: string })?.code
|
|
369
|
+
if (code === 'DIMENSION_CYCLE') return i18next.t('figure.text.follow-would-loop')
|
|
370
|
+
if (code === 'FASTEN_CYCLE' || code === 'ATTACH_CYCLE') return i18next.t('figure.text.fasten-would-loop')
|
|
371
|
+
return code ? editRefusalOf(error, (key, params) => i18next.t(key, params)) : error instanceof Error ? error.message : String(error)
|
|
367
372
|
}
|
|
368
373
|
private run(action: () => unknown) {
|
|
369
374
|
try {
|
|
@@ -818,6 +823,8 @@ export class FigureEditor extends LitElement {
|
|
|
818
823
|
.partId=${this.selected}
|
|
819
824
|
?locked=${unavailable}
|
|
820
825
|
@command=${(e: CustomEvent) => this.linkCommand(e.detail.action)}
|
|
826
|
+
@size-recipe=${(e: CustomEvent) => this.applySizeSteps(e.detail.steps)}
|
|
827
|
+
@size-input=${(e: CustomEvent) => this.run(() => this.session!.apply(figureSizeEditOf(this.shown()!.asset, e.detail.part, [{ kind: 'set-design', input: e.detail.input, unit: 'mm', value: e.detail.value }], { keep: 'centre' }).changes))}
|
|
821
828
|
@fasten-open=${() => this.openFastening()}
|
|
822
829
|
.previewWords=${this.commandPreview.words()}
|
|
823
830
|
.detachWords=${this.commandPreview.detachWords(shown)}
|
|
@@ -837,21 +844,25 @@ export class FigureEditor extends LitElement {
|
|
|
837
844
|
*/
|
|
838
845
|
private renderPartGroups(shown: FigureRevision | undefined, geometry: readonly { id: string }[], fields: FieldLike[], unavailable: boolean, held?: HeldValues) {
|
|
839
846
|
const groups = this.withSizeRow(groupFields(fields, this.selected), held)
|
|
840
|
-
const
|
|
847
|
+
const integrated = !!shown && canFollow(shown.asset, this.selected)
|
|
848
|
+
const dimensionInputs = new Set(integrated ? followableAxes(shown!.asset, this.selected).map(axis => sizeInputOf(shown!.asset, this.selected, axis)) : [])
|
|
849
|
+
const singles = groups.singles.filter(g => g.kind !== 'single' || !dimensionInputs.has(g.field.id))
|
|
850
|
+
const size = integrated ? [] : groups.vectors.filter(g => g.kind === 'vector' && g.name === 'size')
|
|
841
851
|
const place = groups.vectors.filter(g => g.kind === 'vector' && g.name !== 'size')
|
|
842
852
|
const fastened = !!this.asset()?.joints?.some(j => j.body1.part === ownerOf(this.selected))
|
|
843
853
|
const placeLocked = fastened && place.every(g => g.kind === 'vector' && (['x', 'y', 'z'] as const).every(axis => !g.cells[axis]))
|
|
844
854
|
return this.renderLinks(
|
|
845
855
|
unavailable,
|
|
846
856
|
'shape',
|
|
847
|
-
html`${size.length ||
|
|
848
|
-
? html`<
|
|
857
|
+
html`${size.length || singles.length
|
|
858
|
+
? html`<section slot="size" group-fields>
|
|
859
|
+
<h3>${i18next.t(integrated ? 'figure.label.group-shape' : 'figure.label.group-size')}</h3>
|
|
849
860
|
${size.map(g => this.renderVector(g, unavailable, held))}
|
|
850
|
-
${
|
|
851
|
-
? html`<div form>${
|
|
861
|
+
${singles.length
|
|
862
|
+
? html`<div form>${singles.map(g => (g.kind === 'single' ? this.renderSingle(g.name, g.field, unavailable) : nothing))}</div>`
|
|
852
863
|
: nothing}
|
|
853
|
-
${this.renderSizeKeeps()}
|
|
854
|
-
</
|
|
864
|
+
${!integrated ? this.renderSizeKeeps() : nothing}
|
|
865
|
+
</section>`
|
|
855
866
|
: nothing}
|
|
856
867
|
${place.length && !placeLocked
|
|
857
868
|
? html`<div slot="place" group-fields>${place.map(g => this.renderVector(g, unavailable, held))}${this.renderHeldAxes(place)}</div>`
|
|
@@ -860,7 +871,7 @@ export class FigureEditor extends LitElement {
|
|
|
860
871
|
}
|
|
861
872
|
/** The 「반응형」 tab of a part: the links' response groups, its row's own section put in by slot. */
|
|
862
873
|
private renderResponse(shown: FigureRevision | undefined, geometry: readonly { id: string }[], unavailable: boolean) {
|
|
863
|
-
return this.renderLinks(unavailable, 'response', this.renderRepeat(shown, geometry, unavailable))
|
|
874
|
+
return this.renderLinks(unavailable, 'response', html`<div slot="size-note">${this.renderSizeKeeps()}</div>${this.renderRepeat(shown, geometry, unavailable)}`)
|
|
864
875
|
}
|
|
865
876
|
/**
|
|
866
877
|
* 「더 보기」: what few parts need, folded by default and left as this person last left it (chief architect
|
|
@@ -967,6 +978,24 @@ export class FigureEditor extends LitElement {
|
|
|
967
978
|
const done = this.linkCommand({ ...held.action, remap } as FigureAuthoringAction, held.from)
|
|
968
979
|
if (done && held.from === 'section-editor') this.closeSectionEditor()
|
|
969
980
|
}
|
|
981
|
+
/** The figure's size, declared for a part to follow, in one line (ADR ruling Q1). */
|
|
982
|
+
private sizeDeclaredWords() {
|
|
983
|
+
const size = this.designValues()
|
|
984
|
+
return i18next.t('figure.text.size-declared-to-follow', {
|
|
985
|
+
x: Math.round(size['size.x'] ?? 0).toLocaleString(),
|
|
986
|
+
y: Math.round(size['size.y'] ?? 0).toLocaleString(),
|
|
987
|
+
z: Math.round(size['size.z'] ?? 0).toLocaleString()
|
|
988
|
+
})
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* The size editor's steps, one step of the history. A measure made to follow the figure on a figure with no size
|
|
992
|
+
* declares it on the way; that is said in one line, as follow-size said it (ADR ruling Q1, 2026-10-24).
|
|
993
|
+
*/
|
|
994
|
+
private applySizeSteps(steps: readonly FigureProposalStep[]) {
|
|
995
|
+
const hadSize = this.hasSize()
|
|
996
|
+
this.run(() => this.session!.applyRecipe(steps))
|
|
997
|
+
if (!hadSize && !this.error && this.hasSize()) this.notice = this.sizeDeclaredWords()
|
|
998
|
+
}
|
|
970
999
|
/** Runs a command; true when it was taken. */
|
|
971
1000
|
private linkCommand(action: FigureAuthoringAction, from: 'section-editor' | 'panel' = 'panel'): boolean {
|
|
972
1001
|
/* Committed or refused, a command in hand is let go of: the figure as it stands is drawn again. */
|
|
@@ -978,14 +1007,7 @@ export class FigureEditor extends LitElement {
|
|
|
978
1007
|
this.error = ''
|
|
979
1008
|
this.faceMapping = undefined
|
|
980
1009
|
/* What the command did besides what was asked, in one line: the figure's size declared to follow (ADR ruling Q1). */
|
|
981
|
-
if (action.kind === 'follow-size' && !hadSize)
|
|
982
|
-
const size = this.designValues()
|
|
983
|
-
this.notice = i18next.t('figure.text.size-declared-to-follow', {
|
|
984
|
-
x: Math.round(size['size.x'] ?? 0).toLocaleString(),
|
|
985
|
-
y: Math.round(size['size.y'] ?? 0).toLocaleString(),
|
|
986
|
-
z: Math.round(size['size.z'] ?? 0).toLocaleString()
|
|
987
|
-
})
|
|
988
|
-
}
|
|
1010
|
+
if (action.kind === 'follow-size' && !hadSize) this.notice = this.sizeDeclaredWords()
|
|
989
1011
|
this.changed()
|
|
990
1012
|
return true
|
|
991
1013
|
} catch (error) {
|
|
@@ -998,12 +1020,7 @@ export class FigureEditor extends LitElement {
|
|
|
998
1020
|
this.error = ''
|
|
999
1021
|
return false
|
|
1000
1022
|
}
|
|
1001
|
-
this.error =
|
|
1002
|
-
code === 'DIMENSION_CYCLE'
|
|
1003
|
-
? i18next.t('figure.text.follow-would-loop')
|
|
1004
|
-
: code === 'FASTEN_CYCLE' || code === 'ATTACH_CYCLE'
|
|
1005
|
-
? i18next.t('figure.text.fasten-would-loop')
|
|
1006
|
-
: this.said(error)
|
|
1023
|
+
this.error = this.said(error)
|
|
1007
1024
|
return false
|
|
1008
1025
|
}
|
|
1009
1026
|
}
|
|
@@ -1243,45 +1260,45 @@ export class FigureEditor extends LitElement {
|
|
|
1243
1260
|
const axis = model.nodes.find(n => n.id === `${this.selected}.repeat`)?.params?.axis ?? '?'
|
|
1244
1261
|
const own = `${this.selected}.repeat.pitch`
|
|
1245
1262
|
/* The pitch as it stands: the part's own number, a constant, or what a pitch that follows a length comes to. */
|
|
1246
|
-
const pitch = asset.designInputs[own] ?? model.constants.find(c => c.id === layout?.args[4])?.value ?? valueNow(shown, layout?.args[4])
|
|
1247
1263
|
const fits = layout?.op === 'fit-pitch@1'
|
|
1248
|
-
|
|
1249
|
-
const lengthRef = layout?.args[0] ?? ''
|
|
1250
|
-
const length = asset.designInputs[lengthRef]
|
|
1251
|
-
const longest = model.inputs.find(i => i.id === lengthRef)?.max
|
|
1252
|
-
const limit = model.constants.find(c => c.id === layout?.args[6])?.value
|
|
1264
|
+
const pitch = fits ? asset.designInputs[own] ?? model.constants.find(c => c.id === layout?.args[4])?.value ?? valueNow(shown, layout?.args[4]) : this.rowNow(shown)?.pitch
|
|
1253
1265
|
const field = { id: own, unit: 'mm' as const, role: 'design', value: pitch ?? 0 }
|
|
1254
|
-
const
|
|
1266
|
+
const record = asset.joints?.find(j => j.body1.part === this.selected && j.repeat)
|
|
1267
|
+
const parent = record && 'part' in record.body0 ? record.body0.part : undefined
|
|
1268
|
+
const removable = !!record
|
|
1269
|
+
const axes = record && 'part' in record.body0 && 'face' in record.body0 ? figureFastenAxesOf(asset, record.body0) : []
|
|
1270
|
+
const update = (change: Partial<NonNullable<typeof record>['repeat']>) => {
|
|
1271
|
+
if (!record?.repeat || unavailable) return
|
|
1272
|
+
this.run(() => this.session!.command({ kind: 'set-joint', joint: { ...record, repeat: { ...record.repeat!, ...change } } }))
|
|
1273
|
+
}
|
|
1255
1274
|
return html`<section slot="row" row>
|
|
1256
1275
|
<h3>
|
|
1257
1276
|
${i18next.t('figure.label.repeat')}
|
|
1258
|
-
|
|
1277
|
+
<span section-state active>${i18next.t('figure.label.repeat-active')}</span>
|
|
1259
1278
|
</h3>
|
|
1260
|
-
<div
|
|
1261
|
-
|
|
1279
|
+
<div choice-row role="radiogroup" aria-label=${i18next.t('figure.label.row-basis')}>
|
|
1280
|
+
<label toggle><input type="radio" name="applied-repeat-mode" .checked=${live(fits)} ?disabled=${unavailable || !record} @change=${() => update({ count: undefined, pitch: pitch && pitch > 0 ? pitch : 200 })}>${i18next.t('figure.label.repeat-fills')}</label>
|
|
1281
|
+
<label toggle><input type="radio" name="applied-repeat-mode" .checked=${live(!fits)} ?disabled=${unavailable || !record} @change=${() => update({ count: repeat.count })}>${i18next.t('figure.label.row-by-count')}</label>
|
|
1282
|
+
</div>
|
|
1283
|
+
<p hint>${i18next.t(fits ? 'figure.text.repeat-mode-pitch' : 'figure.text.repeat-mode-count')}</p>
|
|
1284
|
+
<div inline-field><label>${i18next.t('figure.label.size-source')}</label><span value>${parent ?? '—'}</span></div>
|
|
1285
|
+
<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>
|
|
1286
|
+
<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>
|
|
1262
1287
|
<div inline-field>
|
|
1263
1288
|
<label>${i18next.t('figure.label.repeat-gap')} <span unit>mm</span></label>
|
|
1264
|
-
|
|
1289
|
+
${fits ? html`<input
|
|
1265
1290
|
type="number"
|
|
1266
1291
|
step="any"
|
|
1267
|
-
aria-label=${
|
|
1268
|
-
.value=${String(pitch ?? '')}
|
|
1269
|
-
?disabled=${unavailable
|
|
1292
|
+
aria-label=${i18next.t('figure.label.repeat-gap')}
|
|
1293
|
+
.value=${live(String(pitch ?? ''))}
|
|
1294
|
+
?disabled=${unavailable}
|
|
1270
1295
|
@change=${(e: Event) => this.setPitch(field, e)}
|
|
1271
|
-
|
|
1296
|
+
/>` : html`<span value>${pitch === undefined ? '—' : Number(pitch.toFixed(3))}</span>`}
|
|
1272
1297
|
</div>
|
|
1273
|
-
<ul row-reading>${rowWordsOf(this.rowNow(shown), (key, params) => i18next.t(key, params)).map(line => html`<li>${line}</li>`)}</ul>
|
|
1274
|
-
<
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
length: length.toLocaleString(),
|
|
1278
|
-
count: repeat.count,
|
|
1279
|
-
longest: longest.toLocaleString(),
|
|
1280
|
-
limit
|
|
1281
|
-
})
|
|
1282
|
-
: i18next.t(fits ? 'figure.text.repeat-fits-length' : 'figure.text.repeat-fixed-count')}
|
|
1283
|
-
</p>
|
|
1284
|
-
${this.renderPublicPitch(asset, pitch ?? 0, unavailable)}
|
|
1298
|
+
<ul row-reading>${rowWordsOf(this.rowNow(shown), (key, params) => i18next.t(key, params)).slice(1).map(line => html`<li>${line}</li>`)}</ul>
|
|
1299
|
+
<button quiet ?disabled=${unavailable || !removable} @click=${() => this.run(() => this.session!.command({ kind: 'remove-repeat', part: this.selected }))}>${i18next.t('figure.button.remove-repeat')}</button>
|
|
1300
|
+
<p hint>${i18next.t('figure.text.repeat-remove-explained')}</p>
|
|
1301
|
+
${fits ? this.renderPublicPitch(asset, pitch ?? 0, unavailable) : nothing}
|
|
1285
1302
|
</section>`
|
|
1286
1303
|
}
|
|
1287
1304
|
/** The selected row as it is drawn now, read by figure-model: nothing when it cannot be read. */
|
|
@@ -1589,8 +1606,8 @@ export class FigureEditor extends LitElement {
|
|
|
1589
1606
|
/** A side tab says what it is in words under its icon: the icons alone had to be learnt (designer 2026-09-27, U05). */
|
|
1590
1607
|
private renderTab(tab: SideTab, icon: string, label: string) {
|
|
1591
1608
|
/* Its accessible name is the name it shows (designer 2026-09-27); the longer title stays as its tooltip. */
|
|
1592
|
-
const name = i18next.t('figure.label.tab-short-' + tab)
|
|
1593
|
-
return html`<button tab ?on=${this.tab === tab} title=${label} aria-label=${name} @click=${() => (this.tab = tab)}>
|
|
1609
|
+
const name = i18next.t(tab === 'response' && this.selected ? 'figure.label.tab-arrangement' : 'figure.label.tab-short-' + tab)
|
|
1610
|
+
return html`<button tab ?on=${this.tab === tab} title=${tab === 'response' && this.selected ? name : label} aria-label=${name} @click=${() => (this.tab = tab)}>
|
|
1594
1611
|
<md-icon>${icon}</md-icon><span tab-label aria-hidden="true">${name}</span>
|
|
1595
1612
|
</button>`
|
|
1596
1613
|
}
|
|
@@ -1717,8 +1734,8 @@ export class FigureEditor extends LitElement {
|
|
|
1717
1734
|
if (!summary) return nothing
|
|
1718
1735
|
return html`<div subject>
|
|
1719
1736
|
<div subject-summary>
|
|
1720
|
-
|
|
1721
|
-
${summary.
|
|
1737
|
+
<b>${ownerOf(this.selected)}</b>
|
|
1738
|
+
${summary.kind ? html`<small>${summary.kind}</small>` : nothing}
|
|
1722
1739
|
</div>
|
|
1723
1740
|
</div>`
|
|
1724
1741
|
}
|
|
@@ -126,7 +126,8 @@ export function overhangOf(view: FaceView, at: Record<Axis, number>): { touching
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/** Whether the part turns about a joint: then 가운데를 면에 is offered -- a roller spinning in place. */
|
|
129
|
-
export const turnsInPlace = (asset: FigureAsset, part: string) =>
|
|
129
|
+
export const turnsInPlace = (asset: FigureAsset, part: string) =>
|
|
130
|
+
(asset.joints ?? []).some(j => j.body1.part === part && ((j.motion ?? []).some(a => a.type === 'revolute') || j.origin?.facing === 'centre'))
|
|
130
131
|
|
|
131
132
|
/**
|
|
132
133
|
* How the face lies on the screen: X across when it is one of the face's axes, else Z; the other down the screen -- up
|
|
@@ -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
10
|
import { figureFaceOverhangOf } from '@hatiolab/figure-model'
|
|
10
|
-
import type { FigureAsset, FigureAuthoringAction,
|
|
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,
|
|
@@ -40,9 +36,7 @@ import {
|
|
|
40
36
|
fixedJointOf,
|
|
41
37
|
rowOf,
|
|
42
38
|
positionsWorkedOut,
|
|
43
|
-
measureNow,
|
|
44
39
|
type Axis,
|
|
45
|
-
type Follow,
|
|
46
40
|
type MotionForm
|
|
47
41
|
} from './links.js'
|
|
48
42
|
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
|
|
@@ -113,8 +107,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
113
107
|
font-weight: 600;
|
|
114
108
|
}
|
|
115
109
|
/* 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:
|
|
110
|
+
div[resize-now] { display: grid; grid-template-columns: 1fr auto auto auto; gap: 4px 8px; margin: 12px 0; }
|
|
111
|
+
div[resize-now] div[row] { grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
|
|
118
112
|
div[resize-now] span[measure] { font-variant-numeric: tabular-nums; }
|
|
119
113
|
/* Size behaviour is a choice about dimensions only. It never implies a placement or a fastening. */
|
|
120
114
|
/* 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 +118,11 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
124
118
|
details { margin-top: 8px; }
|
|
125
119
|
details[row-fold] { margin: 0; }
|
|
126
120
|
details[row-fold] > summary { padding: 0; color: var(--md-sys-color-on-surface); }
|
|
127
|
-
details[row-fold] > summary h3 { margin: 0; }
|
|
121
|
+
details[row-fold] > summary h3 { margin: 0; flex: 1; }
|
|
128
122
|
details[row-fold][open] > summary { margin-bottom: 8px; }
|
|
129
123
|
/* What the command would do, said beside the result drawn on the canvas. */
|
|
130
124
|
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
125
|
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
126
|
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
127
|
div[overhang] p { margin: 0; }
|
|
136
128
|
ul[detach-words] { margin: 4px 0 10px; padding-left: 20px; display: grid; gap: 2px; }
|
|
@@ -209,6 +201,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
209
201
|
this.motion = freshMotion()
|
|
210
202
|
this.place = freshPlace()
|
|
211
203
|
this.row = freshRow()
|
|
204
|
+
this.rowOpen = false
|
|
205
|
+
this.rowCandidate = undefined
|
|
212
206
|
this.editingMotion = false
|
|
213
207
|
}
|
|
214
208
|
}
|
|
@@ -273,13 +267,14 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
273
267
|
How the part answers a size it is placed at (user 2026-10-17, the 「반응형」 tab): its own measures, where it
|
|
274
268
|
stands, and the row it may make -- what Build Once, Fit Anywhere is authored with, in one place.
|
|
275
269
|
*/
|
|
276
|
-
const positions = positionsWorkedOut(asset, this.partId)
|
|
270
|
+
const positions = held ? [] : positionsWorkedOut(asset, this.partId)
|
|
271
|
+
this.rowCandidate = undefined
|
|
277
272
|
const response = html`
|
|
278
|
-
|
|
273
|
+
${canFollow(asset, this.partId) ? html`<section size>
|
|
279
274
|
<h3>${i18next.t('figure.label.group-size')}</h3>
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
</section
|
|
275
|
+
<figure-size-editor .revision=${this.revision} .partId=${this.partId} ?locked=${this.locked}></figure-size-editor>
|
|
276
|
+
<slot name="size-note"></slot>
|
|
277
|
+
</section>` : nothing}
|
|
283
278
|
${positions.length
|
|
284
279
|
? html`<section position-response>
|
|
285
280
|
<h3>${i18next.t('figure.label.group-position-response')}</h3>
|
|
@@ -292,10 +287,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
292
287
|
return response
|
|
293
288
|
}
|
|
294
289
|
const shown = html`
|
|
295
|
-
<
|
|
296
|
-
<h3>${i18next.t('figure.label.group-size')}</h3>
|
|
297
|
-
<slot name="size"></slot>
|
|
298
|
-
</section>
|
|
290
|
+
<slot name="size"></slot>
|
|
299
291
|
<section fastening>
|
|
300
292
|
<h3>
|
|
301
293
|
${i18next.t('figure.label.group-place')}${this.help.button('figure.text.fastening-explained')}
|
|
@@ -379,80 +371,6 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
379
371
|
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit: { kind: 'set-capabilities', names } }, bubbles: true, composed: true }))
|
|
380
372
|
}
|
|
381
373
|
|
|
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
374
|
/**
|
|
457
375
|
* 정렬·간격 고치기: the part moved inside the face it is fastened on, as one step -- the least move on each side it is
|
|
458
376
|
* 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 +421,6 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
503
421
|
return where
|
|
504
422
|
}
|
|
505
423
|
|
|
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
424
|
/**
|
|
521
425
|
* Positions worked out from other values, each with the way to
|
|
522
426
|
* 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 +521,9 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
617
521
|
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
522
|
where the section said its reason open on every one. Opened, it says why or asks how.
|
|
619
523
|
*/
|
|
620
|
-
const head = html`<summary><h3>${i18next.t('figure.label.repeat')}</h3></summary>`
|
|
524
|
+
const head = html`<summary><h3>${i18next.t('figure.label.repeat')} <span section-state>${i18next.t('figure.label.repeat-inactive')}</span></h3></summary>`
|
|
621
525
|
if (blocked || !axes.length)
|
|
622
|
-
return html`<section row><details row-fold>
|
|
526
|
+
return html`<section row><details row-fold open>
|
|
623
527
|
${head}
|
|
624
528
|
<p hint row-blocked>${i18next.t(`figure.text.row-blocked-${blocked ?? 'unfastened'}`, { part: this.partId })}</p>
|
|
625
529
|
${blocked === 'unfastened'
|
|
@@ -640,27 +544,29 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
640
544
|
const toggled = (e: Event) => (this.rowOpen = (e.target as HTMLDetailsElement).open)
|
|
641
545
|
return html`<section row><details row-fold ?open=${this.rowOpen} @toggle=${toggled}>
|
|
642
546
|
${head}
|
|
547
|
+
<p hint>${i18next.t('figure.text.repeat-intro')}</p>
|
|
643
548
|
<div form>
|
|
644
549
|
<span>${i18next.t('figure.label.repeat-axis')}</span>
|
|
645
|
-
<select @change=${(e: Event) => set({ ...r, axis: (e.target as HTMLSelectElement).value as Axis })}>
|
|
550
|
+
<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
551
|
${axes.map(a => html`<option value=${a} ?selected=${a === axis}>${a.toUpperCase()}</option>`)}
|
|
647
552
|
</select>
|
|
648
553
|
</div>
|
|
649
|
-
<
|
|
650
|
-
|
|
651
|
-
<label><input type="radio" name="row-basis" .checked=${
|
|
554
|
+
<p hint>${i18next.t('figure.text.repeat-direction')}</p>
|
|
555
|
+
<div choice-row role="radiogroup" aria-label=${i18next.t('figure.label.row-basis')}>
|
|
556
|
+
<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>
|
|
557
|
+
<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
558
|
</div>
|
|
653
559
|
${r.fills
|
|
654
560
|
? html`<div form>
|
|
655
561
|
<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) })} />
|
|
562
|
+
<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
563
|
</div>`
|
|
658
564
|
: html`<div form>
|
|
659
565
|
<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) })} />
|
|
566
|
+
<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
567
|
</div>
|
|
662
568
|
<p hint>${i18next.t('figure.text.row-count-spreads')}</p>`}
|
|
663
|
-
${action && this.rowOpen ? this.renderPreviewWords() : nothing}
|
|
569
|
+
${action && this.rowOpen ? html`<p hint>${i18next.t('figure.text.repeat-draft')}</p>${this.renderPreviewWords()}` : nothing}
|
|
664
570
|
<button act ?disabled=${this.locked || !action} @click=${() => action && this.command(action)}>
|
|
665
571
|
<md-icon>view_week</md-icon>${i18next.t('figure.button.make-repeat')}
|
|
666
572
|
</button>
|
|
@@ -799,7 +705,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
799
705
|
<span>${i18next.t('figure.label.motion-follow')}</span>
|
|
800
706
|
<select @change=${(e: Event) => (this.motion = { ...m, follow: (e.target as HTMLSelectElement).value })}>
|
|
801
707
|
<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.
|
|
708
|
+
${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
709
|
</select>
|
|
804
710
|
</div>`
|
|
805
711
|
: nothing}
|