@things-factory/figure-ui 10.1.204 → 10.1.206
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/modeller/canvas.ts +91 -9
- package/client/modeller/editor-styles.ts +23 -22
- package/client/modeller/editor.ts +217 -184
- package/client/modeller/fasten-form.ts +130 -12
- package/client/modeller/figure-fasten-dialog.ts +45 -11
- package/client/modeller/figure-fasten-drawing.ts +233 -12
- package/client/modeller/figure-links.ts +2 -2
- package/client/modeller/figure-preview.ts +6 -6
- package/client/modeller/figure-section-editor.ts +6 -6
- package/client/modeller/figure-slot-layout-panel.ts +1 -1
- package/client/modeller/gizmo.ts +22 -1
- package/client/modeller/keys.ts +10 -1
- package/client/modeller/links.ts +79 -46
- package/client/modeller/palette.ts +1 -1
- package/client/modeller/product-session.ts +16 -4
- package/client/modeller/refusal-teller.ts +3 -25
- package/client/modeller/refusals.ts +19 -26
- package/client/modeller/section-drawing.ts +7 -7
- package/client/modeller/size-drag.ts +112 -0
- package/client/modeller/slot-layout-host.ts +3 -3
- package/client/modeller/stage-model.ts +6 -9
- package/client/pages/figure-modeller-page.ts +1 -4
- package/dist-client/modeller/canvas.d.ts +24 -3
- package/dist-client/modeller/canvas.js +96 -11
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +23 -22
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +35 -35
- package/dist-client/modeller/editor.js +221 -193
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +46 -4
- package/dist-client/modeller/fasten-form.js +120 -11
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.d.ts +18 -4
- package/dist-client/modeller/figure-fasten-dialog.js +49 -12
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.d.ts +33 -1
- package/dist-client/modeller/figure-fasten-drawing.js +235 -11
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +1 -1
- package/dist-client/modeller/figure-links.js +2 -2
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-preview.js +4 -4
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +3 -3
- package/dist-client/modeller/figure-section-editor.js +4 -4
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +18 -1
- package/dist-client/modeller/gizmo.js +19 -0
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/keys.d.ts +1 -1
- package/dist-client/modeller/keys.js +6 -0
- package/dist-client/modeller/keys.js.map +1 -1
- package/dist-client/modeller/links.d.ts +30 -24
- package/dist-client/modeller/links.js +67 -42
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/palette.js +1 -1
- package/dist-client/modeller/palette.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +15 -13
- package/dist-client/modeller/product-session.js +15 -5
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/refusal-teller.d.ts +1 -12
- package/dist-client/modeller/refusal-teller.js +3 -22
- package/dist-client/modeller/refusal-teller.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +6 -14
- package/dist-client/modeller/refusals.js +16 -27
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +3 -4
- package/dist-client/modeller/section-drawing.js +10 -9
- package/dist-client/modeller/section-drawing.js.map +1 -1
- package/dist-client/modeller/size-drag.d.ts +38 -0
- package/dist-client/modeller/size-drag.js +85 -0
- package/dist-client/modeller/size-drag.js.map +1 -0
- package/dist-client/modeller/slot-layout-host.d.ts +1 -3
- package/dist-client/modeller/slot-layout-host.js +3 -3
- package/dist-client/modeller/slot-layout-host.js.map +1 -1
- package/dist-client/modeller/stage-model.d.ts +5 -7
- package/dist-client/modeller/stage-model.js +6 -6
- package/dist-client/modeller/stage-model.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +0 -2
- package/dist-client/pages/figure-modeller-page.js +1 -6
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/align.test.ts +7 -11
- package/test/browser/acceptance-page.js +11 -1
- package/test/browser/editing.check.mjs +17 -13
- package/test/browser/links-page.js +2 -2
- package/test/browser/links.check.mjs +124 -69
- package/test/browser/modeller-page.js +30 -18
- package/test/browser/modeller-revision.check.mjs +39 -0
- package/test/browser/modeller.check.mjs +77 -26
- package/test/browser/parts.check.mjs +49 -0
- package/test/browser/screen-page.js +0 -2
- package/test/browser/screen.check.mjs +11 -13
- package/test/browser/section.check.mjs +29 -2
- package/test/fasten-form.test.ts +102 -1
- package/test/gizmo.test.ts +25 -16
- package/test/keys.test.ts +11 -0
- package/test/links.test.ts +46 -36
- package/test/refusals.test.ts +17 -21
- package/test/resize-with-placement.test.ts +14 -13
- package/test/revision.test.ts +3 -2
- package/test/section-editor.test.ts +9 -10
- package/test/size-drag.test.ts +114 -0
- package/test/slot-layout-host.test.ts +13 -8
- package/translations/en.json +32 -34
- package/translations/es.json +32 -34
- package/translations/ja.json +32 -34
- package/translations/ko.json +32 -34
- package/translations/ms.json +32 -34
- package/translations/zh.json +32 -34
|
@@ -7,7 +7,7 @@ import { openPopup } from '@operato/layout';
|
|
|
7
7
|
import { OxPrompt } from '@operato/popup';
|
|
8
8
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
9
|
import { live } from 'lit/directives/live.js';
|
|
10
|
-
import { figurePartFaceOf, PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor,
|
|
10
|
+
import { figurePartFaceOf, PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditOf, figureSizeEditorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model';
|
|
11
11
|
import './figure-recipe-catalog.js';
|
|
12
12
|
import { captureFigureThumbnail } from './thumbnail.js';
|
|
13
13
|
import { drawableFigureParts } from './renderer.js';
|
|
@@ -28,7 +28,8 @@ import './figure-preview.js';
|
|
|
28
28
|
import './figure-settings.js';
|
|
29
29
|
import { DEFAULT_VIEW } from '@things-factory/figure-viewer/dist-client/figure-view.js';
|
|
30
30
|
import './figure-capability-panel.js';
|
|
31
|
-
import { gizmoChanges, gizmoReachOf } from './gizmo.js';
|
|
31
|
+
import { gizmoChanges, gizmoReachOf, sameMeasureAxesOf, sizeKeepOf } from './gizmo.js';
|
|
32
|
+
import { keptBySizeOf, partsAtFrame, SIZE_KEEPS_WORDS, startSizeDrag, throughPlaneOf } from './size-drag.js';
|
|
32
33
|
import { ModellerKeys } from './modeller-keys.js';
|
|
33
34
|
import { PaneFold } from './pane-fold.js';
|
|
34
35
|
import { PanelMemory } from './panel-memory.js';
|
|
@@ -49,8 +50,9 @@ import { END_FACE } from './section-controls.js';
|
|
|
49
50
|
import { nudgeOf, placementOf } from './stage-model.js';
|
|
50
51
|
import { occupancyFromParts, roomRefitRowsOf } from './occupancy.js';
|
|
51
52
|
import { gateFindingStyles, renderGateFinding } from './gate-findings.js';
|
|
52
|
-
import { editRefusalOf, inDeclaredRoom,
|
|
53
|
-
import {
|
|
53
|
+
import { editRefusalOf, inDeclaredRoom, partNameRefusalOf, sizeRangeFitsOf, partRefusalOf, placedRefusalOf, recipeRefusalOf, removalNoticeOf } from './refusals.js';
|
|
54
|
+
import { HelpToggles } from './help.js';
|
|
55
|
+
import { endPlaceKeyOf, fieldWordsOf, followOf, sizeFollowersOf, measureNow, partSummaryOf, instanceRangeOf, instanceSizeToDeclare, proportionToFollow, rangeWith, turnsAboutAJoint, valueNow } from './links.js';
|
|
54
56
|
import { jointFanOf } from './joint-fan.js';
|
|
55
57
|
import { derivedOf } from './revision.js';
|
|
56
58
|
import { jointDragOf } from './joint-drag.js';
|
|
@@ -76,14 +78,16 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
76
78
|
/** Why the last recipe was refused, in the kernel's words. */
|
|
77
79
|
this.recipeError = '';
|
|
78
80
|
this.revision = 0;
|
|
81
|
+
/** The 「?」 beside a group's title and the sentence it opens. */
|
|
82
|
+
this.help = new HelpToggles(this);
|
|
79
83
|
this.selected = '';
|
|
80
84
|
this.error = '';
|
|
81
85
|
this.tab = 'properties';
|
|
82
86
|
/**
|
|
83
|
-
* The fastening window open for the selected part (user 2026-10-17)
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
+
* The fastening window open for the selected part (user 2026-10-17), in the standard popup, moved by its head
|
|
88
|
+
* (2026-10-18; put back 2026-10-20 -- the side panel it was moved to could not be moved), with no backdrop, so the
|
|
89
|
+
* canvas can be pressed while it is open: chosen there, applied once. It closes with nothing written when the part,
|
|
90
|
+
* the tab or the middle changes under it.
|
|
87
91
|
*/
|
|
88
92
|
this.fastening = false;
|
|
89
93
|
/** Why the window's 「적용」 was refused, said in it. */
|
|
@@ -133,19 +137,18 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
133
137
|
selected: () => this.selected,
|
|
134
138
|
act: action => this.keyAction(action)
|
|
135
139
|
});
|
|
140
|
+
/** The part Ctrl+C took: pasted as the kernel's duplicate. Not kept past the editor, nor written anywhere. */
|
|
141
|
+
this.copied = '';
|
|
136
142
|
this.paneFold = new PaneFold(this, () => this.panes);
|
|
137
143
|
this.refusals = new RefusalTeller({
|
|
138
144
|
session: () => this.session,
|
|
139
145
|
error: () => this.error,
|
|
140
146
|
notice: () => this.notice,
|
|
141
|
-
|
|
142
|
-
refuse: message => (this.error = message),
|
|
143
|
-
command: action => this.linkCommand(action)
|
|
147
|
+
refuse: message => (this.error = message)
|
|
144
148
|
});
|
|
145
149
|
this.panelMemory = new PanelMemory(() => this.renderRoot);
|
|
146
150
|
/** The preview is made when first asked for, and kept (hidden) after that; it is taken down with the editor. */
|
|
147
151
|
this.previewMounted = false;
|
|
148
|
-
this.lastAnchors = {};
|
|
149
152
|
/** Data tried in the data tab, given to the preview's figures as their `data`; null when none is (ADR-0051 §8). */
|
|
150
153
|
this.dataSample = null;
|
|
151
154
|
/*
|
|
@@ -221,13 +224,20 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
221
224
|
aria-pressed=${String(this.gizmoMode === tool.mode)}
|
|
222
225
|
?disabled=${!tool.can}
|
|
223
226
|
aria-label=${i18next.t('figure.button.gizmo-' + tool.mode)}
|
|
224
|
-
title=${`${i18next.t('figure.button.gizmo-' + tool.mode)} (${tool.key})${tool.can || !this.selected ? '' : ' · ' +
|
|
227
|
+
title=${`${i18next.t('figure.button.gizmo-' + tool.mode)} (${tool.key})${tool.can || !this.selected ? '' : ' · ' + i18next.t('figure.text.gizmo-not-own')}`}
|
|
225
228
|
@click=${() => this.useGizmo(tool.mode)}
|
|
226
229
|
>
|
|
227
230
|
<md-icon>${tool.icon}</md-icon>
|
|
228
231
|
</button>`)}
|
|
229
232
|
</span>`;
|
|
230
233
|
}
|
|
234
|
+
/** A copied part still in the figure is duplicated -- a part of its own, named by figure-model; one gone, nothing. */
|
|
235
|
+
pasteCopied() {
|
|
236
|
+
const asset = this.session?.revisionNow()?.asset;
|
|
237
|
+
if (!this.copied || !asset?.document.model.nodes.some(n => n.op === 'place@1' && n.id === this.copied))
|
|
238
|
+
return;
|
|
239
|
+
this.duplicatePart(this.copied);
|
|
240
|
+
}
|
|
231
241
|
keyAction(action) {
|
|
232
242
|
const view = this.session.read();
|
|
233
243
|
const canvas = this.renderRoot.querySelector('figure-canvas');
|
|
@@ -239,6 +249,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
239
249
|
view.canRedo && this.run(() => this.session.redo());
|
|
240
250
|
else if (action === 'duplicate')
|
|
241
251
|
this.duplicatePart(this.selected);
|
|
252
|
+
else if (action === 'copy')
|
|
253
|
+
this.copied = this.selected;
|
|
254
|
+
else if (action === 'paste')
|
|
255
|
+
this.pasteCopied();
|
|
256
|
+
else if (action === 'hold')
|
|
257
|
+
return;
|
|
242
258
|
else if (action === 'remove')
|
|
243
259
|
this.removePart(this.selected);
|
|
244
260
|
else
|
|
@@ -263,7 +279,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
263
279
|
if (_changed.has('selected'))
|
|
264
280
|
this.gizmoMode = undefined;
|
|
265
281
|
/* The window is about the part chosen on the properties tab, being edited: it does not follow anywhere else. */
|
|
266
|
-
if (this.fastening && (_changed.has('selected') || !this.selected || this.tab !== 'properties' || this.middle !== 'edit'
|
|
282
|
+
if (this.fastening && (_changed.has('selected') || !this.selected || this.tab !== 'properties' || this.middle !== 'edit'))
|
|
267
283
|
this.closeFastening();
|
|
268
284
|
/*
|
|
269
285
|
* The section editor is of one part: when the selection moves off it by any way -- another part picked, the scene's
|
|
@@ -292,19 +308,23 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
292
308
|
this.selected = next;
|
|
293
309
|
});
|
|
294
310
|
}
|
|
311
|
+
/** While the size tool is on a round part: which of its handles change the one diameter (ADR-0140 4.1). */
|
|
312
|
+
renderSameMeasure(unavailable) {
|
|
313
|
+
if (unavailable || this.gizmoMode !== 'scale' || !this.selected)
|
|
314
|
+
return nothing;
|
|
315
|
+
const axes = sameMeasureAxesOf(this.reachOf(this.selected));
|
|
316
|
+
return axes
|
|
317
|
+
? html `<p same-measure role="status">${i18next.t('figure.text.gizmo-same-measure', { axes: axes.map(a => a.toUpperCase()).join(' · ') })}</p>`
|
|
318
|
+
: nothing;
|
|
319
|
+
}
|
|
295
320
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
296
321
|
useGizmo(mode) {
|
|
297
322
|
/*
|
|
298
323
|
R does what the bar's resize tool does: on a part whose size is not its own to drag the tool is off, so no gizmo is
|
|
299
|
-
put on it to drag in vain (:3300, 10.1.196) -- the tool's own words are said instead
|
|
300
|
-
its size fields.
|
|
324
|
+
put on it to drag in vain (:3300, 10.1.196) -- the tool's own words are said instead.
|
|
301
325
|
*/
|
|
302
326
|
if (mode === 'scale' && this.selected && this.gizmoMode !== 'scale' && !this.reachOf(this.selected).scale) {
|
|
303
|
-
|
|
304
|
-
if (asset && this.sizeDragTurned(asset, this.selected))
|
|
305
|
-
this.tellSizeByFields(this.selected);
|
|
306
|
-
else
|
|
307
|
-
this.refusals.say(i18next.t('figure.text.gizmo-not-own'));
|
|
327
|
+
this.refusals.say(i18next.t('figure.text.gizmo-not-own'));
|
|
308
328
|
return;
|
|
309
329
|
}
|
|
310
330
|
this.renderRoot.querySelector('figure-canvas')?.gizmo(mode);
|
|
@@ -333,6 +353,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
333
353
|
finally {
|
|
334
354
|
/* Committed or refused, the value in hand is gone: the figure as it stands is drawn again. */
|
|
335
355
|
this.live = NOTHING_LIVE;
|
|
356
|
+
this.sizing = undefined;
|
|
336
357
|
}
|
|
337
358
|
}
|
|
338
359
|
/** The bodies drawn with the value in hand, or null when nothing is in hand (or the figure cannot take it). */
|
|
@@ -342,7 +363,8 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
342
363
|
/** What the canvas draws: the figure with the value in hand when there is one, and without the parts hidden on the workbench. */
|
|
343
364
|
drawnParts(geometry, live) {
|
|
344
365
|
const parts = live ?? drawableFigureParts(geometry ?? []);
|
|
345
|
-
|
|
366
|
+
const frame = this.sizing?.frame;
|
|
367
|
+
return (frame ? partsAtFrame(parts, frame.moved) : parts).filter(body => !this.hiddenParts.has(ownerOf(body.id)));
|
|
346
368
|
}
|
|
347
369
|
/**
|
|
348
370
|
* Where the selected part turns and how far, with the pose as it is drawn now -- a value in hand included, so the
|
|
@@ -380,6 +402,10 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
380
402
|
this.live = action ? { ...rest, action } : rest;
|
|
381
403
|
}
|
|
382
404
|
changed() {
|
|
405
|
+
/* What the step did to the size ends the author typed, said with whatever else it said. */
|
|
406
|
+
const fits = this.session?.takeSizeRangeFits() ?? [];
|
|
407
|
+
if (fits.length)
|
|
408
|
+
this.notice = [this.notice, sizeRangeFitsOf(fits, (key, params) => i18next.t(key, params))].filter(Boolean).join(' ');
|
|
383
409
|
this.requestUpdate();
|
|
384
410
|
this.dispatchEvent(new CustomEvent('changed', { bubbles: true, composed: true }));
|
|
385
411
|
}
|
|
@@ -483,7 +509,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
483
509
|
this.run(() => this.session.edit({ kind: 'move-part', target: id, to }));
|
|
484
510
|
}
|
|
485
511
|
removePart(id, refitRoom = false) {
|
|
486
|
-
this.retry = undefined;
|
|
487
512
|
try {
|
|
488
513
|
const view = this.session.edit({ kind: 'remove-part', target: id, ...(refitRoom ? { refitRoom: true } : {}) });
|
|
489
514
|
this.error = '';
|
|
@@ -560,64 +585,45 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
560
585
|
return none;
|
|
561
586
|
try {
|
|
562
587
|
const asset = this.shown().asset;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
A size drag shows the face it keeps and the commit keeps the same (figureScaleAnchorOf). Where figure-model cannot
|
|
566
|
-
say which face that is -- a part fastened turned or twisted -- the drag would show one result and write another,
|
|
567
|
-
so sizing by the gizmo is not offered there; the fields still size it (designer 2026-09-27).
|
|
568
|
-
*/
|
|
569
|
-
if (reach.scale && this.sizeDragUnsure(asset, id))
|
|
570
|
-
return { ...reach, scale: false };
|
|
571
|
-
return reach;
|
|
588
|
+
/* Turned any way, fastened or not: a size drag is drawn from figure-model's one size function, frame by frame (ADR-0140 4.4). */
|
|
589
|
+
return gizmoReachOf(asset, id);
|
|
572
590
|
}
|
|
573
591
|
catch {
|
|
574
592
|
return none;
|
|
575
593
|
}
|
|
576
594
|
}
|
|
577
595
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
* 2026-10-19, the fan's blade-2). Its tool says it is placed by something else, as before.
|
|
596
|
+
* A size drag in hand, a frame of it: the sizes the handle comes to, through figure-model's size function and the
|
|
597
|
+
* evaluator stepped to them, drawn and written nowhere (ADR-0140 4.4). Begun at the first frame, from the figure as it
|
|
598
|
+
* is drawn and the handle the scene says was held; the release writes the last frame's changes.
|
|
582
599
|
*/
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return false;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
/** Whether a size drag on this part could not show what is written: figure-model gives no face it keeps. */
|
|
592
|
-
sizeDragUnsure(asset, id) {
|
|
600
|
+
resizing(id, result) {
|
|
601
|
+
const part = ownerOf(id);
|
|
602
|
+
const shown = this.shown();
|
|
603
|
+
if (!shown || !result.scale)
|
|
604
|
+
return;
|
|
593
605
|
try {
|
|
594
|
-
|
|
606
|
+
const drawn = drawableFigureParts(this.session.read().geometry ?? []);
|
|
607
|
+
if (this.sizing?.part !== part)
|
|
608
|
+
this.sizing = { part, drag: startSizeDrag(shown.asset, part, shown.inputs, sizeKeepOf(result.grab)), through: throughPlaneOf(shown.asset, drawn, part) };
|
|
609
|
+
const sizes = gizmoChanges(id, this.reachOf(id), { scale: result.scale }, { ...shown.inputs });
|
|
610
|
+
this.sizing = { ...this.sizing, frame: this.sizing.drag.frame(sizes) };
|
|
595
611
|
}
|
|
596
612
|
catch {
|
|
597
|
-
|
|
613
|
+
/* A size the figure cannot take on the way: the figure as it stands is drawn, and the release says why. */
|
|
614
|
+
this.sizing = undefined;
|
|
598
615
|
}
|
|
599
616
|
}
|
|
600
617
|
/**
|
|
601
|
-
*
|
|
602
|
-
*
|
|
618
|
+
* A free part a size change takes past the mounting plane: said, with 「…에 붙이기」 to fasten it there (ADR-0140 5). The
|
|
619
|
+
* size rule is not changed for it: what the author picks is the fastening, one step of the history.
|
|
603
620
|
*/
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
this.refusals.point(this.sizeDragRefusalOf(id), { label: i18next.t('figure.button.to-size-fields'), run: () => this.toSizeFields(id) });
|
|
611
|
-
}
|
|
612
|
-
/** To the part's first size field it takes typed, on the properties tab, ready to type over. */
|
|
613
|
-
async toSizeFields(id) {
|
|
614
|
-
this.middle = 'edit';
|
|
615
|
-
this.tab = 'properties';
|
|
616
|
-
this.selected = id;
|
|
617
|
-
await this.updateComplete;
|
|
618
|
-
const field = this.renderRoot.querySelector('[slot="size"] input:not([disabled]):not([readonly])');
|
|
619
|
-
field?.focus();
|
|
620
|
-
field?.select();
|
|
621
|
+
tellThroughPlane(part, mm) {
|
|
622
|
+
const plane = i18next.t('figure.label.resize-place-' + (this.asset()?.placement ?? 'floor'));
|
|
623
|
+
this.refusals.point(i18next.t('figure.text.size-through-plane', { part: partLabelOf(part), plane, mm: String(mm) }), {
|
|
624
|
+
label: i18next.t('figure.button.fasten-to-plane', { plane }),
|
|
625
|
+
run: () => this.run(() => this.session.command({ kind: 'attach', part, to: { plane: 'mounting-plane' } }))
|
|
626
|
+
});
|
|
621
627
|
}
|
|
622
628
|
/** A drag, written back as the part's own inputs — one step of the history, like typing the numbers. */
|
|
623
629
|
transformed(id, result) {
|
|
@@ -645,21 +651,22 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
645
651
|
the shift had been taken for a move of the joint, and the size was dropped).
|
|
646
652
|
*/
|
|
647
653
|
const jointed = !!asset.joints?.some(j => j.body1.part === ownerOf(id));
|
|
648
|
-
/* A size drag this part cannot show truly is not written: it springs back, and the reason is said. */
|
|
649
|
-
if (result.scale && result.scale.some(k => Math.abs(k - 1) > 1e-4) && this.sizeDragTurned(asset, id)) {
|
|
650
|
-
this.tellSizeByFields(id);
|
|
651
|
-
return this.requestUpdate();
|
|
652
|
-
}
|
|
653
654
|
/*
|
|
654
|
-
A size dragged
|
|
655
|
-
|
|
655
|
+
A size dragged is written as its last frame was drawn: figure-model's one size function, keeping the side opposite
|
|
656
|
+
the handle held -- or, fastened, its fastening (ADR-0140 4.1 · 4.4). What the scene put the part at is not read.
|
|
656
657
|
*/
|
|
657
658
|
const scaled = !!result.scale && !result.rotation && result.scale.some(k => Math.abs(k - 1) > 1e-4);
|
|
658
659
|
const taken = jointed || scaled ? { scale: result.scale } : result;
|
|
659
660
|
const written = gizmoChanges(id, this.reachOf(id), taken, sizes);
|
|
660
|
-
const
|
|
661
|
+
const drawnLast = this.sizing?.part === ownerOf(id) ? this.sizing.frame : undefined;
|
|
662
|
+
const changes = !scaled ? written : drawnLast ? drawnLast.changes : figureSizeEditOf(asset, ownerOf(id), written, sizeKeepOf(result.grab)).changes;
|
|
661
663
|
if (changes.length) {
|
|
664
|
+
const through = scaled ? (this.sizing?.part === ownerOf(id) ? this.sizing.through : undefined) ?? throughPlaneOf(asset, drawableFigureParts(view.geometry), ownerOf(id)) : 0;
|
|
662
665
|
this.run(() => this.session.apply(changes));
|
|
666
|
+
/* Taken past the mounting plane by the size it was given: said with how far, once it is written, and the way to fasten it (ADR-0140 5). */
|
|
667
|
+
const now = scaled ? throughPlaneOf(this.shown().asset, drawableFigureParts(this.session.read().geometry ?? []), ownerOf(id)) : 0;
|
|
668
|
+
if (now > through + 0.5)
|
|
669
|
+
this.tellThroughPlane(ownerOf(id), now);
|
|
663
670
|
return;
|
|
664
671
|
}
|
|
665
672
|
if (jointed && result.scale) {
|
|
@@ -859,6 +866,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
859
866
|
${groups.singles.length
|
|
860
867
|
? html `<div form>${groups.singles.map(g => (g.kind === 'single' ? this.renderSingle(g.name, g.field, unavailable) : nothing))}</div>`
|
|
861
868
|
: nothing}
|
|
869
|
+
${this.renderSizeKeeps()}
|
|
862
870
|
</div>`
|
|
863
871
|
: nothing}
|
|
864
872
|
${place.length && !placeLocked
|
|
@@ -942,26 +950,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
942
950
|
return html `${panel('view', 'figure.label.tab-environment', html `<figure-settings .view=${this.view} @update-view=${(e) => this.setView(e.detail.view)}></figure-settings>`)}
|
|
943
951
|
${panel('history', 'figure.button.release', html `<slot name="history"></slot>`)}`;
|
|
944
952
|
}
|
|
945
|
-
/**
|
|
946
|
-
* The face a size drag keeps for the selected part, figure-model's rule (`figureScaleAnchorOf`): the gizmo shows it
|
|
947
|
-
* while dragging and the commit keeps it, so nothing jumps on release. A part whose fastening is turned has none the
|
|
948
|
-
* rule can say, and the gizmo keeps its own (the face across from the handle).
|
|
949
|
-
*/
|
|
950
|
-
anchorsOf(asset) {
|
|
951
|
-
let next = {};
|
|
952
|
-
try {
|
|
953
|
-
const anchor = asset && this.selected ? figureScaleAnchorOf(asset, this.selected) : undefined;
|
|
954
|
-
if (anchor)
|
|
955
|
-
next = { [this.selected]: anchor };
|
|
956
|
-
}
|
|
957
|
-
catch {
|
|
958
|
-
next = {};
|
|
959
|
-
}
|
|
960
|
-
/* The same object while it says the same thing: the canvas stands its parts up again when this changes. */
|
|
961
|
-
if (JSON.stringify(next) !== JSON.stringify(this.lastAnchors))
|
|
962
|
-
this.lastAnchors = next;
|
|
963
|
-
return this.lastAnchors;
|
|
964
|
-
}
|
|
965
953
|
/** The figure's controls: try a value, name it, set its range and travel time, keep the pose. */
|
|
966
954
|
renderControls(view, unavailable) {
|
|
967
955
|
const asset = this.shown()?.asset;
|
|
@@ -999,7 +987,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
999
987
|
linkCommand(action, from = 'panel') {
|
|
1000
988
|
/* Committed or refused, a command in hand is let go of: the figure as it stands is drawn again. */
|
|
1001
989
|
this.holdAction(null);
|
|
1002
|
-
this.retry = undefined;
|
|
1003
990
|
this.notice = '';
|
|
1004
991
|
const hadSize = this.hasSize();
|
|
1005
992
|
const hadSpace = !!this.shown()?.asset.occupancy;
|
|
@@ -1032,12 +1019,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1032
1019
|
this.error = '';
|
|
1033
1020
|
return false;
|
|
1034
1021
|
}
|
|
1035
|
-
const narrowing = narrowingRetryOf(action, error, (key, params) => i18next.t(key, params));
|
|
1036
|
-
if (narrowing) {
|
|
1037
|
-
this.error = narrowing.message;
|
|
1038
|
-
this.retry = { label: narrowing.label, action: narrowing.retry };
|
|
1039
|
-
return false;
|
|
1040
|
-
}
|
|
1041
1022
|
this.error =
|
|
1042
1023
|
code === 'DIMENSION_CYCLE'
|
|
1043
1024
|
? i18next.t('figure.text.follow-would-loop')
|
|
@@ -1049,10 +1030,16 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1049
1030
|
}
|
|
1050
1031
|
changeField(field, event) {
|
|
1051
1032
|
const input = event.target, value = input.valueAsNumber;
|
|
1052
|
-
/* A
|
|
1033
|
+
/* A number typed keeps the part's middle, or its fastening -- figure-model's one size function (ADR-0140 4.1). */
|
|
1053
1034
|
const part = this.selected;
|
|
1054
1035
|
const change = { kind: field.role === 'state' ? 'set-state' : 'set-design', input: field.id, unit: field.unit, value };
|
|
1055
|
-
|
|
1036
|
+
const asset = this.shown()?.asset;
|
|
1037
|
+
const before = asset && part ? throughPlaneOf(asset, drawableFigureParts(this.session.read().geometry ?? []), part) : 0;
|
|
1038
|
+
this.run(() => this.session.apply(field.role !== 'state' && part ? figureSizeEditOf(this.shown().asset, part, [change], { keep: 'centre' }).changes : [change]));
|
|
1039
|
+
/* Typed past the mounting plane: said, with the way to fasten it there (ADR-0140 5). */
|
|
1040
|
+
const after = asset && part && field.role !== 'state' ? throughPlaneOf(this.shown().asset, drawableFigureParts(this.session.read().geometry ?? []), part) : 0;
|
|
1041
|
+
if (after > before + 0.5)
|
|
1042
|
+
this.tellThroughPlane(part, after);
|
|
1056
1043
|
// Rejected edits must also restore the displayed numeric value.
|
|
1057
1044
|
input.value = String(this.session.read().fields.find(i => i.id === field.id).value);
|
|
1058
1045
|
}
|
|
@@ -1060,7 +1047,25 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1060
1047
|
typing(field, event) {
|
|
1061
1048
|
const value = event.target.valueAsNumber;
|
|
1062
1049
|
if (Number.isFinite(value))
|
|
1063
|
-
this.hold(
|
|
1050
|
+
this.hold(this.heldOf(field, value));
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* The values in hand for a number typed or scrubbed: what its release writes -- a size with the part's middle kept,
|
|
1054
|
+
* or its fastening (figure-model's one size function, ADR-0140 4.1), so the figure does not jump on the release.
|
|
1055
|
+
*/
|
|
1056
|
+
heldOf(field, value, sized) {
|
|
1057
|
+
const part = this.selected;
|
|
1058
|
+
const asset = this.shown()?.asset;
|
|
1059
|
+
if (field.role === 'state' || !part || !asset)
|
|
1060
|
+
return { [field.id]: value };
|
|
1061
|
+
const change = { kind: 'set-design', input: field.id, unit: field.unit ?? 'mm', value };
|
|
1062
|
+
try {
|
|
1063
|
+
const { changes } = sized ? sized([change], { keep: 'centre' }) : figureSizeEditOf(asset, part, [change], { keep: 'centre' });
|
|
1064
|
+
return Object.fromEntries(changes.map(c => [c.input, c.value]));
|
|
1065
|
+
}
|
|
1066
|
+
catch {
|
|
1067
|
+
return { [field.id]: value };
|
|
1068
|
+
}
|
|
1064
1069
|
}
|
|
1065
1070
|
/**
|
|
1066
1071
|
* Drag a field's label (or an axis letter) sideways to scrub its value, as vector tools do: the figure follows the
|
|
@@ -1077,10 +1082,18 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1077
1082
|
label.setPointerCapture(down.pointerId);
|
|
1078
1083
|
const start = field.value;
|
|
1079
1084
|
let value = start;
|
|
1085
|
+
/* Prepared once for the scrub: each move is a size through the same function its release writes with. */
|
|
1086
|
+
const asset = this.shown()?.asset;
|
|
1087
|
+
const sized = asset && this.selected && field.role !== 'state' ? (() => { try {
|
|
1088
|
+
return figureSizeEditorOf(asset, this.selected);
|
|
1089
|
+
}
|
|
1090
|
+
catch {
|
|
1091
|
+
return undefined;
|
|
1092
|
+
} })() : undefined;
|
|
1080
1093
|
const move = (e) => {
|
|
1081
1094
|
value = scrubbed(start, e.clientX - down.clientX, scrubStep(field.unit, start, e.shiftKey));
|
|
1082
1095
|
input.value = String(value);
|
|
1083
|
-
this.hold(
|
|
1096
|
+
this.hold(this.heldOf(field, value, sized));
|
|
1084
1097
|
};
|
|
1085
1098
|
const up = (e) => {
|
|
1086
1099
|
label.removeEventListener('pointermove', move);
|
|
@@ -1101,12 +1114,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1101
1114
|
label.addEventListener('pointerup', up);
|
|
1102
1115
|
label.addEventListener('pointercancel', up);
|
|
1103
1116
|
}
|
|
1104
|
-
/** The sizes a board may give the figure, and the
|
|
1117
|
+
/** The sizes a board may give the figure: the ends the author typed, and the rest as far as its parts allow. */
|
|
1105
1118
|
renderInstanceRange(shown, unavailable) {
|
|
1106
1119
|
if (!shown)
|
|
1107
1120
|
return nothing;
|
|
1108
1121
|
const asset = shown.asset;
|
|
1109
|
-
/*
|
|
1122
|
+
/* Computed once per version: figure-model evaluates the figure at its edges. */
|
|
1110
1123
|
const measured = derivedOf(shown, 'instance-range', () => {
|
|
1111
1124
|
let of;
|
|
1112
1125
|
let toDeclare;
|
|
@@ -1117,7 +1130,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1117
1130
|
of = undefined;
|
|
1118
1131
|
}
|
|
1119
1132
|
try {
|
|
1120
|
-
toDeclare = of ? undefined :
|
|
1133
|
+
toDeclare = of ? undefined : instanceSizeToDeclare(asset);
|
|
1121
1134
|
}
|
|
1122
1135
|
catch {
|
|
1123
1136
|
toDeclare = undefined;
|
|
@@ -1125,52 +1138,57 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1125
1138
|
return { of, toDeclare };
|
|
1126
1139
|
});
|
|
1127
1140
|
const range = measured.of;
|
|
1128
|
-
/*
|
|
1141
|
+
/*
|
|
1142
|
+
In words a person reads (user 2026-10-19/20): what the figure does on a drawing, not the inputs behind it. Fixed,
|
|
1143
|
+
it says the size it is always drawn at; resizable, the sizes it may take on each axis and the parts that change
|
|
1144
|
+
with it. The way to it stays one press -- a figure no part follows (a robot scaled whole) reaches the one-ratio
|
|
1145
|
+
rule only from here -- and the help mark says how parts are made to follow.
|
|
1146
|
+
*/
|
|
1147
|
+
const mm = (v) => Math.round(v).toLocaleString();
|
|
1129
1148
|
const toDeclare = measured.toDeclare;
|
|
1149
|
+
const title = (explained) => html `<h3>${i18next.t('figure.label.board-size')}${this.help.button(explained)}</h3>${this.help.hint(explained)}`;
|
|
1130
1150
|
if (!range && toDeclare)
|
|
1131
|
-
return html `<section>
|
|
1132
|
-
|
|
1133
|
-
<p
|
|
1134
|
-
<button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size',
|
|
1135
|
-
<md-icon>
|
|
1151
|
+
return html `<section board-size>
|
|
1152
|
+
${title('figure.text.board-size-fixed-explained')}
|
|
1153
|
+
<p fixed-size>${i18next.t('figure.text.board-size-fixed', { x: mm(toDeclare.at.x), y: mm(toDeclare.at.y), z: mm(toDeclare.at.z) })}</p>
|
|
1154
|
+
<button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size', at: toDeclare.at })}>
|
|
1155
|
+
<md-icon>open_in_full</md-icon>${i18next.t('figure.button.declare-instance-size')}
|
|
1136
1156
|
</button>
|
|
1137
1157
|
</section>`;
|
|
1138
1158
|
if (!range)
|
|
1139
1159
|
return nothing;
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1160
|
+
const followers = sizeFollowersOf(asset);
|
|
1161
|
+
/*
|
|
1162
|
+
An end the author typed shows its number; an open end is empty, with what the parts allow written faintly in it
|
|
1163
|
+
(ADR-0087 decision 6). Typing narrows that end, emptying the field opens it again.
|
|
1164
|
+
*/
|
|
1165
|
+
const shownEnd = (r, bound) => (bound === 'max' && !Number.isFinite(r.max) ? '∞' : mm(r[bound]));
|
|
1166
|
+
return html `<section board-size>
|
|
1167
|
+
${title('figure.text.board-size-range-explained')}
|
|
1144
1168
|
${['x', 'y', 'z'].map(axis => html `<div inline-field>
|
|
1145
|
-
<label>${
|
|
1146
|
-
<div pair>
|
|
1147
|
-
${['min', 'max'].map(bound => html `<input
|
|
1169
|
+
<label>${i18next.t('figure.label.resize-' + axis)} <span unit>mm</span></label>
|
|
1170
|
+
<div pair range>
|
|
1171
|
+
${['min', 'max'].map((bound, i) => html `${i ? html `<span dash aria-hidden="true">–</span>` : nothing}<input
|
|
1148
1172
|
aria-label=${`size.${axis}.${bound}`}
|
|
1149
1173
|
type="number"
|
|
1150
1174
|
step="any"
|
|
1151
|
-
.value=${live(String(Math.round(range
|
|
1175
|
+
.value=${live(range[axis].narrowed[bound] ? String(Math.round(range[axis][bound])) : '')}
|
|
1176
|
+
placeholder=${shownEnd(range[axis], bound)}
|
|
1152
1177
|
?disabled=${unavailable}
|
|
1153
1178
|
@change=${(e) => {
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
this.linkCommand({ kind: 'declare-instance-size', range: next, replace: true });
|
|
1179
|
+
const typed = e.target.valueAsNumber;
|
|
1180
|
+
this.linkCommand({ kind: 'declare-instance-size', range: rangeWith(range, axis, bound, typed), replace: true });
|
|
1157
1181
|
}}
|
|
1158
1182
|
/>`)}
|
|
1159
1183
|
</div>
|
|
1160
1184
|
</div>`)}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
})}
|
|
1169
|
-
</p>
|
|
1170
|
-
<button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size', range: range.measured, replace: true })}>
|
|
1171
|
-
<md-icon>straighten</md-icon>${i18next.t('figure.button.remeasure-range')}
|
|
1172
|
-
</button>`
|
|
1173
|
-
: nothing}
|
|
1185
|
+
<p followers>
|
|
1186
|
+
${followers.length
|
|
1187
|
+
? i18next.t('figure.text.board-size-followers', {
|
|
1188
|
+
parts: followers.length > 4 ? i18next.t('figure.text.board-size-followers-more', { first: followers.slice(0, 4).join(', '), rest: String(followers.length - 4) }) : followers.join(', ')
|
|
1189
|
+
})
|
|
1190
|
+
: i18next.t('figure.text.board-size-no-followers')}
|
|
1191
|
+
</p>
|
|
1174
1192
|
${this.renderSizingRule(asset, unavailable)}
|
|
1175
1193
|
</section>`;
|
|
1176
1194
|
}
|
|
@@ -1325,7 +1343,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1325
1343
|
const form = this.publishing?.part === part
|
|
1326
1344
|
? this.publishing
|
|
1327
1345
|
: listed && input
|
|
1328
|
-
? { part, label: listed.label, min: input.min, max: input.max }
|
|
1346
|
+
? /* A published input has both ends (figure-model checkPublicInput). */ { part, label: listed.label, min: input.min, max: input.max }
|
|
1329
1347
|
: undefined;
|
|
1330
1348
|
const open = !!form;
|
|
1331
1349
|
const edit = (key) => (e) => {
|
|
@@ -1504,46 +1522,51 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1504
1522
|
this.openFastening();
|
|
1505
1523
|
}
|
|
1506
1524
|
openFastening() {
|
|
1507
|
-
if (!this.selected || this.
|
|
1525
|
+
if (!this.selected || this.fastenPopup)
|
|
1508
1526
|
return;
|
|
1509
1527
|
this.fastenError = '';
|
|
1510
1528
|
this.fastenHover = null;
|
|
1511
|
-
/* One panel over the side at a time: the view or the history gives way to the window. */
|
|
1512
|
-
this.overlay = undefined;
|
|
1513
1529
|
this.fastening = true;
|
|
1530
|
+
const content = document.createElement('figure-fasten-dialog');
|
|
1531
|
+
content.addEventListener('preview-command', e => this.commandPreview.ask(e.detail));
|
|
1532
|
+
content.addEventListener('fasten-hover', e => this.hoverFastening(e.detail));
|
|
1533
|
+
content.addEventListener('fasten-apply', e => this.applyFastening(e.detail.action));
|
|
1534
|
+
content.addEventListener('fasten-cancel', () => this.closeFastening());
|
|
1535
|
+
this.fastenWindow = content;
|
|
1536
|
+
this.tellFastening();
|
|
1537
|
+
/* No backdrop: the canvas stays reachable while it is open -- a part pressed there is aimed at (ADR-0140 stage 4). */
|
|
1538
|
+
const popup = openPopup(html `${content}`, { title: i18next.t('figure.label.fastening'), size: 'fit', backdrop: false, movable: true, closable: true, escapable: true });
|
|
1539
|
+
this.fastenPopup = popup;
|
|
1540
|
+
/* Closed by its own X or Esc as well: the editor lets go of it the same way. */
|
|
1541
|
+
popup.result.then(() => this.fastenPopup === popup && this.fastenClosed());
|
|
1514
1542
|
}
|
|
1515
1543
|
closeFastening() {
|
|
1544
|
+
const popup = this.fastenPopup;
|
|
1545
|
+
this.fastenClosed();
|
|
1546
|
+
popup?.close();
|
|
1547
|
+
}
|
|
1548
|
+
fastenClosed() {
|
|
1549
|
+
this.fastenPopup = undefined;
|
|
1550
|
+
this.fastenWindow = undefined;
|
|
1516
1551
|
this.fastening = false;
|
|
1517
1552
|
this.fastenError = '';
|
|
1518
1553
|
this.fastenHover = null;
|
|
1519
1554
|
}
|
|
1520
|
-
/**
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
overlay-body
|
|
1536
|
-
.revision=${this.shown()}
|
|
1537
|
-
.partId=${this.selected}
|
|
1538
|
-
?locked=${unavailable}
|
|
1539
|
-
.previewWords=${this.commandPreview.words()}
|
|
1540
|
-
.error=${this.fastenError}
|
|
1541
|
-
@preview-command=${(e) => this.commandPreview.ask(e.detail)}
|
|
1542
|
-
@fasten-hover=${(e) => this.hoverFastening(e.detail)}
|
|
1543
|
-
@fasten-apply=${(e) => this.applyFastening(e.detail.action)}
|
|
1544
|
-
@fasten-cancel=${() => this.closeFastening()}
|
|
1545
|
-
></figure-fasten-dialog>
|
|
1546
|
-
</div>`;
|
|
1555
|
+
/** What the window shows is the editor's: the figure on screen, the part, and what the preview and a refusal said. */
|
|
1556
|
+
tellFastening() {
|
|
1557
|
+
const content = this.fastenWindow;
|
|
1558
|
+
const view = this.session?.read();
|
|
1559
|
+
if (!content || !view)
|
|
1560
|
+
return;
|
|
1561
|
+
content.revision = this.shown();
|
|
1562
|
+
content.partId = this.selected;
|
|
1563
|
+
content.locked = view.loading || !!view.error || view.saving || !view.context;
|
|
1564
|
+
content.previewWords = this.commandPreview.words();
|
|
1565
|
+
content.error = this.fastenError;
|
|
1566
|
+
}
|
|
1567
|
+
disconnectedCallback() {
|
|
1568
|
+
super.disconnectedCallback();
|
|
1569
|
+
this.closeFastening();
|
|
1547
1570
|
}
|
|
1548
1571
|
/** 「적용」: the command, one step; refused, the window stays open and says why. */
|
|
1549
1572
|
applyFastening(action) {
|
|
@@ -1606,15 +1629,16 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1606
1629
|
return this.shown()?.asset;
|
|
1607
1630
|
}
|
|
1608
1631
|
/** A side tab says what it is in words under its icon: the icons alone had to be learnt (designer 2026-09-27, U05). */
|
|
1609
|
-
renderTab(tab, icon, label
|
|
1632
|
+
renderTab(tab, icon, label) {
|
|
1610
1633
|
/* Its accessible name is the name it shows (designer 2026-09-27); the longer title stays as its tooltip. */
|
|
1611
1634
|
const name = i18next.t('figure.label.tab-short-' + tab);
|
|
1612
1635
|
return html `<button tab ?on=${this.tab === tab} title=${label} aria-label=${name} @click=${() => (this.tab = tab)}>
|
|
1613
|
-
<md-icon>${icon}</md-icon><span tab-label aria-hidden="true">${name}</span
|
|
1636
|
+
<md-icon>${icon}</md-icon><span tab-label aria-hidden="true">${name}</span>
|
|
1614
1637
|
</button>`;
|
|
1615
1638
|
}
|
|
1616
1639
|
updated(changed) {
|
|
1617
1640
|
super.updated?.(changed);
|
|
1641
|
+
this.tellFastening();
|
|
1618
1642
|
/* Nothing blocks release any more: the list under the bar closes, and opens fresh when something does again. */
|
|
1619
1643
|
if (changed.has('releaseBlockers') && !this.releaseBlockers.length && this.blockersOpen)
|
|
1620
1644
|
this.blockersOpen = false;
|
|
@@ -1641,7 +1665,8 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1641
1665
|
return nothing;
|
|
1642
1666
|
const asset = this.asset();
|
|
1643
1667
|
const measures = figureSectionMeasuresOf(asset, this.selected);
|
|
1644
|
-
|
|
1668
|
+
/* What a measure typed here keeps: the part's middle, or its fastening (ADR-0140 4.1). */
|
|
1669
|
+
const kept = keptBySizeOf(asset, this.selected);
|
|
1645
1670
|
return html `<figure-section-editor
|
|
1646
1671
|
.body=${{ ...part, id: this.selected }}
|
|
1647
1672
|
.measures=${measures}
|
|
@@ -1793,13 +1818,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1793
1818
|
held = undefined;
|
|
1794
1819
|
}
|
|
1795
1820
|
const roomFields = own.filter(i => roomOnly.includes(i.id));
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
: 'figure.text.saved';
|
|
1821
|
+
/*
|
|
1822
|
+
Only what is under way is said. Saved or not is the save button's: filled and pressable when there is something
|
|
1823
|
+
to save, dimmed when not -- the saved and unsaved words beside it said the same thing again in the primary
|
|
1824
|
+
colour, read as a button (screen copy rule: a state is the control's).
|
|
1825
|
+
*/
|
|
1826
|
+
const status = view.loading ? 'figure.text.loading-figure' : view.saving ? 'figure.text.saving-figure' : '';
|
|
1803
1827
|
/*
|
|
1804
1828
|
A figure with no parts yet is not one that failed: what blocks its release is said once it has something to
|
|
1805
1829
|
release (first 15 minutes, 2026-10-16 evening -- red from the first click read as a failure). The verdict tab
|
|
@@ -1825,7 +1849,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1825
1849
|
${['edit', 'preview'].map(mode => html `<button slot="mode" mode aria-pressed=${String(this.middle === mode)} title=${i18next.t('figure.text.mode-' + mode + '-explained')} @click=${() => (this.middle = mode)}>
|
|
1826
1850
|
${i18next.t('figure.label.mode-' + mode)}
|
|
1827
1851
|
</button>`)}
|
|
1828
|
-
|
|
1852
|
+
${status ? html `<span slot="status" class="status" role="status">${i18next.t(status)}</span>` : nothing}
|
|
1829
1853
|
<span slot="status" release-state>${this.releaseWords(view)}</span>
|
|
1830
1854
|
${blockers.length
|
|
1831
1855
|
? html `<button slot="commit" release-blocked aria-expanded=${String(this.blockersOpen)} @click=${() => (this.blockersOpen = !this.blockersOpen)}>
|
|
@@ -1907,7 +1931,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1907
1931
|
<figure-canvas
|
|
1908
1932
|
.view=${this.view}
|
|
1909
1933
|
.parts=${this.drawnParts(view.geometry, live)}
|
|
1910
|
-
.anchors=${this.anchorsOf(asset)}
|
|
1911
1934
|
.fan=${this.fanOf(shown, live)}
|
|
1912
1935
|
.ghost=${this.previewShown().ghost}
|
|
1913
1936
|
.highlight=${this.previewShown().highlight}
|
|
@@ -1915,13 +1938,16 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1915
1938
|
.palette=${view.palette ?? {}}
|
|
1916
1939
|
.context=${view.context ?? ''}
|
|
1917
1940
|
.selected=${this.selected}
|
|
1941
|
+
?aiming=${this.fastening}
|
|
1918
1942
|
.reach=${unavailable ? { translate: false, rotate: false, scale: false } : this.reachOf(this.selected)}
|
|
1943
|
+
@part-sizing=${(e) => this.resizing(e.detail.id, e.detail.result)}
|
|
1919
1944
|
@part-transformed=${(e) => this.transformed(e.detail.id, e.detail.result)}
|
|
1920
1945
|
@part-selected=${(e) => this.choose(e.detail)}
|
|
1946
|
+
@part-aimed=${(e) => this.fastenWindow?.aimAt?.(e.detail)}
|
|
1921
1947
|
@render-error=${(e) => {
|
|
1922
1948
|
this.error = e.detail.message;
|
|
1923
1949
|
}}
|
|
1924
|
-
></figure-canvas
|
|
1950
|
+
></figure-canvas>${this.renderSameMeasure(unavailable)}</div>
|
|
1925
1951
|
<aside slot="end">
|
|
1926
1952
|
<div tabs>
|
|
1927
1953
|
${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
|
|
@@ -1929,7 +1955,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1929
1955
|
${this.renderTab('motion', 'animation', i18next.t('figure.label.motion'))}
|
|
1930
1956
|
${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}
|
|
1931
1957
|
${this.renderTab('data', 'sensors', i18next.t('figure.label.tab-data'))}
|
|
1932
|
-
${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity')
|
|
1958
|
+
${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'))}
|
|
1933
1959
|
</div>
|
|
1934
1960
|
<slot name="notice"></slot>
|
|
1935
1961
|
<div panel ?off=${tab !== 'properties'}>
|
|
@@ -2005,7 +2031,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2005
2031
|
<div panel flush ?off=${tab !== 'verdict'}><slot name="verdict"></slot></div>
|
|
2006
2032
|
</aside>
|
|
2007
2033
|
</ox-three-pane>
|
|
2008
|
-
${this.renderOverlay()}
|
|
2034
|
+
${this.renderOverlay()}`;
|
|
2009
2035
|
}
|
|
2010
2036
|
/** Position, rotation and size as rows of three cells; the shape's own measures one to a line, under their title. */
|
|
2011
2037
|
renderGroups(groups, vectorTitle, singleTitle, unavailable, tools = null, held) {
|
|
@@ -2076,6 +2102,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2076
2102
|
* rule" named nothing): a fastened part's place and turn are its fastening's; other numbers are worked out from
|
|
2077
2103
|
* something else. A size that follows something is said at the panel's head, so not again here.
|
|
2078
2104
|
*/
|
|
2105
|
+
/** What a size typed keeps, under the size fields: the part's middle, where it is fastened, or only its size (ADR-0140 4.1). */
|
|
2106
|
+
renderSizeKeeps() {
|
|
2107
|
+
const asset = this.asset();
|
|
2108
|
+
const kept = asset && this.selected ? keptBySizeOf(asset, this.selected) : undefined;
|
|
2109
|
+
return kept ? html `<p hint size-keeps>${i18next.t(SIZE_KEEPS_WORDS[kept])}</p>` : nothing;
|
|
2110
|
+
}
|
|
2079
2111
|
renderHeldAxes(vectors) {
|
|
2080
2112
|
const fastened = !!(this.asset())?.joints?.some(j => j.body1.part === ownerOf(this.selected));
|
|
2081
2113
|
const held = vectors.filter(g => g.kind === 'vector' && g.name !== 'size' && ['x', 'y', 'z'].some(axis => !g.cells[axis]));
|
|
@@ -2196,10 +2228,6 @@ __decorate([
|
|
|
2196
2228
|
property({ type: Number }),
|
|
2197
2229
|
__metadata("design:type", Object)
|
|
2198
2230
|
], FigureEditor.prototype, "revision", void 0);
|
|
2199
|
-
__decorate([
|
|
2200
|
-
property({ type: Number }),
|
|
2201
|
-
__metadata("design:type", Number)
|
|
2202
|
-
], FigureEditor.prototype, "instances", void 0);
|
|
2203
2231
|
__decorate([
|
|
2204
2232
|
property({ attribute: false }),
|
|
2205
2233
|
__metadata("design:type", Object)
|
|
@@ -2224,10 +2252,6 @@ __decorate([
|
|
|
2224
2252
|
state(),
|
|
2225
2253
|
__metadata("design:type", Object)
|
|
2226
2254
|
], FigureEditor.prototype, "faceMapping", void 0);
|
|
2227
|
-
__decorate([
|
|
2228
|
-
state(),
|
|
2229
|
-
__metadata("design:type", Object)
|
|
2230
|
-
], FigureEditor.prototype, "retry", void 0);
|
|
2231
2255
|
__decorate([
|
|
2232
2256
|
state(),
|
|
2233
2257
|
__metadata("design:type", String)
|
|
@@ -2284,6 +2308,10 @@ __decorate([
|
|
|
2284
2308
|
state(),
|
|
2285
2309
|
__metadata("design:type", Object)
|
|
2286
2310
|
], FigureEditor.prototype, "live", void 0);
|
|
2311
|
+
__decorate([
|
|
2312
|
+
state(),
|
|
2313
|
+
__metadata("design:type", Object)
|
|
2314
|
+
], FigureEditor.prototype, "sizing", void 0);
|
|
2287
2315
|
__decorate([
|
|
2288
2316
|
state(),
|
|
2289
2317
|
__metadata("design:type", Object)
|