@things-factory/figure-ui 10.1.46 → 10.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/modeller/angle-dial.ts +70 -0
- package/client/modeller/figure-settings.ts +328 -0
- package/client/modeller/figure-v3-capability.ts +137 -0
- package/client/modeller/figure-v3-clips.ts +253 -0
- package/client/modeller/figure-v3-controls.ts +212 -0
- package/client/modeller/figure-v3-links.ts +61 -81
- package/client/modeller/figure-v3-material.ts +108 -35
- package/client/modeller/figure-v3-parts.ts +96 -3
- package/client/modeller/figure-v3-preview.ts +326 -0
- package/client/modeller/figure-v3-properties.ts +29 -57
- package/client/modeller/figure-v3-verdict.ts +598 -60
- package/client/modeller/figure-view.ts +152 -0
- package/client/modeller/side-panel-styles.ts +326 -0
- package/client/modeller/v3-canvas.ts +221 -263
- package/client/modeller/v3-clips.ts +89 -0
- package/client/modeller/v3-controls.ts +54 -0
- package/client/modeller/v3-editor.ts +531 -223
- package/client/modeller/v3-fields.ts +62 -0
- package/client/modeller/v3-help.ts +41 -0
- package/client/modeller/v3-keys.ts +34 -0
- package/client/modeller/v3-links.ts +93 -9
- package/client/modeller/v3-live.ts +79 -0
- package/client/modeller/v3-maturity.ts +415 -0
- package/client/modeller/v3-palette.ts +45 -0
- package/client/modeller/v3-preview-model.ts +54 -0
- package/client/modeller/v3-stage-model.ts +159 -0
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/figure-v3-modeller-page.ts +66 -134
- package/dist-client/modeller/angle-dial.d.ts +32 -0
- package/dist-client/modeller/angle-dial.js +41 -0
- package/dist-client/modeller/angle-dial.js.map +1 -0
- package/dist-client/modeller/figure-settings.d.ts +39 -0
- package/dist-client/modeller/figure-settings.js +317 -0
- package/dist-client/modeller/figure-settings.js.map +1 -0
- package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
- package/dist-client/modeller/figure-v3-capability.js +141 -0
- package/dist-client/modeller/figure-v3-capability.js.map +1 -0
- package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
- package/dist-client/modeller/figure-v3-clips.js +268 -0
- package/dist-client/modeller/figure-v3-clips.js.map +1 -0
- package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
- package/dist-client/modeller/figure-v3-controls.js +226 -0
- package/dist-client/modeller/figure-v3-controls.js.map +1 -0
- package/dist-client/modeller/figure-v3-links.d.ts +5 -2
- package/dist-client/modeller/figure-v3-links.js +64 -82
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +5 -2
- package/dist-client/modeller/figure-v3-material.js +109 -35
- package/dist-client/modeller/figure-v3-material.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
- package/dist-client/modeller/figure-v3-parts.js +88 -3
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
- package/dist-client/modeller/figure-v3-preview.js +322 -0
- package/dist-client/modeller/figure-v3-preview.js.map +1 -0
- package/dist-client/modeller/figure-v3-properties.d.ts +2 -2
- package/dist-client/modeller/figure-v3-properties.js +29 -57
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
- package/dist-client/modeller/figure-v3-verdict.js +579 -59
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +118 -0
- package/dist-client/modeller/figure-view.js +78 -0
- package/dist-client/modeller/figure-view.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.d.ts +23 -0
- package/dist-client/modeller/side-panel-styles.js +326 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +29 -37
- package/dist-client/modeller/v3-canvas.js +188 -260
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +39 -0
- package/dist-client/modeller/v3-clips.js +58 -0
- package/dist-client/modeller/v3-clips.js.map +1 -0
- package/dist-client/modeller/v3-controls.d.ts +28 -0
- package/dist-client/modeller/v3-controls.js +28 -0
- package/dist-client/modeller/v3-controls.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +58 -1
- package/dist-client/modeller/v3-editor.js +555 -218
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +30 -0
- package/dist-client/modeller/v3-fields.js +46 -0
- package/dist-client/modeller/v3-fields.js.map +1 -0
- package/dist-client/modeller/v3-help.d.ts +18 -0
- package/dist-client/modeller/v3-help.js +37 -0
- package/dist-client/modeller/v3-help.js.map +1 -0
- package/dist-client/modeller/v3-keys.d.ts +14 -0
- package/dist-client/modeller/v3-keys.js +23 -0
- package/dist-client/modeller/v3-keys.js.map +1 -0
- package/dist-client/modeller/v3-links.d.ts +30 -1
- package/dist-client/modeller/v3-links.js +94 -8
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-live.d.ts +31 -0
- package/dist-client/modeller/v3-live.js +58 -0
- package/dist-client/modeller/v3-live.js.map +1 -0
- package/dist-client/modeller/v3-maturity.d.ts +120 -0
- package/dist-client/modeller/v3-maturity.js +317 -0
- package/dist-client/modeller/v3-maturity.js.map +1 -0
- package/dist-client/modeller/v3-palette.d.ts +7 -0
- package/dist-client/modeller/v3-palette.js +42 -0
- package/dist-client/modeller/v3-palette.js.map +1 -0
- package/dist-client/modeller/v3-preview-model.d.ts +32 -0
- package/dist-client/modeller/v3-preview-model.js +48 -0
- package/dist-client/modeller/v3-preview-model.js.map +1 -0
- package/dist-client/modeller/v3-stage-model.d.ts +31 -0
- package/dist-client/modeller/v3-stage-model.js +121 -0
- package/dist-client/modeller/v3-stage-model.js.map +1 -0
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
- package/dist-client/pages/figure-v3-modeller-page.js +64 -136
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
- package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/v3-clips.test.ts +70 -0
- package/test/v3-controls.test.ts +68 -0
- package/test/v3-fields.test.ts +62 -0
- package/test/v3-gizmo.test.ts +3 -5
- package/test/v3-keys.test.ts +37 -0
- package/test/v3-layout.test.ts +78 -0
- package/test/v3-links.test.ts +77 -6
- package/test/v3-live.test.ts +99 -0
- package/test/v3-material.test.ts +6 -0
- package/test/v3-maturity.test.ts +268 -0
- package/test/v3-parts-wiring.test.ts +3 -1
- package/test/v3-preview.test.ts +42 -0
- package/test/v3-properties.test.ts +4 -2
- package/test/v3-scene-canvas.test.ts +85 -0
- package/test/v3-stage-model.test.ts +25 -0
- package/test/v3-theme.test.ts +16 -25
- package/test/v3-verdict.test.ts +12 -6
- package/test/v3-view.test.ts +20 -0
- package/translations/en.json +84 -5
- package/translations/ja.json +86 -7
- package/translations/ko.json +84 -5
- package/translations/ms.json +86 -7
- package/translations/zh.json +86 -7
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { css, html, LitElement, nothing, unsafeCSS } from 'lit'
|
|
2
|
+
import { customElement, property, query, state } from 'lit/decorators.js'
|
|
3
|
+
|
|
4
|
+
import { i18next, localize } from '@operato/i18n'
|
|
5
|
+
import { compileV3Asset } from '@hatiolab/figure-model'
|
|
6
|
+
import type { V3Asset } from '@hatiolab/figure-model'
|
|
7
|
+
import { create, registerFigureV3, unregisterFigureV3 } from '@hatiolab/things-scene'
|
|
8
|
+
|
|
9
|
+
import { DEFAULT_VIEW, previewBoardDepth, sceneLook, WORKBENCH, type ViewSettings } from './figure-view.js'
|
|
10
|
+
import { dialShape } from './angle-dial.js'
|
|
11
|
+
import { authoredSizeOf, PREVIEW_FACTOR, PREVIEW_WAYS, previewInstances } from './v3-preview-model.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The figure placed on a board -- V2's preview, for a V3 figure.
|
|
15
|
+
*
|
|
16
|
+
* The editing canvas shows the figure at the size it was made. Half of what a figure is lies in how it answers a
|
|
17
|
+
* different size, so here the figure being authored is registered as a board registers it and placed as
|
|
18
|
+
* `FigureInstance`s: the size it was made, and each of the seven ways of growing one, two or three axes twice as
|
|
19
|
+
* large, side by side at one scale, four to a row (V2's eight). The board draws them as it will on a real board --
|
|
20
|
+
* placement, lights, environment, the same V3 renderer.
|
|
21
|
+
*
|
|
22
|
+
* The controls are V2's too: each value that poses the figure with a slider (and a dial for an angle), each clip's
|
|
23
|
+
* speed, and 「이 자세를 기본으로」 to keep the pose as the one it stands in. Nothing here is edited; the canvas edits.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface SceneHandle {
|
|
27
|
+
dispose?: () => void
|
|
28
|
+
resize?: () => void
|
|
29
|
+
fit?: (type?: string) => void
|
|
30
|
+
root?: {
|
|
31
|
+
set?: (patch: Record<string, unknown>) => void
|
|
32
|
+
fitCameraToModel?: () => void
|
|
33
|
+
components?: { set?: (property: string, value: unknown) => void }[]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@customElement('figure-v3-preview')
|
|
38
|
+
export class FigureV3Preview extends localize(i18next)(LitElement) {
|
|
39
|
+
static styles = css`
|
|
40
|
+
:host {
|
|
41
|
+
position: relative;
|
|
42
|
+
display: flex;
|
|
43
|
+
min-width: 0;
|
|
44
|
+
min-height: 0;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
background-color: ${unsafeCSS(WORKBENCH)};
|
|
47
|
+
font-size: 0.76rem;
|
|
48
|
+
}
|
|
49
|
+
div[stage] {
|
|
50
|
+
position: absolute;
|
|
51
|
+
inset: 0;
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
}
|
|
55
|
+
/* Which is which: side by side means nothing without a legend. */
|
|
56
|
+
div[legend] {
|
|
57
|
+
position: absolute;
|
|
58
|
+
top: 8px;
|
|
59
|
+
left: 8px;
|
|
60
|
+
display: flex;
|
|
61
|
+
gap: 6px;
|
|
62
|
+
padding: 4px 8px;
|
|
63
|
+
color: rgba(255, 255, 255, 0.9);
|
|
64
|
+
background-color: rgba(28, 28, 30, 0.6);
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
font-size: 0.72rem;
|
|
67
|
+
}
|
|
68
|
+
div[clips] {
|
|
69
|
+
position: absolute;
|
|
70
|
+
right: 8px;
|
|
71
|
+
bottom: 8px;
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
gap: 6px;
|
|
75
|
+
padding: 8px 10px;
|
|
76
|
+
color: rgba(255, 255, 255, 0.92);
|
|
77
|
+
background-color: rgba(28, 28, 30, 0.72);
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
font-size: 0.72rem;
|
|
80
|
+
}
|
|
81
|
+
div[clips] label {
|
|
82
|
+
display: grid;
|
|
83
|
+
grid-template-columns: auto 20px 96px 34px;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 8px;
|
|
86
|
+
}
|
|
87
|
+
div[clips] svg[dial] {
|
|
88
|
+
width: 20px;
|
|
89
|
+
height: 20px;
|
|
90
|
+
}
|
|
91
|
+
div[clips] svg[dial] path {
|
|
92
|
+
fill: rgba(255, 255, 255, 0.22);
|
|
93
|
+
}
|
|
94
|
+
div[clips] svg[dial] line {
|
|
95
|
+
stroke: var(--md-sys-color-primary, #7cc6ff);
|
|
96
|
+
stroke-width: 2;
|
|
97
|
+
stroke-linecap: round;
|
|
98
|
+
}
|
|
99
|
+
div[clips] svg[dial] circle {
|
|
100
|
+
fill: none;
|
|
101
|
+
stroke: rgba(255, 255, 255, 0.35);
|
|
102
|
+
}
|
|
103
|
+
div[clips] button[pose] {
|
|
104
|
+
align-self: stretch;
|
|
105
|
+
padding: 4px 8px;
|
|
106
|
+
font: inherit;
|
|
107
|
+
font-size: 0.7rem;
|
|
108
|
+
color: rgba(255, 255, 255, 0.92);
|
|
109
|
+
background-color: rgba(255, 255, 255, 0.14);
|
|
110
|
+
border: 1px solid rgba(255, 255, 255, 0.22);
|
|
111
|
+
border-radius: 5px;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
}
|
|
114
|
+
div[clips] button[pose]:hover {
|
|
115
|
+
background-color: rgba(255, 255, 255, 0.24);
|
|
116
|
+
}
|
|
117
|
+
div[clips] span[drive] {
|
|
118
|
+
opacity: 0.6;
|
|
119
|
+
font-size: 0.66rem;
|
|
120
|
+
}
|
|
121
|
+
div[clips] input {
|
|
122
|
+
width: 96px;
|
|
123
|
+
accent-color: var(--md-sys-color-primary);
|
|
124
|
+
}
|
|
125
|
+
div[clips] output {
|
|
126
|
+
font-variant-numeric: tabular-nums;
|
|
127
|
+
text-align: right;
|
|
128
|
+
opacity: 0.85;
|
|
129
|
+
}
|
|
130
|
+
div[cannot] {
|
|
131
|
+
position: absolute;
|
|
132
|
+
inset: 0;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
padding: 12px;
|
|
137
|
+
text-align: center;
|
|
138
|
+
line-height: 1.6;
|
|
139
|
+
color: #f4f4f4;
|
|
140
|
+
background-color: ${unsafeCSS(WORKBENCH)};
|
|
141
|
+
}
|
|
142
|
+
`
|
|
143
|
+
|
|
144
|
+
/** The figure being authored, as the editor holds it now. */
|
|
145
|
+
@property({ attribute: false }) asset?: V3Asset
|
|
146
|
+
/** Which figure: the type it is registered under here is this one's own, never a real type a board uses. */
|
|
147
|
+
@property() context = ''
|
|
148
|
+
@property({ attribute: false }) view: ViewSettings = DEFAULT_VIEW
|
|
149
|
+
|
|
150
|
+
@query('div[stage]') private stage!: HTMLDivElement
|
|
151
|
+
@state() private cannot = ''
|
|
152
|
+
/** Clip speeds and control values given here -- the preview's, not the figure's. */
|
|
153
|
+
@state() private driving: Record<string, number> = {}
|
|
154
|
+
@state() private giving: Record<string, number> = {}
|
|
155
|
+
|
|
156
|
+
private scene?: SceneHandle
|
|
157
|
+
private standing?: string
|
|
158
|
+
private registered?: string
|
|
159
|
+
private watcher?: ResizeObserver
|
|
160
|
+
|
|
161
|
+
render() {
|
|
162
|
+
const asset = this.asset
|
|
163
|
+
const controls = asset ? asset.document.model.inputs.filter(i => i.role === 'state') : []
|
|
164
|
+
const clips = asset ? [...new Set((asset.drivers ?? []).map(d => d.clip))] : []
|
|
165
|
+
return html`<div stage></div>
|
|
166
|
+
${this.cannot
|
|
167
|
+
? html`<div cannot>${this.cannot}</div>`
|
|
168
|
+
: html`<div legend>${PREVIEW_WAYS.map(way => html`<span>${way.axes.length ? way.name : i18next.t('figure.label.preview-base')}</span>`)}</div>
|
|
169
|
+
${controls.length + clips.length ? this.renderDrivers(asset!, controls, clips) : nothing}`}`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private renderDrivers(asset: V3Asset, controls: V3Asset['document']['model']['inputs'], clips: string[]) {
|
|
173
|
+
const changed = controls.filter(c => this.giving[c.id] !== undefined && this.giving[c.id] !== asset.stateDefaults[c.id])
|
|
174
|
+
return html`<div clips>
|
|
175
|
+
${changed.length
|
|
176
|
+
? html`<button pose title=${i18next.t('figure.text.pose-as-default-explained')} @click=${() => this.poseAsDefault()}>
|
|
177
|
+
${i18next.t('figure.button.pose-as-default')}
|
|
178
|
+
</button>`
|
|
179
|
+
: nothing}
|
|
180
|
+
${controls.map(control => {
|
|
181
|
+
const value = this.giving[control.id] ?? asset.stateDefaults[control.id] ?? control.min
|
|
182
|
+
const step = Math.max((control.max - control.min) / 100, 0.01)
|
|
183
|
+
const label = asset.stateInputs?.[control.id]?.label ?? control.id
|
|
184
|
+
return html`<label>
|
|
185
|
+
<span>${label} <span drive>${control.unit}</span></span>
|
|
186
|
+
${this.renderDial(control, value)}
|
|
187
|
+
<input
|
|
188
|
+
type="range"
|
|
189
|
+
min=${control.min}
|
|
190
|
+
max=${control.max}
|
|
191
|
+
step=${step}
|
|
192
|
+
.value=${String(value)}
|
|
193
|
+
@input=${(e: Event) => this.give(control.id, Number((e.target as HTMLInputElement).value))}
|
|
194
|
+
/>
|
|
195
|
+
<output>${Math.round(value)}</output>
|
|
196
|
+
</label>`
|
|
197
|
+
})}
|
|
198
|
+
${clips.map(clip => {
|
|
199
|
+
const value = this.driving[clip] ?? 1
|
|
200
|
+
return html`<label>
|
|
201
|
+
<span>${clip} <span drive>${i18next.t('figure.label.playback-speed')}</span></span>
|
|
202
|
+
<span></span>
|
|
203
|
+
<input type="range" min="0" max="3" step="0.05" .value=${String(value)} @input=${(e: Event) => this.drive(clip, Number((e.target as HTMLInputElement).value))} />
|
|
204
|
+
<output>${value.toFixed(2)}</output>
|
|
205
|
+
</label>`
|
|
206
|
+
})}
|
|
207
|
+
</div>`
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private renderDial(control: { unit: string; min: number; max: number }, value: number) {
|
|
211
|
+
if (control.unit !== 'deg') return html`<span></span>`
|
|
212
|
+
const dial = dialShape(control.min, control.max, value)
|
|
213
|
+
const span = dial.radius * 2 + 2
|
|
214
|
+
return html`<svg dial viewBox="0 0 ${span} ${span}" aria-hidden="true">
|
|
215
|
+
<path d=${dial.sector}></path>
|
|
216
|
+
<circle cx=${dial.centre.x} cy=${dial.centre.y} r=${dial.radius}></circle>
|
|
217
|
+
<line x1=${dial.centre.x} y1=${dial.centre.y} x2=${dial.needle.x} y2=${dial.needle.y}></line>
|
|
218
|
+
</svg>`
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private drive(clip: string, value: number) {
|
|
222
|
+
this.driving = { ...this.driving, [clip]: value }
|
|
223
|
+
this.tellInstances('clips', this.driving)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private give(id: string, value: number) {
|
|
227
|
+
this.giving = { ...this.giving, [id]: value }
|
|
228
|
+
this.tellInstances('parameters', this.giving)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private poseAsDefault() {
|
|
232
|
+
this.dispatchEvent(new CustomEvent('v3-part-edit', { detail: { edit: { kind: 'set-state-defaults', values: { ...this.giving } } }, bubbles: true, composed: true }))
|
|
233
|
+
this.giving = {}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private tellInstances(key: 'clips' | 'parameters', values: Record<string, number>) {
|
|
237
|
+
for (const component of this.scene?.root?.components ?? []) component.set?.(key, { ...values })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
willUpdate(changed: Map<string, unknown>) {
|
|
241
|
+
if (!this.hasUpdated) return
|
|
242
|
+
if (changed.has('asset') || changed.has('context')) {
|
|
243
|
+
this.standUp()
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
if (!changed.has('view')) return
|
|
247
|
+
const before = changed.get('view') as ViewSettings | undefined
|
|
248
|
+
const now = this.view ?? DEFAULT_VIEW
|
|
249
|
+
if (before?.placement !== now.placement || before?.ceilingHeight !== now.ceilingHeight) this.standUp()
|
|
250
|
+
else this.scene?.root?.set?.(sceneLook(now))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
firstUpdated() {
|
|
254
|
+
this.standUp()
|
|
255
|
+
this.watcher = new ResizeObserver(() => this.scene?.resize?.())
|
|
256
|
+
this.watcher.observe(this)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
disconnectedCallback() {
|
|
260
|
+
super.disconnectedCallback()
|
|
261
|
+
this.watcher?.disconnect()
|
|
262
|
+
this.watcher = undefined
|
|
263
|
+
this.teardown()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private standUp() {
|
|
267
|
+
const asset = this.asset
|
|
268
|
+
const view = this.view ?? DEFAULT_VIEW
|
|
269
|
+
if (!asset || !asset.appearance.length) {
|
|
270
|
+
this.cannot = i18next.t('figure.text.preview-needs-a-part')
|
|
271
|
+
this.teardown()
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
const signature = JSON.stringify([asset, view.placement, view.ceilingHeight])
|
|
275
|
+
if (this.scene && this.standing === signature) return
|
|
276
|
+
try {
|
|
277
|
+
compileV3Asset(asset)
|
|
278
|
+
} catch {
|
|
279
|
+
this.cannot = i18next.t('figure.text.preview-blocked-by-errors')
|
|
280
|
+
this.teardown()
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
this.teardown()
|
|
284
|
+
this.driving = {}
|
|
285
|
+
try {
|
|
286
|
+
/* Registered as a board registers it, under a type of this preview's own: a real type's drawings stay as they are. */
|
|
287
|
+
const type = `figure-v3-preview-${this.context || 'new'}`
|
|
288
|
+
registerFigureV3(type, asset)
|
|
289
|
+
this.registered = type
|
|
290
|
+
const size = authoredSizeOf(asset)
|
|
291
|
+
const { components, board } = previewInstances(type, size)
|
|
292
|
+
this.scene = create({
|
|
293
|
+
target: this.stage,
|
|
294
|
+
model: {
|
|
295
|
+
type: 'model-layer',
|
|
296
|
+
width: board.width,
|
|
297
|
+
height: board.height,
|
|
298
|
+
depth: previewBoardDepth({ placement: asset.occupancy?.placement, base: { y: size.y } }, view.ceilingHeight, PREVIEW_FACTOR),
|
|
299
|
+
threed: true,
|
|
300
|
+
sky: 'color',
|
|
301
|
+
...sceneLook(view),
|
|
302
|
+
placement: view.placement,
|
|
303
|
+
heights: { floor: 0, ceiling: view.ceilingHeight },
|
|
304
|
+
pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),
|
|
305
|
+
components
|
|
306
|
+
},
|
|
307
|
+
mode: 0
|
|
308
|
+
}) as SceneHandle
|
|
309
|
+
this.standing = signature
|
|
310
|
+
this.scene.resize?.()
|
|
311
|
+
this.scene.fit?.('ratio')
|
|
312
|
+
this.scene.root?.fitCameraToModel?.()
|
|
313
|
+
this.cannot = ''
|
|
314
|
+
} catch (error) {
|
|
315
|
+
this.cannot = (error as Error).message
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private teardown() {
|
|
320
|
+
this.scene?.dispose?.()
|
|
321
|
+
this.scene = undefined
|
|
322
|
+
this.standing = undefined
|
|
323
|
+
if (this.registered) unregisterFigureV3(this.registered)
|
|
324
|
+
this.registered = undefined
|
|
325
|
+
}
|
|
326
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { V3AuthoringAction, V3Face } from '@hatiolab/figure-model'
|
|
|
9
9
|
|
|
10
10
|
import { occupancyFromParts } from './v3-occupancy.js'
|
|
11
11
|
import { tagsOf, v3FigureFacts, type Placement, type V3FigureFacts } from './v3-figure-facts.js'
|
|
12
|
+
import { SidePanelStyles } from './side-panel-styles.js'
|
|
12
13
|
|
|
13
14
|
const PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center']
|
|
14
15
|
const FACES = Object.keys(V3_FACES) as V3Face[]
|
|
@@ -31,71 +32,30 @@ const FACES = Object.keys(V3_FACES) as V3Face[]
|
|
|
31
32
|
*/
|
|
32
33
|
@customElement('figure-v3-properties')
|
|
33
34
|
export class FigureV3Properties extends localize(i18next)(LitElement) {
|
|
34
|
-
static styles = css`
|
|
35
|
+
static styles = [SidePanelStyles, css`
|
|
35
36
|
:host {
|
|
36
|
-
display:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
padding: 12px;
|
|
37
|
+
display: block;
|
|
38
|
+
/* It stands inside the properties tab's panel, which gives the margin; a second one indented it past the type. */
|
|
39
|
+
padding: 0;
|
|
40
40
|
overflow: auto;
|
|
41
|
-
font-size: 0.76rem;
|
|
42
|
-
color: var(--md-sys-color-on-surface);
|
|
43
|
-
}
|
|
44
|
-
div[field] {
|
|
45
|
-
display: grid;
|
|
46
|
-
gap: 4px;
|
|
47
|
-
}
|
|
48
|
-
div[field] > label {
|
|
49
|
-
font-weight: 600;
|
|
50
41
|
}
|
|
51
42
|
div[row] {
|
|
52
43
|
display: flex;
|
|
53
44
|
gap: 6px;
|
|
54
45
|
align-items: center;
|
|
55
46
|
}
|
|
56
|
-
input,
|
|
57
|
-
select,
|
|
58
|
-
button {
|
|
59
|
-
font: inherit;
|
|
60
|
-
min-width: 0;
|
|
61
|
-
}
|
|
62
|
-
input,
|
|
63
|
-
select {
|
|
64
|
-
padding: 5px 8px;
|
|
65
|
-
border: 1px solid var(--md-sys-color-outline-variant);
|
|
66
|
-
border-radius: 4px;
|
|
67
|
-
background: var(--md-sys-color-surface);
|
|
68
|
-
color: var(--md-sys-color-on-surface);
|
|
69
|
-
}
|
|
70
47
|
div[row] > select {
|
|
71
48
|
flex: 1;
|
|
72
49
|
}
|
|
73
|
-
button {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
padding: 4px 10px;
|
|
78
|
-
border: none;
|
|
79
|
-
border-radius: 999em;
|
|
80
|
-
cursor: pointer;
|
|
81
|
-
color: var(--md-sys-color-on-secondary-container);
|
|
82
|
-
background: var(--md-sys-color-secondary-container);
|
|
83
|
-
}
|
|
84
|
-
button:disabled,
|
|
85
|
-
input:disabled,
|
|
86
|
-
select:disabled {
|
|
87
|
-
opacity: 0.5;
|
|
88
|
-
cursor: default;
|
|
89
|
-
}
|
|
90
|
-
button md-icon {
|
|
91
|
-
font-size: 16px;
|
|
92
|
-
width: 16px;
|
|
93
|
-
height: 16px;
|
|
50
|
+
div[row] > button[act] {
|
|
51
|
+
flex: 1;
|
|
52
|
+
width: auto;
|
|
53
|
+
margin-top: 0;
|
|
94
54
|
}
|
|
95
|
-
|
|
96
|
-
|
|
55
|
+
div[row] > span {
|
|
56
|
+
flex: 1;
|
|
97
57
|
}
|
|
98
|
-
`
|
|
58
|
+
`]
|
|
99
59
|
|
|
100
60
|
/** The source being edited. */
|
|
101
61
|
@property({ type: String }) source?: string
|
|
@@ -119,8 +79,20 @@ export class FigureV3Properties extends localize(i18next)(LitElement) {
|
|
|
119
79
|
render() {
|
|
120
80
|
const facts = this.facts()
|
|
121
81
|
if (!facts) return nothing
|
|
122
|
-
|
|
123
|
-
|
|
82
|
+
/* The figure's type is the page's (it lives on the row); it is slotted into the first section, as V2 had it. */
|
|
83
|
+
return html`<section>
|
|
84
|
+
<h3>${i18next.t('figure.label.group-figure')}</h3>
|
|
85
|
+
<slot name="type"></slot>
|
|
86
|
+
${this.renderName(facts)} ${this.renderPlacement(facts)}
|
|
87
|
+
</section>
|
|
88
|
+
<section>
|
|
89
|
+
<h3>${i18next.t('figure.label.group-standing')}</h3>
|
|
90
|
+
${this.renderSpace(facts)} ${this.renderMounting(facts)}
|
|
91
|
+
</section>
|
|
92
|
+
<section>
|
|
93
|
+
<h3>${i18next.t('figure.label.group-catalog')}</h3>
|
|
94
|
+
${this.renderCatalog()}
|
|
95
|
+
</section>`
|
|
124
96
|
}
|
|
125
97
|
|
|
126
98
|
private renderName(facts: V3FigureFacts) {
|
|
@@ -172,7 +144,7 @@ export class FigureV3Properties extends localize(i18next)(LitElement) {
|
|
|
172
144
|
<label>${i18next.t('figure.label.declared-space')}</label>
|
|
173
145
|
<div row>
|
|
174
146
|
<span>${i18next.t(facts.declared ? 'figure.text.declared-space-declared' : 'figure.text.declared-space-none')}</span>
|
|
175
|
-
<button
|
|
147
|
+
<button act ?disabled=${!facts.parts.length} @click=${() => this.declare(facts.placement ?? 'floor')}>
|
|
176
148
|
<md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}
|
|
177
149
|
</button>
|
|
178
150
|
</div>
|
|
@@ -196,10 +168,10 @@ export class FigureV3Properties extends localize(i18next)(LitElement) {
|
|
|
196
168
|
</select>
|
|
197
169
|
</div>
|
|
198
170
|
<div row>
|
|
199
|
-
<button ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part!, face })}>
|
|
171
|
+
<button act ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part!, face })}>
|
|
200
172
|
${i18next.t('figure.button.set-mounting-face')}
|
|
201
173
|
</button>
|
|
202
|
-
<button ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>
|
|
174
|
+
<button act ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>
|
|
203
175
|
${i18next.t('figure.button.clear-mounting-face')}
|
|
204
176
|
</button>
|
|
205
177
|
</div>
|