@fmsim/board 1.0.88 → 2.0.0-beta.101
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 +7366 -3448
- 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 +36 -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-attachment-creation-card.js +1 -1
- package/dist/src/component/restful-attachment-creation-card.js.map +1 -1
- package/dist/src/component/restful-attachment-selector.js +8 -9
- package/dist/src/component/restful-attachment-selector.js.map +1 -1
- package/dist/src/component/restful-input-attachment-selector.d.ts +1 -1
- package/dist/src/component/restful-input-attachment-selector.js +3 -3
- package/dist/src/component/restful-input-attachment-selector.js.map +1 -1
- package/dist/src/component/restful-input-fill-style.d.ts +13 -2
- package/dist/src/component/restful-input-fill-style.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/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/layers/mcs-mouse-event-handler.js +8 -0
- package/dist/src/layers/mcs-mouse-event-handler.js.map +1 -1
- package/dist/src/layers/movement-layer.d.ts +7 -6
- package/dist/src/layers/movement-layer.js +56 -11
- 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 +4 -4
- package/dist/src/modeller/component-toolbar/component-toolbar.js.map +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 +21 -14
- package/dist/src/modeller/edit-toolbar.js +371 -213
- 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-mapper.js +1 -1
- package/dist/src/modeller/property-sidebar/data-binding/data-binding-mapper.js.map +1 -1
- package/dist/src/modeller/property-sidebar/data-binding/data-binding.d.ts +2 -2
- package/dist/src/modeller/property-sidebar/data-binding/data-binding.js +18 -18
- package/dist/src/modeller/property-sidebar/data-binding/data-binding.js.map +1 -1
- package/dist/src/modeller/property-sidebar/effects/effects.d.ts +10 -1
- package/dist/src/modeller/property-sidebar/effects/effects.js +66 -1
- package/dist/src/modeller/property-sidebar/effects/effects.js.map +1 -1
- package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.d.ts +44 -0
- package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js +70 -0
- package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js.map +1 -0
- package/dist/src/modeller/property-sidebar/effects/property-event-hover.js +3 -3
- package/dist/src/modeller/property-sidebar/effects/property-event-hover.js.map +1 -1
- package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +13 -3
- package/dist/src/modeller/property-sidebar/effects/property-event.js +20 -11
- package/dist/src/modeller/property-sidebar/effects/property-event.js.map +1 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +62 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +276 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.d.ts +15 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +56 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.d.ts +7 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js +25 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-model.d.ts +44 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-model.js +72 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-model.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.d.ts +18 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js +61 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector.d.ts +17 -12
- package/dist/src/modeller/property-sidebar/inspector/inspector.js +281 -154
- package/dist/src/modeller/property-sidebar/inspector/inspector.js.map +1 -1
- package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +2 -2
- package/dist/src/modeller/property-sidebar/property-sidebar.js +11 -24
- package/dist/src/modeller/property-sidebar/property-sidebar.js.map +1 -1
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.d.ts +75 -0
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js +346 -0
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js.map +1 -0
- package/dist/src/modeller/property-sidebar/shapes/shapes.d.ts +23 -2
- package/dist/src/modeller/property-sidebar/shapes/shapes.js +96 -4
- package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
- package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +25 -1
- package/dist/src/modeller/property-sidebar/specifics/specifics.js +112 -2
- package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
- package/dist/src/modeller/property-sidebar/styles/styles.d.ts +6 -4
- package/dist/src/modeller/property-sidebar/styles/styles.js +140 -437
- package/dist/src/modeller/property-sidebar/styles/styles.js.map +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 +6 -5
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -3
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
- package/dist/src/ox-board-component-info.d.ts +3 -3
- package/dist/src/ox-board-component-info.js +58 -19
- package/dist/src/ox-board-component-info.js.map +1 -1
- package/dist/src/ox-board-list.js +12 -12
- package/dist/src/ox-board-list.js.map +1 -1
- package/dist/src/ox-board-modeller.d.ts +9 -6
- package/dist/src/ox-board-modeller.js +32 -33
- package/dist/src/ox-board-modeller.js.map +1 -1
- package/dist/src/ox-board-viewer.d.ts +129 -17
- package/dist/src/ox-board-viewer.js +804 -115
- package/dist/src/ox-board-viewer.js.map +1 -1
- package/dist/src/ox-editor-board-selector.d.ts +4 -1
- package/dist/src/ox-editor-board-selector.js +41 -12
- 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/ox-property-editor-theme.js +2 -2
- package/dist/src/ox-property-editor-theme.js.map +1 -1
- package/dist/src/playback/carrier-tracking-store.d.ts +51 -0
- package/dist/src/playback/carrier-tracking-store.js +115 -0
- package/dist/src/playback/carrier-tracking-store.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.d.ts +4 -5
- package/dist/src/selector/board-creation-popup.js +16 -19
- package/dist/src/selector/board-creation-popup.js.map +1 -1
- package/dist/src/selector/board-thumbnail-card.js +8 -8
- package/dist/src/selector/board-thumbnail-card.js.map +1 -1
- package/dist/src/selector/ox-board-creation-card.d.ts +1 -1
- package/dist/src/selector/ox-board-creation-card.js +7 -7
- 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/fab/mode-2d.svg +25 -0
- package/icons/fab/mode-3d.svg +25 -0
- package/icons/fab/playback-play.svg +49 -0
- package/icons/fab/playback-stop.svg +50 -0
- package/icons/icon-htoolbar.png +0 -0
- package/package.json +22 -21
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
var BoardViewer_1;
|
|
1
2
|
import { __decorate } from "tslib";
|
|
2
|
-
import '@material/
|
|
3
|
-
import '@material/
|
|
3
|
+
import '@material/web/fab/fab.js';
|
|
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 { CarrierTrackingStore } from './playback/carrier-tracking-store.js';
|
|
13
|
+
import { create, LoadTracker, LoadingOverlay, SCENE_MODE } from '@hatiolab/things-scene';
|
|
14
|
+
import { isIOS, fullscreen, exitfullscreen, togglefullscreen } from '@operato/utils';
|
|
11
15
|
import { ScrollbarStyles } from '@operato/styles';
|
|
12
16
|
import { DataStorage } from './data-storage/data-storage.js';
|
|
13
17
|
import { runScenario, startScenario, DataSubscriptionProviderImpl, isRestfulAvailable } from '@fmsim/api';
|
|
14
|
-
|
|
18
|
+
// FAB 아이콘 — packages/board/icons/fab/ 의 외부 SVG asset 로 분리.
|
|
19
|
+
// 기존엔 inline SVG 였으나 (a) 가독성 향상, (b) 디자이너 단독 수정, (c) 캐시 활용 목적.
|
|
20
|
+
const ICON_FAB_PLAYBACK_PLAY = new URL('../../icons/fab/playback-play.svg', import.meta.url).href;
|
|
21
|
+
const ICON_FAB_PLAYBACK_STOP = new URL('../../icons/fab/playback-stop.svg', import.meta.url).href;
|
|
22
|
+
const ICON_FAB_MODE_2D = new URL('../../icons/fab/mode-2d.svg', import.meta.url).href;
|
|
23
|
+
const ICON_FAB_MODE_3D = new URL('../../icons/fab/mode-3d.svg', import.meta.url).href;
|
|
24
|
+
let BoardViewer = BoardViewer_1 = class BoardViewer extends LitElement {
|
|
15
25
|
constructor() {
|
|
16
26
|
var _a;
|
|
17
27
|
super(...arguments);
|
|
@@ -24,6 +34,8 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
24
34
|
this._forward = [];
|
|
25
35
|
this._backward = [];
|
|
26
36
|
this._oldtarget = null;
|
|
37
|
+
this._fabExpanded = false;
|
|
38
|
+
this._playbackActive = false;
|
|
27
39
|
this.currentBoardId = (_a = this.board) === null || _a === void 0 ? void 0 : _a.id;
|
|
28
40
|
this.popup = null;
|
|
29
41
|
this.lastComponent = null;
|
|
@@ -33,7 +45,6 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
33
45
|
const id = e.detail.value;
|
|
34
46
|
const components = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.findAll(`#${id}`);
|
|
35
47
|
if (!components || components.length == 0) {
|
|
36
|
-
console.log('No equipment was found.');
|
|
37
48
|
return;
|
|
38
49
|
}
|
|
39
50
|
window.dispatchEvent(new CustomEvent('clickComponent', { detail: components[0] }));
|
|
@@ -41,9 +52,16 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
41
52
|
};
|
|
42
53
|
// 모니터링에서 Movement Data를 보여준다.
|
|
43
54
|
this.fmbShowMovementHandler = (e) => {
|
|
44
|
-
var _a;
|
|
55
|
+
var _a, _b, _c;
|
|
56
|
+
// 플레이백 중에는 외부 모니터링이 보내는 실시간 반송 정보를 그리지 않는다 —
|
|
57
|
+
// 사용자가 플레이백 시점의 *과거 상태* 를 보고 있는데 실시간 화살표가 겹치면
|
|
58
|
+
// 혼란을 줌. (백엔드 상세 정보 요청은 ox-board-component-info.ts:225 에서
|
|
59
|
+
// 같은 playbackMode 가드로 이미 차단됨.)
|
|
60
|
+
if ((_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.rootContainer) === null || _b === void 0 ? void 0 : _b.playbackMode) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
45
63
|
const movement = e.detail.value;
|
|
46
|
-
const movementLayer = (
|
|
64
|
+
const movementLayer = (_c = this._scene) === null || _c === void 0 ? void 0 : _c.findById(`movement-layer`);
|
|
47
65
|
if (!movementLayer) {
|
|
48
66
|
return;
|
|
49
67
|
}
|
|
@@ -107,32 +125,31 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
107
125
|
floatingContainer.hidden = true;
|
|
108
126
|
}
|
|
109
127
|
};
|
|
110
|
-
// BoardComponentInfo popup을
|
|
128
|
+
// BoardComponentInfo popup 을 보여주고 deco-border 로 selected highlight.
|
|
129
|
+
// 영구 추적과는 독립 — query-submit 핸들러 (onQuerySubmit) 가 별도로 담당.
|
|
111
130
|
this.clickComponentHandler = (e) => {
|
|
112
|
-
var _a, _b;
|
|
113
131
|
const component = e.detail;
|
|
114
132
|
const { type } = component.state;
|
|
115
133
|
if (type == 'model-layer') {
|
|
116
|
-
|
|
117
|
-
// this.lastComponent?.trigger('deco-arrow-reset')
|
|
134
|
+
this._clearClickHighlight();
|
|
118
135
|
this.hidePopup();
|
|
119
136
|
return;
|
|
120
137
|
}
|
|
121
138
|
if ('isLVComponent' in component) {
|
|
122
|
-
|
|
123
|
-
// this.lastComponent?.trigger('deco-arrow-reset')
|
|
139
|
+
this._clearClickHighlight();
|
|
124
140
|
this.lastComponent = component;
|
|
125
141
|
component.trigger('deco-border', { distance: 8, lineWidth: 4, color: '#f007' });
|
|
126
|
-
// component.trigger('deco-arrow', { color: 'red', animation: true, size: 20 })
|
|
127
142
|
this.togglePopup(component);
|
|
128
143
|
}
|
|
129
144
|
};
|
|
145
|
+
// 영구 추적 라이프사이클은 things-scene 의 Scene.tracker 가 일괄 관리.
|
|
146
|
+
// (polling, deco-border 부착/해제, 3D outline 색 동기화, centerTo)
|
|
130
147
|
this.fmbStartAnimation = (e) => {
|
|
131
148
|
var _a;
|
|
132
149
|
const { target, animation, decorator, border, arrow } = e.detail || {};
|
|
133
150
|
const { id, type } = target || {};
|
|
134
151
|
if (id) {
|
|
135
|
-
(_a = this._scene) === null || _a === void 0 ? void 0 : _a.findAllById(id).filter(component => !type || component.getState('type') == type).forEach(component => {
|
|
152
|
+
(_a = this._scene) === null || _a === void 0 ? void 0 : _a.findAllById(id).filter((component) => !type || component.getState('type') == type).forEach((component) => {
|
|
136
153
|
if (animation) {
|
|
137
154
|
component.resetAnimation();
|
|
138
155
|
component.setState('animation', {
|
|
@@ -160,7 +177,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
160
177
|
const { target } = e.detail || {};
|
|
161
178
|
const { id, type } = target || {};
|
|
162
179
|
if (id) {
|
|
163
|
-
(_a = this._scene) === null || _a === void 0 ? void 0 : _a.findAllById(id).filter(component => !type || component.getState('type') == type).forEach(component => {
|
|
180
|
+
(_a = this._scene) === null || _a === void 0 ? void 0 : _a.findAllById(id).filter((component) => !type || component.getState('type') == type).forEach((component) => {
|
|
164
181
|
component.trigger('deco-off');
|
|
165
182
|
component.resetAnimation();
|
|
166
183
|
component.setState('animation', {
|
|
@@ -173,118 +190,234 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
173
190
|
var _a;
|
|
174
191
|
(_a = this._scene) === null || _a === void 0 ? void 0 : _a.root.trigger('deco-reset');
|
|
175
192
|
};
|
|
193
|
+
/**
|
|
194
|
+
* 스토커 게이지 표시 모드 전환 (런타임 전용, 비영속).
|
|
195
|
+
* 외부: window.dispatchEvent(new CustomEvent('fmbSetStockerGaugeMode', { detail: { mode } }))
|
|
196
|
+
* mode: 'capacity'(기본) | 'carrier' | 'abnormal'. RootContainer.stockerGaugeMode setter 가
|
|
197
|
+
* 2D 리드로우 + 3D 게이지 재빌드를 트리거한다(fmsim/machine root-container-override).
|
|
198
|
+
* 미지값은 무시 — state 오염 방지(render 는 미지값을 capacity 로 폴백하지만 명시 게이트).
|
|
199
|
+
*
|
|
200
|
+
* 반드시 rootContainer(=RootContainer, isRoot()===true)에 설정해야 한다. scene.root 는
|
|
201
|
+
* model_layer(Layer)라 override 가 없고, 컴포넌트의 this.root 는 RootContainer 로 해석되므로
|
|
202
|
+
* scene.root 에 쓰면 죽은 속성만 박히고 게이지에 반영되지 않는다.
|
|
203
|
+
*/
|
|
204
|
+
this.fmbSetStockerGaugeModeHandler = (e) => {
|
|
205
|
+
var _a, _b;
|
|
206
|
+
const mode = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.mode;
|
|
207
|
+
if (mode !== 'capacity' && mode !== 'carrier' && mode !== 'abnormal') {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const rootContainer = (_b = this._scene) === null || _b === void 0 ? void 0 : _b.rootContainer;
|
|
211
|
+
if (rootContainer) {
|
|
212
|
+
rootContainer.stockerGaugeMode = mode;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
this._pointerMoveHandler = () => this.transientShowButtons();
|
|
216
|
+
/**
|
|
217
|
+
* playback 모드는 전체화면 모드와 한 몸 — 어떤 경로로든 전체화면이 해제되면
|
|
218
|
+
* playback 도 함께 종료. (단독 전체화면 토글은 기존처럼 동작하도록 `_playbackActive` 가드.)
|
|
219
|
+
*/
|
|
220
|
+
this._fullscreenChangeHandler = () => {
|
|
221
|
+
if (!document.fullscreenElement && this._playbackActive) {
|
|
222
|
+
this._onPlaybackStop();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
176
225
|
this.resizeHandler = () => {
|
|
177
226
|
this._scene && this._scene.fit();
|
|
178
227
|
};
|
|
228
|
+
// ── Data Subscription ──
|
|
229
|
+
this._dataSubscriptionProvider = isRestfulAvailable() ? undefined : new DataSubscriptionProviderImpl();
|
|
230
|
+
/**
|
|
231
|
+
* 영구추적 fallback 데이터 캐시 — 플레이백 윈도우 × carrierName 단위로
|
|
232
|
+
* carrier 위치 이력을 보관. carrier 가 scene 에 직접 안 보일 때
|
|
233
|
+
* Tracker fallback 콜백이 lookupAt 으로 머신/유닛을 찾아 반환.
|
|
234
|
+
*/
|
|
235
|
+
this._carrierTrackingStore = new CarrierTrackingStore();
|
|
236
|
+
/**
|
|
237
|
+
* fallback 결과(target 컴포넌트) dedupe — 직전과 동일한 target 일 땐 로그 skip
|
|
238
|
+
* + deco-arrow 라이프사이클 (target 변경 시 이전 끄고 새로 부착).
|
|
239
|
+
*/
|
|
240
|
+
this._lastFallbackTarget = null;
|
|
241
|
+
/** _resolveCarrierFallback 마지막 호출 timestamp — carrier 발견 감지용. */
|
|
242
|
+
this._lastFallbackResolvedAt = 0;
|
|
243
|
+
}
|
|
244
|
+
get _is3dMode() {
|
|
245
|
+
var _a, _b, _c;
|
|
246
|
+
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
247
|
}
|
|
180
248
|
render() {
|
|
181
|
-
var
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
249
|
+
var _a, _b;
|
|
250
|
+
var fabGroup = this.hideFullscreen
|
|
251
|
+
? html ``
|
|
252
|
+
: html `
|
|
253
|
+
<div
|
|
254
|
+
fab-group
|
|
255
|
+
?expanded=${this._fabExpanded}
|
|
187
256
|
@mouseover=${(e) => this.transientShowButtons(true)}
|
|
188
257
|
@mouseout=${(e) => this.transientShowButtons()}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
258
|
+
>
|
|
259
|
+
<md-fab
|
|
260
|
+
class="main-fab"
|
|
261
|
+
?expanded=${this._fabExpanded}
|
|
262
|
+
@click=${() => { this._fabExpanded = !this._fabExpanded; }}
|
|
263
|
+
title="menu"
|
|
264
|
+
><md-icon slot="icon">add</md-icon></md-fab>
|
|
265
|
+
|
|
266
|
+
${isIOS() ? nothing : html `<md-fab
|
|
267
|
+
class="sub-fab"
|
|
268
|
+
@click=${() => { this._fabExpanded = false; this.onTapFullscreen(); }}
|
|
269
|
+
title="fullscreen"
|
|
270
|
+
><md-icon slot="icon">${document.fullscreenElement ? 'fullscreen_exit' : 'fullscreen'}</md-icon></md-fab>`}
|
|
271
|
+
|
|
272
|
+
<md-fab
|
|
273
|
+
class="sub-fab"
|
|
274
|
+
@click=${() => { this._fabExpanded = false; this.onTapPlayback(); }}
|
|
275
|
+
title=${this._playbackActive ? 'stop playback' : 'start playback'}
|
|
276
|
+
><span slot="icon" class="fab-icon-wrap"><img
|
|
277
|
+
class="fab-icon"
|
|
278
|
+
src=${this._playbackActive ? ICON_FAB_PLAYBACK_STOP : ICON_FAB_PLAYBACK_PLAY}
|
|
279
|
+
alt=${this._playbackActive ? 'stop playback' : 'start playback'}
|
|
280
|
+
/></span></md-fab>
|
|
281
|
+
|
|
282
|
+
<md-fab
|
|
283
|
+
class="sub-fab"
|
|
284
|
+
@click=${() => { this._fabExpanded = false; this.onTapToggle3D(); }}
|
|
285
|
+
title=${this._is3dMode ? '2D mode' : '3D mode'}
|
|
286
|
+
><span slot="icon" class="fab-icon-wrap"><img
|
|
287
|
+
class="fab-icon"
|
|
288
|
+
src=${this._is3dMode ? ICON_FAB_MODE_2D : ICON_FAB_MODE_3D}
|
|
289
|
+
alt=${this._is3dMode ? '2D mode' : '3D mode'}
|
|
290
|
+
/></span></md-fab>
|
|
291
|
+
</div>
|
|
292
|
+
`;
|
|
193
293
|
var prev = !this.hideNavigation
|
|
194
294
|
? html `
|
|
195
|
-
<
|
|
295
|
+
<md-icon
|
|
196
296
|
id="prev"
|
|
197
297
|
@click=${(e) => this.onTapPrev()}
|
|
198
298
|
@mouseover=${(e) => this.transientShowButtons(true)}
|
|
199
299
|
@mouseout=${(e) => this.transientShowButtons()}
|
|
200
300
|
hidden
|
|
201
|
-
>keyboard_arrow_left</
|
|
301
|
+
>keyboard_arrow_left</md-icon
|
|
202
302
|
>
|
|
203
303
|
`
|
|
204
304
|
: html ``;
|
|
205
305
|
var next = !this.hideNavigation
|
|
206
306
|
? html `
|
|
207
|
-
<
|
|
307
|
+
<md-icon
|
|
208
308
|
id="next"
|
|
209
309
|
@click=${(e) => this.onTapNext()}
|
|
210
310
|
@mouseover=${(e) => this.transientShowButtons(true)}
|
|
211
311
|
@mouseout=${(e) => this.transientShowButtons()}
|
|
212
312
|
hidden
|
|
213
|
-
>keyboard_arrow_right</
|
|
313
|
+
>keyboard_arrow_right</md-icon
|
|
214
314
|
>
|
|
215
315
|
`
|
|
216
316
|
: html ``;
|
|
217
317
|
return html `
|
|
218
318
|
${prev}
|
|
219
319
|
|
|
220
|
-
<div
|
|
221
|
-
id="target"
|
|
222
|
-
@touchstart=${(e) => this.transientShowButtons()}
|
|
223
|
-
@mousemove=${(e) => this.transientShowButtons()}
|
|
224
|
-
></div>
|
|
320
|
+
<div id="target"></div>
|
|
225
321
|
|
|
226
322
|
<slot></slot>
|
|
227
|
-
${next} ${
|
|
323
|
+
${next} ${fabGroup}
|
|
324
|
+
|
|
325
|
+
${this._playbackActive
|
|
326
|
+
? html `<ox-playback-controls
|
|
327
|
+
.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'}
|
|
328
|
+
@playback-start=${this._onPlaybackStart}
|
|
329
|
+
@playback-pause=${this._onPlaybackPause}
|
|
330
|
+
@playback-resume=${this._onPlaybackResume}
|
|
331
|
+
@playback-stop=${this._onPlaybackStop}
|
|
332
|
+
@playback-seek=${this._onPlaybackSeek}
|
|
333
|
+
@playback-seek-start=${this._onPlaybackSeekStart}
|
|
334
|
+
@playback-seek-preview=${this._onPlaybackSeekPreview}
|
|
335
|
+
@playback-speed=${this._onPlaybackSpeed}
|
|
336
|
+
></ox-playback-controls>`
|
|
337
|
+
: nothing}
|
|
228
338
|
`;
|
|
229
339
|
}
|
|
340
|
+
/** 마우스 클릭/popup 의 selected highlight 해제. */
|
|
341
|
+
_clearClickHighlight() {
|
|
342
|
+
if (this.lastComponent) {
|
|
343
|
+
this.lastComponent.trigger('deco-border-off');
|
|
344
|
+
this.lastComponent = null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/** connect/disconnect 시 일괄 등록/해제할 window 이벤트 목록 (모니터링 연동) */
|
|
348
|
+
get _monitoringEvents() {
|
|
349
|
+
return [
|
|
350
|
+
['fetchIFactsData', this.fetchIFactsDataHandler], // 장비 아이디 object를 캔버스 가운데로
|
|
351
|
+
['fmbShowMovement', this.fmbShowMovementHandler], // Movement Data 표시
|
|
352
|
+
['fmbMonitoringInit', this.fmbMonitoringInitHandler], // 보드 상태 정사이즈 복원
|
|
353
|
+
['clickComponent', this.clickComponentHandler], // BoardComponentInfo popup
|
|
354
|
+
['fmbStartAnimation', this.fmbStartAnimation], // AnimatedIcon 추가/제거
|
|
355
|
+
['fmbStopAnimation', this.fmbStopAnimation],
|
|
356
|
+
['fmbResetAnimation', this.fmbResetAnimation],
|
|
357
|
+
['fmbShowLegend', this.fmbShowLegendHandler], // 범례 표시
|
|
358
|
+
['fmbHideLegend', this.fmbHideLegendHandler],
|
|
359
|
+
['fmbShowPopup', this.fmbShowPopupHandler], // 팝업 표시
|
|
360
|
+
['fmbHidePopup', this.fmbHidePopupHandler],
|
|
361
|
+
['fmbShowFloatingContainer', this.fmbShowFloatingContainerHandler], // FloatingContainer
|
|
362
|
+
['fmbHideFloatingContainer', this.fmbHideFloatingContainerHandler],
|
|
363
|
+
['fmbSetStockerGaugeMode', this.fmbSetStockerGaugeModeHandler] // 스토커 게이지 모드 전환
|
|
364
|
+
];
|
|
365
|
+
}
|
|
230
366
|
connectedCallback() {
|
|
231
367
|
super.connectedCallback();
|
|
232
368
|
window.addEventListener('resize', this.resizeHandler);
|
|
369
|
+
// 3D 모드에서도 마우스 움직임 감지 (document 레벨, capture phase)
|
|
370
|
+
document.addEventListener('pointermove', this._pointerMoveHandler, true);
|
|
371
|
+
document.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
|
|
372
|
+
document.addEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
|
|
233
373
|
this.renderRoot.addEventListener('close-scene', e => {
|
|
234
374
|
e.preventDefault();
|
|
235
375
|
this.onTapPrev();
|
|
236
376
|
}, false);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
window.addEventListener('fmbShowMovement', this.fmbShowMovementHandler);
|
|
241
|
-
// 모니터링에서 보드 상태를 원래상태 (정사이즈)로 돌려놓는다.
|
|
242
|
-
window.addEventListener('fmbMonitoringInit', this.fmbMonitoringInitHandler);
|
|
243
|
-
// BoardComponentInfo popup을 보여준다.
|
|
244
|
-
window.addEventListener('clickComponent', this.clickComponentHandler);
|
|
245
|
-
// AnimatedIcon 을 추가하거나 제거한다.
|
|
246
|
-
window.addEventListener('fmbStartAnimation', this.fmbStartAnimation);
|
|
247
|
-
window.addEventListener('fmbStopAnimation', this.fmbStopAnimation);
|
|
248
|
-
window.addEventListener('fmbResetAnimation', this.fmbResetAnimation);
|
|
249
|
-
// 모니터링에서 범례를 보여준다.
|
|
250
|
-
window.addEventListener('fmbShowLegend', this.fmbShowLegendHandler);
|
|
251
|
-
window.addEventListener('fmbHideLegend', this.fmbHideLegendHandler);
|
|
252
|
-
// 모니터링에서 범례를 보여준다.
|
|
253
|
-
window.addEventListener('fmbShowPopup', this.fmbShowPopupHandler);
|
|
254
|
-
window.addEventListener('fmbHidePopup', this.fmbHidePopupHandler);
|
|
255
|
-
// 모니터링에서 FloatingContainer를 보여준다.
|
|
256
|
-
window.addEventListener('fmbShowFloatingContainer', this.fmbShowFloatingContainerHandler);
|
|
257
|
-
window.addEventListener('fmbHideFloatingContainer', this.fmbHideFloatingContainerHandler);
|
|
377
|
+
for (const [type, handler] of this._monitoringEvents) {
|
|
378
|
+
window.addEventListener(type, handler);
|
|
379
|
+
}
|
|
258
380
|
}
|
|
259
381
|
disconnectedCallback() {
|
|
382
|
+
var _a, _b, _c, _d, _e;
|
|
260
383
|
super.disconnectedCallback();
|
|
261
384
|
window.removeEventListener('resize', this.resizeHandler);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
385
|
+
document.removeEventListener('pointermove', this._pointerMoveHandler, true);
|
|
386
|
+
document.removeEventListener('fullscreenchange', this._fullscreenChangeHandler);
|
|
387
|
+
document.removeEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
|
|
388
|
+
for (const [type, handler] of this._monitoringEvents) {
|
|
389
|
+
window.removeEventListener(type, handler);
|
|
390
|
+
}
|
|
391
|
+
this._clearClickHighlight();
|
|
392
|
+
(_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.tracker) === null || _b === void 0 ? void 0 : _b.stop();
|
|
393
|
+
// 플레이백이 활성 상태면 명시적 stop (provider resume 포함)
|
|
394
|
+
if (this._playbackActive) {
|
|
395
|
+
(_c = this._playbackEngine) === null || _c === void 0 ? void 0 : _c.stop();
|
|
396
|
+
(_d = this._playbackEngine) === null || _d === void 0 ? void 0 : _d.detachScene();
|
|
397
|
+
(_e = this._dataSubscriptionProvider) === null || _e === void 0 ? void 0 : _e.resume();
|
|
398
|
+
this._playbackActive = false;
|
|
399
|
+
}
|
|
400
|
+
this._playbackWindow = undefined;
|
|
401
|
+
this._carrierTrackingStore.clear();
|
|
402
|
+
this._clearFallbackArrow();
|
|
403
|
+
this._stopFallbackArrowMonitor();
|
|
404
|
+
if (this._playbackEngine) {
|
|
405
|
+
this._playbackEngine.dispose();
|
|
406
|
+
this._playbackEngine = undefined;
|
|
407
|
+
}
|
|
284
408
|
this.closeScene();
|
|
285
409
|
}
|
|
286
410
|
updated(changes) {
|
|
411
|
+
// loadTracker가 외부에서 전달되면 즉시 overlay 연결 (fetch 중에도 표시)
|
|
412
|
+
if (changes.has('loadTracker') && this.loadTracker) {
|
|
413
|
+
this._ensureOverlay(this.loadTracker);
|
|
414
|
+
}
|
|
287
415
|
if (changes.has('board')) {
|
|
416
|
+
// tracker: 외부에서 안 왔으면 여기서 생성
|
|
417
|
+
if (!this.loadTracker) {
|
|
418
|
+
this.loadTracker = new LoadTracker();
|
|
419
|
+
this._ensureOverlay(this.loadTracker);
|
|
420
|
+
}
|
|
288
421
|
this.hidePopup();
|
|
289
422
|
this.closeScene();
|
|
290
423
|
if (this.board && this.board.id) {
|
|
@@ -304,33 +437,47 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
304
437
|
}
|
|
305
438
|
}
|
|
306
439
|
async initSceneAsync() {
|
|
307
|
-
if (!this.board || !this.board.id)
|
|
440
|
+
if (!this.board || !this.board.id || !this.provider)
|
|
308
441
|
return;
|
|
309
|
-
|
|
310
|
-
|
|
442
|
+
try {
|
|
443
|
+
this._scene = await this.provider.create(this.board.id);
|
|
444
|
+
this.setupScene({ id: this.board.id, scene: this._scene });
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
console.error('Failed to load scene:', this.board.id, e);
|
|
448
|
+
}
|
|
311
449
|
}
|
|
312
450
|
initScene() {
|
|
313
|
-
if (!this.board || !this.board.id) {
|
|
451
|
+
if (!this.board || !this.board.id || !this.board.model) {
|
|
314
452
|
return;
|
|
315
453
|
}
|
|
316
454
|
this._scene = create({
|
|
317
|
-
model:
|
|
318
|
-
...this.board.model
|
|
319
|
-
},
|
|
455
|
+
model: JSON.parse(JSON.stringify(this.board.model)),
|
|
320
456
|
style: this.board.style,
|
|
321
457
|
mode: SCENE_MODE.VIEW,
|
|
322
458
|
handlers: ['mcs-event-handler', 'zoom-handler', 'shift-handler', 'waypoint-handler'],
|
|
323
459
|
layers: [{ type: 'scroll-layer' }, { type: 'movement-layer', id: 'movement-layer' }],
|
|
324
460
|
refProvider: this.provider,
|
|
325
461
|
dataStorage: this.board.id !== 'preview' ? new DataStorage(this.board.id) : undefined,
|
|
326
|
-
dataSubscriptionProvider:
|
|
462
|
+
dataSubscriptionProvider: this._dataSubscriptionProvider,
|
|
463
|
+
loading: this.loadTracker ? { tracker: this.loadTracker } : undefined
|
|
327
464
|
});
|
|
328
465
|
if (this.baseUrl) {
|
|
329
466
|
this._scene.baseUrl = this.baseUrl;
|
|
330
467
|
}
|
|
331
468
|
this.setupScene({ id: this.board.id, scene: this._scene });
|
|
332
469
|
}
|
|
470
|
+
_ensureOverlay(tracker) {
|
|
471
|
+
if (!this._loadingOverlay) {
|
|
472
|
+
this._loadingOverlay = new LoadingOverlay();
|
|
473
|
+
}
|
|
474
|
+
this._loadingOverlay.attach(this.renderRoot, tracker);
|
|
475
|
+
}
|
|
333
476
|
closeScene() {
|
|
477
|
+
var _a, _b;
|
|
478
|
+
// scene 교체/종료 시 highlight 상태도 함께 정리. tracking watcher 누수 방지.
|
|
479
|
+
this._clearClickHighlight();
|
|
480
|
+
(_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.tracker) === null || _b === void 0 ? void 0 : _b.stop();
|
|
334
481
|
if (this._scene) {
|
|
335
482
|
this.unbindSceneEvents(this._scene);
|
|
336
483
|
this._scene.target = null;
|
|
@@ -349,6 +496,10 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
349
496
|
}
|
|
350
497
|
setupScene({ id, scene }) {
|
|
351
498
|
var _a;
|
|
499
|
+
// 플레이백 리셋
|
|
500
|
+
if (this._playbackActive) {
|
|
501
|
+
this._onPlaybackStop();
|
|
502
|
+
}
|
|
352
503
|
this._scene = scene;
|
|
353
504
|
const backgroundColor = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root.state.fillStyle;
|
|
354
505
|
if (typeof backgroundColor === 'string') {
|
|
@@ -372,7 +523,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
372
523
|
this.transientShowButtons();
|
|
373
524
|
}
|
|
374
525
|
async showScene(boardId, bindingData) {
|
|
375
|
-
if (!boardId)
|
|
526
|
+
if (!boardId || !this.provider)
|
|
376
527
|
return;
|
|
377
528
|
try {
|
|
378
529
|
var scene = await this.provider.get(boardId, true);
|
|
@@ -410,6 +561,206 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
410
561
|
this._scene.on('run-scenario', this.onRunScenario, this);
|
|
411
562
|
this._scene.on('legend-toggle', this.onLegendToggle, this);
|
|
412
563
|
this._scene.on('open-popup', this.onOpenPopup, this);
|
|
564
|
+
// VIEW 모드 SearchOverlay 디폴트 = 영구 추적 (things-scene 자동 처리).
|
|
565
|
+
// application 은 trackerOptions (color/fallback 등) 또는 onSearchSubmit/onSearchClose
|
|
566
|
+
// hook (디폴트 override) 만 제공.
|
|
567
|
+
//
|
|
568
|
+
// fallback: 원본 carrier 컴포넌트가 scene 에 없을 때 호출 (Tracker 1s polling).
|
|
569
|
+
// 플레이백 중이면 carrier-location history 에서 현재 시각의 머신/유닛을 lookup 해
|
|
570
|
+
// 해당 컴포넌트를 highlight 대상으로 반환. 플레이백 비활성 / 시간 정보 없음 /
|
|
571
|
+
// NA / 보드에 머신 없음 → null 반환해서 highlight 없이 추적만 유지.
|
|
572
|
+
this._scene.trackerOptions = {
|
|
573
|
+
color: BoardViewer_1.TRACKING_COLOR,
|
|
574
|
+
fallback: (_id, scene) => this._resolveCarrierFallback(scene)
|
|
575
|
+
};
|
|
576
|
+
// SearchOverlay submit/close hook — carrier-location store 의 충전/정리 트리거.
|
|
577
|
+
// 디폴트 동작 (tracker.start/stop) 은 그대로 위임 (return undefined).
|
|
578
|
+
this._scene.onSearchSubmit = (detail) => {
|
|
579
|
+
this._onCarrierTrackingSubmit(detail === null || detail === void 0 ? void 0 : detail.query);
|
|
580
|
+
};
|
|
581
|
+
this._scene.onSearchClose = () => {
|
|
582
|
+
this._carrierTrackingStore.clear();
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Tracker fallback resolver — 플레이백 시각의 carrier 위치를 컴포넌트로 변환.
|
|
587
|
+
* NA / 정보 없음 / 머신 미존재 → null (Tracker 가 highlight 없이 추적만 유지).
|
|
588
|
+
*
|
|
589
|
+
* 매 1초 polling 마다 호출되며, 결과 target 이 직전과 동일하면 로그 skip.
|
|
590
|
+
*/
|
|
591
|
+
_resolveCarrierFallback(scene) {
|
|
592
|
+
var _a, _b, _c, _d;
|
|
593
|
+
if (!this._playbackActive)
|
|
594
|
+
return null;
|
|
595
|
+
const currentTime = (_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.currentTime;
|
|
596
|
+
if (!currentTime)
|
|
597
|
+
return null;
|
|
598
|
+
const timeMs = Date.parse(currentTime.replace(' ', 'T'));
|
|
599
|
+
if (!Number.isFinite(timeMs))
|
|
600
|
+
return null;
|
|
601
|
+
const carrier = (_d = (_c = (_b = this._scene) === null || _b === void 0 ? void 0 : _b.tracker) === null || _c === void 0 ? void 0 : _c.trackingId) !== null && _d !== void 0 ? _d : '?';
|
|
602
|
+
const loc = this._carrierTrackingStore.lookupAt(timeMs);
|
|
603
|
+
this._lastFallbackResolvedAt = Date.now();
|
|
604
|
+
const logIfChanged = (target, msg) => {
|
|
605
|
+
if (target === this._lastFallbackTarget)
|
|
606
|
+
return;
|
|
607
|
+
// 이전 target 의 bouncing arrow 해제 + 새 target 에 부착 (가시성 강화).
|
|
608
|
+
// deco-border 는 things-scene Tracker 가 처리하므로 우리는 deco-arrow 만 관리.
|
|
609
|
+
this._setFallbackArrow(this._lastFallbackTarget, null);
|
|
610
|
+
this._setFallbackArrow(null, target);
|
|
611
|
+
this._lastFallbackTarget = target;
|
|
612
|
+
console.log(msg);
|
|
613
|
+
};
|
|
614
|
+
if (!loc) {
|
|
615
|
+
logIfChanged(null, `[CarrierTracking] fallback: history miss (carrier=${carrier}, t=${currentTime})`);
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
if (loc.machineName === 'NA') {
|
|
619
|
+
logIfChanged(null, `[CarrierTracking] fallback: NA (carrier=${carrier}, t=${currentTime})`);
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
// unitName 우선 (더 정밀), 없으면 machineName 으로 폴백.
|
|
623
|
+
// 같은 id 컴포넌트가 여러 개일 수 있어 (예: 머신 본체 + 부착된 게이지) findAllById
|
|
624
|
+
// 로 후보 모두 가져온 뒤, mcs-mouse-event-handler 의 클릭 dispatch 와 동일한
|
|
625
|
+
// 패턴으로 머신 본체로 수렴:
|
|
626
|
+
// - 'isLVComponent' 후보 선택
|
|
627
|
+
// - parentObject 가 있으면 그쪽으로 escalate (게이지·바 → 머신 본체)
|
|
628
|
+
// - 없으면 자기 자신 사용
|
|
629
|
+
const resolveLVById = (id) => {
|
|
630
|
+
var _a, _b;
|
|
631
|
+
const candidates = (((_a = scene === null || scene === void 0 ? void 0 : scene.findAllById) === null || _a === void 0 ? void 0 : _a.call(scene, id)) || []);
|
|
632
|
+
const c = candidates.find(c => 'isLVComponent' in c);
|
|
633
|
+
if (!c)
|
|
634
|
+
return null;
|
|
635
|
+
return (_b = c.parentObject) !== null && _b !== void 0 ? _b : c;
|
|
636
|
+
};
|
|
637
|
+
let target = null;
|
|
638
|
+
let hitVia = null;
|
|
639
|
+
if (loc.unitName) {
|
|
640
|
+
const t = resolveLVById(loc.unitName);
|
|
641
|
+
if (t) {
|
|
642
|
+
target = t;
|
|
643
|
+
hitVia = 'unit';
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (!target && loc.machineName) {
|
|
647
|
+
const t = resolveLVById(loc.machineName);
|
|
648
|
+
if (t) {
|
|
649
|
+
target = t;
|
|
650
|
+
hitVia = 'machine';
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (hitVia === 'unit') {
|
|
654
|
+
logIfChanged(target, `[CarrierTracking] fallback applied via unit=${loc.unitName} ` +
|
|
655
|
+
`(machine=${loc.machineName} not tried) (carrier=${carrier}, t=${currentTime})`);
|
|
656
|
+
}
|
|
657
|
+
else if (hitVia === 'machine') {
|
|
658
|
+
logIfChanged(target, `[CarrierTracking] fallback applied via machine=${loc.machineName} ` +
|
|
659
|
+
`(unit=${loc.unitName} miss) (carrier=${carrier}, t=${currentTime})`);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
logIfChanged(target, `[CarrierTracking] fallback: scene 미존재 — unit=${loc.unitName} (miss), ` +
|
|
663
|
+
`machine=${loc.machineName} (miss) (carrier=${carrier}, t=${currentTime})`);
|
|
664
|
+
}
|
|
665
|
+
return target;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* deco-arrow 라이프사이클 — 이전 target 끄고 새 target 에 bouncing arrow 부착.
|
|
669
|
+
* 둘 다 null 가능 (해제만, 부착만).
|
|
670
|
+
*/
|
|
671
|
+
_setFallbackArrow(off, on) {
|
|
672
|
+
var _a, _b, _c, _d;
|
|
673
|
+
if (off) {
|
|
674
|
+
try {
|
|
675
|
+
(_b = (_a = off).trigger) === null || _b === void 0 ? void 0 : _b.call(_a, 'deco-arrow-off');
|
|
676
|
+
}
|
|
677
|
+
catch (_e) { }
|
|
678
|
+
}
|
|
679
|
+
if (on) {
|
|
680
|
+
try {
|
|
681
|
+
;
|
|
682
|
+
(_d = (_c = on).trigger) === null || _d === void 0 ? void 0 : _d.call(_c, 'deco-arrow', {
|
|
683
|
+
animation: true,
|
|
684
|
+
repeat: true,
|
|
685
|
+
size: 0.75,
|
|
686
|
+
// TRACKING_COLOR(#00aaff7f) 의 색조는 유지하되 알파(7f) 만 제거해 불투명.
|
|
687
|
+
color: BoardViewer_1.TRACKING_COLOR.slice(0, 7),
|
|
688
|
+
duration: 5000
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
catch (_f) { }
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* 1초 polling — 화살표 부착 대상을 결정.
|
|
696
|
+
* 1) 플레이백 종료 / 추적 ID 없음 → 화살표 해제
|
|
697
|
+
* 2) carrier 가 scene 에 직접 존재 → **carrier 자체에 화살표 부착**
|
|
698
|
+
* (things-scene Tracker 가 carrier 발견 시 fallback 콜백 호출을 멈춰
|
|
699
|
+
* resolver 경로로는 우리가 모름. monitor 에서 직접 책임)
|
|
700
|
+
* 3) carrier 없음 + resolver 가 3초 이상 호출 안 됨 → 화살표 해제
|
|
701
|
+
* (그 외엔 resolver 의 logIfChanged 가 fallback target 으로 부착 중)
|
|
702
|
+
*/
|
|
703
|
+
_startFallbackArrowMonitor() {
|
|
704
|
+
if (this._fallbackArrowMonitor != null)
|
|
705
|
+
return;
|
|
706
|
+
this._fallbackArrowMonitor = window.setInterval(() => {
|
|
707
|
+
var _a, _b, _c, _d, _e;
|
|
708
|
+
// 1) 플레이백 종료 / 추적 ID 해제
|
|
709
|
+
const trackingId = (_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.tracker) === null || _b === void 0 ? void 0 : _b.trackingId;
|
|
710
|
+
if (!this._playbackActive || !trackingId) {
|
|
711
|
+
this._clearFallbackArrow();
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
// 2) carrier 가 scene 에 직접 존재 — carrier(또는 parentObject)에 화살표 부착
|
|
715
|
+
const carrierComp = (((_d = (_c = this._scene) === null || _c === void 0 ? void 0 : _c.findAllById) === null || _d === void 0 ? void 0 : _d.call(_c, trackingId)) || [])
|
|
716
|
+
.find(c => 'isLVComponent' in c);
|
|
717
|
+
if (carrierComp) {
|
|
718
|
+
const arrowTarget = (_e = carrierComp.parentObject) !== null && _e !== void 0 ? _e : carrierComp;
|
|
719
|
+
if (arrowTarget !== this._lastFallbackTarget) {
|
|
720
|
+
this._setFallbackArrow(this._lastFallbackTarget, null);
|
|
721
|
+
this._setFallbackArrow(null, arrowTarget);
|
|
722
|
+
this._lastFallbackTarget = arrowTarget;
|
|
723
|
+
}
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
// 3) resolver 가 일정 시간 호출되지 않음 — Tracker 가 추적 중단했을 가능성
|
|
727
|
+
if (this._lastFallbackTarget && Date.now() - this._lastFallbackResolvedAt > 3000) {
|
|
728
|
+
this._clearFallbackArrow();
|
|
729
|
+
}
|
|
730
|
+
}, 1000);
|
|
731
|
+
}
|
|
732
|
+
_stopFallbackArrowMonitor() {
|
|
733
|
+
if (this._fallbackArrowMonitor != null) {
|
|
734
|
+
clearInterval(this._fallbackArrowMonitor);
|
|
735
|
+
this._fallbackArrowMonitor = undefined;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
/** 부착된 deco-arrow 강제 해제 + dedupe 상태 초기화. */
|
|
739
|
+
_clearFallbackArrow() {
|
|
740
|
+
if (this._lastFallbackTarget) {
|
|
741
|
+
this._setFallbackArrow(this._lastFallbackTarget, null);
|
|
742
|
+
this._lastFallbackTarget = null;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* onSearchSubmit hook — 영구추적 carrierName 이 설정되는 순간.
|
|
747
|
+
* 플레이백 윈도우가 잡혀 있으면 즉시 store 충전. 윈도우가 없으면
|
|
748
|
+
* 플레이백 시작 시 (_onPlaybackStart) 에 충전.
|
|
749
|
+
*/
|
|
750
|
+
_onCarrierTrackingSubmit(carrierName) {
|
|
751
|
+
this._clearFallbackArrow(); // 새 carrier — 기존 화살표/dedupe 정리
|
|
752
|
+
if (!carrierName) {
|
|
753
|
+
this._carrierTrackingStore.clear();
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const win = this._playbackWindow;
|
|
757
|
+
if (this._playbackActive && win) {
|
|
758
|
+
this._carrierTrackingStore.load(carrierName, win.from, win.to);
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
// 플레이백 비활성 — 윈도우 정해질 때까지 대기. 캐시는 비워둠.
|
|
762
|
+
this._carrierTrackingStore.clear();
|
|
763
|
+
}
|
|
413
764
|
}
|
|
414
765
|
unbindSceneEvents(scene) {
|
|
415
766
|
scene.off('run', this.onRunBoard, this);
|
|
@@ -425,7 +776,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
425
776
|
transientShowButtons(stop) {
|
|
426
777
|
var buttons = [];
|
|
427
778
|
!this.hideNavigation && buttons.push(this._next, this._prev);
|
|
428
|
-
!this.hideFullscreen && buttons.push(this.
|
|
779
|
+
!this.hideFullscreen && buttons.push(this._fabGroup);
|
|
429
780
|
if (buttons.length == 0) {
|
|
430
781
|
return;
|
|
431
782
|
}
|
|
@@ -448,7 +799,7 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
448
799
|
}
|
|
449
800
|
this._forward.length <= 0 ? this._next.setAttribute('hidden', '') : this._next.removeAttribute('hidden');
|
|
450
801
|
this._backward.length <= 0 ? this._prev.setAttribute('hidden', '') : this._prev.removeAttribute('hidden');
|
|
451
|
-
this.
|
|
802
|
+
this._fabGroup && this._fabGroup.removeAttribute('hidden');
|
|
452
803
|
this._fade_animations.forEach(animation => {
|
|
453
804
|
animation.cancel();
|
|
454
805
|
if (stop)
|
|
@@ -486,6 +837,264 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
486
837
|
onTapFullscreen() {
|
|
487
838
|
togglefullscreen(this, () => this.requestUpdate(), () => this.requestUpdate());
|
|
488
839
|
}
|
|
840
|
+
onTapToggle3D() {
|
|
841
|
+
var _a, _b, _c;
|
|
842
|
+
const modelLayer = (_b = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.rootModel;
|
|
843
|
+
if (!(modelLayer === null || modelLayer === void 0 ? void 0 : modelLayer.toggleThreeD))
|
|
844
|
+
return;
|
|
845
|
+
const entering3D = !modelLayer.is3dMode;
|
|
846
|
+
modelLayer.toggleThreeD();
|
|
847
|
+
this.requestUpdate();
|
|
848
|
+
// fmsim: 3D 진입 시 자동 회전
|
|
849
|
+
if (entering3D && ((_c = modelLayer._threeCapability) === null || _c === void 0 ? void 0 : _c.isInitialized)) {
|
|
850
|
+
modelLayer._threeCapability.controlsManager.reset();
|
|
851
|
+
modelLayer._threeCapability.controlsManager.setAutoRotate(true);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
onTapPlayback() {
|
|
855
|
+
var _a, _b;
|
|
856
|
+
if (this._playbackActive) {
|
|
857
|
+
this._onPlaybackStop();
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
// 플레이백 시작 시 팝업 닫기
|
|
861
|
+
this.hidePopup();
|
|
862
|
+
// 1) 라이브 데이터 구독 실제 종료
|
|
863
|
+
(_a = this._dataSubscriptionProvider) === null || _a === void 0 ? void 0 : _a.pause();
|
|
864
|
+
this._unsubscribeSceneData();
|
|
865
|
+
// 2) 라이브 데이터 초기화 (깨끗한 상태에서 playback 시작)
|
|
866
|
+
this._resetSceneData();
|
|
867
|
+
this._playbackActive = true;
|
|
868
|
+
// playbackMode 를 "라이브 아님" 의미로 통일 — 준비 단계부터 set 해서
|
|
869
|
+
// FAB 클릭 직후 ~ Play 누르기 전 사이의 갭에서도 가드 (mouse handler,
|
|
870
|
+
// movement layer, restful subscription) 가 작동하도록.
|
|
871
|
+
if ((_b = this._scene) === null || _b === void 0 ? void 0 : _b.rootContainer)
|
|
872
|
+
this._scene.rootContainer.playbackMode = true;
|
|
873
|
+
if (!this._playbackEngine) {
|
|
874
|
+
this._playbackEngine = new PlaybackEngine();
|
|
875
|
+
this._playbackEngine.on('frame', ({ time }) => {
|
|
876
|
+
const controls = this.renderRoot.querySelector('ox-playback-controls');
|
|
877
|
+
if (controls)
|
|
878
|
+
controls.currentTime = time;
|
|
879
|
+
});
|
|
880
|
+
this._playbackEngine.on('state-change', (state) => {
|
|
881
|
+
if (state === 'stopped') {
|
|
882
|
+
const controls = this.renderRoot.querySelector('ox-playback-controls');
|
|
883
|
+
if (controls)
|
|
884
|
+
controls.playbackState = 'stopped';
|
|
885
|
+
}
|
|
886
|
+
this.requestUpdate();
|
|
887
|
+
});
|
|
888
|
+
this._playbackEngine.on('buffer-update', ({ bufferedRanges }) => {
|
|
889
|
+
const controls = this.renderRoot.querySelector('ox-playback-controls');
|
|
890
|
+
if (controls)
|
|
891
|
+
controls.bufferedRanges = bufferedRanges;
|
|
892
|
+
});
|
|
893
|
+
if (this._fetchPlaybackData) {
|
|
894
|
+
this._playbackEngine.setFetchFunction(this._fetchPlaybackData);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (this._scene) {
|
|
898
|
+
this._playbackEngine.attachScene(this._scene);
|
|
899
|
+
}
|
|
900
|
+
this.dispatchEvent(new CustomEvent('playback-toggle', {
|
|
901
|
+
bubbles: true,
|
|
902
|
+
composed: true,
|
|
903
|
+
detail: { active: true }
|
|
904
|
+
}));
|
|
905
|
+
// playback 은 전체화면에서만 동작 — 이미 전체화면이면 그대로 유지
|
|
906
|
+
if (!document.fullscreenElement && !isIOS()) {
|
|
907
|
+
fullscreen(this, () => this.requestUpdate(), () => this.requestUpdate());
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
async _onPlaybackStart(e) {
|
|
912
|
+
var _a, _b, _c;
|
|
913
|
+
const { fromTime, toTime, speed } = e.detail;
|
|
914
|
+
if (!this._playbackEngine)
|
|
915
|
+
return;
|
|
916
|
+
// toTime is the user-selected window END (= controls' _appliedTo).
|
|
917
|
+
// The previous hardcoded `fromTime + 1h` ignored the controls'
|
|
918
|
+
// range selection, so 10m / 30m / Range modes silently played past
|
|
919
|
+
// the requested window. Falling back to `+1h` only if toTime was
|
|
920
|
+
// omitted (legacy callers).
|
|
921
|
+
const endTime = toTime instanceof Date ? toTime : new Date(fromTime.getTime() + 3600000);
|
|
922
|
+
// playbackMode 는 onTapPlayback (준비 단계) 에서 이미 set — idempotent
|
|
923
|
+
// 재설정으로 안전망 유지.
|
|
924
|
+
if ((_a = this._scene) === null || _a === void 0 ? void 0 : _a.rootContainer)
|
|
925
|
+
this._scene.rootContainer.playbackMode = true;
|
|
926
|
+
// 플레이백 윈도우 기록 + 영구추적 활성 시 carrier history 충전.
|
|
927
|
+
// 윈도우 변경 (재시작) 시에도 같은 경로 — store.load 가 key 비교로
|
|
928
|
+
// 동일 (carrierName, from, to) 이면 no-op.
|
|
929
|
+
this._playbackWindow = { from: fromTime, to: endTime };
|
|
930
|
+
const carrierName = (_c = (_b = this._scene) === null || _b === void 0 ? void 0 : _b.tracker) === null || _c === void 0 ? void 0 : _c.trackingId;
|
|
931
|
+
if (carrierName) {
|
|
932
|
+
this._carrierTrackingStore.load(carrierName, fromTime, endTime);
|
|
933
|
+
}
|
|
934
|
+
this._startFallbackArrowMonitor();
|
|
935
|
+
try {
|
|
936
|
+
await this._playbackEngine.startChunked(fromTime, endTime, speed);
|
|
937
|
+
}
|
|
938
|
+
catch (ex) {
|
|
939
|
+
console.error('[Playback] load failed:', ex);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
_onPlaybackPause() {
|
|
943
|
+
var _a;
|
|
944
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.pause();
|
|
945
|
+
}
|
|
946
|
+
_onPlaybackResume() {
|
|
947
|
+
var _a;
|
|
948
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.resume();
|
|
949
|
+
}
|
|
950
|
+
async _onPlaybackStop() {
|
|
951
|
+
var _a, _b, _c;
|
|
952
|
+
const scene = this._scene; // 비동기 구간 scene 교체 감지용 캡처
|
|
953
|
+
// 1) 즉시: 엔진 정지 + 애니메이션 억제 ON (in-flight 애니메이션 즉시 종료)
|
|
954
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.detachScene();
|
|
955
|
+
(_b = this._playbackEngine) === null || _b === void 0 ? void 0 : _b.stop();
|
|
956
|
+
this._playbackActive = false;
|
|
957
|
+
this._playbackWindow = undefined;
|
|
958
|
+
this._carrierTrackingStore.clear();
|
|
959
|
+
this._clearFallbackArrow();
|
|
960
|
+
this._stopFallbackArrowMonitor();
|
|
961
|
+
if (scene) {
|
|
962
|
+
scene.suppressAnimations = true;
|
|
963
|
+
if (scene.rootContainer)
|
|
964
|
+
scene.rootContainer.playbackMode = false;
|
|
965
|
+
}
|
|
966
|
+
// UI 신호 + fullscreen 해제
|
|
967
|
+
this.dispatchEvent(new CustomEvent('playback-toggle', {
|
|
968
|
+
bubbles: true,
|
|
969
|
+
composed: true,
|
|
970
|
+
detail: { active: false }
|
|
971
|
+
}));
|
|
972
|
+
if (document.fullscreenElement) {
|
|
973
|
+
exitfullscreen();
|
|
974
|
+
}
|
|
975
|
+
// 2) 1초 후: 보드 리셋 + live 구독 재개
|
|
976
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
977
|
+
if (this._playbackActive || this._scene !== scene)
|
|
978
|
+
return;
|
|
979
|
+
if (!scene)
|
|
980
|
+
return;
|
|
981
|
+
this._resetSceneData();
|
|
982
|
+
this._resubscribeSceneData();
|
|
983
|
+
(_c = this._dataSubscriptionProvider) === null || _c === void 0 ? void 0 : _c.resume();
|
|
984
|
+
// 3) 2초 후 (추가 1초): 억제 해제 — 첫 live batch 가 instant 배치된 뒤 정상 애니메이션 복원
|
|
985
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
986
|
+
if (this._scene === scene) {
|
|
987
|
+
scene.suppressAnimations = false;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* 플레이백 시작 시: RESTful data-subscription 컴포넌트의 WebSocket 을 실제 disconnect.
|
|
992
|
+
* GraphQL 경로는 _dataSubscriptionProvider.pause() 에서 일괄 처리.
|
|
993
|
+
*/
|
|
994
|
+
_unsubscribeSceneData() {
|
|
995
|
+
var _a;
|
|
996
|
+
const root = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root;
|
|
997
|
+
if (!root || typeof root.traverse !== 'function')
|
|
998
|
+
return;
|
|
999
|
+
root.traverse((c) => {
|
|
1000
|
+
if (!c || c === root)
|
|
1001
|
+
return;
|
|
1002
|
+
// RESTful data-subscription 컴포넌트 — 자체 WebSocket disconnect
|
|
1003
|
+
if (c.subscription) {
|
|
1004
|
+
try {
|
|
1005
|
+
c.subscription.unsubscribe();
|
|
1006
|
+
}
|
|
1007
|
+
catch (_a) { }
|
|
1008
|
+
c.subscription = null;
|
|
1009
|
+
}
|
|
1010
|
+
if (c.client) {
|
|
1011
|
+
try {
|
|
1012
|
+
c.client.unsubscribeAll();
|
|
1013
|
+
c.client.close(true);
|
|
1014
|
+
}
|
|
1015
|
+
catch (_b) { }
|
|
1016
|
+
c.client = null;
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* 플레이백 종료 시: RESTful data-subscription 컴포넌트의 WebSocket 을 재연결.
|
|
1022
|
+
* GraphQL 경로는 _dataSubscriptionProvider.resume() 에서 일괄 처리.
|
|
1023
|
+
*/
|
|
1024
|
+
_resubscribeSceneData() {
|
|
1025
|
+
var _a;
|
|
1026
|
+
const root = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root;
|
|
1027
|
+
if (!root || typeof root.traverse !== 'function')
|
|
1028
|
+
return;
|
|
1029
|
+
root.traverse((c) => {
|
|
1030
|
+
if (!c || c === root)
|
|
1031
|
+
return;
|
|
1032
|
+
// RESTful data-subscription 컴포넌트 — WebSocket 재연결
|
|
1033
|
+
if (typeof c.requestData === 'function' && !c.client) {
|
|
1034
|
+
c.requestData();
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
_resetSceneData() {
|
|
1039
|
+
var _a, _b, _c, _d;
|
|
1040
|
+
const root = (_a = this._scene) === null || _a === void 0 ? void 0 : _a.root;
|
|
1041
|
+
if (!root || typeof root.traverse !== 'function')
|
|
1042
|
+
return;
|
|
1043
|
+
root.traverse((c) => {
|
|
1044
|
+
if (!c || c === root)
|
|
1045
|
+
return;
|
|
1046
|
+
// null 대신 {} 로 리셋 — typeof null === 'object' 함정 회피 + destructuring 안전 +
|
|
1047
|
+
// statusColor 등 data?.X 형태 getter 가 자연스럽게 default 로 폴백
|
|
1048
|
+
try {
|
|
1049
|
+
if ('data' in c)
|
|
1050
|
+
c.data = {};
|
|
1051
|
+
}
|
|
1052
|
+
catch (_a) { }
|
|
1053
|
+
// 차량 등의 위치 캐시 초기화 — 없으면 이전 waypoint 경로로 스냅백되는 잔상 발생
|
|
1054
|
+
if ('lastWaypoint' in c)
|
|
1055
|
+
c.lastWaypoint = undefined;
|
|
1056
|
+
});
|
|
1057
|
+
// vehicle 의 waypoint 이동 애니메이션 전체 중단
|
|
1058
|
+
// (Animation.stepAnimation 의 suppress 체크만으로는 WaypointHandler 의 animatorMap 이
|
|
1059
|
+
// 완전히 비워지지 않을 수 있어 명시적 reset)
|
|
1060
|
+
if ((_b = root.waypointHandler) === null || _b === void 0 ? void 0 : _b.reset)
|
|
1061
|
+
root.waypointHandler.reset();
|
|
1062
|
+
// 모든 decorator 제거 — bouncing-arrow/border-highlighting 같은 SVG 애니메이션 decorator 는
|
|
1063
|
+
// decorator-registry 가 붙잡고 있어 data=null 만으로는 제거되지 않음. 명시적 reset 필요.
|
|
1064
|
+
try {
|
|
1065
|
+
root.trigger('deco-reset');
|
|
1066
|
+
}
|
|
1067
|
+
catch (_e) { }
|
|
1068
|
+
if ((_c = root.carrierManager) === null || _c === void 0 ? void 0 : _c.dispose)
|
|
1069
|
+
root.carrierManager.dispose();
|
|
1070
|
+
if ((_d = root.transitionEffect) === null || _d === void 0 ? void 0 : _d.dispose)
|
|
1071
|
+
root.transitionEffect.dispose();
|
|
1072
|
+
}
|
|
1073
|
+
_onPlaybackSeek(e) {
|
|
1074
|
+
var _a;
|
|
1075
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.seekTo(e.detail.toTime);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* 드래그 시작 신호 — 엔진의 재생 타이머 정지해서 스크러빙 동안
|
|
1079
|
+
* scene 이 엔진 시간으로 독립 갱신되지 않도록 함. _state 는 유지되므로
|
|
1080
|
+
* 드래그 종료 시 seekTo 가 원래 재생/일시정지 상태로 복원.
|
|
1081
|
+
*/
|
|
1082
|
+
_onPlaybackSeekStart() {
|
|
1083
|
+
var _a;
|
|
1084
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.pauseForSeek();
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* 드래그 중 디바운스된 seek preview. 해당 시간의 snapshot 만 scene 에 적용.
|
|
1088
|
+
* 보드 리셋 없음, 애니메이션 억제.
|
|
1089
|
+
*/
|
|
1090
|
+
_onPlaybackSeekPreview(e) {
|
|
1091
|
+
var _a;
|
|
1092
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.seekPreview(e.detail.toTime);
|
|
1093
|
+
}
|
|
1094
|
+
_onPlaybackSpeed(e) {
|
|
1095
|
+
var _a;
|
|
1096
|
+
(_a = this._playbackEngine) === null || _a === void 0 ? void 0 : _a.setSpeed(e.detail.speed);
|
|
1097
|
+
}
|
|
489
1098
|
onRunBoard() {
|
|
490
1099
|
this.dispatchEvent(new CustomEvent('run-board', { bubbles: true, composed: true, detail: this.board.id }));
|
|
491
1100
|
}
|
|
@@ -578,6 +1187,10 @@ let BoardViewer = class BoardViewer extends LitElement {
|
|
|
578
1187
|
this.popup = popup;
|
|
579
1188
|
}
|
|
580
1189
|
if (this.popup) {
|
|
1190
|
+
// playbackMode 를 component 보다 먼저 set — BoardComponentInfo 의 updated()
|
|
1191
|
+
// 가 component 변경을 보는 시점에 playbackMode 가 반드시 최신값이어야
|
|
1192
|
+
// detailInfo() 백엔드 호출 가드가 일관 동작.
|
|
1193
|
+
this.popup.playbackMode = this._playbackActive;
|
|
581
1194
|
this.popup.component = component;
|
|
582
1195
|
}
|
|
583
1196
|
}
|
|
@@ -611,8 +1224,8 @@ BoardViewer.styles = [
|
|
|
611
1224
|
outline: 0;
|
|
612
1225
|
}
|
|
613
1226
|
|
|
614
|
-
/* navigation buttons */
|
|
615
|
-
|
|
1227
|
+
/* navigation buttons (standalone only, not inside md-fab) */
|
|
1228
|
+
md-icon:not([slot]) {
|
|
616
1229
|
z-index: 10;
|
|
617
1230
|
position: absolute;
|
|
618
1231
|
top: 45%;
|
|
@@ -624,14 +1237,14 @@ BoardViewer.styles = [
|
|
|
624
1237
|
color: #fff;
|
|
625
1238
|
user-select: none;
|
|
626
1239
|
|
|
627
|
-
--
|
|
1240
|
+
--md-icon-size: 3em;
|
|
628
1241
|
}
|
|
629
1242
|
|
|
630
|
-
|
|
1243
|
+
md-icon[hidden] {
|
|
631
1244
|
display: none;
|
|
632
1245
|
}
|
|
633
1246
|
|
|
634
|
-
|
|
1247
|
+
md-icon:not([slot]):hover {
|
|
635
1248
|
background-color: rgba(0, 0, 0, 0.2);
|
|
636
1249
|
border-radius: 50%;
|
|
637
1250
|
}
|
|
@@ -644,10 +1257,85 @@ BoardViewer.styles = [
|
|
|
644
1257
|
right: 5px;
|
|
645
1258
|
}
|
|
646
1259
|
|
|
647
|
-
|
|
1260
|
+
[fab-group] {
|
|
648
1261
|
position: absolute;
|
|
649
1262
|
bottom: 15px;
|
|
650
1263
|
right: 16px;
|
|
1264
|
+
display: flex;
|
|
1265
|
+
flex-direction: column-reverse;
|
|
1266
|
+
align-items: center;
|
|
1267
|
+
gap: 12px;
|
|
1268
|
+
z-index: 1000;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
[fab-group] md-fab {
|
|
1272
|
+
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
[fab-group] md-fab.main-fab md-icon {
|
|
1276
|
+
transition: transform 0.3s ease;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
[fab-group] md-fab.main-fab[expanded] md-icon {
|
|
1280
|
+
transform: rotate(45deg);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
[fab-group] md-fab.sub-fab {
|
|
1284
|
+
--md-fab-container-width: 48px;
|
|
1285
|
+
--md-fab-container-height: 48px;
|
|
1286
|
+
/* fab 표면 색 = image 의 자체 원형 배경 (#F9EBEA) 과 일치.
|
|
1287
|
+
* 이렇게 하면 md-fab 의 내부 padding 영역도 같은 색이라
|
|
1288
|
+
* 시각적으로 사라짐 — 세 버튼 모두 visible 48px 컨테이너 통일. */
|
|
1289
|
+
--md-fab-container-color: #F9EBEA;
|
|
1290
|
+
--md-fab-icon-color: #F87171;
|
|
1291
|
+
--md-icon-size: 36px;
|
|
1292
|
+
transform: scale(0);
|
|
1293
|
+
opacity: 0;
|
|
1294
|
+
pointer-events: none;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/* visible 영역은 컨테이너(48px) 그대로 — fullscreen 버튼과 동일.
|
|
1298
|
+
* img 자체는 48px 박스이고 transform scale 로 내부 그래픽만 확대.
|
|
1299
|
+
* wrap 의 overflow:hidden + border-radius:50% 가 fab 원형 밖으로
|
|
1300
|
+
* 넘치는 영역을 잘라내어 외형 크기는 변하지 않음. */
|
|
1301
|
+
[fab-group] md-fab.sub-fab .fab-icon-wrap {
|
|
1302
|
+
width: 48px;
|
|
1303
|
+
height: 48px;
|
|
1304
|
+
display: block;
|
|
1305
|
+
overflow: hidden;
|
|
1306
|
+
border-radius: 50%;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
[fab-group] md-fab.sub-fab .fab-icon-wrap img.fab-icon {
|
|
1310
|
+
width: 48px;
|
|
1311
|
+
height: 48px;
|
|
1312
|
+
display: block;
|
|
1313
|
+
transform: scale(1.15);
|
|
1314
|
+
transform-origin: center;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/* legacy inline svg 경로 — 동일하게 box 48 유지 + scale 로 그래픽만 확대. */
|
|
1318
|
+
[fab-group] md-fab.sub-fab svg[slot="icon"] {
|
|
1319
|
+
width: 48px;
|
|
1320
|
+
height: 48px;
|
|
1321
|
+
display: block;
|
|
1322
|
+
transform: scale(1.15);
|
|
1323
|
+
transform-origin: center;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
[fab-group][expanded] md-fab.sub-fab {
|
|
1327
|
+
transform: scale(1);
|
|
1328
|
+
opacity: 1;
|
|
1329
|
+
pointer-events: auto;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
[fab-group] md-fab.sub-fab[disabled] {
|
|
1333
|
+
opacity: 0.4;
|
|
1334
|
+
pointer-events: none;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
ox-playback-controls {
|
|
1338
|
+
transition: opacity 0.3s ease;
|
|
651
1339
|
}
|
|
652
1340
|
|
|
653
1341
|
[hidden] {
|
|
@@ -666,18 +1354,22 @@ BoardViewer.styles = [
|
|
|
666
1354
|
z-index: 1000;
|
|
667
1355
|
}
|
|
668
1356
|
|
|
669
|
-
|
|
1357
|
+
md-fab {
|
|
670
1358
|
z-index: 1000;
|
|
1359
|
+
--md-icon-size: 24px;
|
|
671
1360
|
}
|
|
672
1361
|
|
|
673
1362
|
@media print {
|
|
674
|
-
|
|
675
|
-
|
|
1363
|
+
md-fab,
|
|
1364
|
+
md-icon {
|
|
676
1365
|
display: none;
|
|
677
1366
|
}
|
|
678
1367
|
}
|
|
679
1368
|
`
|
|
680
1369
|
];
|
|
1370
|
+
/** SearchOverlay 의 query-submit 시 사용할 추적 색깔. things-scene 의 Scene.tracker
|
|
1371
|
+
* 가 폴링/데코/3D outline 색 동기화 일괄 관리. */
|
|
1372
|
+
BoardViewer.TRACKING_COLOR = '#00aaff7f';
|
|
681
1373
|
__decorate([
|
|
682
1374
|
property({ type: String })
|
|
683
1375
|
], BoardViewer.prototype, "baseUrl", void 0);
|
|
@@ -693,6 +1385,9 @@ __decorate([
|
|
|
693
1385
|
__decorate([
|
|
694
1386
|
property({ type: Object })
|
|
695
1387
|
], BoardViewer.prototype, "values", void 0);
|
|
1388
|
+
__decorate([
|
|
1389
|
+
property({ type: Object })
|
|
1390
|
+
], BoardViewer.prototype, "loadTracker", void 0);
|
|
696
1391
|
__decorate([
|
|
697
1392
|
property({ type: Boolean, reflect: true, attribute: 'hide-fullscreen' })
|
|
698
1393
|
], BoardViewer.prototype, "hideFullscreen", void 0);
|
|
@@ -701,19 +1396,10 @@ __decorate([
|
|
|
701
1396
|
], BoardViewer.prototype, "hideNavigation", void 0);
|
|
702
1397
|
__decorate([
|
|
703
1398
|
state()
|
|
704
|
-
], BoardViewer.prototype, "
|
|
705
|
-
__decorate([
|
|
706
|
-
state()
|
|
707
|
-
], BoardViewer.prototype, "_forward", void 0);
|
|
1399
|
+
], BoardViewer.prototype, "_fabExpanded", void 0);
|
|
708
1400
|
__decorate([
|
|
709
1401
|
state()
|
|
710
|
-
], BoardViewer.prototype, "
|
|
711
|
-
__decorate([
|
|
712
|
-
state()
|
|
713
|
-
], BoardViewer.prototype, "_oldtarget", void 0);
|
|
714
|
-
__decorate([
|
|
715
|
-
state()
|
|
716
|
-
], BoardViewer.prototype, "_fade_animations", void 0);
|
|
1402
|
+
], BoardViewer.prototype, "_playbackActive", void 0);
|
|
717
1403
|
__decorate([
|
|
718
1404
|
query('#target')
|
|
719
1405
|
], BoardViewer.prototype, "_target", void 0);
|
|
@@ -724,9 +1410,12 @@ __decorate([
|
|
|
724
1410
|
query('#next')
|
|
725
1411
|
], BoardViewer.prototype, "_next", void 0);
|
|
726
1412
|
__decorate([
|
|
727
|
-
query('
|
|
728
|
-
], BoardViewer.prototype, "
|
|
729
|
-
|
|
1413
|
+
query('[fab-group]')
|
|
1414
|
+
], BoardViewer.prototype, "_fabGroup", void 0);
|
|
1415
|
+
__decorate([
|
|
1416
|
+
property({ attribute: false })
|
|
1417
|
+
], BoardViewer.prototype, "_fetchPlaybackData", void 0);
|
|
1418
|
+
BoardViewer = BoardViewer_1 = __decorate([
|
|
730
1419
|
customElement('ox-board-viewer')
|
|
731
1420
|
], BoardViewer);
|
|
732
1421
|
export { BoardViewer };
|