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