@things-factory/figure-ui 10.1.46 → 10.1.48
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/angle-dial.ts +70 -0
- package/client/modeller/figure-settings.ts +328 -0
- package/client/modeller/figure-v3-capability.ts +137 -0
- package/client/modeller/figure-v3-clips.ts +253 -0
- package/client/modeller/figure-v3-controls.ts +212 -0
- package/client/modeller/figure-v3-links.ts +61 -81
- package/client/modeller/figure-v3-material.ts +108 -35
- package/client/modeller/figure-v3-parts.ts +96 -3
- package/client/modeller/figure-v3-preview.ts +326 -0
- package/client/modeller/figure-v3-properties.ts +29 -57
- package/client/modeller/figure-v3-verdict.ts +598 -60
- package/client/modeller/figure-view.ts +152 -0
- package/client/modeller/side-panel-styles.ts +326 -0
- package/client/modeller/v3-canvas.ts +254 -262
- package/client/modeller/v3-clips.ts +89 -0
- package/client/modeller/v3-controls.ts +54 -0
- package/client/modeller/v3-editor.ts +566 -223
- package/client/modeller/v3-fields.ts +84 -0
- package/client/modeller/v3-help.ts +41 -0
- package/client/modeller/v3-joint-fan.ts +292 -0
- package/client/modeller/v3-keys.ts +34 -0
- package/client/modeller/v3-links.ts +101 -9
- package/client/modeller/v3-live.ts +79 -0
- package/client/modeller/v3-maturity.ts +415 -0
- package/client/modeller/v3-palette.ts +45 -0
- package/client/modeller/v3-preview-model.ts +54 -0
- package/client/modeller/v3-stage-model.ts +159 -0
- package/client/modeller/v3-thumbnail.ts +8 -4
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/figure-v3-modeller-page.ts +66 -134
- package/dist-client/modeller/angle-dial.d.ts +32 -0
- package/dist-client/modeller/angle-dial.js +41 -0
- package/dist-client/modeller/angle-dial.js.map +1 -0
- package/dist-client/modeller/figure-settings.d.ts +39 -0
- package/dist-client/modeller/figure-settings.js +317 -0
- package/dist-client/modeller/figure-settings.js.map +1 -0
- package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
- package/dist-client/modeller/figure-v3-capability.js +141 -0
- package/dist-client/modeller/figure-v3-capability.js.map +1 -0
- package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
- package/dist-client/modeller/figure-v3-clips.js +268 -0
- package/dist-client/modeller/figure-v3-clips.js.map +1 -0
- package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
- package/dist-client/modeller/figure-v3-controls.js +226 -0
- package/dist-client/modeller/figure-v3-controls.js.map +1 -0
- package/dist-client/modeller/figure-v3-links.d.ts +4 -1
- package/dist-client/modeller/figure-v3-links.js +64 -82
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +4 -1
- package/dist-client/modeller/figure-v3-material.js +109 -35
- package/dist-client/modeller/figure-v3-material.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
- package/dist-client/modeller/figure-v3-parts.js +88 -3
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
- package/dist-client/modeller/figure-v3-preview.js +322 -0
- package/dist-client/modeller/figure-v3-preview.js.map +1 -0
- package/dist-client/modeller/figure-v3-properties.d.ts +1 -1
- package/dist-client/modeller/figure-v3-properties.js +29 -57
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
- package/dist-client/modeller/figure-v3-verdict.js +579 -59
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +118 -0
- package/dist-client/modeller/figure-view.js +78 -0
- package/dist-client/modeller/figure-view.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.d.ts +23 -0
- package/dist-client/modeller/side-panel-styles.js +326 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +44 -37
- package/dist-client/modeller/v3-canvas.js +230 -260
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +39 -0
- package/dist-client/modeller/v3-clips.js +58 -0
- package/dist-client/modeller/v3-clips.js.map +1 -0
- package/dist-client/modeller/v3-controls.d.ts +28 -0
- package/dist-client/modeller/v3-controls.js +28 -0
- package/dist-client/modeller/v3-controls.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +73 -1
- package/dist-client/modeller/v3-editor.js +593 -218
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +39 -0
- package/dist-client/modeller/v3-fields.js +69 -0
- package/dist-client/modeller/v3-fields.js.map +1 -0
- package/dist-client/modeller/v3-help.d.ts +18 -0
- package/dist-client/modeller/v3-help.js +37 -0
- package/dist-client/modeller/v3-help.js.map +1 -0
- package/dist-client/modeller/v3-joint-fan.d.ts +9 -0
- package/dist-client/modeller/v3-joint-fan.js +264 -0
- package/dist-client/modeller/v3-joint-fan.js.map +1 -0
- package/dist-client/modeller/v3-keys.d.ts +14 -0
- package/dist-client/modeller/v3-keys.js +23 -0
- package/dist-client/modeller/v3-keys.js.map +1 -0
- package/dist-client/modeller/v3-links.d.ts +35 -1
- package/dist-client/modeller/v3-links.js +101 -8
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-live.d.ts +31 -0
- package/dist-client/modeller/v3-live.js +58 -0
- package/dist-client/modeller/v3-live.js.map +1 -0
- package/dist-client/modeller/v3-maturity.d.ts +120 -0
- package/dist-client/modeller/v3-maturity.js +317 -0
- package/dist-client/modeller/v3-maturity.js.map +1 -0
- package/dist-client/modeller/v3-palette.d.ts +7 -0
- package/dist-client/modeller/v3-palette.js +42 -0
- package/dist-client/modeller/v3-palette.js.map +1 -0
- package/dist-client/modeller/v3-preview-model.d.ts +32 -0
- package/dist-client/modeller/v3-preview-model.js +48 -0
- package/dist-client/modeller/v3-preview-model.js.map +1 -0
- package/dist-client/modeller/v3-stage-model.d.ts +31 -0
- package/dist-client/modeller/v3-stage-model.js +121 -0
- package/dist-client/modeller/v3-stage-model.js.map +1 -0
- package/dist-client/modeller/v3-thumbnail.js +6 -3
- package/dist-client/modeller/v3-thumbnail.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
- package/dist-client/pages/figure-v3-modeller-page.js +64 -136
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
- package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/v3-clips.test.ts +70 -0
- package/test/v3-controls.test.ts +68 -0
- package/test/v3-fields.test.ts +71 -0
- package/test/v3-gizmo.test.ts +3 -5
- package/test/v3-joint-fan.test.ts +210 -0
- package/test/v3-keys.test.ts +37 -0
- package/test/v3-layout.test.ts +78 -0
- package/test/v3-links.test.ts +89 -6
- package/test/v3-live.test.ts +99 -0
- package/test/v3-material.test.ts +6 -0
- package/test/v3-maturity.test.ts +268 -0
- package/test/v3-palette-icons.test.ts +22 -0
- package/test/v3-parts-wiring.test.ts +3 -1
- package/test/v3-preview.test.ts +42 -0
- package/test/v3-properties.test.ts +4 -2
- package/test/v3-scene-canvas.test.ts +85 -0
- package/test/v3-stage-model.test.ts +25 -0
- package/test/v3-theme.test.ts +16 -25
- package/test/v3-thumbnail.test.ts +29 -0
- package/test/v3-verdict.test.ts +12 -6
- package/test/v3-view.test.ts +20 -0
- package/translations/en.json +90 -6
- package/translations/ja.json +92 -8
- package/translations/ko.json +90 -6
- package/translations/ms.json +92 -8
- package/translations/zh.json +92 -8
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
var FigureV3Editor_1;
|
|
1
2
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
-
import { LitElement, html, css } from 'lit';
|
|
3
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
3
4
|
import { i18next } from '@operato/i18n';
|
|
5
|
+
import '@operato/layout';
|
|
4
6
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
5
7
|
import { captureV3Thumbnail } from './v3-thumbnail.js';
|
|
6
8
|
import { drawableV3Parts } from './v3-renderer.js';
|
|
@@ -8,33 +10,21 @@ import './v3-canvas.js';
|
|
|
8
10
|
import './figure-v3-parts.js';
|
|
9
11
|
import './figure-v3-material.js';
|
|
10
12
|
import './figure-v3-links.js';
|
|
13
|
+
import './figure-v3-controls.js';
|
|
14
|
+
import './figure-v3-clips.js';
|
|
15
|
+
import './figure-v3-preview.js';
|
|
16
|
+
import './figure-settings.js';
|
|
17
|
+
import { DEFAULT_VIEW } from './figure-view.js';
|
|
18
|
+
import './figure-v3-capability.js';
|
|
11
19
|
import { gizmoChanges, gizmoReachOf } from './v3-gizmo.js';
|
|
20
|
+
import { keyActionOf } from './v3-keys.js';
|
|
21
|
+
import { groupFields, partLabelOf, repeatOf } from './v3-fields.js';
|
|
22
|
+
import { SidePanelStyles } from './side-panel-styles.js';
|
|
12
23
|
import { alignChanges, boxOf, canMirror, mirrorChanges } from './v3-align.js';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
dimension: '치수',
|
|
18
|
-
width: '너비',
|
|
19
|
-
height: '높이',
|
|
20
|
-
depth: '깊이',
|
|
21
|
-
radius: '반지름',
|
|
22
|
-
length: '길이',
|
|
23
|
-
round: '모서리 반경',
|
|
24
|
-
left: '왼쪽',
|
|
25
|
-
right: '오른쪽',
|
|
26
|
-
bottom: '아랫면',
|
|
27
|
-
top: '윗면',
|
|
28
|
-
openingLeft: '개구부 왼쪽',
|
|
29
|
-
openingRight: '개구부 오른쪽',
|
|
30
|
-
openingTop: '개구부 윗면'
|
|
31
|
-
};
|
|
32
|
-
function fieldLabel(id) {
|
|
33
|
-
return id
|
|
34
|
-
.split('.')
|
|
35
|
-
.map(p => names[p] ?? p.toUpperCase())
|
|
36
|
-
.join(' · ');
|
|
37
|
-
}
|
|
24
|
+
import { paletteRecipe } from './v3-palette.js';
|
|
25
|
+
import { instanceRangeOf, rangeDiffers, turnsAboutAJoint } from './v3-links.js';
|
|
26
|
+
import { jointFanOf } from './v3-joint-fan.js';
|
|
27
|
+
import { isLive, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from './v3-live.js';
|
|
38
28
|
/** Product editor surface. The injected session is shared by page persistence and AI commands. */
|
|
39
29
|
let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
40
30
|
constructor() {
|
|
@@ -42,69 +32,193 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
42
32
|
this.revision = 0;
|
|
43
33
|
this.selected = '';
|
|
44
34
|
this.error = '';
|
|
35
|
+
this.tab = 'properties';
|
|
36
|
+
/** What the middle shows: the parts being edited, or the figure placed on a board at eight sizes (V2's two modes). */
|
|
37
|
+
this.middle = 'edit';
|
|
38
|
+
/**
|
|
39
|
+
* How the workbench shows the figure -- V2's view settings, the board's own state names. The viewer's, kept in
|
|
40
|
+
* their browser, never in the figure.
|
|
41
|
+
*/
|
|
42
|
+
this.view = FigureV3Editor_1.storedView();
|
|
45
43
|
/**
|
|
46
44
|
* Parts hidden on the workbench — the author's view while working, not the asset's visibility. Not saved, as in
|
|
47
45
|
* the V2 modeller: a part hidden here is still in the figure, still checked, still drawn on a board.
|
|
48
46
|
*/
|
|
49
47
|
this.hiddenParts = new Set();
|
|
48
|
+
/** A value in hand -- typed, held on a spinner, scrubbed, dragged -- drawn at once and recorded on release. */
|
|
49
|
+
this.live = NOTHING_LIVE;
|
|
50
50
|
this.lastContext = null;
|
|
51
|
+
/** The modeller's keys (`v3-keys.ts`): undo, redo, duplicate, remove, back to the whole figure. */
|
|
52
|
+
this.onKey = (e) => {
|
|
53
|
+
const view = this.session?.read();
|
|
54
|
+
if (!view || view.loading || view.saving || !view.context || !this.isConnected)
|
|
55
|
+
return;
|
|
56
|
+
const action = keyActionOf({ key: e.key, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey, altKey: e.altKey, target: e.composedPath()[0] }, this.selected);
|
|
57
|
+
if (!action)
|
|
58
|
+
return;
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
if (action === 'undo')
|
|
61
|
+
view.canUndo && this.run(() => this.session.undo());
|
|
62
|
+
else if (action === 'redo')
|
|
63
|
+
view.canRedo && this.run(() => this.session.redo());
|
|
64
|
+
else if (action === 'duplicate')
|
|
65
|
+
this.duplicatePart(this.selected);
|
|
66
|
+
else if (action === 'remove')
|
|
67
|
+
this.removePart(this.selected);
|
|
68
|
+
else
|
|
69
|
+
this.selected = '';
|
|
70
|
+
};
|
|
51
71
|
}
|
|
52
|
-
static {
|
|
72
|
+
static { FigureV3Editor_1 = this; }
|
|
73
|
+
static { this.styles = [SidePanelStyles, css `
|
|
53
74
|
:host {
|
|
54
75
|
display: flex;
|
|
55
76
|
flex-direction: column;
|
|
56
77
|
min-height: 0;
|
|
57
78
|
height: 100%;
|
|
58
|
-
/*
|
|
59
|
-
The editor folds by the width it is given, not the window's. The rail (208 open, 56 closed) and the page's
|
|
60
|
-
320 side take their share first, so a window rule was wrong whenever either was there: at 945 with the rail
|
|
61
|
-
open the editor had 417 and still drew its 660 of columns, over the side (UX ruling, 2026-09-24).
|
|
62
|
-
*/
|
|
79
|
+
/* The editor folds by the width it is given, not the window's (UX ruling, 2026-09-24). */
|
|
63
80
|
container-type: inline-size;
|
|
64
81
|
font: 13px sans-serif;
|
|
65
|
-
color: var(--md-sys-color-on-surface
|
|
82
|
+
color: var(--md-sys-color-on-surface);
|
|
83
|
+
}
|
|
84
|
+
/* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and
|
|
85
|
+
fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */
|
|
86
|
+
ox-canvas-bar button[mode] {
|
|
87
|
+
height: 28px;
|
|
88
|
+
padding: 0 10px;
|
|
89
|
+
border-radius: 999em;
|
|
90
|
+
border-color: transparent;
|
|
91
|
+
background: none;
|
|
92
|
+
}
|
|
93
|
+
ox-canvas-bar button[mode][aria-pressed='true'] {
|
|
94
|
+
border-color: var(--md-sys-color-primary);
|
|
66
95
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
figure-v3-preview {
|
|
97
|
+
min-width: 0;
|
|
98
|
+
min-height: 0;
|
|
99
|
+
}
|
|
100
|
+
ox-canvas-bar .status {
|
|
101
|
+
padding: 0 8px;
|
|
102
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
103
|
+
}
|
|
104
|
+
ox-canvas-bar .status[dirty] {
|
|
105
|
+
color: var(--md-sys-color-primary);
|
|
106
|
+
font-weight: 600;
|
|
74
107
|
}
|
|
75
|
-
|
|
108
|
+
ox-canvas-bar button[save] {
|
|
109
|
+
height: 32px;
|
|
110
|
+
padding: 0 14px;
|
|
111
|
+
border: none;
|
|
112
|
+
border-radius: 999em;
|
|
113
|
+
color: var(--md-sys-color-on-primary);
|
|
114
|
+
background: var(--md-sys-color-primary);
|
|
115
|
+
}
|
|
116
|
+
/* The three panes are ox-three-pane's (ADR-0060 decision 8): widths 200 | 1fr | 340, 160 | 300 under
|
|
117
|
+
860px and one column with the middle first under 560px are the frame's, as are the dividers. */
|
|
118
|
+
ox-three-pane {
|
|
76
119
|
flex: 1;
|
|
77
120
|
min-height: 0;
|
|
78
|
-
display: grid;
|
|
79
|
-
grid-template-columns: 180px minmax(180px, 1fr) 300px;
|
|
80
121
|
}
|
|
122
|
+
figure-v3-parts {
|
|
123
|
+
min-height: 0;
|
|
124
|
+
}
|
|
125
|
+
figure-v3-canvas {
|
|
126
|
+
min-width: 0;
|
|
127
|
+
min-height: 0;
|
|
128
|
+
}
|
|
129
|
+
/* The side: tabs over one panel at a time, each using the whole height (V2's side, and why it had tabs). */
|
|
81
130
|
aside {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
min-height: 0;
|
|
134
|
+
background: var(--md-sys-color-surface-container-lowest);
|
|
85
135
|
}
|
|
86
|
-
|
|
136
|
+
div[tabs] {
|
|
137
|
+
display: flex;
|
|
138
|
+
flex: none;
|
|
139
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
140
|
+
}
|
|
141
|
+
button[tab] {
|
|
142
|
+
flex: 1;
|
|
143
|
+
position: relative;
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
height: 38px;
|
|
148
|
+
padding: 0;
|
|
149
|
+
border: none;
|
|
150
|
+
border-radius: 0;
|
|
151
|
+
border-bottom: 2px solid transparent;
|
|
152
|
+
background: none;
|
|
153
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
154
|
+
}
|
|
155
|
+
button[tab]:hover:not([on]) {
|
|
156
|
+
background: var(--md-sys-color-surface-container);
|
|
157
|
+
}
|
|
158
|
+
button[tab][on] {
|
|
159
|
+
color: var(--md-sys-color-primary);
|
|
160
|
+
border-bottom-color: var(--md-sys-color-primary);
|
|
161
|
+
}
|
|
162
|
+
button[tab] md-icon {
|
|
163
|
+
font-size: 20px;
|
|
164
|
+
width: 20px;
|
|
165
|
+
height: 20px;
|
|
166
|
+
}
|
|
167
|
+
span[badge] {
|
|
168
|
+
position: absolute;
|
|
169
|
+
top: 3px;
|
|
170
|
+
right: 2px;
|
|
171
|
+
padding: 0 5px;
|
|
172
|
+
font-size: 0.62rem;
|
|
173
|
+
font-weight: 700;
|
|
174
|
+
line-height: 1.5;
|
|
175
|
+
color: var(--md-sys-color-on-primary-container);
|
|
176
|
+
background: var(--md-sys-color-primary-container);
|
|
177
|
+
border-radius: 999em;
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
}
|
|
180
|
+
/* The sections inside carry the margins (side-panel-styles); the panel only scrolls. */
|
|
181
|
+
div[panel] {
|
|
182
|
+
flex: 1;
|
|
87
183
|
min-height: 0;
|
|
184
|
+
overflow: auto;
|
|
185
|
+
padding: 0;
|
|
186
|
+
}
|
|
187
|
+
div[panel][flush] {
|
|
188
|
+
padding: 0;
|
|
189
|
+
}
|
|
190
|
+
/* Hidden, not removed: a panel keeps what it holds (the verdict its last answer) while another tab is open. */
|
|
191
|
+
[off] {
|
|
192
|
+
display: none !important;
|
|
88
193
|
}
|
|
89
|
-
|
|
90
|
-
|
|
194
|
+
/* Which part the panel speaks of, and the way back to the whole figure. */
|
|
195
|
+
div[subject] {
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
gap: 6px;
|
|
199
|
+
padding: 6px 10px 0;
|
|
91
200
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
201
|
+
div[subject] button[chip] {
|
|
202
|
+
display: inline-flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 2px;
|
|
205
|
+
height: 26px;
|
|
206
|
+
padding: 0 10px 0 6px;
|
|
207
|
+
border-radius: 999em;
|
|
208
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
95
209
|
}
|
|
96
|
-
|
|
97
|
-
|
|
210
|
+
div[subject] md-icon {
|
|
211
|
+
font-size: 16px;
|
|
212
|
+
width: 16px;
|
|
213
|
+
height: 16px;
|
|
98
214
|
}
|
|
99
|
-
button
|
|
100
|
-
input,
|
|
101
|
-
select {
|
|
215
|
+
button {
|
|
102
216
|
font: inherit;
|
|
103
217
|
box-sizing: border-box;
|
|
104
|
-
height:
|
|
218
|
+
height: 32px;
|
|
105
219
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
106
220
|
border-radius: 6px;
|
|
107
|
-
background: var(--md-sys-color-surface
|
|
221
|
+
background: var(--md-sys-color-surface);
|
|
108
222
|
color: inherit;
|
|
109
223
|
padding: 0 8px;
|
|
110
224
|
min-width: 0;
|
|
@@ -121,94 +235,43 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
121
235
|
color: var(--md-sys-color-on-secondary-container);
|
|
122
236
|
border-color: var(--md-sys-color-primary);
|
|
123
237
|
}
|
|
124
|
-
label[name-field] {
|
|
125
|
-
margin: 0 0 8px;
|
|
126
|
-
}
|
|
127
|
-
label[name-field] input {
|
|
128
|
-
font-weight: 600;
|
|
129
|
-
}
|
|
130
|
-
div[tools] {
|
|
131
|
-
display: flex;
|
|
132
|
-
flex-wrap: wrap;
|
|
133
|
-
gap: 4px;
|
|
134
|
-
margin: 0 0 8px;
|
|
135
|
-
}
|
|
136
|
-
div[tools] button {
|
|
137
|
-
display: inline-flex;
|
|
138
|
-
align-items: center;
|
|
139
|
-
justify-content: center;
|
|
140
|
-
width: 34px;
|
|
141
|
-
padding: 0;
|
|
142
|
-
}
|
|
143
|
-
div[tools] md-icon {
|
|
144
|
-
font-size: 18px;
|
|
145
|
-
width: 18px;
|
|
146
|
-
height: 18px;
|
|
147
|
-
}
|
|
148
|
-
.parts {
|
|
149
|
-
display: grid;
|
|
150
|
-
gap: 6px;
|
|
151
|
-
}
|
|
152
|
-
.recipes {
|
|
153
|
-
display: grid;
|
|
154
|
-
grid-template-columns: 1fr 1fr;
|
|
155
|
-
gap: 6px;
|
|
156
|
-
}
|
|
157
|
-
label {
|
|
158
|
-
display: grid;
|
|
159
|
-
grid-template-columns: 112px minmax(0, 1fr);
|
|
160
|
-
align-items: center;
|
|
161
|
-
gap: 8px;
|
|
162
|
-
margin: 8px 0;
|
|
163
|
-
}
|
|
164
|
-
label span {
|
|
165
|
-
overflow-wrap: anywhere;
|
|
166
|
-
}
|
|
167
238
|
.error {
|
|
168
239
|
padding: 10px;
|
|
169
240
|
background: var(--md-sys-color-error-container);
|
|
170
241
|
color: var(--md-sys-color-on-error-container);
|
|
171
242
|
white-space: pre-wrap;
|
|
172
243
|
}
|
|
173
|
-
.status {
|
|
174
|
-
margin-left: auto;
|
|
175
|
-
}
|
|
176
244
|
.hint {
|
|
177
245
|
color: var(--md-sys-color-on-surface-variant);
|
|
178
246
|
line-height: 1.5;
|
|
179
247
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
aside:last-child {
|
|
208
|
-
max-height: none;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
`; }
|
|
248
|
+
`]; }
|
|
249
|
+
static storedView() {
|
|
250
|
+
try {
|
|
251
|
+
const stored = JSON.parse(localStorage.getItem('figure-v3-view') ?? 'null');
|
|
252
|
+
return stored && typeof stored === 'object' ? { ...DEFAULT_VIEW, ...stored } : { ...DEFAULT_VIEW };
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return { ...DEFAULT_VIEW };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
setView(view) {
|
|
259
|
+
this.view = { ...DEFAULT_VIEW, ...view };
|
|
260
|
+
try {
|
|
261
|
+
localStorage.setItem('figure-v3-view', JSON.stringify(this.view));
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
/* A browser that keeps nothing still shows the change for this visit. */
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
connectedCallback() {
|
|
268
|
+
super.connectedCallback();
|
|
269
|
+
window.addEventListener('keydown', this.onKey);
|
|
270
|
+
}
|
|
271
|
+
disconnectedCallback() {
|
|
272
|
+
window.removeEventListener('keydown', this.onKey);
|
|
273
|
+
super.disconnectedCallback();
|
|
274
|
+
}
|
|
212
275
|
willUpdate(_changed) {
|
|
213
276
|
const view = this.session?.read();
|
|
214
277
|
if (view?.context !== this.lastContext) {
|
|
@@ -229,6 +292,33 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
229
292
|
catch (error) {
|
|
230
293
|
this.error = error instanceof Error ? error.message : String(error);
|
|
231
294
|
}
|
|
295
|
+
finally {
|
|
296
|
+
/* Committed or refused, the value in hand is gone: the figure as it stands is drawn again. */
|
|
297
|
+
this.live = NOTHING_LIVE;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** What the canvas draws: the figure with the value in hand when there is one, and without the parts hidden on the workbench. */
|
|
301
|
+
drawnParts(geometry, asset) {
|
|
302
|
+
const parts = (isLive(this.live) && asset ? livePartsOf(asset, this.live) : null) ?? drawableV3Parts(geometry ?? []);
|
|
303
|
+
return parts.filter(body => !this.hiddenParts.has(body.id));
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Where the selected part turns and how far, with the pose as it is drawn now -- a value in hand included, so the
|
|
307
|
+
* fan follows the arm while a control is dragged. Its size comes from what the joint turns (`v3-joint-fan`).
|
|
308
|
+
*/
|
|
309
|
+
fanOf(asset) {
|
|
310
|
+
if (!asset || !this.selected || this.hiddenParts.has(this.selected))
|
|
311
|
+
return null;
|
|
312
|
+
try {
|
|
313
|
+
return jointFanOf(asset, this.selected, this.live.values);
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** A value in hand: an input's value (design or pose) or a part edit. Nothing is recorded. */
|
|
320
|
+
hold(values, edit) {
|
|
321
|
+
this.live = { values: { ...this.live.values, ...values }, ...(edit ? { edit } : this.live.edit ? { edit: this.live.edit } : {}) };
|
|
232
322
|
}
|
|
233
323
|
changed() {
|
|
234
324
|
this.requestUpdate();
|
|
@@ -265,28 +355,7 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
265
355
|
let n = 1;
|
|
266
356
|
while (ids.has(`${kind}-${n}`))
|
|
267
357
|
n++;
|
|
268
|
-
const
|
|
269
|
-
const part = kind === 'box'
|
|
270
|
-
? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100 } }
|
|
271
|
-
: kind === 'cylinder'
|
|
272
|
-
? { ...common, kind, dimensions: { radius: 50, length: 100 } }
|
|
273
|
-
: kind === 'rounded-box'
|
|
274
|
-
? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } }
|
|
275
|
-
: {
|
|
276
|
-
...common,
|
|
277
|
-
position: { x: 0, y: 0, z: 0 },
|
|
278
|
-
kind: 'portal',
|
|
279
|
-
dimensions: {
|
|
280
|
-
left: -100,
|
|
281
|
-
right: 100,
|
|
282
|
-
bottom: 0,
|
|
283
|
-
top: 200,
|
|
284
|
-
openingLeft: -70,
|
|
285
|
-
openingRight: 70,
|
|
286
|
-
openingTop: 170,
|
|
287
|
-
depth: 30
|
|
288
|
-
}
|
|
289
|
-
};
|
|
358
|
+
const part = paletteRecipe(kind, `${kind}-${n}`);
|
|
290
359
|
this.session.add(part);
|
|
291
360
|
this.selected = part.id;
|
|
292
361
|
});
|
|
@@ -411,6 +480,14 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
411
480
|
* A part's name — the V2 part name field. It renames every value the part owns, and what refers to them follows
|
|
412
481
|
* (figure-model's rename-part), as one step of the history.
|
|
413
482
|
*/
|
|
483
|
+
/**
|
|
484
|
+
* A repeated body is one of its repeat's copies and is named after it -- the list and this field read `roller 15`, not
|
|
485
|
+
* its path through the repeat (user, 2026-09-24: 「부품 이름 ???」). A copy has no name of its own to change.
|
|
486
|
+
*/
|
|
487
|
+
renderRepeatNote(geometry) {
|
|
488
|
+
const repeat = repeatOf(this.selected, geometry);
|
|
489
|
+
return repeat ? html `<p hint>${i18next.t('figure.text.repeat-member', { name: repeat.name, count: repeat.count, index: repeat.index })}</p>` : nothing;
|
|
490
|
+
}
|
|
414
491
|
renamePart(input) {
|
|
415
492
|
const name = input.value.trim();
|
|
416
493
|
const from = this.selected;
|
|
@@ -436,7 +513,7 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
436
513
|
}
|
|
437
514
|
}
|
|
438
515
|
/** Size response, fastening and motion for the selected part (parity ④ ⑤ ⑥), as authoring commands. */
|
|
439
|
-
renderLinks(unavailable) {
|
|
516
|
+
renderLinks(unavailable, section) {
|
|
440
517
|
let asset;
|
|
441
518
|
try {
|
|
442
519
|
asset = JSON.parse(this.session.source());
|
|
@@ -453,6 +530,7 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
453
530
|
}
|
|
454
531
|
return html `<figure-v3-links
|
|
455
532
|
.asset=${asset}
|
|
533
|
+
.section=${section}
|
|
456
534
|
.partId=${this.selected}
|
|
457
535
|
.dims=${dims}
|
|
458
536
|
?locked=${unavailable}
|
|
@@ -460,6 +538,33 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
460
538
|
@v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
|
|
461
539
|
></figure-v3-links>`;
|
|
462
540
|
}
|
|
541
|
+
/** The figure's controls: try a value, name it, set its range and travel time, keep the pose (parity row ⑥). */
|
|
542
|
+
renderControls(view, unavailable) {
|
|
543
|
+
let asset;
|
|
544
|
+
try {
|
|
545
|
+
asset = JSON.parse(this.session.source());
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
const values = Object.fromEntries(view.fields.filter(f => f.role === 'state').map(f => [f.id, f.value]));
|
|
551
|
+
return html `<figure-v3-controls
|
|
552
|
+
.asset=${asset}
|
|
553
|
+
.values=${values}
|
|
554
|
+
?locked=${unavailable}
|
|
555
|
+
@v3-state=${(e) => this.run(() => this.session.apply([{ kind: 'set-state', input: e.detail.input, unit: e.detail.unit, value: e.detail.value }]))}
|
|
556
|
+
@v3-state-live=${(e) => this.hold({ [e.detail.input]: e.detail.value })}
|
|
557
|
+
@v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
|
|
558
|
+
></figure-v3-controls>
|
|
559
|
+
<figure-v3-clips
|
|
560
|
+
.asset=${asset}
|
|
561
|
+
.values=${values}
|
|
562
|
+
?locked=${unavailable}
|
|
563
|
+
@v3-states=${(e) => this.run(() => this.session.apply(e.detail.changes))}
|
|
564
|
+
@v3-states-live=${(e) => this.hold(e.detail.values)}
|
|
565
|
+
@v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
|
|
566
|
+
></figure-v3-clips>`;
|
|
567
|
+
}
|
|
463
568
|
/** One command, one step of the history. A refusal a person can meet here is said in their words. */
|
|
464
569
|
linkCommand(action) {
|
|
465
570
|
try {
|
|
@@ -487,35 +592,183 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
487
592
|
// Rejected edits must also restore the displayed numeric value.
|
|
488
593
|
input.value = String(this.session.read().fields.find(i => i.id === field.id).value);
|
|
489
594
|
}
|
|
595
|
+
/** Typing or a held spinner: each number on the way is drawn; the field's change records it. */
|
|
596
|
+
typing(field, event) {
|
|
597
|
+
const value = event.target.valueAsNumber;
|
|
598
|
+
if (Number.isFinite(value))
|
|
599
|
+
this.hold({ [field.id]: value });
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Drag a field's label (or an axis letter) sideways to scrub its value, as vector tools do: the figure follows the
|
|
603
|
+
* pointer, and the release records one step. The field's number follows too, so the value in hand is always read.
|
|
604
|
+
*/
|
|
605
|
+
scrub(field, down, unavailable) {
|
|
606
|
+
if (unavailable || down.button !== 0)
|
|
607
|
+
return;
|
|
608
|
+
const label = down.currentTarget;
|
|
609
|
+
const input = (label.closest('div[cell]')?.querySelector('input') ?? label.nextElementSibling);
|
|
610
|
+
if (!input)
|
|
611
|
+
return;
|
|
612
|
+
down.preventDefault();
|
|
613
|
+
label.setPointerCapture(down.pointerId);
|
|
614
|
+
const start = field.value;
|
|
615
|
+
let value = start;
|
|
616
|
+
const move = (e) => {
|
|
617
|
+
value = scrubbed(start, e.clientX - down.clientX, scrubStep(field.unit, start, e.shiftKey));
|
|
618
|
+
input.value = String(value);
|
|
619
|
+
this.hold({ [field.id]: value });
|
|
620
|
+
};
|
|
621
|
+
const up = (e) => {
|
|
622
|
+
label.removeEventListener('pointermove', move);
|
|
623
|
+
label.removeEventListener('pointerup', up);
|
|
624
|
+
label.removeEventListener('pointercancel', up);
|
|
625
|
+
/* A drag the system took away is not the author's value: the field goes back to where it was. */
|
|
626
|
+
if (e.type === 'pointercancel')
|
|
627
|
+
value = start;
|
|
628
|
+
if (value === start) {
|
|
629
|
+
input.value = String(start);
|
|
630
|
+
this.live = NOTHING_LIVE;
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
input.value = String(value);
|
|
634
|
+
this.changeField(field, { target: input });
|
|
635
|
+
};
|
|
636
|
+
label.addEventListener('pointermove', move);
|
|
637
|
+
label.addEventListener('pointerup', up);
|
|
638
|
+
label.addEventListener('pointercancel', up);
|
|
639
|
+
}
|
|
640
|
+
/** The sizes a board may give the figure, and the way to measure them again after its parts changed. */
|
|
641
|
+
renderInstanceRange(asset, unavailable) {
|
|
642
|
+
if (!asset)
|
|
643
|
+
return nothing;
|
|
644
|
+
/* Another figure, or another step of this one, is measured again. */
|
|
645
|
+
const at = `${this.session?.read().context ?? ''}:${this.revision}`;
|
|
646
|
+
if (this.range?.at !== at) {
|
|
647
|
+
let of;
|
|
648
|
+
try {
|
|
649
|
+
of = instanceRangeOf(asset);
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
of = undefined;
|
|
653
|
+
}
|
|
654
|
+
this.range = { at, of };
|
|
655
|
+
}
|
|
656
|
+
const range = this.range.of;
|
|
657
|
+
if (!range)
|
|
658
|
+
return nothing;
|
|
659
|
+
const mm = (v) => Math.round(v).toLocaleString();
|
|
660
|
+
const differs = rangeDiffers(range.declared, range.measured);
|
|
661
|
+
return html `<section>
|
|
662
|
+
<h3>${i18next.t('figure.label.instance-range')}</h3>
|
|
663
|
+
${['x', 'y', 'z'].map(axis => html `<div inline-field>
|
|
664
|
+
<label>${axis.toUpperCase()}</label><span>${mm(range.declared[axis].min)} – ${mm(range.declared[axis].max)} mm</span>
|
|
665
|
+
</div>`)}
|
|
666
|
+
${differs
|
|
667
|
+
? html `<p hint>
|
|
668
|
+
${i18next.t('figure.text.instance-range-can-widen', {
|
|
669
|
+
ranges: ['x', 'y', 'z']
|
|
670
|
+
.filter(axis => rangeDiffers(range.declared, { ...range.declared, [axis]: range.measured[axis] }))
|
|
671
|
+
.map(axis => `${axis.toUpperCase()} ${mm(range.measured[axis].min)} – ${mm(range.measured[axis].max)}`)
|
|
672
|
+
.join(' · ')
|
|
673
|
+
})}
|
|
674
|
+
</p>
|
|
675
|
+
<button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size', range: range.measured, replace: true })}>
|
|
676
|
+
<md-icon>straighten</md-icon>${i18next.t('figure.button.remeasure-range')}
|
|
677
|
+
</button>`
|
|
678
|
+
: nothing}
|
|
679
|
+
${this.renderSizingRule(asset, unavailable)}
|
|
680
|
+
</section>`;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* How the figure grows. One ratio (`sizing: 'uniform'`) is said as it is; a figure with joints that has not declared
|
|
684
|
+
* it is offered it -- a mechanism does not shear -- and the author decides (chief architect's ruling 2026-09-24).
|
|
685
|
+
*/
|
|
686
|
+
renderSizingRule(asset, unavailable) {
|
|
687
|
+
if (asset.sizing === 'uniform')
|
|
688
|
+
return html `<div inline-field><label>${i18next.t('figure.label.sizing-rule')}</label><span>${i18next.t('figure.label.sizing-uniform')}</span></div>`;
|
|
689
|
+
return html `${turnsAboutAJoint(asset) ? html `<p hint>${i18next.t('figure.text.sizing-uniform-proposed')}</p>` : nothing}
|
|
690
|
+
<button act ?disabled=${unavailable} title=${i18next.t('figure.text.sizing-uniform-explained')} @click=${() => this.linkCommand({ kind: 'scale-uniformly' })}>
|
|
691
|
+
<md-icon>aspect_ratio</md-icon>${i18next.t('figure.button.scale-uniformly')}
|
|
692
|
+
</button>`;
|
|
693
|
+
}
|
|
694
|
+
/** Choose a part, from the list, the canvas or a place row. A figure-level tab gives way to the part's properties. */
|
|
695
|
+
choose(id) {
|
|
696
|
+
this.selected = id;
|
|
697
|
+
if (id && (this.tab === 'verdict' || this.tab === 'view' || this.tab === 'history'))
|
|
698
|
+
this.tab = 'properties';
|
|
699
|
+
}
|
|
700
|
+
asset() {
|
|
701
|
+
try {
|
|
702
|
+
return JSON.parse(this.session.source());
|
|
703
|
+
}
|
|
704
|
+
catch {
|
|
705
|
+
return undefined;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
renderTab(tab, icon, label, badge) {
|
|
709
|
+
return html `<button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>
|
|
710
|
+
<md-icon>${icon}</md-icon>${badge == null ? '' : html `<span badge>${badge.toLocaleString()}</span>`}
|
|
711
|
+
</button>`;
|
|
712
|
+
}
|
|
713
|
+
updated(changed) {
|
|
714
|
+
super.updated?.(changed);
|
|
715
|
+
const panes = this.renderRoot.querySelector('ox-three-pane') ?? undefined;
|
|
716
|
+
if (panes !== this.panes)
|
|
717
|
+
this.panes = panes;
|
|
718
|
+
}
|
|
719
|
+
/** Which part the panel speaks of, with the way back to the whole figure. */
|
|
720
|
+
renderSubject() {
|
|
721
|
+
if (!this.selected)
|
|
722
|
+
return null;
|
|
723
|
+
return html `<div subject>
|
|
724
|
+
<button chip title=${i18next.t('figure.button.whole-figure')} @click=${() => (this.selected = '')}>
|
|
725
|
+
<md-icon>arrow_back</md-icon>${i18next.t('figure.button.whole-figure')}
|
|
726
|
+
</button>
|
|
727
|
+
</div>`;
|
|
728
|
+
}
|
|
490
729
|
render() {
|
|
491
730
|
const view = this.session?.read();
|
|
492
731
|
if (!view)
|
|
493
|
-
return html `<p
|
|
732
|
+
return html `<p>${i18next.t('figure.text.opening-model')}</p>`;
|
|
494
733
|
const unavailable = view.loading || !!view.error || view.saving || !view.context;
|
|
495
734
|
const part = view.geometry.find(g => g.id === this.selected);
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
735
|
+
/* The controls (state inputs) have their own place in the motion tab; these are the design values. */
|
|
736
|
+
const design = view.fields.filter(i => i.role !== 'state');
|
|
737
|
+
const own = design.filter(i => this.selected && i.id.startsWith(this.selected + '.'));
|
|
738
|
+
const common = design.filter(i => !view.geometry.some(g => i.id.startsWith(g.id + '.')));
|
|
739
|
+
const status = view.loading
|
|
740
|
+
? 'figure.text.loading-figure'
|
|
741
|
+
: view.saving
|
|
742
|
+
? 'figure.text.saving-figure'
|
|
743
|
+
: view.dirty || !view.id
|
|
744
|
+
? 'figure.text.unsaved-changes'
|
|
745
|
+
: 'figure.text.saved';
|
|
746
|
+
const asset = this.asset();
|
|
747
|
+
const tab = this.tab;
|
|
748
|
+
return html `<ox-canvas-bar
|
|
749
|
+
history
|
|
750
|
+
fit
|
|
751
|
+
undo-shortcut="Ctrl+Z"
|
|
752
|
+
redo-shortcut="Ctrl+Y"
|
|
753
|
+
?can-undo=${!unavailable && view.canUndo}
|
|
754
|
+
?can-redo=${!unavailable && view.canRedo}
|
|
755
|
+
.pane=${this.panes}
|
|
756
|
+
@canvas-undo=${() => this.run(() => this.session.undo())}
|
|
757
|
+
@canvas-redo=${() => this.run(() => this.session.redo())}
|
|
758
|
+
@canvas-fit=${() => this.renderRoot.querySelector('figure-v3-canvas')?.fit()}
|
|
759
|
+
>
|
|
760
|
+
${['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)}>
|
|
761
|
+
${i18next.t('figure.label.mode-' + mode)}
|
|
762
|
+
</button>`)}
|
|
763
|
+
<span slot="status" class="status" role="status" ?dirty=${status === 'figure.text.unsaved-changes'}>${i18next.t(status)}</span>
|
|
764
|
+
<button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>${i18next.t('figure.button.save')}</button>
|
|
765
|
+
</ox-canvas-bar>
|
|
516
766
|
${this.error || view.error ? html `<div role="alert" class="error">${this.error || view.error}</div>` : null}
|
|
517
|
-
<
|
|
767
|
+
<ox-three-pane>
|
|
518
768
|
<figure-v3-parts
|
|
769
|
+
slot="start"
|
|
770
|
+
.budget=${this.budget}
|
|
771
|
+
@show-maturity=${() => (this.tab = 'verdict')}
|
|
519
772
|
.parts=${view.geometry.map((g) => ({
|
|
520
773
|
id: g.id,
|
|
521
774
|
provider: g.shape?.provider ?? '',
|
|
@@ -525,7 +778,7 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
525
778
|
.selected=${this.selected}
|
|
526
779
|
?locked=${unavailable}
|
|
527
780
|
@add-part=${(e) => this.add(e.detail.kind)}
|
|
528
|
-
@select-part=${(e) =>
|
|
781
|
+
@select-part=${(e) => this.choose(e.detail.id)}
|
|
529
782
|
@toggle-part-hidden=${(e) => this.toggleHiddenPart(e.detail.id)}
|
|
530
783
|
@set-all-parts-hidden=${(e) => (this.hiddenParts = e.detail.hidden ? new Set(view.geometry.map(g => g.id)) : new Set())}
|
|
531
784
|
@move-part-up=${(e) => this.movePart(e.detail.id, e.detail.to)}
|
|
@@ -533,47 +786,141 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
|
|
|
533
786
|
@duplicate-part=${(e) => this.duplicatePart(e.detail.id)}
|
|
534
787
|
@remove-part=${(e) => this.removePart(e.detail.id)}
|
|
535
788
|
></figure-v3-parts>
|
|
536
|
-
|
|
537
|
-
|
|
789
|
+
${this.middle === 'preview'
|
|
790
|
+
? html `<figure-v3-preview
|
|
791
|
+
.asset=${asset}
|
|
792
|
+
.context=${view.context ?? ''}
|
|
793
|
+
.view=${this.view}
|
|
794
|
+
@v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
|
|
795
|
+
></figure-v3-preview>`
|
|
796
|
+
: html `<figure-v3-canvas
|
|
797
|
+
.view=${this.view}
|
|
798
|
+
.parts=${this.drawnParts(view.geometry, asset)}
|
|
799
|
+
.fan=${this.fanOf(asset)}
|
|
538
800
|
.palette=${view.palette ?? {}}
|
|
539
801
|
.context=${view.context ?? ''}
|
|
540
802
|
.selected=${this.selected}
|
|
541
803
|
.reach=${unavailable ? { translate: false, rotate: false, scale: false } : this.reachOf(this.selected)}
|
|
542
804
|
@part-transformed=${(e) => this.transformed(e.detail.id, e.detail.result)}
|
|
543
|
-
@part-selected=${(e) =>
|
|
544
|
-
this.selected = e.detail;
|
|
545
|
-
}}
|
|
805
|
+
@part-selected=${(e) => this.choose(e.detail)}
|
|
546
806
|
@render-error=${(e) => {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
></figure-v3-canvas
|
|
550
|
-
<aside>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
807
|
+
this.error = e.detail.message;
|
|
808
|
+
}}
|
|
809
|
+
></figure-v3-canvas>`}
|
|
810
|
+
<aside slot="end">
|
|
811
|
+
<div tabs>
|
|
812
|
+
${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
|
|
813
|
+
${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}
|
|
814
|
+
${this.renderTab('motion', 'animation', i18next.t('figure.label.motion'))}
|
|
815
|
+
${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
|
|
816
|
+
${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}
|
|
817
|
+
${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
|
|
818
|
+
</div>
|
|
819
|
+
<slot name="notice"></slot>
|
|
820
|
+
<div panel ?off=${tab !== 'properties'}>
|
|
821
|
+
${this.selected && part
|
|
822
|
+
? html `${this.renderSubject()}
|
|
823
|
+
<section>
|
|
824
|
+
<h3>${i18next.t('figure.label.group-part')}</h3>
|
|
825
|
+
<div inline-field>
|
|
826
|
+
<label>${i18next.t('figure.label.name')}</label>
|
|
827
|
+
<input
|
|
828
|
+
name-field
|
|
829
|
+
aria-label=${i18next.t('figure.label.name')}
|
|
830
|
+
.value=${partLabelOf(this.selected)}
|
|
831
|
+
?disabled=${unavailable || !!repeatOf(this.selected, view.geometry)}
|
|
832
|
+
@change=${(e) => this.renamePart(e.target)}
|
|
833
|
+
/>
|
|
834
|
+
</div>
|
|
835
|
+
${this.renderRepeatNote(view.geometry)}
|
|
836
|
+
</section>
|
|
837
|
+
${this.renderGroups(groupFields(design, this.selected), 'figure.label.group-placement-size', 'figure.label.group-shape', unavailable, this.renderTools(unavailable))}
|
|
838
|
+
${this.renderLinks(unavailable, 'shape')}
|
|
839
|
+
<figure-v3-material
|
|
840
|
+
.body=${part}
|
|
841
|
+
.palette=${view.palette ?? {}}
|
|
842
|
+
?locked=${unavailable}
|
|
843
|
+
@v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
|
|
844
|
+
@v3-part-live=${(e) => this.hold({}, e.detail.edit)}
|
|
845
|
+
></figure-v3-material>`
|
|
846
|
+
: html `<slot name="figure"></slot>
|
|
847
|
+
${this.renderGroups(groupFields(common), 'figure.label.group-figure-size', 'figure.label.design-inputs', unavailable)}
|
|
848
|
+
${this.renderInstanceRange(asset, unavailable)}`}
|
|
849
|
+
</div>
|
|
850
|
+
<div panel ?off=${tab !== 'capability'}>
|
|
851
|
+
${this.renderSubject()}
|
|
852
|
+
${this.selected && part ? this.renderLinks(unavailable, 'places') : null}
|
|
853
|
+
<figure-v3-capability
|
|
854
|
+
.asset=${asset}
|
|
855
|
+
.selected=${this.selected}
|
|
856
|
+
?locked=${unavailable}
|
|
857
|
+
@select-part=${(e) => this.choose(e.detail.id)}
|
|
858
|
+
@v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
|
|
859
|
+
></figure-v3-capability>
|
|
860
|
+
</div>
|
|
861
|
+
<div panel ?off=${tab !== 'motion'}>
|
|
862
|
+
${this.renderSubject()}
|
|
863
|
+
${this.selected && part ? this.renderLinks(unavailable, 'motion') : html `<section><p hint>${i18next.t('figure.text.choose-a-part-to-move')}</p></section>`}
|
|
864
|
+
${this.renderControls(view, unavailable)}
|
|
865
|
+
</div>
|
|
866
|
+
<div panel flush ?off=${tab !== 'verdict'}><slot name="verdict"></slot></div>
|
|
867
|
+
<div panel flush ?off=${tab !== 'view'}>
|
|
868
|
+
<figure-settings .view=${this.view} @update-view=${(e) => this.setView(e.detail.view)}></figure-settings>
|
|
869
|
+
</div>
|
|
870
|
+
<div panel flush ?off=${tab !== 'history'}><slot name="history"></slot></div>
|
|
575
871
|
</aside>
|
|
576
|
-
</
|
|
872
|
+
</ox-three-pane>`;
|
|
873
|
+
}
|
|
874
|
+
/** Position, rotation and size as rows of three cells; the shape's own measures one to a line, under their title. */
|
|
875
|
+
renderGroups(groups, vectorTitle, singleTitle, unavailable, tools = null) {
|
|
876
|
+
return html `${groups.vectors.length || tools
|
|
877
|
+
? html `<section>
|
|
878
|
+
<h3>${i18next.t(vectorTitle)}</h3>
|
|
879
|
+
${groups.vectors.map(g => this.renderVector(g, unavailable))}
|
|
880
|
+
${tools ? html `<div vector-row><label>${i18next.t('figure.label.tools')}</label>${tools}</div>` : nothing}
|
|
881
|
+
</section>`
|
|
882
|
+
: nothing}
|
|
883
|
+
${groups.singles.length
|
|
884
|
+
? html `<section>
|
|
885
|
+
<h3>${i18next.t(singleTitle)}</h3>
|
|
886
|
+
<div form>${groups.singles.map(g => (g.kind === 'single' ? this.renderSingle(g.name, g.field, unavailable) : nothing))}</div>
|
|
887
|
+
</section>`
|
|
888
|
+
: nothing}`;
|
|
889
|
+
}
|
|
890
|
+
renderVector(group, unavailable) {
|
|
891
|
+
if (group.kind !== 'vector')
|
|
892
|
+
return nothing;
|
|
893
|
+
const unit = group.cells.x.unit === 'deg' ? '°' : group.cells.x.unit;
|
|
894
|
+
return html `<div vector-row>
|
|
895
|
+
<label>${i18next.t('figure.label.group-' + group.name)}${group.name === 'rotation' ? '' : html ` <span unit>${unit}</span>`}</label>
|
|
896
|
+
<div triple>
|
|
897
|
+
${['x', 'y', 'z'].map(axis => html `<div cell>
|
|
898
|
+
<span axis ?scrub=${!unavailable} @pointerdown=${(e) => this.scrub(group.cells[axis], e, unavailable)}>${axis.toUpperCase()}</span>
|
|
899
|
+
<input
|
|
900
|
+
aria-label=${group.cells[axis].id}
|
|
901
|
+
type="number"
|
|
902
|
+
step="any"
|
|
903
|
+
.value=${String(group.cells[axis].value)}
|
|
904
|
+
?disabled=${unavailable}
|
|
905
|
+
@input=${(e) => this.typing(group.cells[axis], e)}
|
|
906
|
+
@change=${(e) => this.changeField(group.cells[axis], e)}
|
|
907
|
+
/>
|
|
908
|
+
</div>`)}
|
|
909
|
+
</div>
|
|
910
|
+
</div>`;
|
|
911
|
+
}
|
|
912
|
+
renderSingle(name, field, unavailable) {
|
|
913
|
+
const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }));
|
|
914
|
+
return html `<span ?scrub=${!unavailable} @pointerdown=${(e) => this.scrub(field, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
915
|
+
><input
|
|
916
|
+
aria-label=${field.id}
|
|
917
|
+
type="number"
|
|
918
|
+
step="any"
|
|
919
|
+
.value=${String(field.value)}
|
|
920
|
+
?disabled=${unavailable}
|
|
921
|
+
@input=${(e) => this.typing(field, e)}
|
|
922
|
+
@change=${(e) => this.changeField(field, e)}
|
|
923
|
+
/>`;
|
|
577
924
|
}
|
|
578
925
|
};
|
|
579
926
|
__decorate([
|
|
@@ -584,6 +931,14 @@ __decorate([
|
|
|
584
931
|
property({ type: Number }),
|
|
585
932
|
__metadata("design:type", Object)
|
|
586
933
|
], FigureV3Editor.prototype, "revision", void 0);
|
|
934
|
+
__decorate([
|
|
935
|
+
property({ type: Number }),
|
|
936
|
+
__metadata("design:type", Number)
|
|
937
|
+
], FigureV3Editor.prototype, "instances", void 0);
|
|
938
|
+
__decorate([
|
|
939
|
+
property({ attribute: false }),
|
|
940
|
+
__metadata("design:type", Object)
|
|
941
|
+
], FigureV3Editor.prototype, "budget", void 0);
|
|
587
942
|
__decorate([
|
|
588
943
|
state(),
|
|
589
944
|
__metadata("design:type", Object)
|
|
@@ -592,11 +947,31 @@ __decorate([
|
|
|
592
947
|
state(),
|
|
593
948
|
__metadata("design:type", Object)
|
|
594
949
|
], FigureV3Editor.prototype, "error", void 0);
|
|
950
|
+
__decorate([
|
|
951
|
+
state(),
|
|
952
|
+
__metadata("design:type", String)
|
|
953
|
+
], FigureV3Editor.prototype, "tab", void 0);
|
|
954
|
+
__decorate([
|
|
955
|
+
state(),
|
|
956
|
+
__metadata("design:type", Function)
|
|
957
|
+
], FigureV3Editor.prototype, "panes", void 0);
|
|
958
|
+
__decorate([
|
|
959
|
+
state(),
|
|
960
|
+
__metadata("design:type", String)
|
|
961
|
+
], FigureV3Editor.prototype, "middle", void 0);
|
|
962
|
+
__decorate([
|
|
963
|
+
state(),
|
|
964
|
+
__metadata("design:type", Object)
|
|
965
|
+
], FigureV3Editor.prototype, "view", void 0);
|
|
595
966
|
__decorate([
|
|
596
967
|
state(),
|
|
597
968
|
__metadata("design:type", Object)
|
|
598
969
|
], FigureV3Editor.prototype, "hiddenParts", void 0);
|
|
599
|
-
|
|
970
|
+
__decorate([
|
|
971
|
+
state(),
|
|
972
|
+
__metadata("design:type", Object)
|
|
973
|
+
], FigureV3Editor.prototype, "live", void 0);
|
|
974
|
+
FigureV3Editor = FigureV3Editor_1 = __decorate([
|
|
600
975
|
customElement('figure-v3-editor')
|
|
601
976
|
], FigureV3Editor);
|
|
602
977
|
export { FigureV3Editor };
|