@things-factory/figure-ui 10.1.160 → 10.1.162
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 +17 -0
- package/client/modeller/figure-preview.ts +51 -15
- package/client/pages/figure-list-page.ts +5 -72
- package/client/setting-let/figure-namespace-setting-let.ts +2 -1
- package/client/viewparts/figure-thumb.ts +10 -37
- package/dist-client/modeller/data-interface.d.ts +9 -0
- package/dist-client/modeller/data-interface.js +18 -0
- package/dist-client/modeller/data-interface.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +5 -0
- package/dist-client/modeller/figure-preview.js +51 -13
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +5 -72
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/setting-let/figure-namespace-setting-let.js +2 -1
- package/dist-client/setting-let/figure-namespace-setting-let.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/figure-thumb.d.ts +3 -1
- package/dist-client/viewparts/figure-thumb.js +10 -37
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +4 -4
- package/test/browser/product-page.check.mjs +1 -1
- package/test/browser/screen.check.mjs +7 -7
- package/test/data-motion.test.ts +16 -1
|
@@ -108,3 +108,20 @@ export function dataSampleOf(asset: FigureAsset, values: Record<string, FigureDa
|
|
|
108
108
|
const issues = validateFigureDataValues(plan, values)
|
|
109
109
|
return issues.length ? { issues } : { result: evaluateFigureDataInterface(plan, values) }
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Which fields of each motion group the data interface writes: its clip (a lookup to `motion-clip`), its speed
|
|
114
|
+
* (`motion-speed`). The data always writes them -- from a live value, a saved one, or the property's default -- and
|
|
115
|
+
* wins over a group's own setting for that field (detail design §5.1), so a control for such a field moves nothing.
|
|
116
|
+
*/
|
|
117
|
+
export function motionWrittenByData(asset: FigureAsset): Record<string, { clip: boolean; speed: boolean }> {
|
|
118
|
+
const out: Record<string, { clip: boolean; speed: boolean }> = {}
|
|
119
|
+
for (const c of asset.dataInterface?.connections ?? []) {
|
|
120
|
+
if (c.target.kind !== 'motion-clip' && c.target.kind !== 'motion-speed') continue
|
|
121
|
+
const one = (out[c.target.group] ??= { clip: false, speed: false })
|
|
122
|
+
if (c.target.kind === 'motion-clip') one.clip = true
|
|
123
|
+
else one.speed = true
|
|
124
|
+
}
|
|
125
|
+
return out
|
|
126
|
+
}
|
|
127
|
+
|
|
@@ -3,11 +3,12 @@ import { customElement, property, query, state } from 'lit/decorators.js'
|
|
|
3
3
|
|
|
4
4
|
import { i18next, localize } from '@operato/i18n'
|
|
5
5
|
import { compileFigureAsset } from '@hatiolab/figure-model'
|
|
6
|
-
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
6
|
+
import type { FigureAsset, FigureDataProperty, FigureDataValue } from '@hatiolab/figure-model'
|
|
7
7
|
import { create, registerFigure, unregisterFigure } from '@hatiolab/things-scene'
|
|
8
8
|
|
|
9
9
|
import { DEFAULT_VIEW, previewBoardDepth, sceneLook, WORKBENCH, type ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js'
|
|
10
10
|
import { dialShape } from './angle-dial.js'
|
|
11
|
+
import { domainOf, motionWrittenByData, sampleValueOf } from './data-interface.js'
|
|
11
12
|
import { derivedOf, type FigureRevision } from './revision.js'
|
|
12
13
|
import { authoredSizeOf, PREVIEW_FACTOR, PREVIEW_WAYS, previewClampsOf, previewInstances, previewTypeOf, setupPartOf } from '@things-factory/figure-viewer/dist-client/preview-model.js'
|
|
13
14
|
|
|
@@ -208,6 +209,8 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
208
209
|
@state() private giving: Record<string, number> = {}
|
|
209
210
|
/** Per motion group, the clip played here and how fast (ADR-0051 §3.2) -- the preview's, as a board's \`motion\`. */
|
|
210
211
|
@state() private moving: Record<string, { clip: string; speed: number }> = {}
|
|
212
|
+
/** Data values given here, as a board's saved `figureValues`: what moves a group whose clip or speed the data writes. */
|
|
213
|
+
@state() private valuing: Record<string, FigureDataValue> = {}
|
|
211
214
|
/** Data values tried in the data tab: the preview's figures run on them as a live snapshot; null when none are. */
|
|
212
215
|
@property({ attribute: false }) sample: Record<string, unknown> | null = null
|
|
213
216
|
/** The part chosen in the editor. The size help starts on it, so the help speaks of the part the author was working on. */
|
|
@@ -261,7 +264,7 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
261
264
|
</select>
|
|
262
265
|
<button @click=${() => this.dispatchEvent(new CustomEvent('configure-size', { detail: { part: setupPart }, bubbles: true, composed: true }))}>${i18next.t('figure.button.configure-size')}</button>
|
|
263
266
|
</div>` : nothing}
|
|
264
|
-
${controls.length + clips.length + (asset?.motionGroups?.length ?? 0) ? this.renderDrivers(asset!, controls, clips) : nothing}</div>`}`
|
|
267
|
+
${controls.length + clips.length + (asset?.motionGroups?.length ?? 0) + (asset?.dataInterface?.properties.length ?? 0) ? this.renderDrivers(asset!, controls, clips) : nothing}</div>`}`
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
private renderDrivers(asset: FigureAsset, controls: FigureAsset['document']['model']['inputs'], clips: string[]) {
|
|
@@ -303,24 +306,56 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
303
306
|
<output>${value.toFixed(2)}</output>
|
|
304
307
|
</label>`
|
|
305
308
|
})}
|
|
309
|
+
${(asset.dataInterface?.properties ?? []).map(property => this.renderDataValue(property))}
|
|
306
310
|
${(asset.motionGroups ?? []).map(group => {
|
|
307
311
|
const playing = this.moving[group.id] ?? { clip: group.defaultClip, speed: 0 }
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
312
|
+
/* A field the data writes wins over the group's own setting, here as on a board: it is moved by the data values above. */
|
|
313
|
+
const byData = motionWrittenByData(asset)[group.id] ?? { clip: false, speed: false }
|
|
314
|
+
return html`${byData.clip
|
|
315
|
+
? nothing
|
|
316
|
+
: html`<label>
|
|
317
|
+
<span>${group.label} <span drive>${i18next.t('figure.label.clip')}</span></span>
|
|
318
|
+
<select aria-label=${group.label} @change=${(e: Event) => this.move(group.id, { ...playing, clip: (e.target as HTMLSelectElement).value })}>
|
|
319
|
+
${group.clips.map(c => html`<option value=${c} ?selected=${c === playing.clip}>${c}</option>`)}
|
|
320
|
+
</select>
|
|
321
|
+
</label>`}
|
|
322
|
+
${byData.speed
|
|
323
|
+
? nothing
|
|
324
|
+
: html`<label>
|
|
325
|
+
<span>${group.label} <span drive>${i18next.t('figure.label.playback-speed')}</span></span>
|
|
326
|
+
<span></span>
|
|
327
|
+
<input type="range" min="0" max="3" step="0.05" .value=${String(playing.speed)} @input=${(e: Event) => this.move(group.id, { ...playing, speed: Number((e.target as HTMLInputElement).value) })} />
|
|
328
|
+
<output>${playing.speed.toFixed(2)}</output>
|
|
329
|
+
</label>`}`
|
|
320
330
|
})}
|
|
321
331
|
</div>`
|
|
322
332
|
}
|
|
323
333
|
|
|
334
|
+
/** A data property's value, as the board's data values give it: a number on a slider, a boolean or an enum chosen. */
|
|
335
|
+
private renderDataValue(property: FigureDataProperty) {
|
|
336
|
+
const value = this.valuing[property.id] ?? property.default
|
|
337
|
+
if (property.kind === 'number') {
|
|
338
|
+
const step = Math.max((property.max - property.min) / 100, 0.01)
|
|
339
|
+
return html`<label>
|
|
340
|
+
<span>${property.label} <span drive>${property.unit}</span></span>
|
|
341
|
+
<span></span>
|
|
342
|
+
<input type="range" min=${property.min} max=${property.max} step=${step} .value=${String(value)} @input=${(e: Event) => this.value(property, (e.target as HTMLInputElement).value)} />
|
|
343
|
+
<output>${Number(value).toFixed(2)}</output>
|
|
344
|
+
</label>`
|
|
345
|
+
}
|
|
346
|
+
return html`<label>
|
|
347
|
+
<span>${property.label}</span>
|
|
348
|
+
<select aria-label=${property.label} @change=${(e: Event) => this.value(property, (e.target as HTMLSelectElement).value)}>
|
|
349
|
+
${domainOf(property).map(v => html`<option value=${String(v)} ?selected=${v === value}>${typeof v === 'boolean' ? i18next.t(v ? 'figure.label.data-true' : 'figure.label.data-false') : v}</option>`)}
|
|
350
|
+
</select>
|
|
351
|
+
</label>`
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private value(property: FigureDataProperty, typed: string) {
|
|
355
|
+
this.valuing = { ...this.valuing, [property.id]: sampleValueOf(property, typed) }
|
|
356
|
+
this.tellInstances('figureValues', this.valuing)
|
|
357
|
+
}
|
|
358
|
+
|
|
324
359
|
private move(group: string, playing: { clip: string; speed: number }) {
|
|
325
360
|
this.moving = { ...this.moving, [group]: playing }
|
|
326
361
|
this.tellInstances('motion', Object.fromEntries(Object.entries(this.moving).map(([id, m]) => [id, { ...m, paused: false }])))
|
|
@@ -352,7 +387,7 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
352
387
|
this.giving = {}
|
|
353
388
|
}
|
|
354
389
|
|
|
355
|
-
private tellInstances(key: 'clips' | 'parameters' | 'motion' | 'figureData', values: Record<string, unknown> | null) {
|
|
390
|
+
private tellInstances(key: 'clips' | 'parameters' | 'motion' | 'figureData' | 'figureValues', values: Record<string, unknown> | null) {
|
|
356
391
|
for (const component of this.scene?.root?.components ?? []) {
|
|
357
392
|
const value = values === null ? null : { ...values }
|
|
358
393
|
/* A live snapshot is transient on the figure (its \`figureData\` accessor), never written into the saved model. */
|
|
@@ -436,6 +471,7 @@ export class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
436
471
|
if (!again) this.teardown()
|
|
437
472
|
this.driving = {}
|
|
438
473
|
this.moving = {}
|
|
474
|
+
this.valuing = {}
|
|
439
475
|
registerFigure(type, asset)
|
|
440
476
|
this.registered = type
|
|
441
477
|
const { components, board } = previewInstances(type, size)
|
|
@@ -10,7 +10,7 @@ import { i18next, localize } from '@operato/i18n'
|
|
|
10
10
|
import { p13n } from '@operato/p13n'
|
|
11
11
|
import { auth } from '@things-factory/auth-base/dist-client'
|
|
12
12
|
import { navigate, PageView } from '@operato/shell'
|
|
13
|
-
import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'
|
|
13
|
+
import { CardCatalogStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'
|
|
14
14
|
|
|
15
15
|
import { capacityFrom } from '../modeller/scene-budget.js'
|
|
16
16
|
import { deleteFigure, fetchFigureList } from '../graphql/index.js'
|
|
@@ -84,88 +84,21 @@ export class FigureListPage extends FigureListPageBase {
|
|
|
84
84
|
ScrollbarStyles,
|
|
85
85
|
CommonGristStyles,
|
|
86
86
|
CommonHeaderStyles,
|
|
87
|
+
CardCatalogStyles,
|
|
87
88
|
css`
|
|
88
89
|
:host {
|
|
89
90
|
background-color: var(--md-sys-color-surface);
|
|
90
91
|
color: var(--md-sys-color-on-surface);
|
|
91
|
-
/* 떠 있는 단추의 기준 상자 */
|
|
92
|
-
position: relative;
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
/*
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
글자를 함께 둔다(extended FAB). 아이콘만 두면 처음 온 사람은 + 표가 무엇을 만드는지
|
|
100
|
-
모른다 — 이 화면에서 만들 수 있는 것이 무엇인지가 그 글자다.
|
|
101
|
-
*/
|
|
102
|
-
button[fab] {
|
|
103
|
-
position: absolute;
|
|
104
|
-
right: var(--spacing-large, 12px);
|
|
105
|
-
bottom: var(--spacing-large, 12px);
|
|
106
|
-
z-index: 10;
|
|
107
|
-
|
|
108
|
-
display: flex;
|
|
109
|
-
align-items: center;
|
|
110
|
-
gap: 6px;
|
|
111
|
-
padding: 10px 18px 10px 14px;
|
|
112
|
-
font: var(--label-font, inherit);
|
|
113
|
-
font-size: 0.82rem;
|
|
114
|
-
font-weight: 600;
|
|
115
|
-
color: var(--md-sys-color-on-primary-container);
|
|
116
|
-
background-color: var(--md-sys-color-primary-container);
|
|
117
|
-
border: none;
|
|
118
|
-
/* 떠 있는 것의 모서리는 알약이다 — 담는 면(6px)을 따르지 않는다 */
|
|
119
|
-
border-radius: var(--md-sys-shape-corner-full, 999px);
|
|
120
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
|
|
121
|
-
cursor: pointer;
|
|
122
|
-
}
|
|
123
|
-
button[fab]:hover {
|
|
124
|
-
color: var(--md-sys-color-on-primary, #fff);
|
|
125
|
-
background-color: var(--md-sys-color-primary);
|
|
126
|
-
}
|
|
127
|
-
button[fab] md-icon {
|
|
128
|
-
--md-icon-size: 20px;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/* 격자가 바깥에 요구하는 것은 확정된 높이 하나뿐이다 — 스크롤은 격자가 안에서 한다 */
|
|
132
|
-
/*
|
|
133
|
-
── 그림이 주인공인 카탈로그 ────────────────────────────────────────
|
|
134
|
-
카드 기본값은 글이 많은 목록(직원·결재함)에 맞춰져 있다. 여기서는 사람이 먼저 보는 것이
|
|
135
|
-
형상 그림이므로, 그림 칸을 비율로 잡아 카드 폭이 달라져도 그림이 나란히 읽히게 한다.
|
|
136
|
-
|
|
137
|
-
그림은 카드 끝까지 닿는다 — 위쪽 모서리만 카드와 같은 6px 로 깎아 한 장처럼 보이게 하고,
|
|
138
|
-
그림 안에 얹히는 초안 표시·삭제는 figure-thumb 이 그린다.
|
|
95
|
+
카드 · 그림 칸 · 만들기 FAB 는 모든 카드 카탈로그와 같다(@operato/styles CardCatalogStyles,
|
|
96
|
+
ADR-0117 개정 2026-10-13). 여기에는 이 화면에만 있는 것만 둔다.
|
|
139
97
|
*/
|
|
140
98
|
ox-grist {
|
|
141
|
-
--data-card-min-width: 240px;
|
|
142
|
-
--data-card-record-card-border-radius: 6px;
|
|
143
|
-
--data-card-record-card-padding: 0;
|
|
144
|
-
--data-card-thumbnail-height: auto;
|
|
145
|
-
--data-card-thumbnail-aspect: 4 / 3;
|
|
146
|
-
--data-card-thumbnail-padding: 0;
|
|
147
|
-
--data-card-thumbnail-radius: 6px 6px 0 0;
|
|
148
|
-
--data-card-thumbnail-background: var(--md-sys-color-surface-container);
|
|
149
|
-
|
|
150
|
-
/*
|
|
151
|
-
카드 기본 글꼴은 굵다(제목 bold 1.1em). 글이 정보를 나르는 목록에서는 맞지만, 여기서
|
|
152
|
-
정보를 나르는 것은 그림이다. 굵은 제목이 카드마다 서면 그림보다 글이 먼저 읽힌다 —
|
|
153
|
-
이름은 알아볼 정도로만, 타입은 곁글로 둔다.
|
|
154
|
-
*/
|
|
155
|
-
--data-card-item-name-font: 500 0.9rem/1.35 var(--theme-font, 'Roboto');
|
|
156
|
-
--data-card-item-name-color: var(--md-sys-color-on-surface);
|
|
157
|
-
--data-card-item-disc-font: normal 0.72rem/1.2 var(--theme-font, 'Roboto');
|
|
158
|
-
--data-card-item-disc-color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
159
|
-
--data-card-item-etc-font: normal 0.72rem/1.2 var(--theme-font, 'Roboto');
|
|
160
|
-
|
|
161
99
|
/* 상태와 성숙도는 짧은 표식이다 — 두 줄로 세우지 않고 한 줄에 뉜다 */
|
|
162
100
|
--data-card-detail-direction: row;
|
|
163
|
-
|
|
164
|
-
/*
|
|
165
|
-
맨 위로 가는 화살표를 만들기 단추 **위로** 올린다. 둘 다 오른쪽 아래에 떠 있으므로
|
|
166
|
-
그냥 두면 겹친다 — 단추(36px + 아래 여백 12px) 위에 12px 을 띄운 자리다.
|
|
167
|
-
*/
|
|
168
|
-
--data-card-fab-position-vertical: 64px;
|
|
101
|
+
/* 표 모드의 맨 위로 단추도 만들기 단추 위로 비킨다 */
|
|
169
102
|
--data-list-fab-position-vertical: 64px;
|
|
170
103
|
}
|
|
171
104
|
`
|
|
@@ -17,9 +17,11 @@ import { fetchFigureNamespace, setFigureNamespace, type FigureNamespaceState } f
|
|
|
17
17
|
export class FigureNamespaceSettingLet extends localize(i18next)(LitElement) {
|
|
18
18
|
static styles = [
|
|
19
19
|
css`
|
|
20
|
+
/* A namespace is lower-case by rule: the card's capitalised words must not reach it, nor what is typed. */
|
|
20
21
|
div[slot='content'] {
|
|
21
22
|
display: grid;
|
|
22
23
|
gap: var(--spacing-small, 4px);
|
|
24
|
+
text-transform: none;
|
|
23
25
|
}
|
|
24
26
|
md-filled-text-field {
|
|
25
27
|
width: 100%;
|
|
@@ -32,7 +34,6 @@ export class FigureNamespaceSettingLet extends localize(i18next)(LitElement) {
|
|
|
32
34
|
margin: 0;
|
|
33
35
|
font-size: 0.8rem;
|
|
34
36
|
color: var(--md-sys-color-on-surface-variant);
|
|
35
|
-
text-transform: none;
|
|
36
37
|
}
|
|
37
38
|
`
|
|
38
39
|
]
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement } from 'lit'
|
|
|
4
4
|
import { customElement, property } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import '@operato/data-grist/ox-card-thumb.js'
|
|
7
8
|
import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js'
|
|
8
9
|
import { getPathInfo } from '@operato/utils'
|
|
9
10
|
|
|
@@ -31,7 +32,8 @@ function thumbnailSrc(figure: Figure): string | undefined {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
|
-
* 카드의 그림 칸.
|
|
35
|
+
* 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가
|
|
36
|
+
* 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).
|
|
35
37
|
*
|
|
36
38
|
* ## 왜 컴포넌트인가
|
|
37
39
|
*
|
|
@@ -53,39 +55,9 @@ function thumbnailSrc(figure: Figure): string | undefined {
|
|
|
53
55
|
export class FigureThumb extends localize(i18next)(LitElement) {
|
|
54
56
|
static styles = css`
|
|
55
57
|
:host {
|
|
56
|
-
position: relative;
|
|
57
58
|
display: block;
|
|
58
59
|
width: 100%;
|
|
59
60
|
height: 100%;
|
|
60
|
-
overflow: hidden;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
img {
|
|
64
|
-
display: block;
|
|
65
|
-
width: 100%;
|
|
66
|
-
height: 100%;
|
|
67
|
-
/* 형상을 자르지 않는다 — 긴 것(컨베이어)과 높은 것(시그널타워)이 한 줄에 선다 */
|
|
68
|
-
object-fit: contain;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/*
|
|
72
|
-
초안 표시.
|
|
73
|
-
|
|
74
|
-
발행된 것에는 아무 말도 하지 않는다 — 카탈로그에 있는 것은 대개 발행된 것이라, 카드마다
|
|
75
|
-
「발행됨」이 붙으면 아무것도 가르지 않고 자리만 먹는다. 초안만 말하면 이 표시가 있는 카드가
|
|
76
|
-
곧 「아직 쓰면 안 되는 것」이 된다.
|
|
77
|
-
*/
|
|
78
|
-
span[draft] {
|
|
79
|
-
position: absolute;
|
|
80
|
-
top: 6px;
|
|
81
|
-
left: 6px;
|
|
82
|
-
padding: 1px 7px;
|
|
83
|
-
font: var(--label-font, inherit);
|
|
84
|
-
font-size: 0.68rem;
|
|
85
|
-
line-height: 1.5;
|
|
86
|
-
color: var(--md-sys-color-on-secondary-container);
|
|
87
|
-
background-color: var(--md-sys-color-secondary-container);
|
|
88
|
-
border-radius: 4px;
|
|
89
61
|
}
|
|
90
62
|
|
|
91
63
|
/*
|
|
@@ -188,12 +160,12 @@ export class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
188
160
|
const src = thumbnailSrc(figure)
|
|
189
161
|
|
|
190
162
|
return html`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
163
|
+
<ox-card-thumb
|
|
164
|
+
src=${src || ''}
|
|
165
|
+
alt=${figure.name || ''}
|
|
166
|
+
badge=${figure.state === 'released' ? '' : i18next.t('figure.label.draft')}
|
|
167
|
+
>
|
|
168
|
+
<figure-thumb-mark slot="empty" .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>
|
|
197
169
|
${figure.tags?.length
|
|
198
170
|
? html`<div tags>
|
|
199
171
|
${figure.tags.map(
|
|
@@ -225,6 +197,7 @@ export class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
225
197
|
>
|
|
226
198
|
<md-icon>delete</md-icon>
|
|
227
199
|
</button>
|
|
200
|
+
</ox-card-thumb>
|
|
228
201
|
`
|
|
229
202
|
}
|
|
230
203
|
}
|
|
@@ -40,3 +40,12 @@ export declare function dataSampleOf(asset: FigureAsset, values: Record<string,
|
|
|
40
40
|
} | {
|
|
41
41
|
issues: FigureDataValueIssue[];
|
|
42
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Which fields of each motion group the data interface writes: its clip (a lookup to `motion-clip`), its speed
|
|
45
|
+
* (`motion-speed`). The data always writes them -- from a live value, a saved one, or the property's default -- and
|
|
46
|
+
* wins over a group's own setting for that field (detail design §5.1), so a control for such a field moves nothing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function motionWrittenByData(asset: FigureAsset): Record<string, {
|
|
49
|
+
clip: boolean;
|
|
50
|
+
speed: boolean;
|
|
51
|
+
}>;
|
|
@@ -92,4 +92,22 @@ export function dataSampleOf(asset, values) {
|
|
|
92
92
|
const issues = validateFigureDataValues(plan, values);
|
|
93
93
|
return issues.length ? { issues } : { result: evaluateFigureDataInterface(plan, values) };
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Which fields of each motion group the data interface writes: its clip (a lookup to `motion-clip`), its speed
|
|
97
|
+
* (`motion-speed`). The data always writes them -- from a live value, a saved one, or the property's default -- and
|
|
98
|
+
* wins over a group's own setting for that field (detail design §5.1), so a control for such a field moves nothing.
|
|
99
|
+
*/
|
|
100
|
+
export function motionWrittenByData(asset) {
|
|
101
|
+
const out = {};
|
|
102
|
+
for (const c of asset.dataInterface?.connections ?? []) {
|
|
103
|
+
if (c.target.kind !== 'motion-clip' && c.target.kind !== 'motion-speed')
|
|
104
|
+
continue;
|
|
105
|
+
const one = (out[c.target.group] ??= { clip: false, speed: false });
|
|
106
|
+
if (c.target.kind === 'motion-clip')
|
|
107
|
+
one.clip = true;
|
|
108
|
+
else
|
|
109
|
+
one.speed = true;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
95
113
|
//# sourceMappingURL=data-interface.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-interface.js","sourceRoot":"","sources":["../../client/modeller/data-interface.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The figure's public data properties and what each moves (ADR-0051 §2.2): what the data tab offers and shows. Every\n * rule is figure-model's -- which transform a property may take, whether a connection holds, what a sample moves; this\n * only lists what there is to choose and drafts a first connection the kernel then checks. Kept apart from the element so\n * it is tested without a browser.\n */\nimport { compileFigureDataInterface, evaluateFigureDataInterface, validateFigureDataValues, FIGURE_PART_ID } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureDataConnection, FigureDataProperty, FigureDataResult, FigureDataTarget, FigureDataValue, FigureDataValueIssue } from '@hatiolab/figure-model'\n\n/** Something a connection can write, with what the screen shows of it. */\nexport interface DataTargetChoice {\n /** One string per target, for a select's value. */\n key: string\n target: FigureDataTarget\n /** The control's label, the colour token, or the group's label. */\n name: string\n unit?: string\n min?: number\n max?: number\n}\n\nexport const targetKeyOf = (t: FigureDataTarget): string =>\n t.kind === 'state' ? `state:${t.input}` : t.kind === 'palette' ? `palette:${t.token}` : `${t.kind}:${t.group}`\n\n/**\n * What a property of this kind can be connected to: a number moves a control or a group's speed; an enum or a boolean\n * moves a control, a colour or which clip of a group plays (through a case per value). A control is offered observed: a\n * value read from the machine applies as it comes.\n */\nexport function dataTargetsOf(asset: FigureAsset, kind: FigureDataProperty['kind']): DataTargetChoice[] {\n const out: DataTargetChoice[] = []\n for (const input of asset.document.model.inputs) {\n if (input.role !== 'state') continue\n const target: FigureDataTarget = { kind: 'state', input: input.id, sourceMode: 'observed' }\n out.push({ key: targetKeyOf(target), target, name: asset.stateInputs?.[input.id]?.label ?? input.id, unit: input.unit, min: input.min, max: input.max })\n }\n if (kind !== 'number')\n for (const token of Object.keys(asset.palette)) {\n const target: FigureDataTarget = { kind: 'palette', token }\n out.push({ key: targetKeyOf(target), target, name: token })\n }\n for (const group of asset.motionGroups ?? []) {\n const target: FigureDataTarget = kind === 'number' ? { kind: 'motion-speed', group: group.id } : { kind: 'motion-clip', group: group.id }\n out.push({ key: targetKeyOf(target), target, name: group.label })\n }\n return out\n}\n\n/** The values a lookup has a case for: an enum's values, or both booleans. Nothing for a number. */\nexport const domainOf = (property: FigureDataProperty): FigureDataValue[] =>\n property.kind === 'enum' ? [...property.values] : property.kind === 'boolean' ? [false, true] : []\n\n/** An id not taken among `taken`: `base`, then `base-2`, `base-3`, … made into the kernel's id shape. */\nexport function freeDataIdOf(taken: Iterable<string>, base: string): string {\n const used = new Set(taken)\n const cleaned = base.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^[^a-zA-Z]+/, '').replace(/-+$/, '') || 'data'\n const id = FIGURE_PART_ID.test(cleaned) ? cleaned : 'data'\n if (!used.has(id)) return id\n let n = 2\n while (used.has(`${id}-${n}`)) n++\n return `${id}-${n}`\n}\n\n/**\n * A first connection of a property to a target, for the author to adjust: a number as it is in the control's own unit,\n * or scaled from another; an enum or a boolean case by case, each case at what the target shows now -- the control's\n * rest value, the colour, the group's first clip. The kernel says whether it holds.\n */\nexport function connectionDraftOf(asset: FigureAsset, property: FigureDataProperty, choice: DataTargetChoice): FigureDataConnection {\n const taken = (asset.dataInterface?.connections ?? []).map(c => c.id)\n const id = freeDataIdOf(taken, `${property.id}-${choice.target.kind === 'state' ? choice.target.input : choice.target.kind === 'palette' ? 'colour' : choice.target.group}`)\n const target = choice.target\n if (property.kind === 'number') {\n const same = target.kind === 'motion-speed' || (target.kind === 'state' && choice.unit === property.unit)\n return { id, property: property.id, transform: same ? { kind: 'identity' } : { kind: 'affine', scale: 1, offset: 0 }, target }\n }\n const now: FigureDataValue =\n target.kind === 'state'\n ? (asset.stateDefaults[target.input] ?? 0)\n : target.kind === 'palette'\n ? (asset.palette[target.token] ?? '#000000')\n : ((asset.motionGroups ?? []).find(g => g.id === target.group)?.defaultClip ?? '')\n return { id, property: property.id, transform: { kind: 'lookup', cases: domainOf(property).map(when => ({ when, value: now })) }, target }\n}\n\n/** What a property looks like when first made: a number 0 to 1, a boolean off, an enum of two values. */\nexport function propertyDraftOf(asset: FigureAsset, label: string, kind: FigureDataProperty['kind']): FigureDataProperty {\n const id = freeDataIdOf((asset.dataInterface?.properties ?? []).map(p => p.id), label)\n if (kind === 'number') return { id, label, kind, unit: 'ratio', min: 0, max: 1, default: 0 }\n if (kind === 'boolean') return { id, label, kind, default: false }\n return { id, label, kind, values: ['off', 'on'], default: 'off' }\n}\n\n/** A value typed in a sample field, as the property takes it: a number, true or false, or the enum value. */\nexport function sampleValueOf(property: FigureDataProperty, typed: string): FigureDataValue {\n if (property.kind === 'number') return typed.trim() === '' ? Number.NaN : Number(typed)\n if (property.kind === 'boolean') return typed === 'true'\n return typed\n}\n\n/**\n * What a sample of values would move, as the figure would run it: the kernel's check and evaluation of the interface.\n * Refused whole when one value cannot be taken, with each one's reason, as a live snapshot is.\n */\nexport function dataSampleOf(asset: FigureAsset, values: Record<string, FigureDataValue>): { result: FigureDataResult } | { issues: FigureDataValueIssue[] } {\n const plan = compileFigureDataInterface(asset)\n if (!plan) return { issues: [] }\n const issues = validateFigureDataValues(plan, values)\n return issues.length ? { issues } : { result: evaluateFigureDataInterface(plan, values) }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"data-interface.js","sourceRoot":"","sources":["../../client/modeller/data-interface.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The figure's public data properties and what each moves (ADR-0051 §2.2): what the data tab offers and shows. Every\n * rule is figure-model's -- which transform a property may take, whether a connection holds, what a sample moves; this\n * only lists what there is to choose and drafts a first connection the kernel then checks. Kept apart from the element so\n * it is tested without a browser.\n */\nimport { compileFigureDataInterface, evaluateFigureDataInterface, validateFigureDataValues, FIGURE_PART_ID } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureDataConnection, FigureDataProperty, FigureDataResult, FigureDataTarget, FigureDataValue, FigureDataValueIssue } from '@hatiolab/figure-model'\n\n/** Something a connection can write, with what the screen shows of it. */\nexport interface DataTargetChoice {\n /** One string per target, for a select's value. */\n key: string\n target: FigureDataTarget\n /** The control's label, the colour token, or the group's label. */\n name: string\n unit?: string\n min?: number\n max?: number\n}\n\nexport const targetKeyOf = (t: FigureDataTarget): string =>\n t.kind === 'state' ? `state:${t.input}` : t.kind === 'palette' ? `palette:${t.token}` : `${t.kind}:${t.group}`\n\n/**\n * What a property of this kind can be connected to: a number moves a control or a group's speed; an enum or a boolean\n * moves a control, a colour or which clip of a group plays (through a case per value). A control is offered observed: a\n * value read from the machine applies as it comes.\n */\nexport function dataTargetsOf(asset: FigureAsset, kind: FigureDataProperty['kind']): DataTargetChoice[] {\n const out: DataTargetChoice[] = []\n for (const input of asset.document.model.inputs) {\n if (input.role !== 'state') continue\n const target: FigureDataTarget = { kind: 'state', input: input.id, sourceMode: 'observed' }\n out.push({ key: targetKeyOf(target), target, name: asset.stateInputs?.[input.id]?.label ?? input.id, unit: input.unit, min: input.min, max: input.max })\n }\n if (kind !== 'number')\n for (const token of Object.keys(asset.palette)) {\n const target: FigureDataTarget = { kind: 'palette', token }\n out.push({ key: targetKeyOf(target), target, name: token })\n }\n for (const group of asset.motionGroups ?? []) {\n const target: FigureDataTarget = kind === 'number' ? { kind: 'motion-speed', group: group.id } : { kind: 'motion-clip', group: group.id }\n out.push({ key: targetKeyOf(target), target, name: group.label })\n }\n return out\n}\n\n/** The values a lookup has a case for: an enum's values, or both booleans. Nothing for a number. */\nexport const domainOf = (property: FigureDataProperty): FigureDataValue[] =>\n property.kind === 'enum' ? [...property.values] : property.kind === 'boolean' ? [false, true] : []\n\n/** An id not taken among `taken`: `base`, then `base-2`, `base-3`, … made into the kernel's id shape. */\nexport function freeDataIdOf(taken: Iterable<string>, base: string): string {\n const used = new Set(taken)\n const cleaned = base.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^[^a-zA-Z]+/, '').replace(/-+$/, '') || 'data'\n const id = FIGURE_PART_ID.test(cleaned) ? cleaned : 'data'\n if (!used.has(id)) return id\n let n = 2\n while (used.has(`${id}-${n}`)) n++\n return `${id}-${n}`\n}\n\n/**\n * A first connection of a property to a target, for the author to adjust: a number as it is in the control's own unit,\n * or scaled from another; an enum or a boolean case by case, each case at what the target shows now -- the control's\n * rest value, the colour, the group's first clip. The kernel says whether it holds.\n */\nexport function connectionDraftOf(asset: FigureAsset, property: FigureDataProperty, choice: DataTargetChoice): FigureDataConnection {\n const taken = (asset.dataInterface?.connections ?? []).map(c => c.id)\n const id = freeDataIdOf(taken, `${property.id}-${choice.target.kind === 'state' ? choice.target.input : choice.target.kind === 'palette' ? 'colour' : choice.target.group}`)\n const target = choice.target\n if (property.kind === 'number') {\n const same = target.kind === 'motion-speed' || (target.kind === 'state' && choice.unit === property.unit)\n return { id, property: property.id, transform: same ? { kind: 'identity' } : { kind: 'affine', scale: 1, offset: 0 }, target }\n }\n const now: FigureDataValue =\n target.kind === 'state'\n ? (asset.stateDefaults[target.input] ?? 0)\n : target.kind === 'palette'\n ? (asset.palette[target.token] ?? '#000000')\n : ((asset.motionGroups ?? []).find(g => g.id === target.group)?.defaultClip ?? '')\n return { id, property: property.id, transform: { kind: 'lookup', cases: domainOf(property).map(when => ({ when, value: now })) }, target }\n}\n\n/** What a property looks like when first made: a number 0 to 1, a boolean off, an enum of two values. */\nexport function propertyDraftOf(asset: FigureAsset, label: string, kind: FigureDataProperty['kind']): FigureDataProperty {\n const id = freeDataIdOf((asset.dataInterface?.properties ?? []).map(p => p.id), label)\n if (kind === 'number') return { id, label, kind, unit: 'ratio', min: 0, max: 1, default: 0 }\n if (kind === 'boolean') return { id, label, kind, default: false }\n return { id, label, kind, values: ['off', 'on'], default: 'off' }\n}\n\n/** A value typed in a sample field, as the property takes it: a number, true or false, or the enum value. */\nexport function sampleValueOf(property: FigureDataProperty, typed: string): FigureDataValue {\n if (property.kind === 'number') return typed.trim() === '' ? Number.NaN : Number(typed)\n if (property.kind === 'boolean') return typed === 'true'\n return typed\n}\n\n/**\n * What a sample of values would move, as the figure would run it: the kernel's check and evaluation of the interface.\n * Refused whole when one value cannot be taken, with each one's reason, as a live snapshot is.\n */\nexport function dataSampleOf(asset: FigureAsset, values: Record<string, FigureDataValue>): { result: FigureDataResult } | { issues: FigureDataValueIssue[] } {\n const plan = compileFigureDataInterface(asset)\n if (!plan) return { issues: [] }\n const issues = validateFigureDataValues(plan, values)\n return issues.length ? { issues } : { result: evaluateFigureDataInterface(plan, values) }\n}\n\n/**\n * Which fields of each motion group the data interface writes: its clip (a lookup to `motion-clip`), its speed\n * (`motion-speed`). The data always writes them -- from a live value, a saved one, or the property's default -- and\n * wins over a group's own setting for that field (detail design §5.1), so a control for such a field moves nothing.\n */\nexport function motionWrittenByData(asset: FigureAsset): Record<string, { clip: boolean; speed: boolean }> {\n const out: Record<string, { clip: boolean; speed: boolean }> = {}\n for (const c of asset.dataInterface?.connections ?? []) {\n if (c.target.kind !== 'motion-clip' && c.target.kind !== 'motion-speed') continue\n const one = (out[c.target.group] ??= { clip: false, speed: false })\n if (c.target.kind === 'motion-clip') one.clip = true\n else one.speed = true\n }\n return out\n}\n\n"]}
|
|
@@ -26,6 +26,8 @@ export declare class FigurePreview extends FigurePreview_base {
|
|
|
26
26
|
private giving;
|
|
27
27
|
/** Per motion group, the clip played here and how fast (ADR-0051 §3.2) -- the preview's, as a board's \`motion\`. */
|
|
28
28
|
private moving;
|
|
29
|
+
/** Data values given here, as a board's saved `figureValues`: what moves a group whose clip or speed the data writes. */
|
|
30
|
+
private valuing;
|
|
29
31
|
/** Data values tried in the data tab: the preview's figures run on them as a live snapshot; null when none are. */
|
|
30
32
|
sample: Record<string, unknown> | null;
|
|
31
33
|
/** The part chosen in the editor. The size help starts on it, so the help speaks of the part the author was working on. */
|
|
@@ -42,6 +44,9 @@ export declare class FigurePreview extends FigurePreview_base {
|
|
|
42
44
|
private watcher?;
|
|
43
45
|
render(): import("lit-html").TemplateResult<1>;
|
|
44
46
|
private renderDrivers;
|
|
47
|
+
/** A data property's value, as the board's data values give it: a number on a slider, a boolean or an enum chosen. */
|
|
48
|
+
private renderDataValue;
|
|
49
|
+
private value;
|
|
45
50
|
private move;
|
|
46
51
|
private renderDial;
|
|
47
52
|
private drive;
|
|
@@ -6,6 +6,7 @@ import { compileFigureAsset } from '@hatiolab/figure-model';
|
|
|
6
6
|
import { create, registerFigure, unregisterFigure } from '@hatiolab/things-scene';
|
|
7
7
|
import { DEFAULT_VIEW, previewBoardDepth, sceneLook, WORKBENCH } from '@things-factory/figure-viewer/dist-client/figure-view.js';
|
|
8
8
|
import { dialShape } from './angle-dial.js';
|
|
9
|
+
import { domainOf, motionWrittenByData, sampleValueOf } from './data-interface.js';
|
|
9
10
|
import { derivedOf } from './revision.js';
|
|
10
11
|
import { authoredSizeOf, PREVIEW_FACTOR, PREVIEW_WAYS, previewClampsOf, previewInstances, previewTypeOf, setupPartOf } from '@things-factory/figure-viewer/dist-client/preview-model.js';
|
|
11
12
|
let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
@@ -27,6 +28,8 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
27
28
|
this.giving = {};
|
|
28
29
|
/** Per motion group, the clip played here and how fast (ADR-0051 §3.2) -- the preview's, as a board's \`motion\`. */
|
|
29
30
|
this.moving = {};
|
|
31
|
+
/** Data values given here, as a board's saved `figureValues`: what moves a group whose clip or speed the data writes. */
|
|
32
|
+
this.valuing = {};
|
|
30
33
|
/** Data values tried in the data tab: the preview's figures run on them as a live snapshot; null when none are. */
|
|
31
34
|
this.sample = null;
|
|
32
35
|
/** The part chosen in the editor. The size help starts on it, so the help speaks of the part the author was working on. */
|
|
@@ -223,7 +226,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
223
226
|
</select>
|
|
224
227
|
<button @click=${() => this.dispatchEvent(new CustomEvent('configure-size', { detail: { part: setupPart }, bubbles: true, composed: true }))}>${i18next.t('figure.button.configure-size')}</button>
|
|
225
228
|
</div>` : nothing}
|
|
226
|
-
${controls.length + clips.length + (asset?.motionGroups?.length ?? 0) ? this.renderDrivers(asset, controls, clips) : nothing}</div>`}`;
|
|
229
|
+
${controls.length + clips.length + (asset?.motionGroups?.length ?? 0) + (asset?.dataInterface?.properties.length ?? 0) ? this.renderDrivers(asset, controls, clips) : nothing}</div>`}`;
|
|
227
230
|
}
|
|
228
231
|
renderDrivers(asset, controls, clips) {
|
|
229
232
|
const changed = controls.filter(c => this.giving[c.id] !== undefined && this.giving[c.id] !== asset.stateDefaults[c.id]);
|
|
@@ -264,23 +267,53 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
264
267
|
<output>${value.toFixed(2)}</output>
|
|
265
268
|
</label>`;
|
|
266
269
|
})}
|
|
270
|
+
${(asset.dataInterface?.properties ?? []).map(property => this.renderDataValue(property))}
|
|
267
271
|
${(asset.motionGroups ?? []).map(group => {
|
|
268
272
|
const playing = this.moving[group.id] ?? { clip: group.defaultClip, speed: 0 };
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
/* A field the data writes wins over the group's own setting, here as on a board: it is moved by the data values above. */
|
|
274
|
+
const byData = motionWrittenByData(asset)[group.id] ?? { clip: false, speed: false };
|
|
275
|
+
return html `${byData.clip
|
|
276
|
+
? nothing
|
|
277
|
+
: html `<label>
|
|
278
|
+
<span>${group.label} <span drive>${i18next.t('figure.label.clip')}</span></span>
|
|
279
|
+
<select aria-label=${group.label} @change=${(e) => this.move(group.id, { ...playing, clip: e.target.value })}>
|
|
280
|
+
${group.clips.map(c => html `<option value=${c} ?selected=${c === playing.clip}>${c}</option>`)}
|
|
281
|
+
</select>
|
|
282
|
+
</label>`}
|
|
283
|
+
${byData.speed
|
|
284
|
+
? nothing
|
|
285
|
+
: html `<label>
|
|
286
|
+
<span>${group.label} <span drive>${i18next.t('figure.label.playback-speed')}</span></span>
|
|
287
|
+
<span></span>
|
|
288
|
+
<input type="range" min="0" max="3" step="0.05" .value=${String(playing.speed)} @input=${(e) => this.move(group.id, { ...playing, speed: Number(e.target.value) })} />
|
|
289
|
+
<output>${playing.speed.toFixed(2)}</output>
|
|
290
|
+
</label>`}`;
|
|
281
291
|
})}
|
|
282
292
|
</div>`;
|
|
283
293
|
}
|
|
294
|
+
/** A data property's value, as the board's data values give it: a number on a slider, a boolean or an enum chosen. */
|
|
295
|
+
renderDataValue(property) {
|
|
296
|
+
const value = this.valuing[property.id] ?? property.default;
|
|
297
|
+
if (property.kind === 'number') {
|
|
298
|
+
const step = Math.max((property.max - property.min) / 100, 0.01);
|
|
299
|
+
return html `<label>
|
|
300
|
+
<span>${property.label} <span drive>${property.unit}</span></span>
|
|
301
|
+
<span></span>
|
|
302
|
+
<input type="range" min=${property.min} max=${property.max} step=${step} .value=${String(value)} @input=${(e) => this.value(property, e.target.value)} />
|
|
303
|
+
<output>${Number(value).toFixed(2)}</output>
|
|
304
|
+
</label>`;
|
|
305
|
+
}
|
|
306
|
+
return html `<label>
|
|
307
|
+
<span>${property.label}</span>
|
|
308
|
+
<select aria-label=${property.label} @change=${(e) => this.value(property, e.target.value)}>
|
|
309
|
+
${domainOf(property).map(v => html `<option value=${String(v)} ?selected=${v === value}>${typeof v === 'boolean' ? i18next.t(v ? 'figure.label.data-true' : 'figure.label.data-false') : v}</option>`)}
|
|
310
|
+
</select>
|
|
311
|
+
</label>`;
|
|
312
|
+
}
|
|
313
|
+
value(property, typed) {
|
|
314
|
+
this.valuing = { ...this.valuing, [property.id]: sampleValueOf(property, typed) };
|
|
315
|
+
this.tellInstances('figureValues', this.valuing);
|
|
316
|
+
}
|
|
284
317
|
move(group, playing) {
|
|
285
318
|
this.moving = { ...this.moving, [group]: playing };
|
|
286
319
|
this.tellInstances('motion', Object.fromEntries(Object.entries(this.moving).map(([id, m]) => [id, { ...m, paused: false }])));
|
|
@@ -398,6 +431,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
398
431
|
this.teardown();
|
|
399
432
|
this.driving = {};
|
|
400
433
|
this.moving = {};
|
|
434
|
+
this.valuing = {};
|
|
401
435
|
registerFigure(type, asset);
|
|
402
436
|
this.registered = type;
|
|
403
437
|
const { components, board } = previewInstances(type, size);
|
|
@@ -489,6 +523,10 @@ __decorate([
|
|
|
489
523
|
state(),
|
|
490
524
|
__metadata("design:type", Object)
|
|
491
525
|
], FigurePreview.prototype, "moving", void 0);
|
|
526
|
+
__decorate([
|
|
527
|
+
state(),
|
|
528
|
+
__metadata("design:type", Object)
|
|
529
|
+
], FigurePreview.prototype, "valuing", void 0);
|
|
492
530
|
__decorate([
|
|
493
531
|
property({ attribute: false }),
|
|
494
532
|
__metadata("design:type", Object)
|