@things-factory/figure-ui 10.1.272 → 10.1.273
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/data-interface.ts +30 -18
- package/client/modeller/editor-styles.ts +17 -6
- package/client/modeller/editor.ts +2 -0
- package/client/modeller/fasten-form.ts +12 -2
- package/client/modeller/fasten-geometry.ts +5 -3
- package/client/modeller/figure-data-panel.ts +384 -200
- package/client/modeller/figure-fasten-drawing.ts +21 -13
- package/client/modeller/figure-links.ts +24 -9
- package/client/modeller/figure-parts.ts +7 -11
- package/client/modeller/figure-preview.ts +258 -76
- package/dist-client/modeller/clips.d.ts +1 -4
- package/dist-client/modeller/data-interface.d.ts +11 -8
- package/dist-client/modeller/data-interface.js +27 -17
- package/dist-client/modeller/data-interface.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +17 -6
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +2 -0
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +5 -0
- package/dist-client/modeller/fasten-form.js +8 -2
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.d.ts +3 -2
- package/dist-client/modeller/fasten-geometry.js +5 -3
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.d.ts +10 -19
- package/dist-client/modeller/figure-data-panel.js +382 -190
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.js +21 -13
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.js +31 -16
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-parts.js +7 -11
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +13 -0
- package/dist-client/modeller/figure-preview.js +257 -77
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/test/browser/editing-page.js +1 -1
- package/test/browser/links.check.mjs +30 -0
- package/test/browser/modeller.check.mjs +2 -2
- package/test/browser/parts-page.js +4 -0
- package/test/browser/parts.check.mjs +16 -10
- package/test/browser/screen-page.js +12 -1
- package/test/browser/screen.check.mjs +142 -11
- package/test/browser/serve-response.mjs +5 -2
- package/test/browser/side-panel-sweep.check.mjs +171 -10
- package/test/data-motion.test.ts +39 -8
- package/test/fasten-form.test.ts +25 -0
- package/test/fasten-geometry.test.ts +13 -0
- package/translations/en.json +23 -11
- package/translations/es.json +22 -10
- package/translations/ja.json +22 -10
- package/translations/ko.json +24 -12
- package/translations/ms.json +22 -10
- package/translations/zh.json +22 -10
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import '@material/web/icon/icon.js';
|
|
3
3
|
import { css, html, LitElement, nothing } from 'lit';
|
|
4
|
+
import JSON5 from 'json5';
|
|
5
|
+
import { ScrollbarStyles } from '@operato/styles';
|
|
4
6
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
5
7
|
import { i18next, localize } from '@operato/i18n';
|
|
6
8
|
import { unitWord } from './unit-words.js';
|
|
7
9
|
import { FIGURE_UNITS, figureDataExample, inferFigureDataSchema } from '@hatiolab/figure-model';
|
|
8
|
-
import { accessorOfRow, behaviorsOf, bindingDraftOf, domainOf, endlessJointsOf, fieldAt, fieldOfKind, fitsField, freeCodeOf, legendRoleOf, methodOf, methodsOf, partsOf, rotationStepsOf, sampleOf, schemaRowsOf, schemaWithField, schemaWithFieldReplaced, schemaWithoutField, schemaFollowingCodes, stateInputsOf, tableColumnsOf, tableDraftOf, tableWithKeys, tableWithLook, tableWithTargets } from './data-interface.js';
|
|
10
|
+
import { accessorOfRow, behaviorsOf, bindingDraftOf, domainOf, endlessJointsOf, fieldAt, fieldOfKind, fitsField, freeCodeOf, legendRoleOf, methodOf, methodsOf, freeNumberCodeOf, tableWithKeep, partsOf, rotationStepsOf, sampleOf, schemaRowsOf, schemaWithField, schemaWithFieldReplaced, schemaWithoutField, schemaFollowingCodes, stateInputsOf, tableColumnsOf, tableDraftOf, tableWithKeys, tableWithLook, tableWithTargets } from './data-interface.js';
|
|
9
11
|
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
|
|
10
12
|
import { dataMoveOf } from '@things-factory/figure-viewer/dist-client/data-connection.js';
|
|
11
|
-
import { kindWords, moveWords } from '@things-factory/figure-viewer/dist-client/data-words.js';
|
|
13
|
+
import { kindName, kindWords, moveWords } from '@things-factory/figure-viewer/dist-client/data-words.js';
|
|
12
14
|
import { figureBindingsTakenOver, figureCapabilityDescriptorOf } from '@hatiolab/things-scene';
|
|
13
15
|
import { consumerDescriptors } from './consumer-capabilities.js';
|
|
14
16
|
import { HelpToggles } from './help.js';
|
|
@@ -25,6 +27,8 @@ const KINDS = ['number', 'boolean', 'text', 'object', 'list'];
|
|
|
25
27
|
* running on it (`data-sample`); a sample legend pasted for a table's role shows it as a board's legend would
|
|
26
28
|
* (`legend-sample`).
|
|
27
29
|
*/
|
|
30
|
+
/** A binding being added to the open field: nothing chosen yet. */
|
|
31
|
+
const NO_CONNECTING = { method: '', behavior: '', parts: [], inputs: [], group: '' };
|
|
28
32
|
let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
29
33
|
constructor() {
|
|
30
34
|
super(...arguments);
|
|
@@ -32,13 +36,15 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
32
36
|
this.locked = false;
|
|
33
37
|
/** The part chosen in the modeller, put among a table's parts by one button. */
|
|
34
38
|
this.selected = '';
|
|
39
|
+
/** Whether the editor shows the preview: the sample shows there only, the edit canvas keeping the figure as made. */
|
|
40
|
+
this.previewing = false;
|
|
35
41
|
/** The schema row chosen, by its path; its field is edited under it. */
|
|
36
42
|
this.chosen = '';
|
|
37
43
|
this.adding = { name: '', kind: 'number' };
|
|
38
44
|
/** A site's data pasted to start the schema from; nothing is kept until the author says so. */
|
|
39
45
|
this.pasted = '';
|
|
40
46
|
/** A binding as it is being added: the field (or a new field's name), the method, the behavior or the table's targets. */
|
|
41
|
-
this.connecting = {
|
|
47
|
+
this.connecting = { ...NO_CONNECTING };
|
|
42
48
|
/** The binding opened to edit its rule, by id. */
|
|
43
49
|
this.opened = '';
|
|
44
50
|
/** Sample legends by role, as JSON typed; not saved. */
|
|
@@ -50,45 +56,87 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
50
56
|
this.sampling = false;
|
|
51
57
|
}
|
|
52
58
|
static { this.styles = [
|
|
59
|
+
/* The panel's scrollbars -- the sample's box among them -- in the shell's look, not the browser's. */
|
|
60
|
+
ScrollbarStyles,
|
|
53
61
|
SidePanelStyles,
|
|
54
62
|
css `
|
|
55
63
|
:host {
|
|
56
64
|
display: block;
|
|
57
65
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
gap: 2px;
|
|
61
|
-
margin: 0;
|
|
62
|
-
padding: 0;
|
|
63
|
-
list-style: none;
|
|
64
|
-
}
|
|
65
|
-
ul[tree] > li > button[row] {
|
|
66
|
+
/* Where the sample shows, said while editing, with the press that goes there. */
|
|
67
|
+
p[sample-where] {
|
|
66
68
|
display: flex;
|
|
67
69
|
flex-wrap: wrap;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: 4px 8px;
|
|
72
|
+
margin: 4px 0 0;
|
|
73
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
74
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
75
|
+
}
|
|
76
|
+
/*
|
|
77
|
+
The fields as a table -- field, kind, what it moves -- a line between rows: a list of two-line names with their
|
|
78
|
+
kinds pushed right ran together, one field not told from the next (user 2026-11-12).
|
|
79
|
+
*/
|
|
80
|
+
div[fields] {
|
|
81
|
+
display: grid;
|
|
82
|
+
grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr);
|
|
83
|
+
border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
84
|
+
}
|
|
85
|
+
div[fields] > div[head],
|
|
86
|
+
div[fields] > button[row] {
|
|
87
|
+
grid-column: 1 / -1;
|
|
88
|
+
display: grid;
|
|
89
|
+
grid-template-columns: subgrid;
|
|
90
|
+
column-gap: 8px;
|
|
91
|
+
align-items: center;
|
|
92
|
+
padding: 5px 6px;
|
|
93
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
94
|
+
}
|
|
95
|
+
div[fields] > div[head] {
|
|
96
|
+
font-size: var(--ui-t-micro, 11px);
|
|
97
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
98
|
+
}
|
|
99
|
+
div[fields] > button[row] {
|
|
70
100
|
min-height: var(--figure-control-height);
|
|
71
101
|
margin: 0;
|
|
72
|
-
|
|
73
|
-
border:
|
|
74
|
-
border-
|
|
102
|
+
border-top: none;
|
|
103
|
+
border-left: none;
|
|
104
|
+
border-right: none;
|
|
75
105
|
background: none;
|
|
76
106
|
color: inherit;
|
|
77
107
|
font: inherit;
|
|
78
108
|
text-align: left;
|
|
79
|
-
align-items: center;
|
|
80
109
|
cursor: pointer;
|
|
81
110
|
}
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
div[fields] > button[row]:hover {
|
|
112
|
+
background: var(--md-sys-color-surface-container-low, rgba(0, 0, 0, 0.03));
|
|
113
|
+
}
|
|
114
|
+
div[fields] > button[row][selected] {
|
|
115
|
+
background: var(--md-sys-color-secondary-container, rgba(0, 0, 0, 0.06));
|
|
116
|
+
}
|
|
117
|
+
div[fields] span[role='cell'] {
|
|
118
|
+
min-width: 0;
|
|
119
|
+
overflow-wrap: anywhere;
|
|
84
120
|
}
|
|
85
|
-
|
|
121
|
+
div[fields] span[name] {
|
|
122
|
+
display: grid;
|
|
123
|
+
}
|
|
124
|
+
div[fields] span[name] small,
|
|
125
|
+
div[fields] span[none] {
|
|
86
126
|
color: var(--md-sys-color-on-surface-variant);
|
|
87
127
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
128
|
+
div[fields] div[bindings] > h4 {
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 4px;
|
|
132
|
+
margin: 12px 0 6px;
|
|
133
|
+
font-size: var(--ui-t-body, 13px);
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
}
|
|
136
|
+
div[fields] > div[detail] {
|
|
137
|
+
grid-column: 1 / -1;
|
|
138
|
+
padding: 8px 6px;
|
|
139
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
92
140
|
}
|
|
93
141
|
section[item] {
|
|
94
142
|
display: grid;
|
|
@@ -132,18 +180,6 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
132
180
|
margin: 0;
|
|
133
181
|
padding: 0;
|
|
134
182
|
}
|
|
135
|
-
div[cases] {
|
|
136
|
-
display: grid;
|
|
137
|
-
grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
|
|
138
|
-
gap: 4px 6px;
|
|
139
|
-
align-items: center;
|
|
140
|
-
}
|
|
141
|
-
div[codes] {
|
|
142
|
-
display: grid;
|
|
143
|
-
grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) 30px;
|
|
144
|
-
gap: 4px 6px;
|
|
145
|
-
align-items: center;
|
|
146
|
-
}
|
|
147
183
|
div[codes] button[act] {
|
|
148
184
|
width: 30px;
|
|
149
185
|
margin: 0;
|
|
@@ -161,52 +197,132 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
161
197
|
padding: 0;
|
|
162
198
|
}
|
|
163
199
|
/* A table's rows: the key (a code, or from and to), a cell per column, and a remove button. */
|
|
164
|
-
|
|
200
|
+
/* A table's values, a box each: the value on the head line, its settings a property grid under it (user 2026-11-12). */
|
|
201
|
+
div[cases] {
|
|
165
202
|
display: grid;
|
|
166
|
-
gap:
|
|
203
|
+
gap: 8px;
|
|
204
|
+
}
|
|
205
|
+
div[case] {
|
|
206
|
+
border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
207
|
+
border-radius: 6px;
|
|
208
|
+
overflow: hidden;
|
|
209
|
+
}
|
|
210
|
+
div[case-head] {
|
|
211
|
+
display: flex;
|
|
167
212
|
align-items: center;
|
|
168
|
-
|
|
213
|
+
gap: 6px;
|
|
214
|
+
min-height: var(--figure-control-height);
|
|
215
|
+
padding: 2px 6px;
|
|
216
|
+
background: var(--md-sys-color-surface-container-low, rgba(0, 0, 0, 0.03));
|
|
169
217
|
}
|
|
170
|
-
div[
|
|
218
|
+
div[case-head] small {
|
|
171
219
|
color: var(--md-sys-color-on-surface-variant);
|
|
172
220
|
}
|
|
173
|
-
div[
|
|
174
|
-
|
|
221
|
+
div[case-head] input {
|
|
222
|
+
flex: 1;
|
|
175
223
|
min-width: 0;
|
|
176
|
-
width: 100%;
|
|
177
224
|
box-sizing: border-box;
|
|
178
225
|
}
|
|
179
|
-
div[
|
|
180
|
-
|
|
226
|
+
div[case-head] button[act] {
|
|
227
|
+
margin-left: auto;
|
|
228
|
+
}
|
|
229
|
+
div[case] > p[hint] {
|
|
181
230
|
margin: 0;
|
|
182
|
-
padding:
|
|
231
|
+
padding: 6px;
|
|
232
|
+
border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
233
|
+
}
|
|
234
|
+
/* The 「그대로 둠」 of the other-values box: a checkbox and its word beside the box's name. */
|
|
235
|
+
div[case-head] label[keep] {
|
|
236
|
+
display: flex;
|
|
237
|
+
align-items: center;
|
|
238
|
+
gap: 4px;
|
|
239
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
240
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
241
|
+
}
|
|
242
|
+
div[case-head] label[keep] input {
|
|
243
|
+
flex: none;
|
|
244
|
+
width: auto;
|
|
245
|
+
margin: 0;
|
|
246
|
+
}
|
|
247
|
+
/* A property grid: a line a setting, its name left, its field right, a line between and between the two. */
|
|
248
|
+
div[props] {
|
|
249
|
+
display: grid;
|
|
250
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
|
|
251
|
+
}
|
|
252
|
+
div[props] > span {
|
|
253
|
+
display: flex;
|
|
254
|
+
align-items: center;
|
|
255
|
+
min-width: 0;
|
|
256
|
+
min-height: var(--figure-control-height);
|
|
257
|
+
padding: 2px 6px;
|
|
258
|
+
border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
259
|
+
}
|
|
260
|
+
div[props] > span:nth-child(odd) {
|
|
261
|
+
border-right: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
262
|
+
}
|
|
263
|
+
div[props] > span:nth-child(odd) small {
|
|
264
|
+
overflow: hidden;
|
|
265
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
266
|
+
white-space: nowrap;
|
|
267
|
+
text-overflow: ellipsis;
|
|
268
|
+
}
|
|
269
|
+
div[props] input,
|
|
270
|
+
div[props] select {
|
|
271
|
+
width: 100%;
|
|
272
|
+
min-width: 0;
|
|
273
|
+
box-sizing: border-box;
|
|
183
274
|
}
|
|
184
275
|
/* A colour and the button that clears it, on one line at the height of the cells beside it. */
|
|
185
|
-
div[
|
|
276
|
+
div[props] span[colour] {
|
|
186
277
|
display: flex;
|
|
278
|
+
flex: 1;
|
|
187
279
|
gap: 2px;
|
|
188
280
|
align-items: center;
|
|
189
281
|
min-width: 0;
|
|
190
282
|
}
|
|
191
|
-
div[
|
|
283
|
+
div[props] span[colour] input[type='color'] {
|
|
192
284
|
flex: 1;
|
|
193
285
|
height: var(--figure-control-height);
|
|
194
286
|
padding: 0 2px;
|
|
195
287
|
}
|
|
196
|
-
div[
|
|
288
|
+
div[props] span[colour] button[act] {
|
|
197
289
|
flex: none;
|
|
198
290
|
width: 24px;
|
|
199
291
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
292
|
+
/* A table's targets as a property list: what each is, its name, its delete. */
|
|
293
|
+
div[targets] {
|
|
294
|
+
display: grid;
|
|
295
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
204
296
|
align-items: center;
|
|
297
|
+
border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
298
|
+
border-radius: 6px;
|
|
299
|
+
overflow: hidden;
|
|
205
300
|
}
|
|
206
|
-
div[
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
301
|
+
div[targets] > * {
|
|
302
|
+
min-width: 0;
|
|
303
|
+
padding: 2px 6px;
|
|
304
|
+
border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
305
|
+
}
|
|
306
|
+
div[targets] > small[head] {
|
|
307
|
+
grid-column: 1 / -1;
|
|
308
|
+
border-top: none;
|
|
309
|
+
padding: 4px 6px;
|
|
310
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
311
|
+
background: var(--md-sys-color-surface-container-low, rgba(0, 0, 0, 0.03));
|
|
312
|
+
}
|
|
313
|
+
div[targets] > span:not([none]):nth-of-type(odd) {
|
|
314
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
315
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
316
|
+
}
|
|
317
|
+
div[targets] > span[none] {
|
|
318
|
+
grid-column: 1 / -1;
|
|
319
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
320
|
+
}
|
|
321
|
+
div[targets] > button[act] {
|
|
322
|
+
margin: 0;
|
|
323
|
+
padding: 0 4px;
|
|
324
|
+
border: none;
|
|
325
|
+
border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
|
|
210
326
|
}
|
|
211
327
|
textarea {
|
|
212
328
|
width: 100%;
|
|
@@ -214,10 +330,11 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
214
330
|
box-sizing: border-box;
|
|
215
331
|
font-family: var(--ui-mono, ui-monospace, monospace);
|
|
216
332
|
font-size: inherit;
|
|
333
|
+
resize: vertical;
|
|
217
334
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
335
|
+
/* The sample is read whole, a record of a dozen lines: room for it, not six lines of it (user 2026-11-12). */
|
|
336
|
+
textarea[sample] {
|
|
337
|
+
min-height: 240px;
|
|
221
338
|
}
|
|
222
339
|
md-icon {
|
|
223
340
|
font-size: 16px;
|
|
@@ -232,7 +349,7 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
232
349
|
return nothing;
|
|
233
350
|
const rows = schemaRowsOf(asset);
|
|
234
351
|
return html `
|
|
235
|
-
${this.renderCapabilityData(asset)} ${this.renderSchema(asset, rows)} ${
|
|
352
|
+
${this.renderCapabilityData(asset)} ${this.renderSchema(asset, rows)} ${rows.length ? this.renderSample(asset) : nothing}
|
|
236
353
|
`;
|
|
237
354
|
}
|
|
238
355
|
/** The data a product's capability the figure wears reads itself (ADR-0167 14): said, read only. */
|
|
@@ -248,27 +365,43 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
248
365
|
/* ---------------------------------------------------------------- the schema */
|
|
249
366
|
renderSchema(asset, rows) {
|
|
250
367
|
const schema = asset.dataInterface?.schema ?? [];
|
|
368
|
+
const bindings = asset.dataInterface?.bindings ?? [];
|
|
369
|
+
const choices = behaviorsOf(asset);
|
|
370
|
+
const resting = figureBindingsTakenOver(asset);
|
|
251
371
|
return html `<section>
|
|
252
372
|
<h3>${i18next.t('figure.label.data-schema')}${this.help.button('figure.text.data-schema-explained')}</h3>
|
|
253
373
|
${this.help.hint('figure.text.data-schema-explained')}
|
|
254
374
|
${rows.length
|
|
255
|
-
? html `<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
375
|
+
? html `<div fields role="table">
|
|
376
|
+
<div head role="row">
|
|
377
|
+
<span role="columnheader">${i18next.t('figure.label.data-column-field')}</span>
|
|
378
|
+
<span role="columnheader">${i18next.t('figure.label.data-column-kind')}</span>
|
|
379
|
+
<span role="columnheader">${i18next.t('figure.label.data-column-moves')}</span>
|
|
380
|
+
</div>
|
|
381
|
+
${rows.map(row => {
|
|
382
|
+
/* What the field moves, named, in place of a count of bindings (「작동 1개」 said nothing, user 2026-11-12). */
|
|
383
|
+
const moves = row.bindings.map(id => bindings.find(b => b.id === id)).filter((b) => !!b).map(b => this.bindingWhat(asset, b));
|
|
384
|
+
return html `<button
|
|
385
|
+
row
|
|
386
|
+
role="row"
|
|
387
|
+
?selected=${row.path === this.chosen}
|
|
388
|
+
@click=${() => {
|
|
389
|
+
this.chosen = row.path === this.chosen ? '' : row.path;
|
|
390
|
+
this.connecting = { ...NO_CONNECTING };
|
|
391
|
+
}}
|
|
392
|
+
>
|
|
393
|
+
<span name role="cell" style="padding-left: ${row.depth * 14}px"><span>${row.name}</span>${row.label ? html `<small>${row.label}</small>` : nothing}</span>
|
|
394
|
+
<span role="cell" title=${kindWords(row.node)}>${kindName(row.node)}</span>
|
|
395
|
+
<span role="cell" ?none=${!moves.length}>${moves.length ? moves.join(', ') : '—'}</span>
|
|
260
396
|
</button>
|
|
261
|
-
${row.path === this.chosen ? this.renderField(asset, schema, row) : nothing}
|
|
262
|
-
|
|
263
|
-
</
|
|
397
|
+
${row.path === this.chosen ? html `<div detail>${this.renderField(asset, schema, row)} ${this.renderFieldBindings(asset, row, choices, resting)}</div>` : nothing}`;
|
|
398
|
+
})}
|
|
399
|
+
</div>`
|
|
264
400
|
: html `<p hint>${i18next.t('figure.text.data-no-fields')}</p>`}
|
|
401
|
+
${this.renderStrayBindings(asset, rows, choices, resting)} ${this.renderRotation(asset, rows)}
|
|
265
402
|
${this.renderAddField(schema, '')} ${this.renderFromSample(schema)}
|
|
266
403
|
</section>`;
|
|
267
404
|
}
|
|
268
|
-
/** A field's kind in the words every screen says it in (figure-viewer kindWords). */
|
|
269
|
-
kindWord(node) {
|
|
270
|
-
return kindWords(node);
|
|
271
|
-
}
|
|
272
405
|
/** The chosen field's own settings, and a field added under it when it holds fields. */
|
|
273
406
|
renderField(asset, schema, row) {
|
|
274
407
|
const field = fieldAt(schema, row.path);
|
|
@@ -379,7 +512,14 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
379
512
|
this.edit({ kind: 'set-data-schema', schema: schemaWithField(schema, under, { name: named, ...nodeOfKind(a.kind) }) });
|
|
380
513
|
this.adding = { ...a, name: '' };
|
|
381
514
|
};
|
|
382
|
-
|
|
515
|
+
const words = i18next.t(under ? 'figure.button.add-data-child' : 'figure.button.add-data-field', { parent: under });
|
|
516
|
+
/*
|
|
517
|
+
Folded under its summary, as a binding's form is: two fields always open under the table read as one more row of
|
|
518
|
+
it (user 2026-11-12). Open while the figure has no field yet: adding one is the only thing to do.
|
|
519
|
+
*/
|
|
520
|
+
return html `<details add-field ?open=${!schema.length}>
|
|
521
|
+
<summary>${words}</summary>
|
|
522
|
+
<div form>
|
|
383
523
|
<span>${i18next.t('figure.label.data-name')}</span>
|
|
384
524
|
<input
|
|
385
525
|
aria-label=${i18next.t('figure.label.data-name')}
|
|
@@ -393,8 +533,9 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
393
533
|
</select>
|
|
394
534
|
</div>
|
|
395
535
|
<button act ?disabled=${this.locked || !named} @click=${add}>
|
|
396
|
-
<md-icon>add</md-icon>${i18next.t(
|
|
397
|
-
</button
|
|
536
|
+
<md-icon>add</md-icon>${i18next.t('figure.button.add')}
|
|
537
|
+
</button>
|
|
538
|
+
</details>`;
|
|
398
539
|
}
|
|
399
540
|
/** A site's data pasted: the schema guessed from it, kept only when the author says so -- units, ranges and closed values are theirs to set. */
|
|
400
541
|
renderFromSample(schema) {
|
|
@@ -402,7 +543,7 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
402
543
|
let unreadable = false;
|
|
403
544
|
if (this.pasted.trim()) {
|
|
404
545
|
try {
|
|
405
|
-
guessed = inferFigureDataSchema(
|
|
546
|
+
guessed = inferFigureDataSchema(JSON5.parse(this.pasted));
|
|
406
547
|
}
|
|
407
548
|
catch {
|
|
408
549
|
unreadable = true;
|
|
@@ -427,32 +568,51 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
427
568
|
</details>`;
|
|
428
569
|
}
|
|
429
570
|
/* ---------------------------------------------------------------- the bindings */
|
|
571
|
+
/** What a binding moves: a table's parts, a value's behavior -- one wording for the binding's line and the field's row. */
|
|
572
|
+
bindingWhat(asset, b) {
|
|
573
|
+
return moveWords(dataMoveOf(asset, b.rule === 'table' ? { kind: 'table', targets: b.targets } : b.behavior));
|
|
574
|
+
}
|
|
430
575
|
/** What a behavior moves, in the words every screen says it in (figure-viewer dataMoveOf · moveWords). */
|
|
431
576
|
behaviorName(asset, b) {
|
|
432
577
|
return moveWords(dataMoveOf(asset, b));
|
|
433
578
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
579
|
+
/*
|
|
580
|
+
A field's bindings under the field, in its opened row: what the field moves and how, edited where the field is. A
|
|
581
|
+
list of bindings of its own below the fields said every field's name and move again (user 2026-11-12).
|
|
582
|
+
*/
|
|
583
|
+
renderFieldBindings(asset, row, choices, resting) {
|
|
584
|
+
const bindings = (asset.dataInterface?.bindings ?? []).filter(b => row.bindings.includes(b.id));
|
|
585
|
+
const bindable = row.node.kind !== 'object';
|
|
586
|
+
if (!bindable && !bindings.length)
|
|
587
|
+
return nothing;
|
|
588
|
+
return html `<div bindings>
|
|
589
|
+
<h4>${i18next.t('figure.label.data-bindings-title')}${this.help.button('figure.text.data-bindings-explained')}</h4>
|
|
440
590
|
${this.help.hint('figure.text.data-bindings-explained')}
|
|
441
591
|
${bindings.map(b => this.renderBinding(asset, choices, b, resting))}
|
|
442
|
-
${this.renderAddBinding(asset,
|
|
443
|
-
|
|
444
|
-
|
|
592
|
+
${bindable ? this.renderAddBinding(asset, row, choices) : nothing}
|
|
593
|
+
</div>`;
|
|
594
|
+
}
|
|
595
|
+
/** A binding no field reads -- its field renamed or taken out -- kept in sight below the fields, to delete or fix. */
|
|
596
|
+
renderStrayBindings(asset, rows, choices, resting) {
|
|
597
|
+
const read = new Set(rows.flatMap(r => r.bindings));
|
|
598
|
+
const stray = (asset.dataInterface?.bindings ?? []).filter(b => !read.has(b.id));
|
|
599
|
+
if (!stray.length)
|
|
600
|
+
return nothing;
|
|
601
|
+
return html `<div bindings>
|
|
602
|
+
<h4>${i18next.t('figure.label.data-bindings-without-field')}</h4>
|
|
603
|
+
${stray.map(b => this.renderBinding(asset, choices, b, resting))}
|
|
604
|
+
</div>`;
|
|
445
605
|
}
|
|
446
606
|
renderBinding(asset, choices, b, resting) {
|
|
447
607
|
const open = this.opened === b.id;
|
|
448
608
|
const set = (next) => this.edit({ kind: 'set-data-binding', binding: next });
|
|
449
|
-
const what =
|
|
609
|
+
const what = this.bindingWhat(asset, b);
|
|
450
610
|
const rests = resting.some(r => r.id === b.id);
|
|
451
611
|
return html `<section item>
|
|
452
612
|
<div head>
|
|
453
613
|
<button line aria-expanded=${open ? 'true' : 'false'} @click=${() => (this.opened = open ? '' : b.id)}>
|
|
454
614
|
<strong>${b.label ?? b.accessor}</strong> → ${what}
|
|
455
|
-
<small>${i18next.t('figure.
|
|
615
|
+
<small>${i18next.t('figure.text.data-method-does-' + methodOf(b))}${rests ? html ` <span rests>${i18next.t('figure.label.data-binding-resting')}</span>` : nothing}</small>
|
|
456
616
|
</button>
|
|
457
617
|
<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-binding')} @click=${() => this.edit({ kind: 'remove-data-binding', id: b.id })}>
|
|
458
618
|
<md-icon>delete</md-icon>
|
|
@@ -556,36 +716,61 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
556
716
|
<p hint>${i18next.t('figure.text.data-legend-explained')}</p>`
|
|
557
717
|
: nothing}`;
|
|
558
718
|
}
|
|
559
|
-
|
|
719
|
+
/*
|
|
720
|
+
Which parts, controls and motion group a table shows on, as a property list: a row a target chosen -- what it is,
|
|
721
|
+
its name, its delete -- and one choice to add the next. Every part and control as a tick box in one heap took two
|
|
722
|
+
screens and hid which were chosen (user 2026-11-12).
|
|
723
|
+
*/
|
|
560
724
|
renderTargets(parts, inputs, groups, t, on) {
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
725
|
+
const chosenParts = t.parts ?? [];
|
|
726
|
+
const chosenInputs = t.inputs ?? [];
|
|
727
|
+
const freeParts = parts.filter(p => !chosenParts.includes(p));
|
|
728
|
+
const freeInputs = inputs.filter(i => !chosenInputs.includes(i.id));
|
|
729
|
+
const labelOf = (id) => inputs.find(i => i.id === id)?.label ?? id;
|
|
730
|
+
const row = (kind, name, remove) => html `<span>${i18next.t(kind)}</span><span>${name}</span>
|
|
731
|
+
<button act ?disabled=${this.locked} title=${i18next.t('figure.button.data-remove-target')} @click=${remove}><md-icon>delete</md-icon></button>`;
|
|
732
|
+
const add = (e) => {
|
|
733
|
+
const select = e.target;
|
|
734
|
+
const [kind, id] = [select.value.slice(0, select.value.indexOf(':')), select.value.slice(select.value.indexOf(':') + 1)];
|
|
735
|
+
select.value = '';
|
|
736
|
+
if (kind === 'part')
|
|
737
|
+
on.part(id, true);
|
|
738
|
+
else if (kind === 'input')
|
|
739
|
+
on.input(id, true);
|
|
740
|
+
};
|
|
741
|
+
return html `<div targets>
|
|
742
|
+
<small head>${i18next.t('figure.label.data-targets')}</small>
|
|
743
|
+
${chosenParts.map(p => row('figure.label.data-target-parts', p, () => on.part(p, false)))}
|
|
744
|
+
${chosenInputs.map(id => row('figure.label.data-target-inputs', labelOf(id), () => on.input(id, false)))}
|
|
745
|
+
${chosenParts.length || chosenInputs.length ? nothing : html `<span none>${i18next.t('figure.text.data-no-targets')}</span>`}
|
|
569
746
|
</div>
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
747
|
+
<div form>
|
|
748
|
+
<span>${i18next.t('figure.label.data-target-add')}</span>
|
|
749
|
+
<select ?disabled=${this.locked || !(freeParts.length + freeInputs.length)} .value=${''} @change=${add}>
|
|
750
|
+
<option value="">${i18next.t('figure.label.data-target-choose')}</option>
|
|
751
|
+
${freeParts.length
|
|
752
|
+
? html `<optgroup label=${i18next.t('figure.label.data-target-parts')}>${freeParts.map(p => html `<option value=${'part:' + p}>${p}</option>`)}</optgroup>`
|
|
753
|
+
: nothing}
|
|
754
|
+
${freeInputs.length
|
|
755
|
+
? html `<optgroup label=${i18next.t('figure.label.data-target-inputs')}>${freeInputs.map(i => html `<option value=${'input:' + i.id}>${i.label}</option>`)}</optgroup>`
|
|
756
|
+
: nothing}
|
|
757
|
+
</select>
|
|
758
|
+
${this.selected && freeParts.includes(this.selected)
|
|
759
|
+
? html `<span></span><button act ?disabled=${this.locked} @click=${() => on.part(this.selected, true)}><md-icon>add</md-icon>${i18next.t('figure.button.data-add-selected-part', { part: this.selected })}</button>`
|
|
575
760
|
: nothing}
|
|
576
|
-
|
|
761
|
+
${groups.length
|
|
577
762
|
? html `<span>${i18next.t('figure.label.data-target-group')}</span>
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
763
|
+
<select ?disabled=${this.locked} @change=${(e) => on.group(e.target.value)}>
|
|
764
|
+
<option value="" ?selected=${!t.group}>${i18next.t('figure.label.data-none')}</option>
|
|
765
|
+
${groups.map(g => html `<option value=${g.id} ?selected=${g.id === t.group}>${g.label}</option>`)}
|
|
766
|
+
</select>`
|
|
582
767
|
: nothing}
|
|
583
|
-
|
|
768
|
+
</div>`;
|
|
584
769
|
}
|
|
585
770
|
/** The column heads of a table's rows, after the key's own one or two. */
|
|
586
771
|
columnHeads(columns) {
|
|
587
772
|
return columns.map(c => c.kind === 'value'
|
|
588
|
-
? html `<small>${c.label}
|
|
773
|
+
? html `<small>${c.label}</small>`
|
|
589
774
|
: html `<small>${i18next.t('figure.label.data-column-' + c.kind)}</small>`);
|
|
590
775
|
}
|
|
591
776
|
/** One look's cells, a column each: a colour, an effect, a control's value, a clip, a speed, a pause -- empty leaves the target as it is. */
|
|
@@ -607,7 +792,9 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
607
792
|
</select>`;
|
|
608
793
|
if (c.kind === 'value') {
|
|
609
794
|
const v = look.values?.[c.id];
|
|
610
|
-
|
|
795
|
+
/* Its unit and range in the field, not beside the name: 「fl-hip (deg -30~30)」 took two lines (user 2026-11-12). */
|
|
796
|
+
const range = [c.min !== undefined && c.max !== undefined ? `${c.min}~${c.max}` : '', c.unit ?? ''].filter(Boolean).join(' ');
|
|
797
|
+
return html `<input type="number" step="any" min=${c.min ?? ''} max=${c.max ?? ''} placeholder=${range} title=${range} aria-label=${c.label} .value=${v === undefined ? '' : String(v)} ?disabled=${this.locked} @change=${(e) => {
|
|
611
798
|
const n = e.target.valueAsNumber;
|
|
612
799
|
const values = { ...look.values };
|
|
613
800
|
if (Number.isNaN(n))
|
|
@@ -633,16 +820,32 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
633
820
|
</select>`;
|
|
634
821
|
});
|
|
635
822
|
}
|
|
823
|
+
/*
|
|
824
|
+
One value's settings as a property grid, a line a setting -- colour, effect, a control's value, clip, speed, pause --
|
|
825
|
+
its name left, its field right. A column a setting ran wider than the panel as controls were added (user 2026-11-12).
|
|
826
|
+
*/
|
|
827
|
+
renderLook(asset, columns, look, write) {
|
|
828
|
+
if (!columns.length)
|
|
829
|
+
return html `<p hint>${i18next.t('figure.text.data-table-needs-target')}</p>`;
|
|
830
|
+
const heads = this.columnHeads(columns);
|
|
831
|
+
const cells = this.lookCells(asset, columns, look, write);
|
|
832
|
+
return html `<div props>${columns.map((_, i) => html `<span>${heads[i]}</span><span>${cells[i]}</span>`)}</div>`;
|
|
833
|
+
}
|
|
636
834
|
/** The look of every other value: a row like the others, added or taken away; kept when a closed field's values are not all rows. */
|
|
637
|
-
renderOtherwise(asset, b, columns, needed, set
|
|
835
|
+
renderOtherwise(asset, b, columns, needed, set) {
|
|
638
836
|
const look = b.table.default;
|
|
639
837
|
if (look === undefined)
|
|
640
838
|
return html `<button act ?disabled=${this.locked} @click=${() => set(tableWithLook(b, 'default', {}))}><md-icon>add</md-icon>${i18next.t('figure.button.add-data-default')}</button>`;
|
|
641
839
|
const { default: _, ...rest } = b.table;
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
840
|
+
/* 「그대로 둠」 (ADR-0166 amendment 1): a value the table has no row for leaves the targets as it last showed them. */
|
|
841
|
+
const keep = look.keep === true;
|
|
842
|
+
return html `<div case>
|
|
843
|
+
<div case-head>
|
|
844
|
+
<strong>${i18next.t('figure.label.data-otherwise')}</strong>
|
|
845
|
+
<label keep><input type="checkbox" .checked=${keep} ?disabled=${this.locked} @change=${(e) => set(tableWithKeep(b, e.target.checked))} />${i18next.t('figure.label.data-keep')}</label>
|
|
846
|
+
${needed ? nothing : html `<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => set({ ...b, table: rest })}><md-icon>delete</md-icon></button>`}
|
|
847
|
+
</div>
|
|
848
|
+
${keep ? html `<p hint>${i18next.t('figure.text.data-keep')}</p>` : this.renderLook(asset, columns, look, l => set(tableWithLook(b, 'default', l)))}
|
|
646
849
|
</div>`;
|
|
647
850
|
}
|
|
648
851
|
/**
|
|
@@ -653,6 +856,8 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
653
856
|
const columns = tableColumnsOf(asset, b);
|
|
654
857
|
const node = fieldAt(asset.dataInterface?.schema ?? [], b.accessor.replace(/\[\d+\]/g, '[]'));
|
|
655
858
|
const boolean = node?.kind === 'boolean';
|
|
859
|
+
/* A number's codes are numbers, as its data gives them (ADR-0166 amendment 1). */
|
|
860
|
+
const numeric = node?.kind === 'number';
|
|
656
861
|
const rows = Object.entries(b.table).filter(([k]) => k !== 'default');
|
|
657
862
|
const domain = node ? domainOf(node).map(String) : [];
|
|
658
863
|
const covered = domain.length > 0 && domain.every(v => b.table[v] !== undefined);
|
|
@@ -664,25 +869,30 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
664
869
|
};
|
|
665
870
|
const rekey = (i, typed) => {
|
|
666
871
|
const key = typed.trim();
|
|
667
|
-
if (!key || key === 'default' || rows.some(([k], j) => j !== i && k === key))
|
|
872
|
+
if (!key || key === 'default' || rows.some(([k], j) => j !== i && k === key) || (numeric && !Number.isFinite(Number(key))))
|
|
668
873
|
return this.requestUpdate();
|
|
669
874
|
rekeyed(rows.map(([k, v], j) => (j === i ? [key, v] : [k, v])));
|
|
670
875
|
};
|
|
671
876
|
const word = (k) => (boolean ? i18next.t(k === 'true' ? 'figure.label.data-true' : 'figure.label.data-false') : k);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
${rows.map(([k, look], i) => html
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
877
|
+
/* A value a box: its code on the box's head line, its settings a property grid under it. */
|
|
878
|
+
return html `<div cases>
|
|
879
|
+
${rows.map(([k, look], i) => html `<div case>
|
|
880
|
+
<div case-head>
|
|
881
|
+
<small>${i18next.t('figure.label.data-case-value')}</small>
|
|
882
|
+
${boolean
|
|
883
|
+
? html `<strong>${word(k)}</strong>`
|
|
884
|
+
: html `<input .value=${k} inputmode=${numeric ? 'decimal' : 'text'} aria-label=${i18next.t('figure.label.data-case-value')} ?disabled=${this.locked} @change=${(e) => rekey(i, e.target.value)} />`}
|
|
885
|
+
${boolean || (rows.length < 2 && domain.length > 0)
|
|
886
|
+
? nothing
|
|
887
|
+
: html `<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-case')} @click=${() => rekeyed(rows.filter((_, j) => j !== i))}><md-icon>delete</md-icon></button>`}
|
|
888
|
+
</div>
|
|
889
|
+
${this.renderLook(asset, columns, look, l => set(tableWithLook(b, k, l)))}
|
|
890
|
+
</div>`)}
|
|
891
|
+
${this.renderOtherwise(asset, b, columns, domain.length > 0 && !covered, set)}
|
|
681
892
|
</div>
|
|
682
|
-
${this.renderOtherwise(asset, b, columns, domain.length > 0 && !covered, set, 1)}
|
|
683
893
|
${boolean
|
|
684
894
|
? nothing
|
|
685
|
-
: html `<button act ?disabled=${this.locked} @click=${() => rekeyed([...rows, [freeCodeOf(rows.map(([k]) => k)), {}]])}>
|
|
895
|
+
: html `<button act ?disabled=${this.locked} @click=${() => rekeyed([...rows, [(numeric ? freeNumberCodeOf : freeCodeOf)(rows.map(([k]) => k)), {}]])}>
|
|
686
896
|
<md-icon>add</md-icon>${i18next.t('figure.button.add-data-case')}
|
|
687
897
|
</button>`}`;
|
|
688
898
|
}
|
|
@@ -713,17 +923,21 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
713
923
|
const last = rows.at(-1);
|
|
714
924
|
const lastEnd = last ? ends(last[0]).to : '';
|
|
715
925
|
const open = !!last && lastEnd === '';
|
|
716
|
-
return html `<div
|
|
717
|
-
<small>${i18next.t('figure.label.data-range-from')}</small><small>${i18next.t('figure.label.data-range-to')}</small>${this.columnHeads(columns)}<span></span>
|
|
926
|
+
return html `<div cases>
|
|
718
927
|
${rows.map(([k, look], i) => {
|
|
719
928
|
const e = ends(k);
|
|
720
|
-
return html `<
|
|
721
|
-
<
|
|
722
|
-
|
|
723
|
-
|
|
929
|
+
return html `<div case>
|
|
930
|
+
<div case-head>
|
|
931
|
+
<input type="number" step="any" aria-label=${i18next.t('figure.label.data-range-from')} placeholder=${i18next.t('figure.label.data-range-from')} .value=${e.from} ?disabled=${this.locked} @change=${(ev) => rekey(i, 'from', ev.target.value)} />
|
|
932
|
+
<span>~</span>
|
|
933
|
+
<input type="number" step="any" aria-label=${i18next.t('figure.label.data-range-to')} placeholder=${i18next.t('figure.label.data-range-to')} .value=${e.to} ?disabled=${this.locked} @change=${(ev) => rekey(i, 'to', ev.target.value)} />
|
|
934
|
+
<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-range')} @click=${() => set(tableWithKeys(b, rows.filter((_, j) => j !== i)))}><md-icon>delete</md-icon></button>
|
|
935
|
+
</div>
|
|
936
|
+
${this.renderLook(asset, columns, look, l => set(tableWithLook(b, k, l)))}
|
|
937
|
+
</div>`;
|
|
724
938
|
})}
|
|
939
|
+
${this.renderOtherwise(asset, b, columns, false, set)}
|
|
725
940
|
</div>
|
|
726
|
-
${this.renderOtherwise(asset, b, columns, false, set, 2)}
|
|
727
941
|
${open ? html `<p hint>${i18next.t('figure.text.data-range-close-last')}</p>` : nothing}
|
|
728
942
|
<button act ?disabled=${this.locked || open} @click=${() => set(tableWithKeys(b, [...rows, [`${lastEnd}~`, {}]]))}>
|
|
729
943
|
<md-icon>add</md-icon>${i18next.t('figure.button.add-data-range')}
|
|
@@ -766,27 +980,23 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
766
980
|
* 「연결 추가」 (ADR-0166 10): a field there or a new name, then a method the field's kind allows. As it is or scaled,
|
|
767
981
|
* the one thing it moves; a table, the parts, controls and motion group it shows on -- a new name makes the field.
|
|
768
982
|
*/
|
|
769
|
-
|
|
770
|
-
|
|
983
|
+
/** A binding added to the field whose row is open: how it is read, and what it moves. */
|
|
984
|
+
renderAddBinding(asset, row, choices) {
|
|
771
985
|
const a = this.connecting;
|
|
772
|
-
|
|
773
|
-
const NEW = '+';
|
|
774
|
-
const row = a.path === NEW || !bindable.length ? null : (bindable.find(r => r.path === a.path) ?? bindable[0]);
|
|
775
|
-
const methods = methodsOf(row?.node ?? null, choices);
|
|
986
|
+
const methods = methodsOf(row.node, choices);
|
|
776
987
|
const method = methods.includes(a.method) ? a.method : methods[0];
|
|
777
988
|
const table = method === 'code' || method === 'ranges';
|
|
778
|
-
const fitting =
|
|
989
|
+
const fitting = !table ? choices.filter(c => fitsField(row.node, c)) : [];
|
|
779
990
|
const choice = fitting.find(c => c.key === a.behavior) ?? fitting[0];
|
|
780
991
|
const set = (patch) => (this.connecting = { ...a, ...patch });
|
|
781
992
|
const typed = (e) => e.target.value;
|
|
782
|
-
const name = a.name.trim();
|
|
783
993
|
const targets = { parts: a.parts, inputs: a.inputs, ...(a.group ? { group: a.group } : {}) };
|
|
784
994
|
const aimed = a.parts.length > 0 || a.inputs.length > 0 || !!a.group;
|
|
785
|
-
const ready = table ? aimed
|
|
995
|
+
const ready = table ? aimed : !!choice;
|
|
786
996
|
const add = () => {
|
|
787
997
|
if (table) {
|
|
788
|
-
const { binding
|
|
789
|
-
this.edit({ kind: 'set-data-binding', binding
|
|
998
|
+
const { binding } = tableDraftOf(asset, { path: row.path, node: row.node }, method, targets);
|
|
999
|
+
this.edit({ kind: 'set-data-binding', binding });
|
|
790
1000
|
this.opened = binding.id;
|
|
791
1001
|
}
|
|
792
1002
|
else {
|
|
@@ -796,19 +1006,13 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
796
1006
|
this.edit({ kind: 'set-data-binding', binding });
|
|
797
1007
|
this.opened = binding.id;
|
|
798
1008
|
}
|
|
799
|
-
this.connecting = {
|
|
1009
|
+
this.connecting = { ...NO_CONNECTING };
|
|
800
1010
|
};
|
|
801
1011
|
const toggle = (list, id, on) => (on ? [...list, id] : list.filter(x => x !== id));
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
<option value=${NEW} ?selected=${row === null}>${i18next.t('figure.label.data-new-field')}</option>
|
|
807
|
-
</select>
|
|
808
|
-
${row === null
|
|
809
|
-
? html `<span>${i18next.t('figure.label.data-name')}</span>
|
|
810
|
-
<input .value=${a.name} @input=${(e) => set({ name: typed(e) })} />`
|
|
811
|
-
: nothing}
|
|
1012
|
+
/* Folded until asked for: open under a field's binding, its fields read as that binding's own (user 2026-11-12). */
|
|
1013
|
+
return html `<details add-binding>
|
|
1014
|
+
<summary>${i18next.t('figure.button.add-data-binding')}</summary>
|
|
1015
|
+
<div form>
|
|
812
1016
|
<span>${i18next.t('figure.label.data-method')}</span>
|
|
813
1017
|
${methods.length === 1
|
|
814
1018
|
? /* One way only (a text is read by a code table): said, not offered as a choice of one (sweep 2026-10-27 U10). */
|
|
@@ -833,8 +1037,9 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
833
1037
|
${table && !aimed ? html `<p hint>${i18next.t('figure.text.data-table-needs-target')}</p>` : nothing}
|
|
834
1038
|
${methods.length ? nothing : html `<p hint>${i18next.t('figure.text.data-no-behavior')}</p>`}
|
|
835
1039
|
<button act ?disabled=${this.locked || !ready} @click=${add}>
|
|
836
|
-
<md-icon>add</md-icon>${i18next.t('figure.button.add
|
|
837
|
-
</button
|
|
1040
|
+
<md-icon>add</md-icon>${i18next.t('figure.button.add')}
|
|
1041
|
+
</button>
|
|
1042
|
+
</details>`;
|
|
838
1043
|
}
|
|
839
1044
|
/**
|
|
840
1045
|
* 「+ 회전」: a joint that turns without end, turned by the data -- on, speed in rpm, direction -- in one step. The
|
|
@@ -913,41 +1118,29 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
913
1118
|
/* ---------------------------------------------------------------- the sample */
|
|
914
1119
|
/** Data to try, read the way a placed figure reads it: what it moves, or why it is refused; in the preview, the figure running on it. */
|
|
915
1120
|
renderSample(asset) {
|
|
916
|
-
|
|
1121
|
+
/* Written and read as JSON5: names without quotes, comments and a last comma are allowed when typed by hand (user 2026-11-12). */
|
|
1122
|
+
const text = this.sample || JSON5.stringify(figureDataExample(asset.dataInterface), null, 2);
|
|
917
1123
|
let data;
|
|
918
1124
|
let unreadable = false;
|
|
919
1125
|
try {
|
|
920
|
-
data =
|
|
1126
|
+
data = JSON5.parse(text);
|
|
921
1127
|
}
|
|
922
1128
|
catch {
|
|
923
1129
|
unreadable = true;
|
|
924
1130
|
}
|
|
925
1131
|
const legends = this.sampleLegends();
|
|
926
1132
|
const read = unreadable ? null : sampleOf(asset, data, legends);
|
|
927
|
-
const r = read?.result;
|
|
928
1133
|
const tables = (asset.dataInterface?.bindings ?? []).filter((b) => b.rule === 'table');
|
|
929
1134
|
return html `<section>
|
|
930
1135
|
<h3>${i18next.t('figure.label.data-sample')}${this.help.button('figure.text.data-sample-explained')}</h3>
|
|
931
1136
|
${this.help.hint('figure.text.data-sample-explained')}
|
|
932
|
-
<textarea .value=${text} @input=${(e) => this.try(e.target.value)}></textarea>
|
|
1137
|
+
<textarea sample .value=${text} @input=${(e) => this.try(e.target.value)}></textarea>
|
|
933
1138
|
${unreadable
|
|
934
1139
|
? html `<p hint>${i18next.t('figure.text.data-not-json')}</p>`
|
|
935
1140
|
: read.issues.length
|
|
936
1141
|
? html `<p hint>${read.issues.map(i => i18next.t('figure.text.data-value-' + i.reason, { path: i.path || '-' })).join(' · ')}</p>`
|
|
937
|
-
:
|
|
938
|
-
${Object.entries(r.states).map(([input, s]) => html `<li>${asset.stateInputs?.[input]?.label ?? input} = ${s.value}</li>`)}
|
|
939
|
-
${Object.entries(r.palette).map(([token, colour]) => html `<li>${token} = ${colour}</li>`)}
|
|
940
|
-
${Object.entries(r.motion).map(([group, m]) => html `<li>
|
|
941
|
-
${(asset.motionGroups ?? []).find(g => g.id === group)?.label ?? group}:
|
|
942
|
-
${[m.clip, m.speed === undefined ? '' : `× ${m.speed}`, m.paused ? i18next.t('figure.label.data-paused') : ''].filter(Boolean).join(' ')}
|
|
943
|
-
</li>`)}
|
|
944
|
-
${tables.map(b => this.renderTableUse(asset, b, read.uses[b.id]))}
|
|
945
|
-
${Object.entries(r.parts).map(([part, look]) => html `<li>${part}: ${[look.color, look.effect ? i18next.t('figure.label.data-effect-' + look.effect.kind) : ''].filter(Boolean).join(' ')}</li>`)}
|
|
946
|
-
${Object.entries(r.pose).map(([axis, p]) => html `<li>${i18next.t('figure.label.data-axis-' + axis)} = ${p.value}</li>`)}
|
|
947
|
-
${r.items.map(i => html `<li>${i18next.t('figure.label.data-items-count', { count: i.records.length })}${i.records.length ? `: ${i.records.map(x => x.id).join(', ')}` : ''}</li>`)}
|
|
948
|
-
</ul>`}
|
|
1142
|
+
: nothing}
|
|
949
1143
|
${read?.notes.length ? html `<p hint>${read.notes.map(n => i18next.t('figure.text.data-note-' + n.reason, { binding: n.binding ?? '', key: n.key ?? '', column: n.column ? i18next.t('figure.label.data-column-' + n.column) : '', value: shownValue(n.value) })).join(' · ')}</p>` : nothing}
|
|
950
|
-
${r && Object.keys(r.pose).length ? html `<p hint>${i18next.t('figure.text.data-sample-moves-all')}</p>` : nothing}
|
|
951
1144
|
${this.renderSampleLegends(tables)}
|
|
952
1145
|
<label toggle>
|
|
953
1146
|
<input
|
|
@@ -960,17 +1153,16 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
960
1153
|
/>
|
|
961
1154
|
${i18next.t('figure.label.data-sample-in-preview')}
|
|
962
1155
|
</label>
|
|
1156
|
+
${this.sampling && !this.previewing
|
|
1157
|
+
? html `<p hint sample-where>
|
|
1158
|
+
${i18next.t('figure.text.data-sample-shown-in-preview')}
|
|
1159
|
+
<button quiet @click=${() => this.dispatchEvent(new CustomEvent('show-preview', { bubbles: true, composed: true }))}>
|
|
1160
|
+
${i18next.t('figure.button.data-sample-show-preview')}
|
|
1161
|
+
</button>
|
|
1162
|
+
</p>`
|
|
1163
|
+
: nothing}
|
|
963
1164
|
</section>`;
|
|
964
1165
|
}
|
|
965
|
-
/** Which row of a table the sample's value matched, and from which table: the figure's own or the sample legend of its role. */
|
|
966
|
-
renderTableUse(asset, b, use) {
|
|
967
|
-
if (!use || use.value === undefined)
|
|
968
|
-
return nothing;
|
|
969
|
-
const word = (v) => (typeof v === 'boolean' || v === 'true' || v === 'false' ? i18next.t(String(v) === 'true' ? 'figure.label.data-true' : 'figure.label.data-false') : String(v));
|
|
970
|
-
const row = use.key === null ? i18next.t('figure.label.data-none') : use.key === 'default' ? i18next.t('figure.label.data-otherwise') : word(use.key);
|
|
971
|
-
const from = use.source === 'figure' ? i18next.t('figure.label.data-table-own') : i18next.t('figure.label.data-table-legend', { name: use.legend ?? '' });
|
|
972
|
-
return html `<li>${b.label ?? b.accessor} = ${word(use.value)} → ${row} <small>(${from})</small></li>`;
|
|
973
|
-
}
|
|
974
1166
|
/** The sample legends as read: by role, those that are JSON. */
|
|
975
1167
|
sampleLegends() {
|
|
976
1168
|
const out = {};
|
|
@@ -978,7 +1170,7 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
978
1170
|
if (!text.trim())
|
|
979
1171
|
continue;
|
|
980
1172
|
try {
|
|
981
|
-
out[role] =
|
|
1173
|
+
out[role] = JSON5.parse(text);
|
|
982
1174
|
}
|
|
983
1175
|
catch {
|
|
984
1176
|
/* Not JSON yet: the figure's own table shows. */
|
|
@@ -1011,7 +1203,7 @@ let FigureDataPanel = class FigureDataPanel extends localize(i18next)(LitElement
|
|
|
1011
1203
|
try(typed) {
|
|
1012
1204
|
this.sample = typed;
|
|
1013
1205
|
try {
|
|
1014
|
-
this.tell(
|
|
1206
|
+
this.tell(JSON5.parse(typed));
|
|
1015
1207
|
}
|
|
1016
1208
|
catch {
|
|
1017
1209
|
/* Not JSON yet: the preview keeps what it ran on. */
|
|
@@ -1037,6 +1229,10 @@ __decorate([
|
|
|
1037
1229
|
property(),
|
|
1038
1230
|
__metadata("design:type", Object)
|
|
1039
1231
|
], FigureDataPanel.prototype, "selected", void 0);
|
|
1232
|
+
__decorate([
|
|
1233
|
+
property({ type: Boolean }),
|
|
1234
|
+
__metadata("design:type", Object)
|
|
1235
|
+
], FigureDataPanel.prototype, "previewing", void 0);
|
|
1040
1236
|
__decorate([
|
|
1041
1237
|
state(),
|
|
1042
1238
|
__metadata("design:type", Object)
|
|
@@ -1087,8 +1283,4 @@ function nodeOfKind(kind) {
|
|
|
1087
1283
|
}
|
|
1088
1284
|
/** A value in a sentence: a text as it is, anything else as JSON. */
|
|
1089
1285
|
const shownValue = (v) => (typeof v === 'string' ? v : JSON.stringify(v ?? ''));
|
|
1090
|
-
/** A table row's columns: the key's one or two, a cell per column, and the remove button. */
|
|
1091
|
-
function gridOf(keyCells, columns) {
|
|
1092
|
-
return `${'minmax(64px, 1fr) '.repeat(keyCells)}${'minmax(72px, 1fr) '.repeat(columns)}30px`;
|
|
1093
|
-
}
|
|
1094
1286
|
//# sourceMappingURL=figure-data-panel.js.map
|