@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.
Files changed (85) hide show
  1. package/custom-elements.json +5568 -3044
  2. package/demo/index-modeller.html +1 -1
  3. package/demo/index-player.html +2 -2
  4. package/demo/index-viewer.html +2 -2
  5. package/demo/index.html +2 -2
  6. package/dist/src/component/etc.js +35 -0
  7. package/dist/src/component/etc.js.map +1 -1
  8. package/dist/src/component/index.d.ts +1 -0
  9. package/dist/src/component/index.js +1 -0
  10. package/dist/src/component/index.js.map +1 -1
  11. package/dist/src/component/restful-property-editor-backdrop-faces.d.ts +50 -0
  12. package/dist/src/component/restful-property-editor-backdrop-faces.js +524 -0
  13. package/dist/src/component/restful-property-editor-backdrop-faces.js.map +1 -0
  14. package/dist/src/data-storage/board-model-cache.d.ts +13 -0
  15. package/dist/src/data-storage/board-model-cache.js +66 -0
  16. package/dist/src/data-storage/board-model-cache.js.map +1 -0
  17. package/dist/src/index.d.ts +3 -0
  18. package/dist/src/index.js +2 -0
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/layers/movement-layer.d.ts +4 -4
  21. package/dist/src/layers/movement-layer.js +16 -8
  22. package/dist/src/layers/movement-layer.js.map +1 -1
  23. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  24. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  25. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  26. package/dist/src/modeller/bulk-create.d.ts +101 -0
  27. package/dist/src/modeller/bulk-create.js +199 -0
  28. package/dist/src/modeller/bulk-create.js.map +1 -0
  29. package/dist/src/modeller/component-toolbar/component-menu.d.ts +1 -1
  30. package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +1 -1
  31. package/dist/src/modeller/edit-toolbar-style.js +43 -3
  32. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  33. package/dist/src/modeller/edit-toolbar.d.ts +13 -0
  34. package/dist/src/modeller/edit-toolbar.js +168 -7
  35. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  36. package/dist/src/modeller/property-sidebar/abstract-property.js +10 -2
  37. package/dist/src/modeller/property-sidebar/abstract-property.js.map +1 -1
  38. package/dist/src/modeller/property-sidebar/data-binding/data-binding.d.ts +1 -1
  39. package/dist/src/modeller/property-sidebar/effects/effects.d.ts +1 -1
  40. package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +1 -1
  41. package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +1 -1
  42. package/dist/src/modeller/property-sidebar/shapes/shapes.js +31 -0
  43. package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
  44. package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +2 -1
  45. package/dist/src/modeller/property-sidebar/specifics/specifics.js +10 -0
  46. package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
  47. package/dist/src/modeller/property-sidebar/styles/styles.d.ts +1 -1
  48. package/dist/src/modeller/scene-viewer/ox-scene-layer.d.ts +4 -3
  49. package/dist/src/modeller/scene-viewer/ox-scene-layer.js +2 -1
  50. package/dist/src/modeller/scene-viewer/ox-scene-layer.js.map +1 -1
  51. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  52. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +5 -1
  53. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  54. package/dist/src/ox-board-component-info.d.ts +1 -0
  55. package/dist/src/ox-board-component-info.js +5 -1
  56. package/dist/src/ox-board-component-info.js.map +1 -1
  57. package/dist/src/ox-board-list.js +2 -2
  58. package/dist/src/ox-board-list.js.map +1 -1
  59. package/dist/src/ox-board-modeller.d.ts +6 -3
  60. package/dist/src/ox-board-modeller.js +13 -1
  61. package/dist/src/ox-board-modeller.js.map +1 -1
  62. package/dist/src/ox-board-viewer.d.ts +65 -13
  63. package/dist/src/ox-board-viewer.js +610 -98
  64. package/dist/src/ox-board-viewer.js.map +1 -1
  65. package/dist/src/ox-editor-board-selector.d.ts +3 -0
  66. package/dist/src/ox-editor-board-selector.js +38 -9
  67. package/dist/src/ox-editor-board-selector.js.map +1 -1
  68. package/dist/src/ox-playback-controls.d.ts +157 -0
  69. package/dist/src/ox-playback-controls.js +1089 -0
  70. package/dist/src/ox-playback-controls.js.map +1 -0
  71. package/dist/src/playback/playback-engine.d.ts +106 -0
  72. package/dist/src/playback/playback-engine.js +395 -0
  73. package/dist/src/playback/playback-engine.js.map +1 -0
  74. package/dist/src/selector/board-creation-popup.js +2 -2
  75. package/dist/src/selector/board-creation-popup.js.map +1 -1
  76. package/dist/src/selector/board-thumbnail-card.js +6 -6
  77. package/dist/src/selector/board-thumbnail-card.js.map +1 -1
  78. package/dist/src/selector/ox-board-creation-card.js +2 -2
  79. package/dist/src/selector/ox-board-creation-card.js.map +1 -1
  80. package/dist/src/selector/ox-board-selector.js +21 -10
  81. package/dist/src/selector/ox-board-selector.js.map +1 -1
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/icons/components/text.png +0 -0
  84. package/icons/icon-htoolbar.png +0 -0
  85. 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 { css, html, LitElement } from 'lit';
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 { create, SCENE_MODE } from '@hatiolab/things-scene';
10
- import { isIOS, togglefullscreen } from '@operato/utils';
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 = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.findById(`movement-layer`);
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
- (_a = this.lastComponent) === null || _a === void 0 ? void 0 : _a.trigger('deco-border-off');
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
- (_b = this.lastComponent) === null || _b === void 0 ? void 0 : _b.trigger('deco-border-off');
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 fullscreen = !isIOS() && !this.hideFullscreen
182
- ? html `
183
- <md-fab
184
- id="fullscreen"
185
- @click=${(e) => this.onTapFullscreen()}
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
- title="fullscreen"
189
- ><md-icon slot="icon">${document.fullscreenElement ? 'fullscreen_exit' : 'fullscreen'}</md-icon></md-fab>
190
- `
191
- : html ``;
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} ${fullscreen}
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
- // 모니터링에서 장비 아이디에 해당하는 object를 찾아 캔버스 가운데 위치하도록 조정한다.
237
- window.addEventListener('fetchIFactsData', this.fetchIFactsDataHandler);
238
- // 모니터링에서 Movement Data를 보여준다.
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
- // 모니터링에서 장비 아이디에 해당하는 object를 찾아 캔버스 가운데 위치하도록 조정한다.
262
- window.removeEventListener('fetchIFactsData', this.fetchIFactsDataHandler);
263
- // 모니터링에서 Movement Data를 보여준다.
264
- window.removeEventListener('fmbShowMovement', this.fmbShowMovementHandler);
265
- // 모니터링에서 보드 상태를 원래상태 (정사이즈)로 돌려놓는다.
266
- window.removeEventListener('fmbMonitoringInit', this.fmbMonitoringInitHandler);
267
- // BoardComponentInfo popup을 보여준다.
268
- window.removeEventListener('clickComponent', this.clickComponentHandler);
269
- // AnimatedIcon을 추가하거나 제거한다.
270
- window.removeEventListener('fmbStartAnimation', this.fmbStartAnimation);
271
- window.removeEventListener('fmbStopAnimation', this.fmbStopAnimation);
272
- window.removeEventListener('fmbResetAnimation', this.fmbResetAnimation);
273
- // 모니터링에서 범례를 보여준다.
274
- window.removeEventListener('fmbShowLegend', this.fmbShowLegendHandler);
275
- window.removeEventListener('fmbHideLegend', this.fmbHideLegendHandler);
276
- // 모니터링에서 범례를 보여준다.
277
- window.removeEventListener('fmbShowPopup', this.fmbShowPopupHandler);
278
- window.removeEventListener('fmbHidePopup', this.fmbHidePopupHandler);
279
- // 모니터링에서 FloatingContainer를 보여준다.
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
- this._scene = await this.provider.create(this.board.id);
309
- this.setupScene({ id: this.board.id, scene: this._scene });
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: isRestfulAvailable() ? undefined : new DataSubscriptionProviderImpl()
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._fullscreen);
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._fullscreen && this._fullscreen.removeAttribute('hidden');
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
- #fullscreen {
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, "_scene", void 0);
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, "_fade_animations", void 0);
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('#fullscreen')
728
- ], BoardViewer.prototype, "_fullscreen", void 0);
729
- BoardViewer = __decorate([
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 };