@fmsim/board 2.0.0-beta.8 → 2.0.0-beta.81
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/custom-elements.json +5568 -3044
- package/demo/index-modeller.html +1 -1
- package/demo/index-player.html +2 -2
- package/demo/index-viewer.html +2 -2
- package/demo/index.html +2 -2
- package/dist/src/component/etc.js +35 -0
- package/dist/src/component/etc.js.map +1 -1
- package/dist/src/component/index.d.ts +1 -0
- package/dist/src/component/index.js +1 -0
- package/dist/src/component/index.js.map +1 -1
- package/dist/src/component/restful-property-editor-backdrop-faces.d.ts +50 -0
- package/dist/src/component/restful-property-editor-backdrop-faces.js +524 -0
- package/dist/src/component/restful-property-editor-backdrop-faces.js.map +1 -0
- package/dist/src/data-storage/board-model-cache.d.ts +13 -0
- package/dist/src/data-storage/board-model-cache.js +66 -0
- package/dist/src/data-storage/board-model-cache.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/layers/movement-layer.d.ts +4 -4
- package/dist/src/layers/movement-layer.js +16 -8
- package/dist/src/layers/movement-layer.js.map +1 -1
- package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
- package/dist/src/modeller/bulk-create-dialog.js +531 -0
- package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
- package/dist/src/modeller/bulk-create.d.ts +101 -0
- package/dist/src/modeller/bulk-create.js +199 -0
- package/dist/src/modeller/bulk-create.js.map +1 -0
- package/dist/src/modeller/component-toolbar/component-menu.d.ts +1 -1
- package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +1 -1
- package/dist/src/modeller/edit-toolbar-style.js +43 -3
- package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
- package/dist/src/modeller/edit-toolbar.d.ts +13 -0
- package/dist/src/modeller/edit-toolbar.js +168 -7
- package/dist/src/modeller/edit-toolbar.js.map +1 -1
- package/dist/src/modeller/property-sidebar/abstract-property.js +10 -2
- package/dist/src/modeller/property-sidebar/abstract-property.js.map +1 -1
- package/dist/src/modeller/property-sidebar/data-binding/data-binding.d.ts +1 -1
- package/dist/src/modeller/property-sidebar/effects/effects.d.ts +1 -1
- package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +1 -1
- package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +1 -1
- package/dist/src/modeller/property-sidebar/shapes/shapes.js +31 -0
- package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
- package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +2 -1
- package/dist/src/modeller/property-sidebar/specifics/specifics.js +10 -0
- package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
- package/dist/src/modeller/property-sidebar/styles/styles.d.ts +1 -1
- package/dist/src/modeller/scene-viewer/ox-scene-layer.d.ts +4 -3
- package/dist/src/modeller/scene-viewer/ox-scene-layer.js +2 -1
- package/dist/src/modeller/scene-viewer/ox-scene-layer.js.map +1 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +5 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
- package/dist/src/ox-board-component-info.d.ts +1 -0
- package/dist/src/ox-board-component-info.js +5 -1
- package/dist/src/ox-board-component-info.js.map +1 -1
- package/dist/src/ox-board-list.js +2 -2
- package/dist/src/ox-board-list.js.map +1 -1
- package/dist/src/ox-board-modeller.d.ts +6 -3
- package/dist/src/ox-board-modeller.js +13 -1
- package/dist/src/ox-board-modeller.js.map +1 -1
- package/dist/src/ox-board-viewer.d.ts +65 -13
- package/dist/src/ox-board-viewer.js +610 -98
- package/dist/src/ox-board-viewer.js.map +1 -1
- package/dist/src/ox-editor-board-selector.d.ts +3 -0
- package/dist/src/ox-editor-board-selector.js +38 -9
- package/dist/src/ox-editor-board-selector.js.map +1 -1
- package/dist/src/ox-playback-controls.d.ts +157 -0
- package/dist/src/ox-playback-controls.js +1089 -0
- package/dist/src/ox-playback-controls.js.map +1 -0
- package/dist/src/playback/playback-engine.d.ts +106 -0
- package/dist/src/playback/playback-engine.js +395 -0
- package/dist/src/playback/playback-engine.js.map +1 -0
- package/dist/src/selector/board-creation-popup.js +2 -2
- package/dist/src/selector/board-creation-popup.js.map +1 -1
- package/dist/src/selector/board-thumbnail-card.js +6 -6
- package/dist/src/selector/board-thumbnail-card.js.map +1 -1
- package/dist/src/selector/ox-board-creation-card.js +2 -2
- package/dist/src/selector/ox-board-creation-card.js.map +1 -1
- package/dist/src/selector/ox-board-selector.js +21 -10
- package/dist/src/selector/ox-board-selector.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/icons/components/text.png +0 -0
- package/icons/icon-htoolbar.png +0 -0
- package/package.json +8 -4
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
var BoardViewer_1;
|
|
1
2
|
import { __decorate } from "tslib";
|
|
2
3
|
import '@material/web/fab/fab.js';
|
|
3
4
|
import '@material/web/icon/icon.js';
|
|
4
5
|
import './layers/movement-layer.js';
|
|
5
6
|
import './ox-board-component-info.js';
|
|
6
7
|
import './layers/mcs-mouse-event-handler.js';
|
|
7
|
-
import
|
|
8
|
+
import './ox-playback-controls.js';
|
|
9
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
8
10
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
+
import { PlaybackEngine } from './playback/playback-engine.js';
|
|
12
|
+
import { create, LoadTracker, LoadingOverlay, SCENE_MODE } from '@hatiolab/things-scene';
|
|
13
|
+
import { isIOS, fullscreen, exitfullscreen, togglefullscreen } from '@operato/utils';
|
|
11
14
|
import { ScrollbarStyles } from '@operato/styles';
|
|
12
15
|
import { DataStorage } from './data-storage/data-storage.js';
|
|
13
16
|
import { runScenario, startScenario, DataSubscriptionProviderImpl, isRestfulAvailable } from '@fmsim/api';
|
|
14
|
-
let BoardViewer = class BoardViewer extends LitElement {
|
|
17
|
+
let BoardViewer = BoardViewer_1 = class BoardViewer extends LitElement {
|
|
15
18
|
constructor() {
|
|
16
19
|
var _a;
|
|
17
20
|
super(...arguments);
|
|
@@ -24,6 +27,8 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
24
27
|
this._forward = [];
|
|
25
28
|
this._backward = [];
|
|
26
29
|
this._oldtarget = null;
|
|
30
|
+
this._fabExpanded = false;
|
|
31
|
+
this._playbackActive = false;
|
|
27
32
|
this.currentBoardId = (_a = this.board) === null || _a === void 0 ? void 0 : _a.id;
|
|
28
33
|
this.popup = null;
|
|
29
34
|
this.lastComponent = null;
|
|
@@ -33,7 +38,6 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
33
38
|
const id = e.detail.value;
|
|
34
39
|
const components = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.findAll(`#${id}`);
|
|
35
40
|
if (!components || components.length == 0) {
|
|
36
|
-
console.log('No equipment was found.');
|
|
37
41
|
return;
|
|
38
42
|
}
|
|
39
43
|
window.dispatchEvent(new CustomEvent('clickComponent', { detail: components[0] }));
|
|
@@ -41,9 +45,16 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
41
45
|
};
|
|
42
46
|
// 모니터링에서 Movement Data를 보여준다.
|
|
43
47
|
this.fmbShowMovementHandler = (e) => {
|
|
44
|
-
var _a;
|
|
48
|
+
var _a, _b;
|
|
49
|
+
// 플레이백 중에는 외부 모니터링이 보내는 실시간 반송 정보를 그리지 않는다 —
|
|
50
|
+
// 사용자가 플레이백 시점의 *과거 상태* 를 보고 있는데 실시간 화살표가 겹치면
|
|
51
|
+
// 혼란을 줌. (백엔드 상세 정보 요청은 ox-board-component-info.ts:225 에서
|
|
52
|
+
// 같은 playbackMode 가드로 이미 차단됨.)
|
|
53
|
+
if ((_a = this._scene) === null || _a === void 0 ? void 0 : _a.playbackMode) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
45
56
|
const movement = e.detail.value;
|
|
46
|
-
const movementLayer = (
|
|
57
|
+
const movementLayer = (_b = this._scene) === null || _b === void 0 ? void 0 : _b.findById(`movement-layer`);
|
|
47
58
|
if (!movementLayer) {
|
|
48
59
|
return;
|
|
49
60
|
}
|
|
@@ -107,26 +118,25 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
107
118
|
floatingContainer.hidden = true;
|
|
108
119
|
}
|
|
109
120
|
};
|
|
110
|
-
// BoardComponentInfo popup을
|
|
121
|
+
// BoardComponentInfo popup 을 보여주고 deco-border 로 selected highlight.
|
|
122
|
+
// 영구 추적과는 독립 — query-submit 핸들러 (onQuerySubmit) 가 별도로 담당.
|
|
111
123
|
this.clickComponentHandler = (e) => {
|
|
112
|
-
var _a, _b;
|
|
113
124
|
const component = e.detail;
|
|
114
125
|
const { type } = component.state;
|
|
115
126
|
if (type == 'model-layer') {
|
|
116
|
-
|
|
117
|
-
// this.lastComponent?.trigger('deco-arrow-reset')
|
|
127
|
+
this._clearClickHighlight();
|
|
118
128
|
this.hidePopup();
|
|
119
129
|
return;
|
|
120
130
|
}
|
|
121
131
|
if ('isLVComponent' in component) {
|
|
122
|
-
|
|
123
|
-
// this.lastComponent?.trigger('deco-arrow-reset')
|
|
132
|
+
this._clearClickHighlight();
|
|
124
133
|
this.lastComponent = component;
|
|
125
134
|
component.trigger('deco-border', { distance: 8, lineWidth: 4, color: '#f007' });
|
|
126
|
-
// component.trigger('deco-arrow', { color: 'red', animation: true, size: 20 })
|
|
127
135
|
this.togglePopup(component);
|
|
128
136
|
}
|
|
129
137
|
};
|
|
138
|
+
// 영구 추적 라이프사이클은 things-scene 의 Scene.tracker 가 일괄 관리.
|
|
139
|
+
// (polling, deco-border 부착/해제, 3D outline 색 동기화, centerTo)
|
|
130
140
|
this.fmbStartAnimation = (e) => {
|
|
131
141
|
var _a;
|
|
132
142
|
const { target, animation, decorator, border, arrow } = e.detail || {};
|
|
@@ -173,22 +183,189 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
173
183
|
var _a;
|
|
174
184
|
(_a = this._scene) === null || _a === void 0 ? void 0 : _a.root.trigger('deco-reset');
|
|
175
185
|
};
|
|
186
|
+
this._pointerMoveHandler = () => this.transientShowButtons();
|
|
187
|
+
/**
|
|
188
|
+
* playback 모드는 전체화면 모드와 한 몸 — 어떤 경로로든 전체화면이 해제되면
|
|
189
|
+
* playback 도 함께 종료. (단독 전체화면 토글은 기존처럼 동작하도록 `_playbackActive` 가드.)
|
|
190
|
+
*/
|
|
191
|
+
this._fullscreenChangeHandler = () => {
|
|
192
|
+
if (!document.fullscreenElement && this._playbackActive) {
|
|
193
|
+
this._onPlaybackStop();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
176
196
|
this.resizeHandler = () => {
|
|
177
197
|
this._scene && this._scene.fit();
|
|
178
198
|
};
|
|
199
|
+
// ── Data Subscription ──
|
|
200
|
+
this._dataSubscriptionProvider = isRestfulAvailable() ? undefined : new DataSubscriptionProviderImpl();
|
|
201
|
+
}
|
|
202
|
+
get _is3dMode() {
|
|
203
|
+
var _a, _b, _c;
|
|
204
|
+
return !!((_c = (_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.rootModel) === null || _c === void 0 ? void 0 : _c.is3dMode);
|
|
179
205
|
}
|
|
180
206
|
render() {
|
|
181
|
-
var
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
207
|
+
var _a, _b;
|
|
208
|
+
var fabGroup = this.hideFullscreen
|
|
209
|
+
? html ``
|
|
210
|
+
: html `
|
|
211
|
+
<div
|
|
212
|
+
fab-group
|
|
213
|
+
?expanded=${this._fabExpanded}
|
|
186
214
|
@mouseover=${(e) => this.transientShowButtons(true)}
|
|
187
215
|
@mouseout=${(e) => this.transientShowButtons()}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
216
|
+
>
|
|
217
|
+
<md-fab
|
|
218
|
+
class="main-fab"
|
|
219
|
+
?expanded=${this._fabExpanded}
|
|
220
|
+
@click=${() => { this._fabExpanded = !this._fabExpanded; }}
|
|
221
|
+
title="menu"
|
|
222
|
+
><md-icon slot="icon">add</md-icon></md-fab>
|
|
223
|
+
|
|
224
|
+
${isIOS() ? nothing : html `<md-fab
|
|
225
|
+
class="sub-fab"
|
|
226
|
+
@click=${() => { this._fabExpanded = false; this.onTapFullscreen(); }}
|
|
227
|
+
title="fullscreen"
|
|
228
|
+
><md-icon slot="icon">${document.fullscreenElement ? 'fullscreen_exit' : 'fullscreen'}</md-icon></md-fab>`}
|
|
229
|
+
|
|
230
|
+
<md-fab
|
|
231
|
+
class="sub-fab"
|
|
232
|
+
@click=${() => { this._fabExpanded = false; this.onTapPlayback(); }}
|
|
233
|
+
title=${this._playbackActive ? 'stop playback' : 'start playback'}
|
|
234
|
+
>${this._playbackActive
|
|
235
|
+
? html `<svg slot="icon" width="48" height="48" viewBox="0 0 100 100" style="display: block;">
|
|
236
|
+
<circle cx="50" cy="50" r="50" fill="#F9EBEA" />
|
|
237
|
+
<g transform="translate(-5, 0)">
|
|
238
|
+
<path
|
|
239
|
+
d="M 52,22 A 28,28 0 1,1 30,40"
|
|
240
|
+
fill="none"
|
|
241
|
+
stroke="#F87171"
|
|
242
|
+
stroke-width="5"
|
|
243
|
+
stroke-linecap="round"
|
|
244
|
+
/>
|
|
245
|
+
<path d="M 52,12 L 37,24 L 55,33 Z" fill="#F87171" />
|
|
246
|
+
</g>
|
|
247
|
+
<rect
|
|
248
|
+
x="36"
|
|
249
|
+
y="36"
|
|
250
|
+
width="28"
|
|
251
|
+
height="28"
|
|
252
|
+
rx="3"
|
|
253
|
+
fill="#F87171"
|
|
254
|
+
stroke="#F87171"
|
|
255
|
+
stroke-width="3"
|
|
256
|
+
stroke-linejoin="round"
|
|
257
|
+
/>
|
|
258
|
+
<text
|
|
259
|
+
x="50"
|
|
260
|
+
y="53.5"
|
|
261
|
+
font-family="Verdana, sans-serif"
|
|
262
|
+
font-size="9"
|
|
263
|
+
font-weight="900"
|
|
264
|
+
fill="#ffffff"
|
|
265
|
+
text-anchor="middle"
|
|
266
|
+
>OFF</text>
|
|
267
|
+
</svg>`
|
|
268
|
+
: html `<svg slot="icon" width="48" height="48" viewBox="0 0 100 100" style="display: block;">
|
|
269
|
+
<circle cx="50" cy="50" r="50" fill="#F9EBEA" />
|
|
270
|
+
<g transform="translate(-5, 0)">
|
|
271
|
+
<path
|
|
272
|
+
d="M 52,22 A 28,28 0 1,1 30,40"
|
|
273
|
+
fill="none"
|
|
274
|
+
stroke="#F87171"
|
|
275
|
+
stroke-width="5"
|
|
276
|
+
stroke-linecap="round"
|
|
277
|
+
/>
|
|
278
|
+
<path d="M 52,12 L 37,24 L 55,33 Z" fill="#F87171" />
|
|
279
|
+
</g>
|
|
280
|
+
<path
|
|
281
|
+
d="M 42,35 L 65,50 L 42,65 Z"
|
|
282
|
+
fill="#F87171"
|
|
283
|
+
stroke="#F87171"
|
|
284
|
+
stroke-width="3"
|
|
285
|
+
stroke-linejoin="round"
|
|
286
|
+
/>
|
|
287
|
+
<text
|
|
288
|
+
x="49.5"
|
|
289
|
+
y="53"
|
|
290
|
+
font-family="Verdana, sans-serif"
|
|
291
|
+
font-size="9"
|
|
292
|
+
font-weight="900"
|
|
293
|
+
fill="#ffffff"
|
|
294
|
+
text-anchor="middle"
|
|
295
|
+
>RE:</text>
|
|
296
|
+
</svg>`}</md-fab>
|
|
297
|
+
|
|
298
|
+
<md-fab
|
|
299
|
+
class="sub-fab"
|
|
300
|
+
@click=${() => { this._fabExpanded = false; this.onTapToggle3D(); }}
|
|
301
|
+
title=${this._is3dMode ? '2D mode' : '3D mode'}
|
|
302
|
+
>${this._is3dMode
|
|
303
|
+
? html `<svg slot="icon" width="48" height="48" viewBox="0 0 100 100" style="display: block;">
|
|
304
|
+
<circle cx="50" cy="50" r="50" fill="#F9EBEA" />
|
|
305
|
+
<g transform="rotate(55, 50, 50)">
|
|
306
|
+
<path
|
|
307
|
+
d="M 72,32 A 30,30 0 0,0 38,28"
|
|
308
|
+
fill="none"
|
|
309
|
+
stroke="#F87171"
|
|
310
|
+
stroke-width="5"
|
|
311
|
+
stroke-linecap="round"
|
|
312
|
+
/>
|
|
313
|
+
<path d="M 38,20 L 22,30 L 38,40 Z" fill="#F87171" />
|
|
314
|
+
</g>
|
|
315
|
+
<g transform="rotate(235, 50, 50)">
|
|
316
|
+
<path
|
|
317
|
+
d="M 72,32 A 30,30 0 0,0 38,28"
|
|
318
|
+
fill="none"
|
|
319
|
+
stroke="#F87171"
|
|
320
|
+
stroke-width="5"
|
|
321
|
+
stroke-linecap="round"
|
|
322
|
+
/>
|
|
323
|
+
<path d="M 38,20 L 22,30 L 38,40 Z" fill="#F87171" />
|
|
324
|
+
</g>
|
|
325
|
+
<text
|
|
326
|
+
x="50"
|
|
327
|
+
y="57.5"
|
|
328
|
+
font-family="Verdana, sans-serif"
|
|
329
|
+
font-size="20"
|
|
330
|
+
font-weight="900"
|
|
331
|
+
fill="#F87171"
|
|
332
|
+
text-anchor="middle"
|
|
333
|
+
>2D</text>
|
|
334
|
+
</svg>`
|
|
335
|
+
: html `<svg slot="icon" width="48" height="48" viewBox="0 0 100 100" style="display: block;">
|
|
336
|
+
<circle cx="50" cy="50" r="50" fill="#F9EBEA" />
|
|
337
|
+
<g transform="rotate(55, 50, 50)">
|
|
338
|
+
<path
|
|
339
|
+
d="M 72,32 A 30,30 0 0,0 38,28"
|
|
340
|
+
fill="none"
|
|
341
|
+
stroke="#F87171"
|
|
342
|
+
stroke-width="5"
|
|
343
|
+
stroke-linecap="round"
|
|
344
|
+
/>
|
|
345
|
+
<path d="M 38,20 L 22,30 L 38,40 Z" fill="#F87171" />
|
|
346
|
+
</g>
|
|
347
|
+
<g transform="rotate(235, 50, 50)">
|
|
348
|
+
<path
|
|
349
|
+
d="M 72,32 A 30,30 0 0,0 38,28"
|
|
350
|
+
fill="none"
|
|
351
|
+
stroke="#F87171"
|
|
352
|
+
stroke-width="5"
|
|
353
|
+
stroke-linecap="round"
|
|
354
|
+
/>
|
|
355
|
+
<path d="M 38,20 L 22,30 L 38,40 Z" fill="#F87171" />
|
|
356
|
+
</g>
|
|
357
|
+
<text
|
|
358
|
+
x="50"
|
|
359
|
+
y="57.5"
|
|
360
|
+
font-family="Verdana, sans-serif"
|
|
361
|
+
font-size="20"
|
|
362
|
+
font-weight="900"
|
|
363
|
+
fill="#F87171"
|
|
364
|
+
text-anchor="middle"
|
|
365
|
+
>3D</text>
|
|
366
|
+
</svg>`}</md-fab>
|
|
367
|
+
</div>
|
|
368
|
+
`;
|
|
192
369
|
var prev = !this.hideNavigation
|
|
193
370
|
? html `
|
|
194
371
|
<md-icon
|
|
@@ -216,74 +393,102 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
216
393
|
return html `
|
|
217
394
|
${prev}
|
|
218
395
|
|
|
219
|
-
<div
|
|
220
|
-
id="target"
|
|
221
|
-
@touchstart=${(e) => this.transientShowButtons()}
|
|
222
|
-
@mousemove=${(e) => this.transientShowButtons()}
|
|
223
|
-
></div>
|
|
396
|
+
<div id="target"></div>
|
|
224
397
|
|
|
225
398
|
<slot></slot>
|
|
226
|
-
${next} ${
|
|
399
|
+
${next} ${fabGroup}
|
|
400
|
+
|
|
401
|
+
${this._playbackActive
|
|
402
|
+
? html `<ox-playback-controls
|
|
403
|
+
.playbackState=${((_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.state) === 'playing' ? 'playing' : ((_b = this._playbackEngine) === null || _b === void 0 ? void 0 : _b.state) === 'paused' ? 'paused' : 'idle'}
|
|
404
|
+
@playback-start=${this._onPlaybackStart}
|
|
405
|
+
@playback-pause=${this._onPlaybackPause}
|
|
406
|
+
@playback-resume=${this._onPlaybackResume}
|
|
407
|
+
@playback-stop=${this._onPlaybackStop}
|
|
408
|
+
@playback-seek=${this._onPlaybackSeek}
|
|
409
|
+
@playback-seek-start=${this._onPlaybackSeekStart}
|
|
410
|
+
@playback-seek-preview=${this._onPlaybackSeekPreview}
|
|
411
|
+
@playback-speed=${this._onPlaybackSpeed}
|
|
412
|
+
></ox-playback-controls>`
|
|
413
|
+
: nothing}
|
|
227
414
|
`;
|
|
228
415
|
}
|
|
416
|
+
/** 마우스 클릭/popup 의 selected highlight 해제. */
|
|
417
|
+
_clearClickHighlight() {
|
|
418
|
+
if (this.lastComponent) {
|
|
419
|
+
this.lastComponent.trigger('deco-border-off');
|
|
420
|
+
this.lastComponent = null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/** connect/disconnect 시 일괄 등록/해제할 window 이벤트 목록 (모니터링 연동) */
|
|
424
|
+
get _monitoringEvents() {
|
|
425
|
+
return [
|
|
426
|
+
['fetchIFactsData', this.fetchIFactsDataHandler], // 장비 아이디 object를 캔버스 가운데로
|
|
427
|
+
['fmbShowMovement', this.fmbShowMovementHandler], // Movement Data 표시
|
|
428
|
+
['fmbMonitoringInit', this.fmbMonitoringInitHandler], // 보드 상태 정사이즈 복원
|
|
429
|
+
['clickComponent', this.clickComponentHandler], // BoardComponentInfo popup
|
|
430
|
+
['fmbStartAnimation', this.fmbStartAnimation], // AnimatedIcon 추가/제거
|
|
431
|
+
['fmbStopAnimation', this.fmbStopAnimation],
|
|
432
|
+
['fmbResetAnimation', this.fmbResetAnimation],
|
|
433
|
+
['fmbShowLegend', this.fmbShowLegendHandler], // 범례 표시
|
|
434
|
+
['fmbHideLegend', this.fmbHideLegendHandler],
|
|
435
|
+
['fmbShowPopup', this.fmbShowPopupHandler], // 팝업 표시
|
|
436
|
+
['fmbHidePopup', this.fmbHidePopupHandler],
|
|
437
|
+
['fmbShowFloatingContainer', this.fmbShowFloatingContainerHandler], // FloatingContainer
|
|
438
|
+
['fmbHideFloatingContainer', this.fmbHideFloatingContainerHandler]
|
|
439
|
+
];
|
|
440
|
+
}
|
|
229
441
|
connectedCallback() {
|
|
230
442
|
super.connectedCallback();
|
|
231
443
|
window.addEventListener('resize', this.resizeHandler);
|
|
444
|
+
// 3D 모드에서도 마우스 움직임 감지 (document 레벨, capture phase)
|
|
445
|
+
document.addEventListener('pointermove', this._pointerMoveHandler, true);
|
|
446
|
+
document.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
|
|
447
|
+
document.addEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
|
|
232
448
|
this.renderRoot.addEventListener('close-scene', e => {
|
|
233
449
|
e.preventDefault();
|
|
234
450
|
this.onTapPrev();
|
|
235
451
|
}, false);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
window.addEventListener('fmbShowMovement', this.fmbShowMovementHandler);
|
|
240
|
-
// 모니터링에서 보드 상태를 원래상태 (정사이즈)로 돌려놓는다.
|
|
241
|
-
window.addEventListener('fmbMonitoringInit', this.fmbMonitoringInitHandler);
|
|
242
|
-
// BoardComponentInfo popup을 보여준다.
|
|
243
|
-
window.addEventListener('clickComponent', this.clickComponentHandler);
|
|
244
|
-
// AnimatedIcon 을 추가하거나 제거한다.
|
|
245
|
-
window.addEventListener('fmbStartAnimation', this.fmbStartAnimation);
|
|
246
|
-
window.addEventListener('fmbStopAnimation', this.fmbStopAnimation);
|
|
247
|
-
window.addEventListener('fmbResetAnimation', this.fmbResetAnimation);
|
|
248
|
-
// 모니터링에서 범례를 보여준다.
|
|
249
|
-
window.addEventListener('fmbShowLegend', this.fmbShowLegendHandler);
|
|
250
|
-
window.addEventListener('fmbHideLegend', this.fmbHideLegendHandler);
|
|
251
|
-
// 모니터링에서 범례를 보여준다.
|
|
252
|
-
window.addEventListener('fmbShowPopup', this.fmbShowPopupHandler);
|
|
253
|
-
window.addEventListener('fmbHidePopup', this.fmbHidePopupHandler);
|
|
254
|
-
// 모니터링에서 FloatingContainer를 보여준다.
|
|
255
|
-
window.addEventListener('fmbShowFloatingContainer', this.fmbShowFloatingContainerHandler);
|
|
256
|
-
window.addEventListener('fmbHideFloatingContainer', this.fmbHideFloatingContainerHandler);
|
|
452
|
+
for (const [type, handler] of this._monitoringEvents) {
|
|
453
|
+
window.addEventListener(type, handler);
|
|
454
|
+
}
|
|
257
455
|
}
|
|
258
456
|
disconnectedCallback() {
|
|
457
|
+
var _a, _b, _c, _d, _e;
|
|
259
458
|
super.disconnectedCallback();
|
|
260
459
|
window.removeEventListener('resize', this.resizeHandler);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
window.removeEventListener('fmbShowFloatingContainer', this.fmbShowFloatingContainerHandler);
|
|
281
|
-
window.removeEventListener('fmbHideFloatingContainer', this.fmbHideFloatingContainerHandler);
|
|
282
|
-
this.lastComponent = null;
|
|
460
|
+
document.removeEventListener('pointermove', this._pointerMoveHandler, true);
|
|
461
|
+
document.removeEventListener('fullscreenchange', this._fullscreenChangeHandler);
|
|
462
|
+
document.removeEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
|
|
463
|
+
for (const [type, handler] of this._monitoringEvents) {
|
|
464
|
+
window.removeEventListener(type, handler);
|
|
465
|
+
}
|
|
466
|
+
this._clearClickHighlight();
|
|
467
|
+
(_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.tracker) === null || _b === void 0 ? void 0 : _b.stop();
|
|
468
|
+
// 플레이백이 활성 상태면 명시적 stop (provider resume 포함)
|
|
469
|
+
if (this._playbackActive) {
|
|
470
|
+
(_c = this._playbackEngine) === null || _c === void 0 ? void 0 : _c.stop();
|
|
471
|
+
(_d = this._playbackEngine) === null || _d === void 0 ? void 0 : _d.detachScene();
|
|
472
|
+
(_e = this._dataSubscriptionProvider) === null || _e === void 0 ? void 0 : _e.resume();
|
|
473
|
+
this._playbackActive = false;
|
|
474
|
+
}
|
|
475
|
+
if (this._playbackEngine) {
|
|
476
|
+
this._playbackEngine.dispose();
|
|
477
|
+
this._playbackEngine = undefined;
|
|
478
|
+
}
|
|
283
479
|
this.closeScene();
|
|
284
480
|
}
|
|
285
481
|
updated(changes) {
|
|
482
|
+
// loadTracker가 외부에서 전달되면 즉시 overlay 연결 (fetch 중에도 표시)
|
|
483
|
+
if (changes.has('loadTracker') && this.loadTracker) {
|
|
484
|
+
this._ensureOverlay(this.loadTracker);
|
|
485
|
+
}
|
|
286
486
|
if (changes.has('board')) {
|
|
487
|
+
// tracker: 외부에서 안 왔으면 여기서 생성
|
|
488
|
+
if (!this.loadTracker) {
|
|
489
|
+
this.loadTracker = new LoadTracker();
|
|
490
|
+
this._ensureOverlay(this.loadTracker);
|
|
491
|
+
}
|
|
287
492
|
this.hidePopup();
|
|
288
493
|
this.closeScene();
|
|
289
494
|
if (this.board && this.board.id) {
|
|
@@ -303,33 +508,47 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
303
508
|
}
|
|
304
509
|
}
|
|
305
510
|
async initSceneAsync() {
|
|
306
|
-
if (!this.board || !this.board.id)
|
|
511
|
+
if (!this.board || !this.board.id || !this.provider)
|
|
307
512
|
return;
|
|
308
|
-
|
|
309
|
-
|
|
513
|
+
try {
|
|
514
|
+
this._scene = await this.provider.create(this.board.id);
|
|
515
|
+
this.setupScene({ id: this.board.id, scene: this._scene });
|
|
516
|
+
}
|
|
517
|
+
catch (e) {
|
|
518
|
+
console.error('Failed to load scene:', this.board.id, e);
|
|
519
|
+
}
|
|
310
520
|
}
|
|
311
521
|
initScene() {
|
|
312
|
-
if (!this.board || !this.board.id) {
|
|
522
|
+
if (!this.board || !this.board.id || !this.board.model) {
|
|
313
523
|
return;
|
|
314
524
|
}
|
|
315
525
|
this._scene = create({
|
|
316
|
-
model:
|
|
317
|
-
...this.board.model
|
|
318
|
-
},
|
|
526
|
+
model: JSON.parse(JSON.stringify(this.board.model)),
|
|
319
527
|
style: this.board.style,
|
|
320
528
|
mode: SCENE_MODE.VIEW,
|
|
321
529
|
handlers: ['mcs-event-handler', 'zoom-handler', 'shift-handler', 'waypoint-handler'],
|
|
322
530
|
layers: [{ type: 'scroll-layer' }, { type: 'movement-layer', id: 'movement-layer' }],
|
|
323
531
|
refProvider: this.provider,
|
|
324
532
|
dataStorage: this.board.id !== 'preview' ? new DataStorage(this.board.id) : undefined,
|
|
325
|
-
dataSubscriptionProvider:
|
|
533
|
+
dataSubscriptionProvider: this._dataSubscriptionProvider,
|
|
534
|
+
loading: this.loadTracker ? { tracker: this.loadTracker } : undefined
|
|
326
535
|
});
|
|
327
536
|
if (this.baseUrl) {
|
|
328
537
|
this._scene.baseUrl = this.baseUrl;
|
|
329
538
|
}
|
|
330
539
|
this.setupScene({ id: this.board.id, scene: this._scene });
|
|
331
540
|
}
|
|
541
|
+
_ensureOverlay(tracker) {
|
|
542
|
+
if (!this._loadingOverlay) {
|
|
543
|
+
this._loadingOverlay = new LoadingOverlay();
|
|
544
|
+
}
|
|
545
|
+
this._loadingOverlay.attach(this.renderRoot, tracker);
|
|
546
|
+
}
|
|
332
547
|
closeScene() {
|
|
548
|
+
var _a, _b;
|
|
549
|
+
// scene 교체/종료 시 highlight 상태도 함께 정리. tracking watcher 누수 방지.
|
|
550
|
+
this._clearClickHighlight();
|
|
551
|
+
(_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.tracker) === null || _b === void 0 ? void 0 : _b.stop();
|
|
333
552
|
if (this._scene) {
|
|
334
553
|
this.unbindSceneEvents(this._scene);
|
|
335
554
|
this._scene.target = null;
|
|
@@ -348,6 +567,10 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
348
567
|
}
|
|
349
568
|
setupScene({ id, scene }) {
|
|
350
569
|
var _a;
|
|
570
|
+
// 플레이백 리셋
|
|
571
|
+
if (this._playbackActive) {
|
|
572
|
+
this._onPlaybackStop();
|
|
573
|
+
}
|
|
351
574
|
this._scene = scene;
|
|
352
575
|
const backgroundColor = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root.state.fillStyle;
|
|
353
576
|
if (typeof backgroundColor === 'string') {
|
|
@@ -371,7 +594,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
371
594
|
this.transientShowButtons();
|
|
372
595
|
}
|
|
373
596
|
async showScene(boardId, bindingData) {
|
|
374
|
-
if (!boardId)
|
|
597
|
+
if (!boardId || !this.provider)
|
|
375
598
|
return;
|
|
376
599
|
try {
|
|
377
600
|
var scene = await this.provider.get(boardId, true);
|
|
@@ -409,6 +632,10 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
409
632
|
this._scene.on('run-scenario', this.onRunScenario, this);
|
|
410
633
|
this._scene.on('legend-toggle', this.onLegendToggle, this);
|
|
411
634
|
this._scene.on('open-popup', this.onOpenPopup, this);
|
|
635
|
+
// VIEW 모드 SearchOverlay 디폴트 = 영구 추적 (things-scene 자동 처리).
|
|
636
|
+
// application 은 trackerOptions (color/fallback 등) 또는 onSearchSubmit/onSearchClose
|
|
637
|
+
// hook (디폴트 override) 만 제공.
|
|
638
|
+
this._scene.trackerOptions = { color: BoardViewer_1.TRACKING_COLOR };
|
|
412
639
|
}
|
|
413
640
|
unbindSceneEvents(scene) {
|
|
414
641
|
scene.off('run', this.onRunBoard, this);
|
|
@@ -424,7 +651,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
424
651
|
transientShowButtons(stop) {
|
|
425
652
|
var buttons = [];
|
|
426
653
|
!this.hideNavigation && buttons.push(this._next, this._prev);
|
|
427
|
-
!this.hideFullscreen && buttons.push(this.
|
|
654
|
+
!this.hideFullscreen && buttons.push(this._fabGroup);
|
|
428
655
|
if (buttons.length == 0) {
|
|
429
656
|
return;
|
|
430
657
|
}
|
|
@@ -447,7 +674,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
447
674
|
}
|
|
448
675
|
this._forward.length <= 0 ? this._next.setAttribute('hidden', '') : this._next.removeAttribute('hidden');
|
|
449
676
|
this._backward.length <= 0 ? this._prev.setAttribute('hidden', '') : this._prev.removeAttribute('hidden');
|
|
450
|
-
this.
|
|
677
|
+
this._fabGroup && this._fabGroup.removeAttribute('hidden');
|
|
451
678
|
this._fade_animations.forEach(animation => {
|
|
452
679
|
animation.cancel();
|
|
453
680
|
if (stop)
|
|
@@ -485,6 +712,242 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
485
712
|
onTapFullscreen() {
|
|
486
713
|
togglefullscreen(this, () => this.requestUpdate(), () => this.requestUpdate());
|
|
487
714
|
}
|
|
715
|
+
onTapToggle3D() {
|
|
716
|
+
var _a, _b, _c;
|
|
717
|
+
const modelLayer = (_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.rootModel;
|
|
718
|
+
if (!(modelLayer === null || modelLayer === void 0 ? void 0 : modelLayer.toggleThreeD))
|
|
719
|
+
return;
|
|
720
|
+
const entering3D = !modelLayer.is3dMode;
|
|
721
|
+
modelLayer.toggleThreeD();
|
|
722
|
+
this.requestUpdate();
|
|
723
|
+
// fmsim: 3D 진입 시 자동 회전
|
|
724
|
+
if (entering3D && ((_c = modelLayer._threeCapability) === null || _c === void 0 ? void 0 : _c.isInitialized)) {
|
|
725
|
+
modelLayer._threeCapability.controlsManager.reset();
|
|
726
|
+
modelLayer._threeCapability.controlsManager.setAutoRotate(true);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
onTapPlayback() {
|
|
730
|
+
var _a;
|
|
731
|
+
if (this._playbackActive) {
|
|
732
|
+
this._onPlaybackStop();
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
// 플레이백 시작 시 팝업 닫기
|
|
736
|
+
this.hidePopup();
|
|
737
|
+
// 1) 라이브 데이터 구독 실제 종료
|
|
738
|
+
(_a = this._dataSubscriptionProvider) === null || _a === void 0 ? void 0 : _a.pause();
|
|
739
|
+
this._unsubscribeSceneData();
|
|
740
|
+
// 2) 라이브 데이터 초기화 (깨끗한 상태에서 playback 시작)
|
|
741
|
+
this._resetSceneData();
|
|
742
|
+
this._playbackActive = true;
|
|
743
|
+
if (!this._playbackEngine) {
|
|
744
|
+
this._playbackEngine = new PlaybackEngine();
|
|
745
|
+
this._playbackEngine.on('frame', ({ time }) => {
|
|
746
|
+
const controls = this.renderRoot.querySelector('ox-playback-controls');
|
|
747
|
+
if (controls)
|
|
748
|
+
controls.currentTime = time;
|
|
749
|
+
});
|
|
750
|
+
this._playbackEngine.on('state-change', (state) => {
|
|
751
|
+
if (state === 'stopped') {
|
|
752
|
+
const controls = this.renderRoot.querySelector('ox-playback-controls');
|
|
753
|
+
if (controls)
|
|
754
|
+
controls.playbackState = 'stopped';
|
|
755
|
+
}
|
|
756
|
+
this.requestUpdate();
|
|
757
|
+
});
|
|
758
|
+
this._playbackEngine.on('buffer-update', ({ bufferedRanges }) => {
|
|
759
|
+
const controls = this.renderRoot.querySelector('ox-playback-controls');
|
|
760
|
+
if (controls)
|
|
761
|
+
controls.bufferedRanges = bufferedRanges;
|
|
762
|
+
});
|
|
763
|
+
if (this._fetchPlaybackData) {
|
|
764
|
+
this._playbackEngine.setFetchFunction(this._fetchPlaybackData);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (this._scene) {
|
|
768
|
+
this._playbackEngine.attachScene(this._scene);
|
|
769
|
+
}
|
|
770
|
+
this.dispatchEvent(new CustomEvent('playback-toggle', {
|
|
771
|
+
bubbles: true,
|
|
772
|
+
composed: true,
|
|
773
|
+
detail: { active: true }
|
|
774
|
+
}));
|
|
775
|
+
// playback 은 전체화면에서만 동작 — 이미 전체화면이면 그대로 유지
|
|
776
|
+
if (!document.fullscreenElement && !isIOS()) {
|
|
777
|
+
fullscreen(this, () => this.requestUpdate(), () => this.requestUpdate());
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
async _onPlaybackStart(e) {
|
|
782
|
+
const { fromTime, toTime, speed } = e.detail;
|
|
783
|
+
if (!this._playbackEngine)
|
|
784
|
+
return;
|
|
785
|
+
// toTime is the user-selected window END (= controls' _appliedTo).
|
|
786
|
+
// The previous hardcoded `fromTime + 1h` ignored the controls'
|
|
787
|
+
// range selection, so 10m / 30m / Range modes silently played past
|
|
788
|
+
// the requested window. Falling back to `+1h` only if toTime was
|
|
789
|
+
// omitted (legacy callers).
|
|
790
|
+
const endTime = toTime instanceof Date ? toTime : new Date(fromTime.getTime() + 3600000);
|
|
791
|
+
if (this._scene)
|
|
792
|
+
this._scene.playbackMode = true;
|
|
793
|
+
try {
|
|
794
|
+
await this._playbackEngine.startChunked(fromTime, endTime, speed);
|
|
795
|
+
}
|
|
796
|
+
catch (ex) {
|
|
797
|
+
console.error('[Playback] load failed:', ex);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
_onPlaybackPause() {
|
|
801
|
+
var _a;
|
|
802
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.pause();
|
|
803
|
+
}
|
|
804
|
+
_onPlaybackResume() {
|
|
805
|
+
var _a;
|
|
806
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.resume();
|
|
807
|
+
}
|
|
808
|
+
async _onPlaybackStop() {
|
|
809
|
+
var _a, _b, _c;
|
|
810
|
+
const scene = this._scene; // 비동기 구간 scene 교체 감지용 캡처
|
|
811
|
+
// 1) 즉시: 엔진 정지 + 애니메이션 억제 ON (in-flight 애니메이션 즉시 종료)
|
|
812
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.detachScene();
|
|
813
|
+
(_b = this._playbackEngine) === null || _b === void 0 ? void 0 : _b.stop();
|
|
814
|
+
this._playbackActive = false;
|
|
815
|
+
if (scene) {
|
|
816
|
+
scene.suppressAnimations = true;
|
|
817
|
+
scene.playbackMode = false;
|
|
818
|
+
}
|
|
819
|
+
// UI 신호 + fullscreen 해제
|
|
820
|
+
this.dispatchEvent(new CustomEvent('playback-toggle', {
|
|
821
|
+
bubbles: true,
|
|
822
|
+
composed: true,
|
|
823
|
+
detail: { active: false }
|
|
824
|
+
}));
|
|
825
|
+
if (document.fullscreenElement) {
|
|
826
|
+
exitfullscreen();
|
|
827
|
+
}
|
|
828
|
+
// 2) 1초 후: 보드 리셋 + live 구독 재개
|
|
829
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
830
|
+
if (this._playbackActive || this._scene !== scene)
|
|
831
|
+
return;
|
|
832
|
+
if (!scene)
|
|
833
|
+
return;
|
|
834
|
+
this._resetSceneData();
|
|
835
|
+
this._resubscribeSceneData();
|
|
836
|
+
(_c = this._dataSubscriptionProvider) === null || _c === void 0 ? void 0 : _c.resume();
|
|
837
|
+
// 3) 2초 후 (추가 1초): 억제 해제 — 첫 live batch 가 instant 배치된 뒤 정상 애니메이션 복원
|
|
838
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
839
|
+
if (this._scene === scene) {
|
|
840
|
+
scene.suppressAnimations = false;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* 플레이백 시작 시: RESTful data-subscription 컴포넌트의 WebSocket 을 실제 disconnect.
|
|
845
|
+
* GraphQL 경로는 _dataSubscriptionProvider.pause() 에서 일괄 처리.
|
|
846
|
+
*/
|
|
847
|
+
_unsubscribeSceneData() {
|
|
848
|
+
var _a;
|
|
849
|
+
const root = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root;
|
|
850
|
+
if (!root || typeof root.traverse !== 'function')
|
|
851
|
+
return;
|
|
852
|
+
root.traverse((c) => {
|
|
853
|
+
if (!c || c === root)
|
|
854
|
+
return;
|
|
855
|
+
// RESTful data-subscription 컴포넌트 — 자체 WebSocket disconnect
|
|
856
|
+
if (c.subscription) {
|
|
857
|
+
try {
|
|
858
|
+
c.subscription.unsubscribe();
|
|
859
|
+
}
|
|
860
|
+
catch (_a) { }
|
|
861
|
+
c.subscription = null;
|
|
862
|
+
}
|
|
863
|
+
if (c.client) {
|
|
864
|
+
try {
|
|
865
|
+
c.client.unsubscribeAll();
|
|
866
|
+
c.client.close(true);
|
|
867
|
+
}
|
|
868
|
+
catch (_b) { }
|
|
869
|
+
c.client = null;
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* 플레이백 종료 시: RESTful data-subscription 컴포넌트의 WebSocket 을 재연결.
|
|
875
|
+
* GraphQL 경로는 _dataSubscriptionProvider.resume() 에서 일괄 처리.
|
|
876
|
+
*/
|
|
877
|
+
_resubscribeSceneData() {
|
|
878
|
+
var _a;
|
|
879
|
+
const root = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root;
|
|
880
|
+
if (!root || typeof root.traverse !== 'function')
|
|
881
|
+
return;
|
|
882
|
+
root.traverse((c) => {
|
|
883
|
+
if (!c || c === root)
|
|
884
|
+
return;
|
|
885
|
+
// RESTful data-subscription 컴포넌트 — WebSocket 재연결
|
|
886
|
+
if (typeof c.requestData === 'function' && !c.client) {
|
|
887
|
+
c.requestData();
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
_resetSceneData() {
|
|
892
|
+
var _a, _b, _c, _d;
|
|
893
|
+
const root = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root;
|
|
894
|
+
if (!root || typeof root.traverse !== 'function')
|
|
895
|
+
return;
|
|
896
|
+
root.traverse((c) => {
|
|
897
|
+
if (!c || c === root)
|
|
898
|
+
return;
|
|
899
|
+
// null 대신 {} 로 리셋 — typeof null === 'object' 함정 회피 + destructuring 안전 +
|
|
900
|
+
// statusColor 등 data?.X 형태 getter 가 자연스럽게 default 로 폴백
|
|
901
|
+
try {
|
|
902
|
+
if ('data' in c)
|
|
903
|
+
c.data = {};
|
|
904
|
+
}
|
|
905
|
+
catch (_a) { }
|
|
906
|
+
// 차량 등의 위치 캐시 초기화 — 없으면 이전 waypoint 경로로 스냅백되는 잔상 발생
|
|
907
|
+
if ('lastWaypoint' in c)
|
|
908
|
+
c.lastWaypoint = undefined;
|
|
909
|
+
});
|
|
910
|
+
// vehicle 의 waypoint 이동 애니메이션 전체 중단
|
|
911
|
+
// (Animation.stepAnimation 의 suppress 체크만으로는 WaypointHandler 의 animatorMap 이
|
|
912
|
+
// 완전히 비워지지 않을 수 있어 명시적 reset)
|
|
913
|
+
if ((_b = root.waypointHandler) === null || _b === void 0 ? void 0 : _b.reset)
|
|
914
|
+
root.waypointHandler.reset();
|
|
915
|
+
// 모든 decorator 제거 — bouncing-arrow/border-highlighting 같은 SVG 애니메이션 decorator 는
|
|
916
|
+
// decorator-registry 가 붙잡고 있어 data=null 만으로는 제거되지 않음. 명시적 reset 필요.
|
|
917
|
+
try {
|
|
918
|
+
root.trigger('deco-reset');
|
|
919
|
+
}
|
|
920
|
+
catch (_e) { }
|
|
921
|
+
if ((_c = root.carrierManager) === null || _c === void 0 ? void 0 : _c.dispose)
|
|
922
|
+
root.carrierManager.dispose();
|
|
923
|
+
if ((_d = root.transitionEffect) === null || _d === void 0 ? void 0 : _d.dispose)
|
|
924
|
+
root.transitionEffect.dispose();
|
|
925
|
+
}
|
|
926
|
+
_onPlaybackSeek(e) {
|
|
927
|
+
var _a;
|
|
928
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.seekTo(e.detail.toTime);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* 드래그 시작 신호 — 엔진의 재생 타이머 정지해서 스크러빙 동안
|
|
932
|
+
* scene 이 엔진 시간으로 독립 갱신되지 않도록 함. _state 는 유지되므로
|
|
933
|
+
* 드래그 종료 시 seekTo 가 원래 재생/일시정지 상태로 복원.
|
|
934
|
+
*/
|
|
935
|
+
_onPlaybackSeekStart() {
|
|
936
|
+
var _a;
|
|
937
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.pauseForSeek();
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* 드래그 중 디바운스된 seek preview. 해당 시간의 snapshot 만 scene 에 적용.
|
|
941
|
+
* 보드 리셋 없음, 애니메이션 억제.
|
|
942
|
+
*/
|
|
943
|
+
_onPlaybackSeekPreview(e) {
|
|
944
|
+
var _a;
|
|
945
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.seekPreview(e.detail.toTime);
|
|
946
|
+
}
|
|
947
|
+
_onPlaybackSpeed(e) {
|
|
948
|
+
var _a;
|
|
949
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.setSpeed(e.detail.speed);
|
|
950
|
+
}
|
|
488
951
|
onRunBoard() {
|
|
489
952
|
this.dispatchEvent(new CustomEvent('run-board', { bubbles: true, composed: true, detail: this.board.id }));
|
|
490
953
|
}
|
|
@@ -578,6 +1041,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
578
1041
|
}
|
|
579
1042
|
if (this.popup) {
|
|
580
1043
|
this.popup.component = component;
|
|
1044
|
+
this.popup.playbackMode = this._playbackActive;
|
|
581
1045
|
}
|
|
582
1046
|
}
|
|
583
1047
|
hidePopup() {
|
|
@@ -643,10 +1107,58 @@ BoardViewer.styles = [
|
|
|
643
1107
|
right: 5px;
|
|
644
1108
|
}
|
|
645
1109
|
|
|
646
|
-
|
|
1110
|
+
[fab-group] {
|
|
647
1111
|
position: absolute;
|
|
648
1112
|
bottom: 15px;
|
|
649
1113
|
right: 16px;
|
|
1114
|
+
display: flex;
|
|
1115
|
+
flex-direction: column-reverse;
|
|
1116
|
+
align-items: center;
|
|
1117
|
+
gap: 12px;
|
|
1118
|
+
z-index: 1000;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
[fab-group] md-fab {
|
|
1122
|
+
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
[fab-group] md-fab.main-fab md-icon {
|
|
1126
|
+
transition: transform 0.3s ease;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
[fab-group] md-fab.main-fab[expanded] md-icon {
|
|
1130
|
+
transform: rotate(45deg);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
[fab-group] md-fab.sub-fab {
|
|
1134
|
+
--md-fab-container-width: 40px;
|
|
1135
|
+
--md-fab-container-height: 40px;
|
|
1136
|
+
--md-icon-size: 20px;
|
|
1137
|
+
transform: scale(0);
|
|
1138
|
+
opacity: 0;
|
|
1139
|
+
pointer-events: none;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/* SVG icon 은 fab 전체 크기로 — 자체 원이 fab 표면을 대체. */
|
|
1143
|
+
[fab-group] md-fab.sub-fab svg[slot="icon"] {
|
|
1144
|
+
width: 40px;
|
|
1145
|
+
height: 40px;
|
|
1146
|
+
display: block;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
[fab-group][expanded] md-fab.sub-fab {
|
|
1150
|
+
transform: scale(1);
|
|
1151
|
+
opacity: 1;
|
|
1152
|
+
pointer-events: auto;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
[fab-group] md-fab.sub-fab[disabled] {
|
|
1156
|
+
opacity: 0.4;
|
|
1157
|
+
pointer-events: none;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
ox-playback-controls {
|
|
1161
|
+
transition: opacity 0.3s ease;
|
|
650
1162
|
}
|
|
651
1163
|
|
|
652
1164
|
[hidden] {
|
|
@@ -678,6 +1190,9 @@ BoardViewer.styles = [
|
|
|
678
1190
|
}
|
|
679
1191
|
`
|
|
680
1192
|
];
|
|
1193
|
+
/** SearchOverlay 의 query-submit 시 사용할 추적 색깔. things-scene 의 Scene.tracker
|
|
1194
|
+
* 가 폴링/데코/3D outline 색 동기화 일괄 관리. */
|
|
1195
|
+
BoardViewer.TRACKING_COLOR = '#00aaff7f';
|
|
681
1196
|
__decorate([
|
|
682
1197
|
property({ type: String })
|
|
683
1198
|
], BoardViewer.prototype, "baseUrl", void 0);
|
|
@@ -693,6 +1208,9 @@ __decorate([
|
|
|
693
1208
|
__decorate([
|
|
694
1209
|
property({ type: Object })
|
|
695
1210
|
], BoardViewer.prototype, "values", void 0);
|
|
1211
|
+
__decorate([
|
|
1212
|
+
property({ type: Object })
|
|
1213
|
+
], BoardViewer.prototype, "loadTracker", void 0);
|
|
696
1214
|
__decorate([
|
|
697
1215
|
property({ type: Boolean, reflect: true, attribute: 'hide-fullscreen' })
|
|
698
1216
|
], BoardViewer.prototype, "hideFullscreen", void 0);
|
|
@@ -701,19 +1219,10 @@ __decorate([
|
|
|
701
1219
|
], BoardViewer.prototype, "hideNavigation", void 0);
|
|
702
1220
|
__decorate([
|
|
703
1221
|
state()
|
|
704
|
-
], BoardViewer.prototype, "
|
|
705
|
-
__decorate([
|
|
706
|
-
state()
|
|
707
|
-
], BoardViewer.prototype, "_forward", void 0);
|
|
708
|
-
__decorate([
|
|
709
|
-
state()
|
|
710
|
-
], BoardViewer.prototype, "_backward", void 0);
|
|
711
|
-
__decorate([
|
|
712
|
-
state()
|
|
713
|
-
], BoardViewer.prototype, "_oldtarget", void 0);
|
|
1222
|
+
], BoardViewer.prototype, "_fabExpanded", void 0);
|
|
714
1223
|
__decorate([
|
|
715
1224
|
state()
|
|
716
|
-
], BoardViewer.prototype, "
|
|
1225
|
+
], BoardViewer.prototype, "_playbackActive", void 0);
|
|
717
1226
|
__decorate([
|
|
718
1227
|
query('#target')
|
|
719
1228
|
], BoardViewer.prototype, "_target", void 0);
|
|
@@ -724,9 +1233,12 @@ __decorate([
|
|
|
724
1233
|
query('#next')
|
|
725
1234
|
], BoardViewer.prototype, "_next", void 0);
|
|
726
1235
|
__decorate([
|
|
727
|
-
query('
|
|
728
|
-
], BoardViewer.prototype, "
|
|
729
|
-
|
|
1236
|
+
query('[fab-group]')
|
|
1237
|
+
], BoardViewer.prototype, "_fabGroup", void 0);
|
|
1238
|
+
__decorate([
|
|
1239
|
+
property({ attribute: false })
|
|
1240
|
+
], BoardViewer.prototype, "_fetchPlaybackData", void 0);
|
|
1241
|
+
BoardViewer = BoardViewer_1 = __decorate([
|
|
730
1242
|
customElement('ox-board-viewer')
|
|
731
1243
|
], BoardViewer);
|
|
732
1244
|
export { BoardViewer };
|