@things-factory/figure-ui 10.1.152 → 10.1.153
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/ai-proposal.ts +14 -13
- package/client/modeller/command-preview-controller.ts +210 -0
- package/client/modeller/editor-styles.ts +307 -0
- package/client/modeller/editor.ts +58 -619
- package/client/modeller/figure-first-save.ts +9 -1
- package/client/modeller/modeller-keys.ts +60 -0
- package/client/modeller/pane-fold.ts +61 -0
- package/client/modeller/panel-memory.ts +53 -0
- package/client/modeller/refusal-teller.ts +93 -0
- package/client/modeller/refusals.ts +18 -1
- package/dist-client/modeller/ai-proposal.d.ts +6 -5
- package/dist-client/modeller/ai-proposal.js +9 -9
- package/dist-client/modeller/ai-proposal.js.map +1 -1
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/command-preview-controller.d.ts +75 -0
- package/dist-client/modeller/command-preview-controller.js +178 -0
- package/dist-client/modeller/command-preview-controller.js.map +1 -0
- package/dist-client/modeller/editor-styles.d.ts +2 -0
- package/dist-client/modeller/editor-styles.js +307 -0
- package/dist-client/modeller/editor-styles.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +5 -50
- package/dist-client/modeller/editor.js +69 -641
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-first-save.d.ts +1 -0
- package/dist-client/modeller/figure-first-save.js +8 -1
- package/dist-client/modeller/figure-first-save.js.map +1 -1
- package/dist-client/modeller/modeller-keys.d.ts +22 -0
- package/dist-client/modeller/modeller-keys.js +43 -0
- package/dist-client/modeller/modeller-keys.js.map +1 -0
- package/dist-client/modeller/pane-fold.d.ts +15 -0
- package/dist-client/modeller/pane-fold.js +60 -0
- package/dist-client/modeller/pane-fold.js.map +1 -0
- package/dist-client/modeller/panel-memory.d.ts +15 -0
- package/dist-client/modeller/panel-memory.js +54 -0
- package/dist-client/modeller/panel-memory.js.map +1 -0
- package/dist-client/modeller/refusal-teller.d.ts +36 -0
- package/dist-client/modeller/refusal-teller.js +81 -0
- package/dist-client/modeller/refusal-teller.js.map +1 -0
- package/dist-client/modeller/refusals.d.ts +10 -0
- package/dist-client/modeller/refusals.js +9 -0
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/browser/fix-alignment.check.mjs +13 -2
- package/test/browser/links.check.mjs +36 -9
- package/test/browser/modeller.check.mjs +25 -0
- package/test/browser/parts-page.js +9 -1
- package/test/browser/parts.check.mjs +22 -0
- package/test/browser/places-mesh.check.mjs +25 -9
- package/test/browser/product-page.check.mjs +42 -2
- package/test/browser/screen.check.mjs +33 -0
- package/test/clip-ai-proposal.test.ts +4 -1
- package/test/clip-consent.test.ts +9 -2
- package/test/layout.test.ts +3 -1
- package/test/refusals.test.ts +18 -10
- package/test/theme.test.ts +5 -15
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
var FigureEditor_1;
|
|
2
2
|
import { __decorate, __metadata } from "tslib";
|
|
3
|
-
import { LitElement, html,
|
|
3
|
+
import { LitElement, html, nothing } from 'lit';
|
|
4
4
|
import { i18next } from '@operato/i18n';
|
|
5
5
|
import '@operato/layout';
|
|
6
|
-
import {
|
|
6
|
+
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 { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditChanges, figureScaleAnchorOf, figureSectionMeasuresOf, figurePlaneAlignChanges,
|
|
10
|
+
import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditChanges, figureScaleAnchorOf, 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';
|
|
@@ -16,7 +16,8 @@ import './figure-parts.js';
|
|
|
16
16
|
import './figure-material-panel.js';
|
|
17
17
|
import './figure-section-panel.js';
|
|
18
18
|
import './figure-section-editor.js';
|
|
19
|
-
import {
|
|
19
|
+
import { rowWordsOf } from './command-preview.js';
|
|
20
|
+
import { CommandPreview, NO_WORDS } from './command-preview-controller.js';
|
|
20
21
|
import { motionEndsOf } from './motion-ends.js';
|
|
21
22
|
import './figure-links.js';
|
|
22
23
|
import './figure-controls.js';
|
|
@@ -26,9 +27,13 @@ import './figure-settings.js';
|
|
|
26
27
|
import { DEFAULT_VIEW } from './figure-view.js';
|
|
27
28
|
import './figure-capability-panel.js';
|
|
28
29
|
import { gizmoChanges, gizmoReachOf } from './gizmo.js';
|
|
29
|
-
import {
|
|
30
|
+
import { ModellerKeys } from './modeller-keys.js';
|
|
31
|
+
import { PaneFold } from './pane-fold.js';
|
|
32
|
+
import { PanelMemory } from './panel-memory.js';
|
|
33
|
+
import { RefusalTeller } from './refusal-teller.js';
|
|
30
34
|
import { groupFields, ownerOf, partLabelOf, repeatOf } from './fields.js';
|
|
31
35
|
import { SidePanelStyles } from './side-panel-styles.js';
|
|
36
|
+
import { EditorStyles } from './editor-styles.js';
|
|
32
37
|
import { ScrollbarStyles } from '@operato/styles';
|
|
33
38
|
import { alignChanges, canMirror, mirrorChanges } from './align.js';
|
|
34
39
|
import { paletteRecipe } from './palette.js';
|
|
@@ -44,28 +49,12 @@ import { roomRefitRowsOf } from './occupancy.js';
|
|
|
44
49
|
import { editRefusalOf, inDeclaredRoom, narrowingRetryOf, partNameRefusalOf, partRefusalOf, placedRefusalOf, recipeRefusalOf, removalNoticeOf } from './refusals.js';
|
|
45
50
|
import { endPlaceKeyOf, fieldWordsOf, followOf, measureNow, partSummaryOf, instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './links.js';
|
|
46
51
|
import { jointFanOf } from './joint-fan.js';
|
|
47
|
-
import {
|
|
52
|
+
import { derivedOf } from './revision.js';
|
|
48
53
|
import { jointDragOf } from './joint-drag.js';
|
|
49
54
|
import { liveOverSession, NOTHING_LIVE, scrubbed, scrubStep } from './live.js';
|
|
50
55
|
import { startingShapeKindOfBody } from './starting-shape.js';
|
|
51
|
-
/** Whether the narrow fold was said in this browser before; true from now on. Storage refused: said, as before. */
|
|
52
|
-
function foldSaid() {
|
|
53
|
-
try {
|
|
54
|
-
if (localStorage.getItem('figure-fold-said'))
|
|
55
|
-
return true;
|
|
56
|
-
localStorage.setItem('figure-fold-said', '1');
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
/* No storage here: said every time, as it was. */
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
56
|
/** Product editor surface. The injected session is shared by page persistence and AI commands. */
|
|
64
|
-
/** The words a panel is given when there are none: one array, so it does not read as a change. */
|
|
65
|
-
const NO_WORDS = [];
|
|
66
57
|
const NO_OVERHANG = { words: NO_WORDS, note: NO_WORDS };
|
|
67
|
-
/** How long a command's candidate rests before its preview is worked out. */
|
|
68
|
-
const PREVIEW_SETTLE_MS = 150;
|
|
69
58
|
let FigureEditor = class FigureEditor extends LitElement {
|
|
70
59
|
constructor() {
|
|
71
60
|
super(...arguments);
|
|
@@ -99,58 +88,36 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
99
88
|
/** The part whose drawn section is open in the section editor (ADR-0100 §3.2), or null. */
|
|
100
89
|
this.sectionEditing = null;
|
|
101
90
|
this.lastContext = null;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.duplicatePart(this.selected);
|
|
133
|
-
else if (action === 'remove')
|
|
134
|
-
this.removePart(this.selected);
|
|
135
|
-
else
|
|
136
|
-
this.selected = '';
|
|
137
|
-
};
|
|
138
|
-
this.foldedForWidth = false;
|
|
139
|
-
this.keptOpenByAuthor = false;
|
|
140
|
-
this.widthWatch = new ResizeObserver(([entry]) => this.fitPanesTo(entry.contentRect.width));
|
|
141
|
-
/**
|
|
142
|
-
* Refusals and notices go to the app's shared surfaces, as every screen's do (user, 2026-09-27: 「왜 모두가 사용하는
|
|
143
|
-
* notification 오류 표현 방법을 사용하지 않고」 · 「혹은 oxprompt 에서 확인하고 진행하던지」). A refusal with a way on --
|
|
144
|
-
* delete and fit the space again, narrow the range -- asks in the app's prompt and goes on when confirmed; any
|
|
145
|
-
* other refusal is the app's notification, which stays until it is closed.
|
|
146
|
-
*/
|
|
147
|
-
this.toldViewError = '';
|
|
148
|
-
/*
|
|
149
|
-
Per model and per part, how far its properties panel was scrolled and which of its folds were open, so going back
|
|
150
|
-
to one finds it as it was left (designer 2026-09-27, B.1). The path stays held at the top, so a scroll kept cannot
|
|
151
|
-
hide which part the panel speaks of. Another model starts clean.
|
|
152
|
-
*/
|
|
153
|
-
this.panelMemory = new Map();
|
|
91
|
+
this.commandPreview = new CommandPreview(this, {
|
|
92
|
+
session: () => this.session,
|
|
93
|
+
selected: () => this.selected,
|
|
94
|
+
shown: () => this.shown(),
|
|
95
|
+
formsInSight: () => this.middle === 'edit' && this.tab === 'properties',
|
|
96
|
+
said: error => this.said(error)
|
|
97
|
+
});
|
|
98
|
+
this.keys = new ModellerKeys(this, {
|
|
99
|
+
taking: () => {
|
|
100
|
+
const view = this.session?.read();
|
|
101
|
+
/*
|
|
102
|
+
* The section editor open takes the keys for its own: Esc cancels it, Delete deletes a point, Ctrl+Z undoes a point.
|
|
103
|
+
* Heard here first, they deselected the part, deleted the part and undid the figure's last step (2026-09-30).
|
|
104
|
+
*/
|
|
105
|
+
return !!view && !view.loading && !view.saving && !!view.context && this.isConnected && !this.sectionEditing;
|
|
106
|
+
},
|
|
107
|
+
scene: () => this.renderRoot.querySelector('figure-canvas'),
|
|
108
|
+
selected: () => this.selected,
|
|
109
|
+
act: action => this.keyAction(action)
|
|
110
|
+
});
|
|
111
|
+
this.paneFold = new PaneFold(this, () => this.panes);
|
|
112
|
+
this.refusals = new RefusalTeller({
|
|
113
|
+
session: () => this.session,
|
|
114
|
+
error: () => this.error,
|
|
115
|
+
notice: () => this.notice,
|
|
116
|
+
retry: () => this.retry,
|
|
117
|
+
refuse: message => (this.error = message),
|
|
118
|
+
command: action => this.linkCommand(action)
|
|
119
|
+
});
|
|
120
|
+
this.panelMemory = new PanelMemory(() => this.renderRoot);
|
|
154
121
|
/** The preview is made when first asked for, and kept (hidden) after that; it is taken down with the editor. */
|
|
155
122
|
this.previewMounted = false;
|
|
156
123
|
this.lastAnchors = {};
|
|
@@ -163,310 +130,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
163
130
|
}
|
|
164
131
|
static { FigureEditor_1 = this; }
|
|
165
132
|
/* The operato scrollbar, as every scrolling panel of the app has it -- the side panel scrolled with the browser's own. */
|
|
166
|
-
static { this.styles = [ScrollbarStyles, SidePanelStyles,
|
|
167
|
-
:host {
|
|
168
|
-
display: flex;
|
|
169
|
-
flex-direction: column;
|
|
170
|
-
min-height: 0;
|
|
171
|
-
height: 100%;
|
|
172
|
-
/* The editor folds by the width it is given, not the window's (UX ruling, 2026-09-24). */
|
|
173
|
-
container-type: inline-size;
|
|
174
|
-
font: 13px sans-serif;
|
|
175
|
-
color: var(--md-sys-color-on-surface);
|
|
176
|
-
}
|
|
177
|
-
/* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and
|
|
178
|
-
fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */
|
|
179
|
-
ox-canvas-bar button[mode] {
|
|
180
|
-
height: 28px;
|
|
181
|
-
padding: 0 10px;
|
|
182
|
-
border-radius: 999em;
|
|
183
|
-
border-color: transparent;
|
|
184
|
-
background: none;
|
|
185
|
-
}
|
|
186
|
-
ox-canvas-bar button[mode][aria-pressed='true'] {
|
|
187
|
-
border-color: var(--md-sys-color-primary);
|
|
188
|
-
}
|
|
189
|
-
/* The gizmo's tools, drawn as the bar's own icon buttons (undo, redo, fit). */
|
|
190
|
-
ox-canvas-bar .gizmo-tools {
|
|
191
|
-
display: inline-flex;
|
|
192
|
-
gap: 2px;
|
|
193
|
-
padding-left: 4px;
|
|
194
|
-
border-left: 1px solid var(--md-sys-color-outline-variant);
|
|
195
|
-
}
|
|
196
|
-
ox-canvas-bar button[tool] {
|
|
197
|
-
display: inline-grid;
|
|
198
|
-
place-items: center;
|
|
199
|
-
width: 32px;
|
|
200
|
-
height: 32px;
|
|
201
|
-
padding: 0;
|
|
202
|
-
border: none;
|
|
203
|
-
border-radius: var(--md-sys-shape-corner-small, 8px);
|
|
204
|
-
background: transparent;
|
|
205
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
206
|
-
cursor: pointer;
|
|
207
|
-
}
|
|
208
|
-
ox-canvas-bar button[tool]:hover:not(:disabled) {
|
|
209
|
-
background: var(--md-sys-color-surface-container-high);
|
|
210
|
-
color: var(--md-sys-color-on-surface);
|
|
211
|
-
}
|
|
212
|
-
ox-canvas-bar button[tool][aria-pressed='true'] {
|
|
213
|
-
background: var(--md-sys-color-secondary-container);
|
|
214
|
-
color: var(--md-sys-color-on-secondary-container);
|
|
215
|
-
}
|
|
216
|
-
ox-canvas-bar button[tool]:disabled {
|
|
217
|
-
opacity: 0.38;
|
|
218
|
-
cursor: default;
|
|
219
|
-
}
|
|
220
|
-
/*
|
|
221
|
-
Slotted into the bar from here, the tools do not get the bar's own icon size (20px, set inside its shadow): they
|
|
222
|
-
showed at md-icon's 24px, heavier than undo, redo and fit beside them. The same size, and the bar's focus ring.
|
|
223
|
-
*/
|
|
224
|
-
ox-canvas-bar button[tool] md-icon {
|
|
225
|
-
--md-icon-size: 20px;
|
|
226
|
-
}
|
|
227
|
-
ox-canvas-bar button[tool]:focus-visible {
|
|
228
|
-
outline: 2px solid var(--md-sys-color-primary);
|
|
229
|
-
outline-offset: 1px;
|
|
230
|
-
}
|
|
231
|
-
figure-preview {
|
|
232
|
-
min-width: 0;
|
|
233
|
-
min-height: 0;
|
|
234
|
-
}
|
|
235
|
-
/* The edit canvas stays standing while the preview is in front (a 3D scene costs a third of a second to make), out of sight. */
|
|
236
|
-
div[middle][hidden] {
|
|
237
|
-
display: none;
|
|
238
|
-
}
|
|
239
|
-
/* The preview, once made, stays standing out of sight too: its 3D scene is not made again each time it is asked for. */
|
|
240
|
-
figure-preview[hidden] {
|
|
241
|
-
display: none;
|
|
242
|
-
}
|
|
243
|
-
ox-canvas-bar .status {
|
|
244
|
-
padding: 0 8px;
|
|
245
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
246
|
-
}
|
|
247
|
-
ox-canvas-bar .status[dirty] {
|
|
248
|
-
color: var(--md-sys-color-primary);
|
|
249
|
-
font-weight: 600;
|
|
250
|
-
}
|
|
251
|
-
ox-canvas-bar button[save] {
|
|
252
|
-
height: 32px;
|
|
253
|
-
padding: 0 14px;
|
|
254
|
-
border: none;
|
|
255
|
-
border-radius: 999em;
|
|
256
|
-
color: var(--md-sys-color-on-primary);
|
|
257
|
-
background: var(--md-sys-color-primary);
|
|
258
|
-
}
|
|
259
|
-
/* The three panes are ox-three-pane's (ADR-0060 decision 8): widths 200 | 1fr | 340, 160 | 300 under
|
|
260
|
-
860px and one column with the middle first under 560px are the frame's, as are the dividers. */
|
|
261
|
-
ox-three-pane {
|
|
262
|
-
flex: 1;
|
|
263
|
-
min-height: 0;
|
|
264
|
-
}
|
|
265
|
-
figure-parts {
|
|
266
|
-
min-height: 0;
|
|
267
|
-
}
|
|
268
|
-
figure-canvas {
|
|
269
|
-
flex: 1;
|
|
270
|
-
min-width: 0;
|
|
271
|
-
min-height: 0;
|
|
272
|
-
}
|
|
273
|
-
div[middle] {
|
|
274
|
-
position: relative;
|
|
275
|
-
display: flex;
|
|
276
|
-
min-width: 0;
|
|
277
|
-
min-height: 0;
|
|
278
|
-
}
|
|
279
|
-
/* A new figure's first question, over the empty canvas. */
|
|
280
|
-
div[start] {
|
|
281
|
-
position: absolute;
|
|
282
|
-
inset: 50% auto auto 50%;
|
|
283
|
-
z-index: 10;
|
|
284
|
-
color: var(--md-sys-color-on-surface);
|
|
285
|
-
transform: translate(-50%, -50%);
|
|
286
|
-
width: min(440px, calc(100% - 32px));
|
|
287
|
-
box-sizing: border-box;
|
|
288
|
-
padding: 20px;
|
|
289
|
-
border-radius: 12px;
|
|
290
|
-
background: var(--md-sys-color-surface, #fff);
|
|
291
|
-
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
|
|
292
|
-
}
|
|
293
|
-
div[start] button[start-button] {
|
|
294
|
-
width: auto;
|
|
295
|
-
margin: 0;
|
|
296
|
-
font-size: var(--ui-t-body);
|
|
297
|
-
height: 36px;
|
|
298
|
-
padding: 0 16px;
|
|
299
|
-
border: 1px solid var(--md-sys-color-outline);
|
|
300
|
-
border-radius: 999em;
|
|
301
|
-
color: var(--md-sys-color-primary);
|
|
302
|
-
background: var(--md-sys-color-surface, #fff);
|
|
303
|
-
cursor: pointer;
|
|
304
|
-
}
|
|
305
|
-
div[start] button[start-button]:hover:not(:disabled) {
|
|
306
|
-
background: var(--md-sys-color-primary-container);
|
|
307
|
-
}
|
|
308
|
-
div[start] button[start-button][pressed] {
|
|
309
|
-
color: var(--md-sys-color-on-primary);
|
|
310
|
-
background: var(--md-sys-color-primary);
|
|
311
|
-
border-color: var(--md-sys-color-primary);
|
|
312
|
-
}
|
|
313
|
-
div[start] button[start-button]:disabled {
|
|
314
|
-
opacity: 0.5;
|
|
315
|
-
cursor: default;
|
|
316
|
-
}
|
|
317
|
-
div[start] h2 {
|
|
318
|
-
margin: 0 0 12px;
|
|
319
|
-
font-size: var(--ui-t-title);
|
|
320
|
-
}
|
|
321
|
-
div[start-paths] {
|
|
322
|
-
display: grid;
|
|
323
|
-
gap: 14px;
|
|
324
|
-
}
|
|
325
|
-
div[start-paths] h3 {
|
|
326
|
-
margin: 0 0 6px;
|
|
327
|
-
font-size: var(--ui-t-body);
|
|
328
|
-
}
|
|
329
|
-
div[start-shapes] {
|
|
330
|
-
display: flex;
|
|
331
|
-
gap: 8px;
|
|
332
|
-
flex-wrap: wrap;
|
|
333
|
-
}
|
|
334
|
-
div[start] small {
|
|
335
|
-
display: block;
|
|
336
|
-
margin-top: 4px;
|
|
337
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
338
|
-
}
|
|
339
|
-
/* The side: tabs over one panel at a time, each using the whole height. */
|
|
340
|
-
aside {
|
|
341
|
-
display: flex;
|
|
342
|
-
flex-direction: column;
|
|
343
|
-
min-height: 0;
|
|
344
|
-
background: var(--md-sys-color-surface-container-lowest);
|
|
345
|
-
}
|
|
346
|
-
div[tabs] {
|
|
347
|
-
display: flex;
|
|
348
|
-
flex: none;
|
|
349
|
-
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
350
|
-
}
|
|
351
|
-
button[tab] {
|
|
352
|
-
flex: 1;
|
|
353
|
-
position: relative;
|
|
354
|
-
display: flex;
|
|
355
|
-
flex-direction: column;
|
|
356
|
-
align-items: center;
|
|
357
|
-
justify-content: center;
|
|
358
|
-
gap: 1px;
|
|
359
|
-
min-width: 0;
|
|
360
|
-
height: 48px;
|
|
361
|
-
padding: 0;
|
|
362
|
-
border: none;
|
|
363
|
-
border-radius: 0;
|
|
364
|
-
border-bottom: 2px solid transparent;
|
|
365
|
-
background: none;
|
|
366
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
367
|
-
}
|
|
368
|
-
button[tab]:hover:not([on]) {
|
|
369
|
-
background: var(--md-sys-color-surface-container);
|
|
370
|
-
}
|
|
371
|
-
button[tab][on] {
|
|
372
|
-
color: var(--md-sys-color-primary);
|
|
373
|
-
border-bottom-color: var(--md-sys-color-primary);
|
|
374
|
-
}
|
|
375
|
-
button[tab] md-icon {
|
|
376
|
-
font-size: 20px;
|
|
377
|
-
width: 20px;
|
|
378
|
-
height: 20px;
|
|
379
|
-
}
|
|
380
|
-
button[tab] span[tab-label] {
|
|
381
|
-
max-width: 100%;
|
|
382
|
-
overflow: hidden;
|
|
383
|
-
font-size: 0.66rem;
|
|
384
|
-
line-height: 1.2;
|
|
385
|
-
white-space: nowrap;
|
|
386
|
-
text-overflow: ellipsis;
|
|
387
|
-
}
|
|
388
|
-
span[badge] {
|
|
389
|
-
position: absolute;
|
|
390
|
-
top: 3px;
|
|
391
|
-
right: 2px;
|
|
392
|
-
padding: 0 5px;
|
|
393
|
-
font-size: 0.62rem;
|
|
394
|
-
font-weight: 700;
|
|
395
|
-
line-height: 1.5;
|
|
396
|
-
color: var(--md-sys-color-on-primary-container);
|
|
397
|
-
background: var(--md-sys-color-primary-container);
|
|
398
|
-
border-radius: 999em;
|
|
399
|
-
pointer-events: none;
|
|
400
|
-
}
|
|
401
|
-
/* The sections inside carry the margins (side-panel-styles); the panel only scrolls. */
|
|
402
|
-
div[panel] {
|
|
403
|
-
flex: 1;
|
|
404
|
-
min-height: 0;
|
|
405
|
-
overflow: auto;
|
|
406
|
-
padding: 0;
|
|
407
|
-
}
|
|
408
|
-
div[panel][flush] {
|
|
409
|
-
padding: 0;
|
|
410
|
-
}
|
|
411
|
-
/* Hidden, not removed: a panel keeps what it holds (the verdict its last answer) while another tab is open. */
|
|
412
|
-
[off] {
|
|
413
|
-
display: none !important;
|
|
414
|
-
}
|
|
415
|
-
/* Which part the panel speaks of. */
|
|
416
|
-
div[subject] {
|
|
417
|
-
position: sticky;
|
|
418
|
-
top: 0;
|
|
419
|
-
z-index: 1;
|
|
420
|
-
display: grid;
|
|
421
|
-
gap: 4px;
|
|
422
|
-
padding: 10px 12px;
|
|
423
|
-
background: var(--md-sys-color-surface-container-lowest);
|
|
424
|
-
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
425
|
-
}
|
|
426
|
-
/* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */
|
|
427
|
-
div[subject-summary] {
|
|
428
|
-
display: grid;
|
|
429
|
-
gap: 2px;
|
|
430
|
-
}
|
|
431
|
-
div[subject-summary] b {
|
|
432
|
-
font-size: var(--ui-t-body, 13px);
|
|
433
|
-
font-weight: 600;
|
|
434
|
-
}
|
|
435
|
-
div[subject-summary] b span {
|
|
436
|
-
font-weight: 400;
|
|
437
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
438
|
-
}
|
|
439
|
-
div[subject-summary] small {
|
|
440
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
441
|
-
}
|
|
442
|
-
button {
|
|
443
|
-
font: inherit;
|
|
444
|
-
box-sizing: border-box;
|
|
445
|
-
height: 32px;
|
|
446
|
-
border: 1px solid var(--md-sys-color-outline-variant);
|
|
447
|
-
border-radius: 6px;
|
|
448
|
-
background: var(--md-sys-color-surface);
|
|
449
|
-
color: inherit;
|
|
450
|
-
padding: 0 8px;
|
|
451
|
-
min-width: 0;
|
|
452
|
-
}
|
|
453
|
-
button {
|
|
454
|
-
cursor: pointer;
|
|
455
|
-
}
|
|
456
|
-
button:disabled {
|
|
457
|
-
opacity: 0.45;
|
|
458
|
-
cursor: default;
|
|
459
|
-
}
|
|
460
|
-
button[aria-pressed='true'] {
|
|
461
|
-
background: var(--md-sys-color-secondary-container);
|
|
462
|
-
color: var(--md-sys-color-on-secondary-container);
|
|
463
|
-
border-color: var(--md-sys-color-primary);
|
|
464
|
-
}
|
|
465
|
-
.hint {
|
|
466
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
467
|
-
line-height: 1.5;
|
|
468
|
-
}
|
|
469
|
-
`]; }
|
|
133
|
+
static { this.styles = [ScrollbarStyles, SidePanelStyles, EditorStyles]; }
|
|
470
134
|
static storedView() {
|
|
471
135
|
try {
|
|
472
136
|
const stored = JSON.parse(localStorage.getItem('figure-view') ?? 'null');
|
|
@@ -510,115 +174,21 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
510
174
|
</button>`)}
|
|
511
175
|
</span>`;
|
|
512
176
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
Narrow, the parts list gives way to the canvas rather than all three shrinking (designer 2026-09-27, U11): under
|
|
527
|
-
FOLD_START_UNDER px the canvas would be left under about 560 px beside both sides. The fold is said once, and the
|
|
528
|
-
bar's own toggle opens it again; opened by the author, it is not folded again until the screen has been wide.
|
|
529
|
-
Said once in this browser, not on every opening (2026-10-02): the list folds as before, the notice does not come back.
|
|
530
|
-
*/
|
|
531
|
-
static { this.FOLD_START_UNDER = 1100; }
|
|
532
|
-
fitPanesTo(width) {
|
|
533
|
-
const panes = this.panes;
|
|
534
|
-
if (!panes || !width)
|
|
535
|
-
return;
|
|
536
|
-
if (this.foldedForWidth && !panes.foldStart)
|
|
537
|
-
this.keptOpenByAuthor = true;
|
|
538
|
-
if (width < FigureEditor_1.FOLD_START_UNDER) {
|
|
539
|
-
if (!panes.foldStart && !this.keptOpenByAuthor) {
|
|
540
|
-
panes.foldStart = true;
|
|
541
|
-
this.foldedForWidth = true;
|
|
542
|
-
if (!foldSaid())
|
|
543
|
-
notify({ level: 'info', message: i18next.t('figure.text.parts-panel-folded-narrow') });
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
if (this.foldedForWidth && panes.foldStart)
|
|
548
|
-
panes.foldStart = false;
|
|
549
|
-
this.foldedForWidth = false;
|
|
550
|
-
this.keptOpenByAuthor = false;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
tell(changed) {
|
|
554
|
-
if (changed.has('error') && this.error) {
|
|
555
|
-
const retry = this.retry;
|
|
556
|
-
if (retry)
|
|
557
|
-
this.ask(this.error, retry);
|
|
558
|
-
else
|
|
559
|
-
notify({ level: 'error', message: this.error });
|
|
560
|
-
}
|
|
561
|
-
if (changed.has('notice') && this.notice)
|
|
562
|
-
notify({ level: 'info', message: this.notice });
|
|
563
|
-
const viewError = this.session?.read()?.error ?? '';
|
|
564
|
-
if (viewError && viewError !== this.toldViewError)
|
|
565
|
-
notify({ level: 'error', message: viewError });
|
|
566
|
-
this.toldViewError = viewError;
|
|
567
|
-
}
|
|
568
|
-
/*
|
|
569
|
-
An edit that would stop clips is refused by figure-model with the clips named (CLIPS_DROPPED). The person is told
|
|
570
|
-
which, and why, before anything is written; agreed, the same edit is made again letting exactly those go, as one
|
|
571
|
-
step undo brings back (designer 2026-09-27).
|
|
572
|
-
*/
|
|
573
|
-
askClipLoss(error, again) {
|
|
574
|
-
if (error?.code !== 'CLIPS_DROPPED')
|
|
575
|
-
return false;
|
|
576
|
-
const clips = error.clips;
|
|
577
|
-
/* The consent is for this edit, on this figure as it is now: held with it while the person reads. */
|
|
578
|
-
const session = this.session;
|
|
579
|
-
const { context, revision } = session.read();
|
|
580
|
-
void this.confirmClipLoss(clips, again, { session, context: context, revision: revision });
|
|
581
|
-
return true;
|
|
582
|
-
}
|
|
583
|
-
async confirmClipLoss(clips, again, asked) {
|
|
584
|
-
const agreed = await OxPrompt.open({
|
|
585
|
-
type: 'warning',
|
|
586
|
-
text: i18next.t('figure.text.clips-would-go', { clips: clips.map(c => i18next.t(`figure.text.clip-${c.why}`, { clip: c.clip })).join(', ') }),
|
|
587
|
-
confirmButton: { text: i18next.t('figure.button.let-clips-go') },
|
|
588
|
-
cancelButton: { text: i18next.t('figure.button.keep-editing') }
|
|
589
|
-
});
|
|
590
|
-
if (!agreed)
|
|
591
|
-
return;
|
|
592
|
-
/* Changed while the person read -- another figure, or an edit in between: the consent was not for this; nothing is applied. */
|
|
593
|
-
const now = this.session === asked.session ? asked.session.read() : undefined;
|
|
594
|
-
if (!now || now.context !== asked.context || now.revision !== asked.revision) {
|
|
595
|
-
this.error = i18next.t('figure.text.clips-consent-stale');
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
asked.session.withConsent({ dropClips: clips.map(c => c.id), context: asked.context, revision: asked.revision }, again);
|
|
599
|
-
}
|
|
600
|
-
async ask(message, retry) {
|
|
601
|
-
const agreed = await OxPrompt.open({
|
|
602
|
-
type: 'question',
|
|
603
|
-
text: message,
|
|
604
|
-
confirmButton: { text: retry.label },
|
|
605
|
-
cancelButton: { text: i18next.t('figure.button.keep-editing') }
|
|
606
|
-
});
|
|
607
|
-
if (!agreed)
|
|
608
|
-
return;
|
|
609
|
-
if (retry.run)
|
|
610
|
-
retry.run();
|
|
177
|
+
keyAction(action) {
|
|
178
|
+
const view = this.session.read();
|
|
179
|
+
const canvas = this.renderRoot.querySelector('figure-canvas');
|
|
180
|
+
if (action === 'translate' || action === 'rotate' || action === 'scale')
|
|
181
|
+
canvas && this.useGizmo(action);
|
|
182
|
+
else if (action === 'undo')
|
|
183
|
+
view.canUndo && this.run(() => this.session.undo());
|
|
184
|
+
else if (action === 'redo')
|
|
185
|
+
view.canRedo && this.run(() => this.session.redo());
|
|
186
|
+
else if (action === 'duplicate')
|
|
187
|
+
this.duplicatePart(this.selected);
|
|
188
|
+
else if (action === 'remove')
|
|
189
|
+
this.removePart(this.selected);
|
|
611
190
|
else
|
|
612
|
-
this.
|
|
613
|
-
}
|
|
614
|
-
propertiesPanel() {
|
|
615
|
-
return this.renderRoot.querySelector('div[panel]');
|
|
616
|
-
}
|
|
617
|
-
foldsOf(panel) {
|
|
618
|
-
const folds = [...panel.querySelectorAll('details')];
|
|
619
|
-
for (const inner of panel.querySelectorAll('figure-links'))
|
|
620
|
-
folds.push(...(inner.shadowRoot?.querySelectorAll('details') ?? []));
|
|
621
|
-
return folds;
|
|
191
|
+
this.selected = '';
|
|
622
192
|
}
|
|
623
193
|
willUpdate(_changed) {
|
|
624
194
|
if (this.middle === 'preview')
|
|
@@ -632,11 +202,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
632
202
|
this.panelMemory.clear();
|
|
633
203
|
}
|
|
634
204
|
else if (_changed.has('selected')) {
|
|
635
|
-
|
|
636
|
-
const was = _changed.get('selected') ?? '';
|
|
637
|
-
if (panel)
|
|
638
|
-
this.panelMemory.set(was || '@model', { top: panel.scrollTop, open: this.foldsOf(panel).map(d => d.open) });
|
|
639
|
-
this.restorePanelFor = this.selected || '@model';
|
|
205
|
+
this.panelMemory.leave(_changed.get('selected') ?? '', this.selected);
|
|
640
206
|
}
|
|
641
207
|
if (view && !view.geometry.some(g => ownerOf(g.id) === this.selected))
|
|
642
208
|
this.selected = '';
|
|
@@ -697,7 +263,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
697
263
|
this.changed();
|
|
698
264
|
}
|
|
699
265
|
catch (error) {
|
|
700
|
-
if (this.askClipLoss(error, () => this.run(action)))
|
|
266
|
+
if (this.refusals.askClipLoss(error, () => this.run(action)))
|
|
701
267
|
return;
|
|
702
268
|
this.error = this.said(error);
|
|
703
269
|
}
|
|
@@ -859,7 +425,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
859
425
|
this.changed();
|
|
860
426
|
}
|
|
861
427
|
catch (error) {
|
|
862
|
-
if (this.askClipLoss(error, () => this.removePart(id, refitRoom)))
|
|
428
|
+
if (this.refusals.askClipLoss(error, () => this.removePart(id, refitRoom)))
|
|
863
429
|
return;
|
|
864
430
|
/*
|
|
865
431
|
A part in the declared space goes, and the space is fitted to the parts left, in the same click: every space
|
|
@@ -1150,15 +716,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1150
716
|
this.error = partNameRefusalOf(error, (key, params) => i18next.t(key, params)) ?? this.said(error);
|
|
1151
717
|
}
|
|
1152
718
|
}
|
|
1153
|
-
|
|
1154
|
-
const preview = this.currentPreview();
|
|
1155
|
-
const language = i18next.language;
|
|
1156
|
-
if (this.previewWordsSaid && this.previewWordsSaid.preview === preview && this.previewWordsSaid.part === this.selected && this.previewWordsSaid.language === language)
|
|
1157
|
-
return this.previewWordsSaid.words;
|
|
1158
|
-
const words = previewWordsOf(preview, this.selected, (key, params) => i18next.t(key, params));
|
|
1159
|
-
this.previewWordsSaid = { preview, part: this.selected, language, words };
|
|
1160
|
-
return words;
|
|
1161
|
-
}
|
|
719
|
+
/** Size response, fastening and motion for the selected part, as authoring commands. */
|
|
1162
720
|
renderLinks(unavailable, section) {
|
|
1163
721
|
const shown = this.shown();
|
|
1164
722
|
if (!shown)
|
|
@@ -1169,13 +727,13 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1169
727
|
.partId=${this.selected}
|
|
1170
728
|
?locked=${unavailable}
|
|
1171
729
|
@command=${(e) => this.linkCommand(e.detail.action)}
|
|
1172
|
-
.previewWords=${this.
|
|
1173
|
-
.detachWords=${this.detachWords(shown)}
|
|
730
|
+
.previewWords=${this.commandPreview.words()}
|
|
731
|
+
.detachWords=${this.commandPreview.detachWords(shown)}
|
|
1174
732
|
.overhangWords=${section === 'shape' ? this.overhangOf(shown).words : NO_WORDS}
|
|
1175
733
|
.overhangNote=${section === 'shape' ? this.overhangOf(shown).note : NO_WORDS}
|
|
1176
734
|
.motionEnds=${section === 'motion' ? this.motionEnds() : null}
|
|
1177
735
|
@go-fasten=${() => this.goFasten()}
|
|
1178
|
-
@preview-command=${(e) => this.
|
|
736
|
+
@preview-command=${(e) => this.commandPreview.ask(e.detail)}
|
|
1179
737
|
@part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
|
|
1180
738
|
></figure-links>`;
|
|
1181
739
|
}
|
|
@@ -1256,7 +814,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1256
814
|
return true;
|
|
1257
815
|
}
|
|
1258
816
|
catch (error) {
|
|
1259
|
-
if (this.askClipLoss(error, () => this.linkCommand(action, from)))
|
|
817
|
+
if (this.refusals.askClipLoss(error, () => this.linkCommand(action, from)))
|
|
1260
818
|
return false;
|
|
1261
819
|
const code = error?.code;
|
|
1262
820
|
/* Something stands on a face this takes away: ask where it goes, there, rather than only saying no. */
|
|
@@ -1657,115 +1215,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1657
1215
|
return i18next.t('figure.text.release-state-released', { version: String(version) });
|
|
1658
1216
|
return version > 0 ? i18next.t('figure.text.release-state-draft-over', { version: String(version) }) : i18next.t('figure.text.release-state-draft-new');
|
|
1659
1217
|
}
|
|
1660
|
-
previewCommand(candidate) {
|
|
1661
|
-
clearTimeout(this.previewTimer);
|
|
1662
|
-
if (!candidate || !this.session || !this.selected) {
|
|
1663
|
-
this.preview = undefined;
|
|
1664
|
-
return;
|
|
1665
|
-
}
|
|
1666
|
-
const view = this.session.read();
|
|
1667
|
-
const key = JSON.stringify(candidate);
|
|
1668
|
-
const same = this.preview && this.preview.key === key && this.preview.context === view.context && this.preview.part === this.selected && 'revision' in this.preview && this.preview.revision === view.revision;
|
|
1669
|
-
if (same)
|
|
1670
|
-
return;
|
|
1671
|
-
/*
|
|
1672
|
-
What another command would do is not said of this one while this one is worked out: a row's pitch refused stayed
|
|
1673
|
-
on screen for the count chosen after it (:3300, 2026-10-11).
|
|
1674
|
-
*/
|
|
1675
|
-
if (this.preview && this.preview.key !== key)
|
|
1676
|
-
this.preview = undefined;
|
|
1677
|
-
this.previewTimer = setTimeout(() => this.computePreview(candidate), PREVIEW_SETTLE_MS);
|
|
1678
|
-
}
|
|
1679
|
-
computePreview(candidate) {
|
|
1680
|
-
if (!this.session || !this.selected)
|
|
1681
|
-
return;
|
|
1682
|
-
const view = this.session.read();
|
|
1683
|
-
const held = { context: view.context, revision: view.revision, key: JSON.stringify(candidate), part: this.selected };
|
|
1684
|
-
try {
|
|
1685
|
-
const seen = this.session.previewCommand(candidate.action);
|
|
1686
|
-
let face;
|
|
1687
|
-
if (candidate.face) {
|
|
1688
|
-
try {
|
|
1689
|
-
const shown = this.shown();
|
|
1690
|
-
face = figurePartFaceOf(shown.asset, candidate.face.part, candidate.face.face, shown.inputs);
|
|
1691
|
-
}
|
|
1692
|
-
catch {
|
|
1693
|
-
face = undefined;
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
/* A row is read on the figure the command would make, by figure-model, as drawn. */
|
|
1697
|
-
const row = candidate.action.kind === 'make-repeat' ? figureRowOf(seen.asset, this.selected, this.shown().inputs) : undefined;
|
|
1698
|
-
this.preview = {
|
|
1699
|
-
...held,
|
|
1700
|
-
context: seen.context,
|
|
1701
|
-
revision: seen.revision,
|
|
1702
|
-
geometry: seen.geometry,
|
|
1703
|
-
moves: seen.moves,
|
|
1704
|
-
added: seen.added,
|
|
1705
|
-
...(row ? { row } : {}),
|
|
1706
|
-
clipsWouldGo: seen.clipsWouldGo,
|
|
1707
|
-
face,
|
|
1708
|
-
faceUnknown: !!candidate.face && !face
|
|
1709
|
-
};
|
|
1710
|
-
}
|
|
1711
|
-
catch (error) {
|
|
1712
|
-
/* Refused: nothing of an earlier result stays on screen, and the reason is said. */
|
|
1713
|
-
this.preview = { ...held, refused: this.said(error) };
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
/** The preview, while it still speaks of what is on screen: this figure, this version, this part. */
|
|
1717
|
-
currentPreview() {
|
|
1718
|
-
const p = this.preview;
|
|
1719
|
-
const view = this.session?.read();
|
|
1720
|
-
/* Only where its form is: the properties panel stays in the page on another tab, and did not clear it (2026-09-27, :3391). */
|
|
1721
|
-
if (!p || !view || p.context !== view.context || p.revision !== view.revision || p.part !== this.selected || this.middle !== 'edit' || this.tab !== 'properties')
|
|
1722
|
-
return undefined;
|
|
1723
|
-
return p;
|
|
1724
|
-
}
|
|
1725
|
-
/**
|
|
1726
|
-
* Said once the figure has rested: working it out is a preview of the command on a copy (tens of milliseconds), and the
|
|
1727
|
-
* edit that made this version should be on screen before it. Until then nothing is said, as for a part with no joint.
|
|
1728
|
-
*/
|
|
1729
|
-
detachWords(shown) {
|
|
1730
|
-
const part = this.selected;
|
|
1731
|
-
if (!this.session || !shown.asset.joints?.some(j => j.body1.part === part))
|
|
1732
|
-
return NO_WORDS;
|
|
1733
|
-
const key = `detach|${part}`;
|
|
1734
|
-
const said = derivedHeld(shown, key);
|
|
1735
|
-
if (said)
|
|
1736
|
-
return said;
|
|
1737
|
-
if (this.detachAsked?.shown !== shown || this.detachAsked.part !== part) {
|
|
1738
|
-
this.detachAsked = { shown, part };
|
|
1739
|
-
clearTimeout(this.detachTimer);
|
|
1740
|
-
this.detachTimer = setTimeout(() => {
|
|
1741
|
-
if (this.shown() !== shown || this.selected !== part)
|
|
1742
|
-
return;
|
|
1743
|
-
derivedOf(shown, key, () => this.computeDetachWords(shown, part));
|
|
1744
|
-
this.requestUpdate();
|
|
1745
|
-
}, PREVIEW_SETTLE_MS);
|
|
1746
|
-
}
|
|
1747
|
-
return NO_WORDS;
|
|
1748
|
-
}
|
|
1749
|
-
computeDetachWords(shown, part) {
|
|
1750
|
-
const say = (k, params) => i18next.t(k, params);
|
|
1751
|
-
let words;
|
|
1752
|
-
try {
|
|
1753
|
-
const seen = this.session.previewCommand({ kind: 'detach', part });
|
|
1754
|
-
const own = seen.moves.some(m => m.body === part);
|
|
1755
|
-
words = own
|
|
1756
|
-
? [say('figure.text.detach-moves'), ...previewWordsOf({ context: seen.context, revision: seen.revision, key: `detach|${part}`, moves: seen.moves }, part, say)]
|
|
1757
|
-
: /* It stays where it is: as expected, so nothing is said (user 2026-10-01). */ [];
|
|
1758
|
-
const children = (shown.asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === part).map(j => partLabelOf(j.body1.part));
|
|
1759
|
-
if (children.length)
|
|
1760
|
-
words.push(say('figure.text.detach-keeps-children', { parts: children.join(', ') }));
|
|
1761
|
-
if (seen.clipsWouldGo.length)
|
|
1762
|
-
words.push(say('figure.text.detach-clips', { clips: seen.clipsWouldGo.map(c => c.clip).join(', ') }));
|
|
1763
|
-
}
|
|
1764
|
-
catch (error) {
|
|
1765
|
-
words = [say('figure.text.detach-refused', { reason: this.said(error) })];
|
|
1766
|
-
}
|
|
1767
|
-
return words;
|
|
1768
|
-
}
|
|
1769
1218
|
/*
|
|
1770
1219
|
Past the edge of the face a part is fastened to (ADR-0097 §5.5, Q3): the selected part's own, and the parts fastened
|
|
1771
1220
|
to it -- said apart from the room, nothing moved. Read once per version, part and language. The note says it is wider
|
|
@@ -1830,15 +1279,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1830
1279
|
}
|
|
1831
1280
|
/** What the canvas draws of it, made once per preview: a new list every render would stand the board up each time. */
|
|
1832
1281
|
previewShown() {
|
|
1833
|
-
const p = this.
|
|
1282
|
+
const p = this.commandPreview.current();
|
|
1834
1283
|
/* On the motion tab, with nothing being tried: where the selected part's motion takes it, at both ends. */
|
|
1835
1284
|
if (!p && this.tab === 'motion' && this.middle === 'edit')
|
|
1836
1285
|
return { ghost: this.motionEnds()?.ghost ?? [], highlight: null };
|
|
1837
|
-
|
|
1838
|
-
return { ghost: [], highlight: null };
|
|
1839
|
-
if (this.previewDrawn?.of !== p)
|
|
1840
|
-
this.previewDrawn = { of: p, ghost: ghostPartsOf(p), highlight: faceHighlightOf(p.face) };
|
|
1841
|
-
return this.previewDrawn;
|
|
1286
|
+
return this.commandPreview.drawnOf(p);
|
|
1842
1287
|
}
|
|
1843
1288
|
/**
|
|
1844
1289
|
* What the author has in view, for the AI dock to hear with a request (ADR-0097 §5.6 item 5): the part selected and
|
|
@@ -1881,25 +1326,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1881
1326
|
}
|
|
1882
1327
|
updated(changed) {
|
|
1883
1328
|
super.updated?.(changed);
|
|
1884
|
-
this.tell(changed);
|
|
1885
|
-
|
|
1886
|
-
const key = this.restorePanelFor;
|
|
1887
|
-
this.restorePanelFor = undefined;
|
|
1888
|
-
requestAnimationFrame(async () => {
|
|
1889
|
-
const panel = this.propertiesPanel();
|
|
1890
|
-
if (!panel || (this.selected || '@model') !== key)
|
|
1891
|
-
return;
|
|
1892
|
-
await Promise.all([...panel.querySelectorAll('figure-links')].map(el => el.updateComplete));
|
|
1893
|
-
const kept = this.panelMemory.get(key);
|
|
1894
|
-
if (kept)
|
|
1895
|
-
this.foldsOf(panel).forEach((fold, i) => i < kept.open.length && (fold.open = kept.open[i]));
|
|
1896
|
-
panel.scrollTop = kept?.top ?? 0;
|
|
1897
|
-
});
|
|
1898
|
-
}
|
|
1329
|
+
this.refusals.tell(changed);
|
|
1330
|
+
this.panelMemory.restore(() => this.selected);
|
|
1899
1331
|
const panes = this.renderRoot.querySelector('ox-three-pane') ?? undefined;
|
|
1900
1332
|
if (panes !== this.panes) {
|
|
1901
1333
|
this.panes = panes;
|
|
1902
|
-
this.
|
|
1334
|
+
this.paneFold.fitTo(this.getBoundingClientRect().width);
|
|
1903
1335
|
}
|
|
1904
1336
|
}
|
|
1905
1337
|
/**
|
|
@@ -2508,10 +1940,6 @@ __decorate([
|
|
|
2508
1940
|
state(),
|
|
2509
1941
|
__metadata("design:type", Object)
|
|
2510
1942
|
], FigureEditor.prototype, "sectionEditing", void 0);
|
|
2511
|
-
__decorate([
|
|
2512
|
-
state(),
|
|
2513
|
-
__metadata("design:type", Object)
|
|
2514
|
-
], FigureEditor.prototype, "preview", void 0);
|
|
2515
1943
|
FigureEditor = FigureEditor_1 = __decorate([
|
|
2516
1944
|
customElement('figure-editor')
|
|
2517
1945
|
], FigureEditor);
|