@things-factory/figure-ui 10.1.85 → 10.1.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/modeller/editor.ts +44 -3
- package/client/modeller/figure-section-editor.ts +643 -0
- package/client/modeller/figure-section-panel.ts +7 -0
- package/client/modeller/live.ts +5 -0
- package/client/modeller/section-drawing.ts +150 -0
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/editor.d.ts +6 -0
- package/dist-client/modeller/editor.js +50 -2
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +87 -0
- package/dist-client/modeller/figure-section-editor.js +635 -0
- package/dist-client/modeller/figure-section-editor.js.map +1 -0
- package/dist-client/modeller/figure-section-panel.js +7 -0
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/live.js +7 -0
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +52 -0
- package/dist-client/modeller/section-drawing.js +123 -0
- package/dist-client/modeller/section-drawing.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/browser/acceptance-page.js +7 -0
- package/test/section-editor.test.ts +232 -0
- package/test/section-panel.test.ts +12 -9
- package/translations/en.json +34 -0
- package/translations/ja.json +34 -0
- package/translations/ko.json +34 -0
- package/translations/ms.json +34 -0
- package/translations/zh.json +34 -0
|
@@ -6,10 +6,10 @@ import { notify } 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 { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figurePlaneAlignChanges, type FigurePlaneContact, type FigureClipLoss, figurePartFaceOf, type FigureFaceArea, figureRowOf, type FigureRowReading, figureFaceOverhangOf, type FigureFaceOverhang, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model'
|
|
9
|
+
import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, type FigurePlaneContact, type FigureClipLoss, figurePartFaceOf, type FigureFaceArea, figureRowOf, type FigureRowReading, figureFaceOverhangOf, type FigureFaceOverhang, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model'
|
|
10
10
|
import type { RecipeTemplate } from './recipes.js'
|
|
11
11
|
import './figure-recipe-catalog.js'
|
|
12
|
-
import type { FigureAsset, FigureAuthoringAction, FigureInputChange, FigurePartEdit, FigurePrimitiveCreation, FigureProposalStep, FigureRecipe, FigureRecipeValue, FigurePlacement } from '@hatiolab/figure-model'
|
|
12
|
+
import type { FigureAsset, FigureAuthoringAction, FigureEvaluatedShape, FigureInputChange, FigurePartEdit, FigurePrimitiveCreation, FigureProposalStep, FigureRecipe, FigureRecipeValue, FigurePlacement } from '@hatiolab/figure-model'
|
|
13
13
|
import type { FigureProductSession } from './product-session.js'
|
|
14
14
|
import { captureFigureThumbnail } from './thumbnail.js'
|
|
15
15
|
import { drawableFigureParts } from './renderer.js'
|
|
@@ -17,6 +17,7 @@ import './canvas.js'
|
|
|
17
17
|
import './figure-parts.js'
|
|
18
18
|
import './figure-material-panel.js'
|
|
19
19
|
import './figure-section-panel.js'
|
|
20
|
+
import './figure-section-editor.js'
|
|
20
21
|
import { faceHighlightOf, ghostPartsOf, previewWordsOf, rowWordsOf, type HeldPreview } from './command-preview.js'
|
|
21
22
|
import { motionEndsOf, type MotionEnds } from './motion-ends.js'
|
|
22
23
|
import './figure-links.js'
|
|
@@ -453,6 +454,8 @@ export class FigureEditor extends LitElement {
|
|
|
453
454
|
@state() private hiddenParts = new Set<string>()
|
|
454
455
|
/** A value in hand -- typed, held on a spinner, scrubbed, dragged -- drawn at once and recorded on release. */
|
|
455
456
|
@state() private live: LiveValues = NOTHING_LIVE
|
|
457
|
+
/** The part whose drawn section is open in the section editor (ADR-0100 §3.2), or null. */
|
|
458
|
+
@state() private sectionEditing: string | null = null
|
|
456
459
|
/**
|
|
457
460
|
* A command seen before it is done (ADR-0097 §5.5): worked out on a copy for the figure, version, part and command it
|
|
458
461
|
* names, and shown only while all four are still the ones on screen. Nothing of it is saved or undone.
|
|
@@ -491,6 +494,11 @@ export class FigureEditor extends LitElement {
|
|
|
491
494
|
private onKey = (e: KeyboardEvent) => {
|
|
492
495
|
const view = this.session?.read()
|
|
493
496
|
if (!view || view.loading || view.saving || !view.context || !this.isConnected) return
|
|
497
|
+
/*
|
|
498
|
+
* The section editor open takes the keys for its own: Esc cancels it, Delete deletes a point, Ctrl+Z undoes a point.
|
|
499
|
+
* Heard here first, they deselected the part, deleted the part and undid the figure's last step (2026-09-30).
|
|
500
|
+
*/
|
|
501
|
+
if (this.sectionEditing) return
|
|
494
502
|
const canvas = this.renderRoot.querySelector<FigureCanvas>('figure-canvas')
|
|
495
503
|
const path = e.composedPath()
|
|
496
504
|
const action = keyActionOf(
|
|
@@ -637,6 +645,11 @@ export class FigureEditor extends LitElement {
|
|
|
637
645
|
}
|
|
638
646
|
if (view && !view.geometry.some(g => ownerOf(g.id) === this.selected)) this.selected = ''
|
|
639
647
|
if (_changed.has('selected')) this.gizmoMode = undefined
|
|
648
|
+
/*
|
|
649
|
+
* The section editor is of one part: when the selection moves off it by any way -- another part picked, the scene's
|
|
650
|
+
* own Esc, the part removed, another figure opened -- it closes and lets go of the preview it held.
|
|
651
|
+
*/
|
|
652
|
+
if (this.sectionEditing && this.sectionEditing !== this.selected) this.closeSectionEditor()
|
|
640
653
|
}
|
|
641
654
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
642
655
|
private useGizmo(mode: 'translate' | 'rotate' | 'scale') {
|
|
@@ -703,6 +716,10 @@ export class FigureEditor extends LitElement {
|
|
|
703
716
|
private hold(values: Record<string, number>, edit?: FigurePartEdit) {
|
|
704
717
|
this.live = { values: { ...this.live.values, ...values }, ...(edit ? { edit } : this.live.edit ? { edit: this.live.edit } : {}) }
|
|
705
718
|
}
|
|
719
|
+
private closeSectionEditor() {
|
|
720
|
+
this.sectionEditing = null
|
|
721
|
+
this.holdAction(null)
|
|
722
|
+
}
|
|
706
723
|
/** A command in hand, drawn and not run; `null` lets go of it and the figure as it stands is drawn again. */
|
|
707
724
|
private holdAction(action: FigureAuthoringAction | null) {
|
|
708
725
|
const { action: _, ...rest } = this.live
|
|
@@ -1764,6 +1781,24 @@ export class FigureEditor extends LitElement {
|
|
|
1764
1781
|
* recipe, or an AI draft when the page offers one. Nothing is added until one is chosen; the type, the space, the
|
|
1765
1782
|
* mounting face and the tags are not asked for first.
|
|
1766
1783
|
*/
|
|
1784
|
+
/** The section editor over the canvas, the figure behind it drawing what the working points make. */
|
|
1785
|
+
private renderSectionEditor(geometry: readonly { id: string }[]) {
|
|
1786
|
+
const part = geometry.find(g => ownerOf(g.id) === this.selected) as { id: string; shape: FigureEvaluatedShape } | undefined
|
|
1787
|
+
/* A door frame's section is its own width, height, jamb and head: its fields edit it, not an outline. */
|
|
1788
|
+
if (!part?.shape?.extrusion || part.shape.dimensions.jamb !== undefined) return nothing
|
|
1789
|
+
const asset = this.asset() as FigureAsset
|
|
1790
|
+
const measures = figureSectionMeasuresOf(asset, this.selected)
|
|
1791
|
+
const kept = figureScaleAnchorOf(asset, this.selected)?.[part.shape.extrusion.axis]
|
|
1792
|
+
return html`<figure-section-editor
|
|
1793
|
+
.body=${{ ...part, id: this.selected }}
|
|
1794
|
+
.measures=${measures}
|
|
1795
|
+
.kept=${kept}
|
|
1796
|
+
.revision=${this.session?.read().revision ?? 0}
|
|
1797
|
+
@command=${(e: CustomEvent) => this.linkCommand(e.detail.action)}
|
|
1798
|
+
@command-live=${(e: CustomEvent) => this.holdAction(e.detail.action)}
|
|
1799
|
+
@section-editor-close=${() => this.closeSectionEditor()}
|
|
1800
|
+
></figure-section-editor>`
|
|
1801
|
+
}
|
|
1767
1802
|
private renderStart() {
|
|
1768
1803
|
const shapes: FigurePaletteKind[] = ['box', 'cylinder', 'plate']
|
|
1769
1804
|
/* Where it is authored from, chosen before any part (ADR-0097): the figure's own setting, no room made up for it. */
|
|
@@ -1973,6 +2008,7 @@ export class FigureEditor extends LitElement {
|
|
|
1973
2008
|
></figure-preview>`
|
|
1974
2009
|
: html`<div middle>
|
|
1975
2010
|
${view.geometry.length === 0 && !unavailable ? this.renderStart() : nothing}
|
|
2011
|
+
${this.sectionEditing && this.sectionEditing === this.selected && !unavailable ? this.renderSectionEditor(view.geometry) : nothing}
|
|
1976
2012
|
<figure-canvas
|
|
1977
2013
|
?startShown=${view.geometry.length === 0 && !unavailable}
|
|
1978
2014
|
.view=${this.view}
|
|
@@ -2027,6 +2063,7 @@ export class FigureEditor extends LitElement {
|
|
|
2027
2063
|
?locked=${unavailable}
|
|
2028
2064
|
@command=${(e: CustomEvent) => this.linkCommand(e.detail.action)}
|
|
2029
2065
|
@command-live=${(e: CustomEvent) => this.holdAction(e.detail.action)}
|
|
2066
|
+
@section-editor-open=${() => (this.sectionEditing = this.selected)}
|
|
2030
2067
|
></figure-section-panel>
|
|
2031
2068
|
<figure-material-panel
|
|
2032
2069
|
.body=${{ ...part, id: this.selected }}
|
|
@@ -2195,7 +2232,11 @@ export class FigureEditor extends LitElement {
|
|
|
2195
2232
|
if (agreed) this.linkCommand(refit.step)
|
|
2196
2233
|
}
|
|
2197
2234
|
private renderSingle(name: string, field: FieldLike, unavailable: boolean) {
|
|
2198
|
-
|
|
2235
|
+
/* A drawn section's point is called by its number and axis, as the section editor calls it (ADR-0100 §2.1, Q9). */
|
|
2236
|
+
const point = /^point\.p(\d+)\.([xyz])$/.exec(name)
|
|
2237
|
+
const words = point
|
|
2238
|
+
? [i18next.t('figure.label.section-editor-coordinate', { point: point[1]!, axis: point[2]!.toUpperCase() })]
|
|
2239
|
+
: name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }))
|
|
2199
2240
|
return html`<span ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(field as never, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
2200
2241
|
><input
|
|
2201
2242
|
aria-label=${field.id}
|