@fmsim/board 2.0.0-beta.2 → 2.0.0-beta.21

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 (54) hide show
  1. package/custom-elements.json +1954 -778
  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 +17 -0
  7. package/dist/src/component/etc.js.map +1 -1
  8. package/dist/src/data-storage/board-model-cache.d.ts +13 -0
  9. package/dist/src/data-storage/board-model-cache.js +66 -0
  10. package/dist/src/data-storage/board-model-cache.js.map +1 -0
  11. package/dist/src/index.d.ts +3 -0
  12. package/dist/src/index.js +2 -0
  13. package/dist/src/index.js.map +1 -1
  14. package/dist/src/modeller/edit-toolbar-style.js +43 -3
  15. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  16. package/dist/src/modeller/edit-toolbar.d.ts +8 -14
  17. package/dist/src/modeller/edit-toolbar.js +208 -211
  18. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  19. package/dist/src/modeller/property-sidebar/property-sidebar.js +3 -16
  20. package/dist/src/modeller/property-sidebar/property-sidebar.js.map +1 -1
  21. package/dist/src/modeller/property-sidebar/styles/styles.d.ts +6 -4
  22. package/dist/src/modeller/property-sidebar/styles/styles.js +139 -436
  23. package/dist/src/modeller/property-sidebar/styles/styles.js.map +1 -1
  24. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  25. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +5 -1
  26. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  27. package/dist/src/ox-board-list.js +2 -2
  28. package/dist/src/ox-board-list.js.map +1 -1
  29. package/dist/src/ox-board-modeller.d.ts +5 -2
  30. package/dist/src/ox-board-modeller.js +14 -4
  31. package/dist/src/ox-board-modeller.js.map +1 -1
  32. package/dist/src/ox-board-viewer.d.ts +22 -2
  33. package/dist/src/ox-board-viewer.js +247 -21
  34. package/dist/src/ox-board-viewer.js.map +1 -1
  35. package/dist/src/ox-editor-board-selector.d.ts +3 -0
  36. package/dist/src/ox-editor-board-selector.js +38 -9
  37. package/dist/src/ox-editor-board-selector.js.map +1 -1
  38. package/dist/src/ox-playback-controls.d.ts +60 -0
  39. package/dist/src/ox-playback-controls.js +512 -0
  40. package/dist/src/ox-playback-controls.js.map +1 -0
  41. package/dist/src/playback/playback-engine.d.ts +78 -0
  42. package/dist/src/playback/playback-engine.js +215 -0
  43. package/dist/src/playback/playback-engine.js.map +1 -0
  44. package/dist/src/selector/board-creation-popup.js +2 -2
  45. package/dist/src/selector/board-creation-popup.js.map +1 -1
  46. package/dist/src/selector/board-thumbnail-card.js +6 -6
  47. package/dist/src/selector/board-thumbnail-card.js.map +1 -1
  48. package/dist/src/selector/ox-board-creation-card.js +2 -2
  49. package/dist/src/selector/ox-board-creation-card.js.map +1 -1
  50. package/dist/src/selector/ox-board-selector.js +21 -10
  51. package/dist/src/selector/ox-board-selector.js.map +1 -1
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/icons/icon-htoolbar.png +0 -0
  54. package/package.json +3 -6
@@ -3,8 +3,9 @@ import '@material/web/icon/icon.js';
3
3
  import './layers/movement-layer.js';
4
4
  import './ox-board-component-info.js';
5
5
  import './layers/mcs-mouse-event-handler.js';
6
+ import './ox-playback-controls.js';
6
7
  import { LitElement, PropertyValues } from 'lit';
7
- import { Component, ReferenceProvider } from '@hatiolab/things-scene';
8
+ import { Component, LoadTracker, ReferenceProvider } from '@hatiolab/things-scene';
8
9
  export declare class BoardViewer extends LitElement {
9
10
  static styles: import("lit").CSSResult[];
10
11
  baseUrl: string;
@@ -12,6 +13,8 @@ export declare class BoardViewer extends LitElement {
12
13
  provider: ReferenceProvider | null;
13
14
  data: any;
14
15
  values: any;
16
+ /** 앱에서 미리 생성한 LoadTracker — fetch 시작 전 생성하면 네트워크 시간도 추적 */
17
+ loadTracker?: LoadTracker;
15
18
  hideFullscreen: boolean;
16
19
  hideNavigation: boolean;
17
20
  _scene: any;
@@ -23,13 +26,17 @@ export declare class BoardViewer extends LitElement {
23
26
  id: string;
24
27
  scene: any;
25
28
  }[];
29
+ private _loadingOverlay?;
26
30
  _oldtarget: HTMLElement | null;
27
31
  _fade_animations?: Array<Animation>;
32
+ _fabExpanded: boolean;
33
+ _playbackActive: boolean;
34
+ get _is3dMode(): boolean;
28
35
  currentBoardId?: string;
29
36
  _target: HTMLElement;
30
37
  _prev: HTMLElement;
31
38
  _next: HTMLElement;
32
- _fullscreen: HTMLElement;
39
+ _fabGroup: HTMLElement;
33
40
  private popup;
34
41
  private lastComponent;
35
42
  render(): import("lit-html").TemplateResult<1>;
@@ -46,11 +53,13 @@ export declare class BoardViewer extends LitElement {
46
53
  fmbStartAnimation: (e: Event) => void;
47
54
  fmbStopAnimation: (e: Event) => void;
48
55
  fmbResetAnimation: () => void;
56
+ private _pointerMoveHandler;
49
57
  connectedCallback(): void;
50
58
  disconnectedCallback(): void;
51
59
  updated(changes: PropertyValues<this>): void;
52
60
  initSceneAsync(): Promise<void>;
53
61
  initScene(): void;
62
+ private _ensureOverlay;
54
63
  closeScene(): void;
55
64
  releaseScene(): void;
56
65
  setupScene({ id, scene }: {
@@ -65,6 +74,17 @@ export declare class BoardViewer extends LitElement {
65
74
  onTapNext(): void;
66
75
  onTapPrev(): void;
67
76
  onTapFullscreen(): void;
77
+ onTapToggle3D(): void;
78
+ private _playbackEngine?;
79
+ onTapPlayback(): void;
80
+ private _onPlaybackStart;
81
+ private _onPlaybackPause;
82
+ private _onPlaybackResume;
83
+ private _onPlaybackStop;
84
+ private _onPlaybackSeek;
85
+ private _onPlaybackSpeed;
86
+ /** 페이지에서 주입하는 playback 데이터 fetch 함수 */
87
+ _fetchPlaybackData?: (startTime: string, endTime: string) => Promise<any>;
68
88
  onRunBoard(): void;
69
89
  onLinkGoto(targetBoardId: string, options: any, fromComponent: any): void;
70
90
  onLinkOpen(url: string, value: any, fromComponent: Component): void;
@@ -4,9 +4,11 @@ import '@material/web/icon/icon.js';
4
4
  import './layers/movement-layer.js';
5
5
  import './ox-board-component-info.js';
6
6
  import './layers/mcs-mouse-event-handler.js';
7
- import { css, html, LitElement } from 'lit';
7
+ import './ox-playback-controls.js';
8
+ import { css, html, LitElement, nothing } from 'lit';
8
9
  import { customElement, property, query, state } from 'lit/decorators.js';
9
- import { create, SCENE_MODE } from '@hatiolab/things-scene';
10
+ import { PlaybackEngine } from './playback/playback-engine.js';
11
+ import { create, LoadTracker, LoadingOverlay, SCENE_MODE } from '@hatiolab/things-scene';
10
12
  import { isIOS, togglefullscreen } from '@operato/utils';
11
13
  import { ScrollbarStyles } from '@operato/styles';
12
14
  import { DataStorage } from './data-storage/data-storage.js';
@@ -24,6 +26,8 @@ let BoardViewer = class BoardViewer extends LitElement {
24
26
  this._forward = [];
25
27
  this._backward = [];
26
28
  this._oldtarget = null;
29
+ this._fabExpanded = false;
30
+ this._playbackActive = false;
27
31
  this.currentBoardId = (_a = this.board) === null || _a === void 0 ? void 0 : _a.id;
28
32
  this.popup = null;
29
33
  this.lastComponent = null;
@@ -33,7 +37,6 @@ let BoardViewer = class BoardViewer extends LitElement {
33
37
  const id = e.detail.value;
34
38
  const components = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.findAll(`#${id}`);
35
39
  if (!components || components.length == 0) {
36
- console.log('No equipment was found.');
37
40
  return;
38
41
  }
39
42
  window.dispatchEvent(new CustomEvent('clickComponent', { detail: components[0] }));
@@ -173,20 +176,50 @@ let BoardViewer = class BoardViewer extends LitElement {
173
176
  var _a;
174
177
  (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root.trigger('deco-reset');
175
178
  };
179
+ this._pointerMoveHandler = () => this.transientShowButtons();
176
180
  this.resizeHandler = () => {
177
181
  this._scene && this._scene.fit();
178
182
  };
179
183
  }
184
+ get _is3dMode() {
185
+ var _a, _b, _c;
186
+ return !!((_c = (_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.state) === null || _c === void 0 ? void 0 : _c.threed);
187
+ }
180
188
  render() {
181
- var fullscreen = !isIOS() && !this.hideFullscreen
189
+ var _a, _b;
190
+ var fabGroup = !isIOS() && !this.hideFullscreen
182
191
  ? html `
183
- <md-fab
184
- id="fullscreen"
185
- @click=${(e) => this.onTapFullscreen()}
192
+ <div
193
+ fab-group
194
+ ?expanded=${this._fabExpanded}
186
195
  @mouseover=${(e) => this.transientShowButtons(true)}
187
196
  @mouseout=${(e) => this.transientShowButtons()}
188
- title="fullscreen"
189
- ><md-icon slot="icon">${document.fullscreenElement ? 'fullscreen_exit' : 'fullscreen'}</md-icon></md-fab>
197
+ >
198
+ <md-fab
199
+ class="main-fab"
200
+ ?expanded=${this._fabExpanded}
201
+ @click=${() => { this._fabExpanded = !this._fabExpanded; }}
202
+ title="menu"
203
+ ><md-icon slot="icon">add</md-icon></md-fab>
204
+
205
+ <md-fab
206
+ class="sub-fab"
207
+ @click=${() => { this._fabExpanded = false; this.onTapFullscreen(); }}
208
+ title="fullscreen"
209
+ ><md-icon slot="icon">${document.fullscreenElement ? 'fullscreen_exit' : 'fullscreen'}</md-icon></md-fab>
210
+
211
+ <md-fab
212
+ class="sub-fab"
213
+ @click=${() => { this._fabExpanded = false; this.onTapPlayback(); }}
214
+ title=${this._playbackActive ? 'stop playback' : 'start playback'}
215
+ ><md-icon slot="icon">${this._playbackActive ? 'stop' : 'replay'}</md-icon></md-fab>
216
+
217
+ <md-fab
218
+ class="sub-fab"
219
+ @click=${() => { this._fabExpanded = false; this.onTapToggle3D(); }}
220
+ title=${this._is3dMode ? '2D mode' : '3D mode'}
221
+ ><md-icon slot="icon">${this._is3dMode ? 'view_in_ar' : '3d_rotation'}</md-icon></md-fab>
222
+ </div>
190
223
  `
191
224
  : html ``;
192
225
  var prev = !this.hideNavigation
@@ -216,19 +249,29 @@ let BoardViewer = class BoardViewer extends LitElement {
216
249
  return html `
217
250
  ${prev}
218
251
 
219
- <div
220
- id="target"
221
- @touchstart=${(e) => this.transientShowButtons()}
222
- @mousemove=${(e) => this.transientShowButtons()}
223
- ></div>
252
+ <div id="target"></div>
224
253
 
225
254
  <slot></slot>
226
- ${next} ${fullscreen}
255
+ ${next} ${fabGroup}
256
+
257
+ ${this._playbackActive
258
+ ? html `<ox-playback-controls
259
+ .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'}
260
+ @playback-start=${this._onPlaybackStart}
261
+ @playback-pause=${this._onPlaybackPause}
262
+ @playback-resume=${this._onPlaybackResume}
263
+ @playback-stop=${this._onPlaybackStop}
264
+ @playback-seek=${this._onPlaybackSeek}
265
+ @playback-speed=${this._onPlaybackSpeed}
266
+ ></ox-playback-controls>`
267
+ : nothing}
227
268
  `;
228
269
  }
229
270
  connectedCallback() {
230
271
  super.connectedCallback();
231
272
  window.addEventListener('resize', this.resizeHandler);
273
+ // 3D 모드에서도 마우스 움직임 감지 (document 레벨, capture phase)
274
+ document.addEventListener('pointermove', this._pointerMoveHandler, true);
232
275
  this.renderRoot.addEventListener('close-scene', e => {
233
276
  e.preventDefault();
234
277
  this.onTapPrev();
@@ -258,6 +301,7 @@ let BoardViewer = class BoardViewer extends LitElement {
258
301
  disconnectedCallback() {
259
302
  super.disconnectedCallback();
260
303
  window.removeEventListener('resize', this.resizeHandler);
304
+ document.removeEventListener('pointermove', this._pointerMoveHandler, true);
261
305
  // 모니터링에서 장비 아이디에 해당하는 object를 찾아 캔버스 가운데 위치하도록 조정한다.
262
306
  window.removeEventListener('fetchIFactsData', this.fetchIFactsDataHandler);
263
307
  // 모니터링에서 Movement Data를 보여준다.
@@ -280,10 +324,25 @@ let BoardViewer = class BoardViewer extends LitElement {
280
324
  window.removeEventListener('fmbShowFloatingContainer', this.fmbShowFloatingContainerHandler);
281
325
  window.removeEventListener('fmbHideFloatingContainer', this.fmbHideFloatingContainerHandler);
282
326
  this.lastComponent = null;
327
+ // 플레이백 정리
328
+ if (this._playbackEngine) {
329
+ this._playbackEngine.dispose();
330
+ this._playbackEngine = undefined;
331
+ }
332
+ this._playbackActive = false;
283
333
  this.closeScene();
284
334
  }
285
335
  updated(changes) {
336
+ // loadTracker가 외부에서 전달되면 즉시 overlay 연결 (fetch 중에도 표시)
337
+ if (changes.has('loadTracker') && this.loadTracker) {
338
+ this._ensureOverlay(this.loadTracker);
339
+ }
286
340
  if (changes.has('board')) {
341
+ // tracker: 외부에서 안 왔으면 여기서 생성
342
+ if (!this.loadTracker) {
343
+ this.loadTracker = new LoadTracker();
344
+ this._ensureOverlay(this.loadTracker);
345
+ }
287
346
  this.hidePopup();
288
347
  this.closeScene();
289
348
  if (this.board && this.board.id) {
@@ -322,13 +381,20 @@ let BoardViewer = class BoardViewer extends LitElement {
322
381
  layers: [{ type: 'scroll-layer' }, { type: 'movement-layer', id: 'movement-layer' }],
323
382
  refProvider: this.provider,
324
383
  dataStorage: this.board.id !== 'preview' ? new DataStorage(this.board.id) : undefined,
325
- dataSubscriptionProvider: isRestfulAvailable() ? undefined : new DataSubscriptionProviderImpl()
384
+ dataSubscriptionProvider: isRestfulAvailable() ? undefined : new DataSubscriptionProviderImpl(),
385
+ loading: this.loadTracker ? { tracker: this.loadTracker } : undefined
326
386
  });
327
387
  if (this.baseUrl) {
328
388
  this._scene.baseUrl = this.baseUrl;
329
389
  }
330
390
  this.setupScene({ id: this.board.id, scene: this._scene });
331
391
  }
392
+ _ensureOverlay(tracker) {
393
+ if (!this._loadingOverlay) {
394
+ this._loadingOverlay = new LoadingOverlay();
395
+ }
396
+ this._loadingOverlay.attach(this.renderRoot, tracker);
397
+ }
332
398
  closeScene() {
333
399
  if (this._scene) {
334
400
  this.unbindSceneEvents(this._scene);
@@ -348,6 +414,10 @@ let BoardViewer = class BoardViewer extends LitElement {
348
414
  }
349
415
  setupScene({ id, scene }) {
350
416
  var _a;
417
+ // 플레이백 리셋
418
+ if (this._playbackActive) {
419
+ this._onPlaybackStop();
420
+ }
351
421
  this._scene = scene;
352
422
  const backgroundColor = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root.state.fillStyle;
353
423
  if (typeof backgroundColor === 'string') {
@@ -424,7 +494,7 @@ let BoardViewer = class BoardViewer extends LitElement {
424
494
  transientShowButtons(stop) {
425
495
  var buttons = [];
426
496
  !this.hideNavigation && buttons.push(this._next, this._prev);
427
- !this.hideFullscreen && buttons.push(this._fullscreen);
497
+ !this.hideFullscreen && buttons.push(this._fabGroup);
428
498
  if (buttons.length == 0) {
429
499
  return;
430
500
  }
@@ -447,7 +517,7 @@ let BoardViewer = class BoardViewer extends LitElement {
447
517
  }
448
518
  this._forward.length <= 0 ? this._next.setAttribute('hidden', '') : this._next.removeAttribute('hidden');
449
519
  this._backward.length <= 0 ? this._prev.setAttribute('hidden', '') : this._prev.removeAttribute('hidden');
450
- this._fullscreen && this._fullscreen.removeAttribute('hidden');
520
+ this._fabGroup && this._fabGroup.removeAttribute('hidden');
451
521
  this._fade_animations.forEach(animation => {
452
522
  animation.cancel();
453
523
  if (stop)
@@ -485,6 +555,109 @@ let BoardViewer = class BoardViewer extends LitElement {
485
555
  onTapFullscreen() {
486
556
  togglefullscreen(this, () => this.requestUpdate(), () => this.requestUpdate());
487
557
  }
558
+ onTapToggle3D() {
559
+ var _a;
560
+ if (!this._scene)
561
+ return;
562
+ const root = this._scene.root;
563
+ if (!root)
564
+ return;
565
+ const entering3D = !this._is3dMode;
566
+ root.set('threed', entering3D);
567
+ this.requestUpdate();
568
+ if (entering3D) {
569
+ const capability = (_a = root.rootModel) === null || _a === void 0 ? void 0 : _a._threeCapability;
570
+ if (capability === null || capability === void 0 ? void 0 : capability.isInitialized) {
571
+ capability.controlsManager.reset();
572
+ capability.controlsManager.setAutoRotate(true);
573
+ }
574
+ }
575
+ }
576
+ onTapPlayback() {
577
+ if (this._playbackActive) {
578
+ this._onPlaybackStop();
579
+ }
580
+ else {
581
+ this._playbackActive = true;
582
+ if (!this._playbackEngine) {
583
+ this._playbackEngine = new PlaybackEngine();
584
+ this._playbackEngine.on('frame', ({ time }) => {
585
+ const controls = this.renderRoot.querySelector('ox-playback-controls');
586
+ if (controls)
587
+ controls.currentTime = time;
588
+ });
589
+ this._playbackEngine.on('state-change', (state) => {
590
+ if (state === 'stopped') {
591
+ const controls = this.renderRoot.querySelector('ox-playback-controls');
592
+ if (controls)
593
+ controls.playbackState = 'stopped';
594
+ }
595
+ this.requestUpdate();
596
+ });
597
+ }
598
+ if (this._scene) {
599
+ this._playbackEngine.attachScene(this._scene);
600
+ }
601
+ this.dispatchEvent(new CustomEvent('playback-toggle', {
602
+ bubbles: true,
603
+ composed: true,
604
+ detail: { active: true }
605
+ }));
606
+ }
607
+ }
608
+ async _onPlaybackStart(e) {
609
+ var _a;
610
+ const { fromTime, speed } = e.detail;
611
+ if (!this._playbackEngine)
612
+ return;
613
+ const endTime = new Date(fromTime.getTime() + 3600000);
614
+ const fmt = (d) => {
615
+ const pad = (n) => String(n).padStart(2, '0');
616
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
617
+ };
618
+ // 라이브 데이터 구독 일시 중단
619
+ this.dispatchEvent(new CustomEvent('playback-live-pause', { bubbles: true, composed: true }));
620
+ try {
621
+ // fetchPlayback은 페이지에서 주입 (api 패키지 의존 방지)
622
+ const result = await ((_a = this._fetchPlaybackData) === null || _a === void 0 ? void 0 : _a.call(this, fmt(fromTime), fmt(endTime)));
623
+ if ((result === null || result === void 0 ? void 0 : result.status) && result.playback) {
624
+ this._playbackEngine.loadData(result.playback.scenarios);
625
+ this._playbackEngine.play(speed);
626
+ }
627
+ }
628
+ catch (ex) {
629
+ console.error('[Playback] load failed:', ex);
630
+ }
631
+ }
632
+ _onPlaybackPause() {
633
+ var _a;
634
+ (_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.pause();
635
+ }
636
+ _onPlaybackResume() {
637
+ var _a;
638
+ (_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.resume();
639
+ }
640
+ _onPlaybackStop() {
641
+ var _a, _b;
642
+ (_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.stop();
643
+ (_b = this._playbackEngine) === null || _b === void 0 ? void 0 : _b.detachScene();
644
+ this._playbackActive = false;
645
+ // 라이브 데이터 구독 재개
646
+ this.dispatchEvent(new CustomEvent('playback-live-resume', { bubbles: true, composed: true }));
647
+ this.dispatchEvent(new CustomEvent('playback-toggle', {
648
+ bubbles: true,
649
+ composed: true,
650
+ detail: { active: false }
651
+ }));
652
+ }
653
+ _onPlaybackSeek(e) {
654
+ var _a;
655
+ (_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.seekTo(e.detail.toTime);
656
+ }
657
+ _onPlaybackSpeed(e) {
658
+ var _a;
659
+ (_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.setSpeed(e.detail.speed);
660
+ }
488
661
  onRunBoard() {
489
662
  this.dispatchEvent(new CustomEvent('run-board', { bubbles: true, composed: true, detail: this.board.id }));
490
663
  }
@@ -643,10 +816,51 @@ BoardViewer.styles = [
643
816
  right: 5px;
644
817
  }
645
818
 
646
- #fullscreen {
819
+ [fab-group] {
647
820
  position: absolute;
648
821
  bottom: 15px;
649
822
  right: 16px;
823
+ display: flex;
824
+ flex-direction: column-reverse;
825
+ align-items: center;
826
+ gap: 12px;
827
+ z-index: 1000;
828
+ }
829
+
830
+ [fab-group] md-fab {
831
+ transition: transform 0.2s ease, opacity 0.2s ease;
832
+ }
833
+
834
+ [fab-group] md-fab.main-fab md-icon {
835
+ transition: transform 0.3s ease;
836
+ }
837
+
838
+ [fab-group] md-fab.main-fab[expanded] md-icon {
839
+ transform: rotate(45deg);
840
+ }
841
+
842
+ [fab-group] md-fab.sub-fab {
843
+ --md-fab-container-width: 40px;
844
+ --md-fab-container-height: 40px;
845
+ --md-icon-size: 20px;
846
+ transform: scale(0);
847
+ opacity: 0;
848
+ pointer-events: none;
849
+ }
850
+
851
+ [fab-group][expanded] md-fab.sub-fab {
852
+ transform: scale(1);
853
+ opacity: 1;
854
+ pointer-events: auto;
855
+ }
856
+
857
+ [fab-group] md-fab.sub-fab[disabled] {
858
+ opacity: 0.4;
859
+ pointer-events: none;
860
+ }
861
+
862
+ ox-playback-controls {
863
+ transition: opacity 0.3s ease;
650
864
  }
651
865
 
652
866
  [hidden] {
@@ -693,6 +907,9 @@ __decorate([
693
907
  __decorate([
694
908
  property({ type: Object })
695
909
  ], BoardViewer.prototype, "values", void 0);
910
+ __decorate([
911
+ property({ type: Object })
912
+ ], BoardViewer.prototype, "loadTracker", void 0);
696
913
  __decorate([
697
914
  property({ type: Boolean, reflect: true, attribute: 'hide-fullscreen' })
698
915
  ], BoardViewer.prototype, "hideFullscreen", void 0);
@@ -714,6 +931,12 @@ __decorate([
714
931
  __decorate([
715
932
  state()
716
933
  ], BoardViewer.prototype, "_fade_animations", void 0);
934
+ __decorate([
935
+ state()
936
+ ], BoardViewer.prototype, "_fabExpanded", void 0);
937
+ __decorate([
938
+ state()
939
+ ], BoardViewer.prototype, "_playbackActive", void 0);
717
940
  __decorate([
718
941
  query('#target')
719
942
  ], BoardViewer.prototype, "_target", void 0);
@@ -724,8 +947,11 @@ __decorate([
724
947
  query('#next')
725
948
  ], BoardViewer.prototype, "_next", void 0);
726
949
  __decorate([
727
- query('#fullscreen')
728
- ], BoardViewer.prototype, "_fullscreen", void 0);
950
+ query('[fab-group]')
951
+ ], BoardViewer.prototype, "_fabGroup", void 0);
952
+ __decorate([
953
+ property({ attribute: false })
954
+ ], BoardViewer.prototype, "_fetchPlaybackData", void 0);
729
955
  BoardViewer = __decorate([
730
956
  customElement('ox-board-viewer')
731
957
  ], BoardViewer);