@operato/board 10.0.0-beta.3 → 10.0.0-beta.30

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 (47) hide show
  1. package/CHANGELOG.md +251 -0
  2. package/dist/src/component/container.js +1 -3
  3. package/dist/src/component/container.js.map +1 -1
  4. package/dist/src/component/etc.js +2 -10
  5. package/dist/src/component/etc.js.map +1 -1
  6. package/dist/src/component/line.js +4 -28
  7. package/dist/src/component/line.js.map +1 -1
  8. package/dist/src/component/shape.js +5 -29
  9. package/dist/src/component/shape.js.map +1 -1
  10. package/dist/src/component/text-and-media.js +2 -25
  11. package/dist/src/component/text-and-media.js.map +1 -1
  12. package/dist/src/data-storage/board-model-cache.d.ts +30 -0
  13. package/dist/src/data-storage/board-model-cache.js +93 -0
  14. package/dist/src/data-storage/board-model-cache.js.map +1 -0
  15. package/dist/src/graphql/playback-buffer.d.ts +79 -0
  16. package/dist/src/graphql/playback-buffer.js +139 -0
  17. package/dist/src/graphql/playback-buffer.js.map +1 -0
  18. package/dist/src/graphql/playback-buffer.test.d.ts +1 -0
  19. package/dist/src/graphql/playback-buffer.test.js +261 -0
  20. package/dist/src/graphql/playback-buffer.test.js.map +1 -0
  21. package/dist/src/graphql/playback-subscription.d.ts +89 -0
  22. package/dist/src/graphql/playback-subscription.js +258 -0
  23. package/dist/src/graphql/playback-subscription.js.map +1 -0
  24. package/dist/src/index.d.ts +2 -0
  25. package/dist/src/index.js +1 -0
  26. package/dist/src/index.js.map +1 -1
  27. package/dist/src/modeller/edit-toolbar-style.js +38 -1
  28. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  29. package/dist/src/modeller/edit-toolbar.d.ts +8 -16
  30. package/dist/src/modeller/edit-toolbar.js +204 -199
  31. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  32. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  33. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -11
  34. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  35. package/dist/src/ox-board-modeller.d.ts +8 -1
  36. package/dist/src/ox-board-modeller.js +125 -6
  37. package/dist/src/ox-board-modeller.js.map +1 -1
  38. package/dist/src/ox-board-viewer.d.ts +50 -1
  39. package/dist/src/ox-board-viewer.js +270 -27
  40. package/dist/src/ox-board-viewer.js.map +1 -1
  41. package/dist/src/ox-playback-controls.d.ts +56 -0
  42. package/dist/src/ox-playback-controls.js +515 -0
  43. package/dist/src/ox-playback-controls.js.map +1 -0
  44. package/dist/src/selector/ox-board-selector.js +11 -1
  45. package/dist/src/selector/ox-board-selector.js.map +1 -1
  46. package/dist/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +13 -12
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playback-buffer.test.js","sourceRoot":"","sources":["../../../src/graphql/playback-buffer.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { PlaybackBuffer, PlaybackSnapshot, CHUNK_DURATION, PREFETCH_THRESHOLD } from './playback-buffer'\n\n// --- 테스트 헬퍼 ---\n\nconst BASE_TIME = new Date('2026-04-12T10:00:00Z').getTime()\nconst ONE_HOUR = 3600000\nconst ONE_MIN = 60000\n\nfunction makeSnapshots(fromMs: number, toMs: number, intervalMs: number = 1000): PlaybackSnapshot[] {\n const snaps: PlaybackSnapshot[] = []\n for (let t = fromMs; t < toMs; t += intervalMs) {\n snaps.push({\n timestamp: t,\n data: { vehicle: { id: 'AGV01', position: t - fromMs } }\n })\n }\n return snaps\n}\n\nfunction createMockFetcher(allSnapshots?: PlaybackSnapshot[]) {\n const calls: { from: number; to: number }[] = []\n\n const fetcher = async (from: Date, to: Date) => {\n calls.push({ from: from.getTime(), to: to.getTime() })\n\n if (allSnapshots) {\n return allSnapshots.filter(s => s.timestamp >= from.getTime() && s.timestamp < to.getTime())\n }\n\n // 기본: 1초 간격 스냅샷 생성\n return makeSnapshots(from.getTime(), to.getTime())\n }\n\n return { fetcher, calls }\n}\n\n// --- PlaybackBuffer 단위 테스트 ---\n\ndescribe('PlaybackBuffer', () => {\n const totalRange = {\n from: new Date(BASE_TIME),\n to: new Date(BASE_TIME + ONE_HOUR)\n }\n\n describe('loadInitial', () => {\n test('10분 청크를 로드한다', async () => {\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(calls).toHaveLength(1)\n expect(calls[0].from).toBe(BASE_TIME)\n expect(calls[0].to).toBe(BASE_TIME + CHUNK_DURATION)\n })\n\n test('기존 버퍼를 폐기하고 새로 로드한다', async () => {\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n await buffer.loadInitial(BASE_TIME + 30 * ONE_MIN)\n\n expect(calls).toHaveLength(2)\n expect(buffer.getBufferedRanges()).toHaveLength(1)\n expect(buffer.getBufferedRanges()[0].from).toBe(BASE_TIME + 30 * ONE_MIN)\n })\n })\n\n describe('getSnapshotAt', () => {\n test('playHead 이하인 가장 가까운 스냅샷을 반환한다', async () => {\n const snaps = makeSnapshots(BASE_TIME, BASE_TIME + CHUNK_DURATION, 5000)\n const { fetcher } = createMockFetcher(snaps)\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n const snap = buffer.getSnapshotAt(BASE_TIME + 7000)\n expect(snap).not.toBeNull()\n expect(snap!.timestamp).toBe(BASE_TIME + 5000) // 5초 스냅샷 (7초 이하 중 가장 가까운)\n })\n\n test('버퍼에 데이터가 없으면 null', async () => {\n const { fetcher } = createMockFetcher([])\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(buffer.getSnapshotAt(BASE_TIME + 5000)).toBeNull()\n })\n\n test('playHead가 첫 스냅샷보다 이전이면 null', async () => {\n const snaps = makeSnapshots(BASE_TIME + 10000, BASE_TIME + CHUNK_DURATION, 5000)\n const { fetcher } = createMockFetcher(snaps)\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(buffer.getSnapshotAt(BASE_TIME + 5000)).toBeNull()\n })\n })\n\n describe('getNextSnapshotTime', () => {\n test('현재 이후 가장 가까운 스냅샷 시간을 반환한다', async () => {\n const snaps = makeSnapshots(BASE_TIME, BASE_TIME + CHUNK_DURATION, 5000)\n const { fetcher } = createMockFetcher(snaps)\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(buffer.getNextSnapshotTime(BASE_TIME + 3000)).toBe(BASE_TIME + 5000)\n })\n\n test('마지막 스냅샷 이후면 null', async () => {\n const snaps = makeSnapshots(BASE_TIME, BASE_TIME + 10000, 5000)\n const { fetcher } = createMockFetcher(snaps)\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(buffer.getNextSnapshotTime(BASE_TIME + 10000)).toBeNull()\n })\n })\n\n describe('checkPrefetch', () => {\n test('남은 버퍼가 1분 이하이면 다음 청크를 prefetch한다', async () => {\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n expect(calls).toHaveLength(1)\n\n // playHead를 9분 위치로 (남은 1분)\n await buffer.checkPrefetch(BASE_TIME + 9 * ONE_MIN)\n\n expect(calls).toHaveLength(2)\n expect(calls[1].from).toBe(BASE_TIME + CHUNK_DURATION) // 10분 후부터\n })\n\n test('남은 버퍼가 1분 초과이면 prefetch하지 않는다', async () => {\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n // playHead를 5분 위치로 (남은 5분)\n await buffer.checkPrefetch(BASE_TIME + 5 * ONE_MIN)\n\n expect(calls).toHaveLength(1) // 추가 fetch 없음\n })\n\n test('전체 범위 끝에 도달하면 prefetch하지 않는다', async () => {\n const shortRange = {\n from: new Date(BASE_TIME),\n to: new Date(BASE_TIME + CHUNK_DURATION) // 전체 10분\n }\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, shortRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n // 9분 위치 — 하지만 전체 범위가 10분이므로 더 가져올 게 없음\n await buffer.checkPrefetch(BASE_TIME + 9 * ONE_MIN)\n\n expect(calls).toHaveLength(1)\n })\n\n test('중복 prefetch를 방지한다', async () => {\n let resolveSecond: () => void\n let fetchCount = 0\n\n const slowFetcher = async (from: Date, to: Date) => {\n fetchCount++\n if (fetchCount === 2) {\n // 두 번째 fetch는 느리게\n await new Promise<void>(r => { resolveSecond = r })\n }\n return makeSnapshots(from.getTime(), to.getTime())\n }\n\n const buffer = new PlaybackBuffer(slowFetcher, totalRange)\n await buffer.loadInitial(BASE_TIME)\n\n // 동시에 두 번 checkPrefetch\n const p1 = buffer.checkPrefetch(BASE_TIME + 9 * ONE_MIN)\n const p2 = buffer.checkPrefetch(BASE_TIME + 9 * ONE_MIN)\n\n resolveSecond!()\n await Promise.all([p1, p2])\n\n // 초기 1 + prefetch 1 = 2 (중복 방지)\n expect(fetchCount).toBe(2)\n })\n })\n\n describe('seek', () => {\n test('기존 버퍼를 폐기하고 새 위치에서 로드한다', async () => {\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n expect(buffer.getBufferedRanges()).toHaveLength(1)\n\n await buffer.seek(BASE_TIME + 30 * ONE_MIN)\n\n expect(buffer.getBufferedRanges()).toHaveLength(1) // 이전 버퍼 폐기, 새 청크 1개\n expect(buffer.getBufferedRanges()[0].from).toBe(BASE_TIME + 30 * ONE_MIN)\n })\n\n test('seek 후 이전 데이터에 접근 불가', async () => {\n const snaps = makeSnapshots(BASE_TIME, BASE_TIME + ONE_HOUR, 5000)\n const { fetcher } = createMockFetcher(snaps)\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n expect(buffer.getSnapshotAt(BASE_TIME + 5000)).not.toBeNull()\n\n await buffer.seek(BASE_TIME + 30 * ONE_MIN)\n expect(buffer.getSnapshotAt(BASE_TIME + 5000)).toBeNull() // 이전 데이터 없음\n })\n })\n\n describe('getBufferedRanges', () => {\n test('로드된 청크들의 시간 범위를 반환한다', async () => {\n const { fetcher } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n await buffer.checkPrefetch(BASE_TIME + 9 * ONE_MIN) // prefetch 트리거\n\n const ranges = buffer.getBufferedRanges()\n expect(ranges).toHaveLength(2)\n expect(ranges[0].from).toBe(BASE_TIME)\n expect(ranges[0].to).toBe(BASE_TIME + CHUNK_DURATION)\n expect(ranges[1].from).toBe(BASE_TIME + CHUNK_DURATION)\n expect(ranges[1].to).toBe(BASE_TIME + 2 * CHUNK_DURATION)\n })\n })\n\n describe('hasDataAt', () => {\n test('버퍼 내 시점은 true', async () => {\n const { fetcher } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(buffer.hasDataAt(BASE_TIME + 5 * ONE_MIN)).toBe(true)\n })\n\n test('버퍼 외 시점은 false', async () => {\n const { fetcher } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(buffer.hasDataAt(BASE_TIME + 15 * ONE_MIN)).toBe(false)\n })\n })\n\n describe('clear', () => {\n test('모든 버퍼를 폐기한다', async () => {\n const { fetcher } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n expect(buffer.getBufferedRanges()).toHaveLength(1)\n\n buffer.clear()\n expect(buffer.getBufferedRanges()).toHaveLength(0)\n })\n })\n\n describe('연속 재생 시나리오', () => {\n test('10분 → prefetch → 20분 연속 재생', async () => {\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n // 1. 초기 로드 (0~10분)\n await buffer.loadInitial(BASE_TIME)\n expect(calls).toHaveLength(1)\n\n // 2. 5분 위치 — prefetch 안 함\n await buffer.checkPrefetch(BASE_TIME + 5 * ONE_MIN)\n expect(calls).toHaveLength(1)\n\n // 3. 9분 위치 — prefetch 트리거 (10~20분)\n await buffer.checkPrefetch(BASE_TIME + 9 * ONE_MIN)\n expect(calls).toHaveLength(2)\n\n // 4. 15분 위치 — 두 번째 청크에서 데이터 조회 가능\n expect(buffer.hasDataAt(BASE_TIME + 15 * ONE_MIN)).toBe(true)\n\n // 5. 19분 위치 — 다시 prefetch (20~30분)\n await buffer.checkPrefetch(BASE_TIME + 19 * ONE_MIN)\n expect(calls).toHaveLength(3)\n })\n\n test('seek 후 prefetch 정상 동작', async () => {\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, totalRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n // seek to 40분\n await buffer.seek(BASE_TIME + 40 * ONE_MIN)\n expect(calls).toHaveLength(2) // initial + seek\n\n // 49분 위치 — prefetch (50~60분)\n await buffer.checkPrefetch(BASE_TIME + 49 * ONE_MIN)\n expect(calls).toHaveLength(3)\n expect(calls[2].from).toBe(BASE_TIME + 50 * ONE_MIN)\n })\n })\n\n describe('경계 조건', () => {\n test('전체 범위가 10분 미만', async () => {\n const shortRange = {\n from: new Date(BASE_TIME),\n to: new Date(BASE_TIME + 5 * ONE_MIN)\n }\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, shortRange)\n\n await buffer.loadInitial(BASE_TIME)\n\n expect(calls).toHaveLength(1)\n expect(calls[0].to).toBe(BASE_TIME + 5 * ONE_MIN) // 5분까지만\n })\n\n test('끝 경계에서 청크 크기 조정', async () => {\n const range55min = {\n from: new Date(BASE_TIME),\n to: new Date(BASE_TIME + 55 * ONE_MIN)\n }\n const { fetcher, calls } = createMockFetcher()\n const buffer = new PlaybackBuffer(fetcher, range55min)\n\n // 50분 위치에서 시작\n await buffer.loadInitial(BASE_TIME + 50 * ONE_MIN)\n\n expect(calls[0].to).toBe(BASE_TIME + 55 * ONE_MIN) // 55분까지만 (10분이 아닌 5분)\n })\n })\n})\n"]}
@@ -0,0 +1,89 @@
1
+ import { Component, DataSubscriptionProvider } from '@hatiolab/things-scene';
2
+ export type PlaybackState = 'idle' | 'playing' | 'paused' | 'stopped';
3
+ export interface PlaybackStatus {
4
+ state: PlaybackState;
5
+ currentTime: string;
6
+ speed: number;
7
+ bufferedRanges?: {
8
+ from: number;
9
+ to: number;
10
+ }[];
11
+ }
12
+ export interface PlaybackConfig {
13
+ /** 플레이백 가능한 시간 범위 */
14
+ timeRange?: {
15
+ from: Date;
16
+ to: Date;
17
+ };
18
+ }
19
+ /**
20
+ * PlaybackProvider — YouTube 스트리밍 방식의 청크 기반 플레이백
21
+ *
22
+ * - 10분 단위로 데이터를 fetch하여 버퍼에 적재
23
+ * - 남은 데이터 1분 이하 시 다음 10분 prefetch
24
+ * - seek 시 기존 버퍼 전체 폐기 후 fresh fetch
25
+ * - requestAnimationFrame 기반 재생 루프
26
+ */
27
+ export declare class PlaybackProvider implements DataSubscriptionProvider {
28
+ private _components;
29
+ private _buffer;
30
+ private _state;
31
+ private _speed;
32
+ private _playHead;
33
+ private _lastFrameTime;
34
+ private _rafId;
35
+ private _lastDistributedSnapshot;
36
+ private _onStatusChange?;
37
+ private _totalRange;
38
+ constructor(onStatusChange?: (status: PlaybackStatus) => void);
39
+ get state(): PlaybackState;
40
+ get speed(): number;
41
+ get currentTime(): string;
42
+ /**
43
+ * DataSubscriptionProvider.subscribe 구현
44
+ */
45
+ subscribe(tag: string, component: Component): Promise<{
46
+ unsubscribe: () => void;
47
+ }>;
48
+ /**
49
+ * 플레이백 시작
50
+ */
51
+ start(fromTime: Date, speed?: number, totalRange?: {
52
+ from: Date;
53
+ to: Date;
54
+ }): Promise<void>;
55
+ /**
56
+ * 일시정지
57
+ */
58
+ pause(): void;
59
+ /**
60
+ * 재개
61
+ */
62
+ resume(): void;
63
+ /**
64
+ * seek — 기존 버퍼 폐기 + 새 위치에서 fresh fetch
65
+ */
66
+ seek(toTime: Date): Promise<void>;
67
+ /**
68
+ * 배속 변경
69
+ */
70
+ setSpeed(speed: number): void;
71
+ /**
72
+ * 중지
73
+ */
74
+ stop(): void;
75
+ /**
76
+ * DataSubscriptionProvider.dispose 구현
77
+ */
78
+ dispose(): void;
79
+ private _startPlayLoop;
80
+ private _stopPlayLoop;
81
+ private _playLoop;
82
+ private _distributeSnapshot;
83
+ private _createFetcher;
84
+ /**
85
+ * 백엔드 응답을 PlaybackSnapshot[] 형식으로 변환
86
+ */
87
+ private _parsePlaybackResponse;
88
+ private _notifyStatus;
89
+ }
@@ -0,0 +1,258 @@
1
+ import gql from 'graphql-tag';
2
+ import { PlaybackBuffer } from './playback-buffer.js';
3
+ /**
4
+ * PlaybackProvider — YouTube 스트리밍 방식의 청크 기반 플레이백
5
+ *
6
+ * - 10분 단위로 데이터를 fetch하여 버퍼에 적재
7
+ * - 남은 데이터 1분 이하 시 다음 10분 prefetch
8
+ * - seek 시 기존 버퍼 전체 폐기 후 fresh fetch
9
+ * - requestAnimationFrame 기반 재생 루프
10
+ */
11
+ export class PlaybackProvider {
12
+ constructor(onStatusChange) {
13
+ this._components = new Map();
14
+ this._buffer = null;
15
+ this._state = 'idle';
16
+ this._speed = 1;
17
+ this._playHead = 0;
18
+ this._lastFrameTime = 0;
19
+ this._rafId = 0;
20
+ this._lastDistributedSnapshot = null;
21
+ this._totalRange = null;
22
+ this._playLoop = (now) => {
23
+ if (this._state !== 'playing' || !this._buffer)
24
+ return;
25
+ const delta = (now - this._lastFrameTime) * this._speed;
26
+ this._lastFrameTime = now;
27
+ this._playHead += delta;
28
+ // 전체 범위 끝 도달 체크
29
+ if (this._totalRange && this._playHead >= this._totalRange.to.getTime()) {
30
+ this._playHead = this._totalRange.to.getTime();
31
+ this._state = 'stopped';
32
+ this._notifyStatus();
33
+ return;
34
+ }
35
+ // 현재 스냅샷 배포
36
+ const snap = this._buffer.getSnapshotAt(this._playHead);
37
+ if (snap && snap !== this._lastDistributedSnapshot) {
38
+ this._distributeSnapshot(snap.data);
39
+ this._lastDistributedSnapshot = snap;
40
+ }
41
+ // prefetch 체크 (비동기, 루프 블로킹 안 함)
42
+ this._buffer.checkPrefetch(this._playHead);
43
+ this._notifyStatus();
44
+ // 다음 프레임
45
+ this._rafId = requestAnimationFrame(this._playLoop);
46
+ };
47
+ this._onStatusChange = onStatusChange;
48
+ }
49
+ get state() { return this._state; }
50
+ get speed() { return this._speed; }
51
+ get currentTime() { return this._playHead ? new Date(this._playHead).toISOString() : ''; }
52
+ /**
53
+ * DataSubscriptionProvider.subscribe 구현
54
+ */
55
+ async subscribe(tag, component) {
56
+ if (!this._components.has(tag)) {
57
+ this._components.set(tag, new Set());
58
+ }
59
+ this._components.get(tag).add(component);
60
+ return {
61
+ unsubscribe: () => {
62
+ const components = this._components.get(tag);
63
+ if (components) {
64
+ components.delete(component);
65
+ if (components.size === 0) {
66
+ this._components.delete(tag);
67
+ }
68
+ }
69
+ }
70
+ };
71
+ }
72
+ /**
73
+ * 플레이백 시작
74
+ */
75
+ async start(fromTime, speed = 1, totalRange) {
76
+ this.stop();
77
+ this._speed = speed;
78
+ this._totalRange = totalRange || { from: fromTime, to: new Date(fromTime.getTime() + 3600000) };
79
+ // 버퍼 생성 + 초기 로드
80
+ this._buffer = new PlaybackBuffer(this._createFetcher(), this._totalRange);
81
+ await this._buffer.loadInitial(fromTime.getTime());
82
+ this._playHead = fromTime.getTime();
83
+ this._state = 'playing';
84
+ this._lastFrameTime = performance.now();
85
+ this._startPlayLoop();
86
+ this._notifyStatus();
87
+ }
88
+ /**
89
+ * 일시정지
90
+ */
91
+ pause() {
92
+ if (this._state !== 'playing')
93
+ return;
94
+ this._state = 'paused';
95
+ this._stopPlayLoop();
96
+ this._notifyStatus();
97
+ }
98
+ /**
99
+ * 재개
100
+ */
101
+ resume() {
102
+ if (this._state !== 'paused')
103
+ return;
104
+ this._state = 'playing';
105
+ this._lastFrameTime = performance.now();
106
+ this._startPlayLoop();
107
+ this._notifyStatus();
108
+ }
109
+ /**
110
+ * seek — 기존 버퍼 폐기 + 새 위치에서 fresh fetch
111
+ */
112
+ async seek(toTime) {
113
+ if (!this._buffer)
114
+ return;
115
+ const wasPlaying = this._state === 'playing';
116
+ this._stopPlayLoop();
117
+ // 버퍼 전체 폐기 + 새 위치 로드
118
+ await this._buffer.seek(toTime.getTime());
119
+ this._playHead = toTime.getTime();
120
+ this._lastDistributedSnapshot = null;
121
+ // 새 위치의 첫 스냅샷 즉시 배포
122
+ const snap = this._buffer.getSnapshotAt(this._playHead);
123
+ if (snap) {
124
+ this._distributeSnapshot(snap.data);
125
+ this._lastDistributedSnapshot = snap;
126
+ }
127
+ if (wasPlaying) {
128
+ this._state = 'playing';
129
+ this._lastFrameTime = performance.now();
130
+ this._startPlayLoop();
131
+ }
132
+ this._notifyStatus();
133
+ }
134
+ /**
135
+ * 배속 변경
136
+ */
137
+ setSpeed(speed) {
138
+ this._speed = speed;
139
+ this._notifyStatus();
140
+ }
141
+ /**
142
+ * 중지
143
+ */
144
+ stop() {
145
+ var _a;
146
+ this._stopPlayLoop();
147
+ (_a = this._buffer) === null || _a === void 0 ? void 0 : _a.clear();
148
+ this._buffer = null;
149
+ this._state = 'stopped';
150
+ this._playHead = 0;
151
+ this._lastDistributedSnapshot = null;
152
+ this._notifyStatus();
153
+ }
154
+ /**
155
+ * DataSubscriptionProvider.dispose 구현
156
+ */
157
+ dispose() {
158
+ this.stop();
159
+ this._state = 'idle';
160
+ this._components.clear();
161
+ }
162
+ // --- 재생 루프 ---
163
+ _startPlayLoop() {
164
+ this._rafId = requestAnimationFrame(this._playLoop);
165
+ }
166
+ _stopPlayLoop() {
167
+ if (this._rafId) {
168
+ cancelAnimationFrame(this._rafId);
169
+ this._rafId = 0;
170
+ }
171
+ }
172
+ // --- 데이터 배포 ---
173
+ _distributeSnapshot(snapshotData) {
174
+ if (!snapshotData || typeof snapshotData !== 'object')
175
+ return;
176
+ for (const [tag, value] of Object.entries(snapshotData)) {
177
+ const components = this._components.get(tag);
178
+ if (components) {
179
+ for (const component of components) {
180
+ component.data = value;
181
+ }
182
+ }
183
+ }
184
+ }
185
+ // --- 데이터 fetcher ---
186
+ _createFetcher() {
187
+ return async (fromTime, toTime) => {
188
+ var _a;
189
+ try {
190
+ const { client } = await import('@operato/graphql');
191
+ const response = await client.query({
192
+ query: gql `
193
+ query PlaybackChunk($startTime: String!, $endTime: String!) {
194
+ playback(startTime: $startTime, endTime: $endTime) {
195
+ status
196
+ playback {
197
+ scenarios {
198
+ scenarioName
199
+ snapshots {
200
+ type
201
+ time
202
+ data
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ `,
209
+ variables: {
210
+ startTime: fromTime.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ''),
211
+ endTime: toTime.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '')
212
+ },
213
+ fetchPolicy: 'no-cache'
214
+ });
215
+ return this._parsePlaybackResponse((_a = response.data) === null || _a === void 0 ? void 0 : _a.playback);
216
+ }
217
+ catch (e) {
218
+ console.error('[PlaybackProvider] chunk fetch error:', e);
219
+ return [];
220
+ }
221
+ };
222
+ }
223
+ /**
224
+ * 백엔드 응답을 PlaybackSnapshot[] 형식으로 변환
225
+ */
226
+ _parsePlaybackResponse(response) {
227
+ var _a;
228
+ if (!(response === null || response === void 0 ? void 0 : response.status) || !((_a = response === null || response === void 0 ? void 0 : response.playback) === null || _a === void 0 ? void 0 : _a.scenarios))
229
+ return [];
230
+ const snapshotMap = new Map();
231
+ for (const scenario of response.playback.scenarios) {
232
+ for (const record of scenario.snapshots) {
233
+ const time = new Date(record.time.replace(' ', 'T')).getTime();
234
+ const data = typeof record.data === 'string' ? JSON.parse(record.data) : record.data;
235
+ if (!snapshotMap.has(time)) {
236
+ snapshotMap.set(time, {});
237
+ }
238
+ const merged = snapshotMap.get(time);
239
+ // scenarioName을 tag로 사용
240
+ merged[scenario.scenarioName] = data;
241
+ }
242
+ }
243
+ return Array.from(snapshotMap.entries())
244
+ .map(([timestamp, data]) => ({ timestamp, data }))
245
+ .sort((a, b) => a.timestamp - b.timestamp);
246
+ }
247
+ // --- 상태 통보 ---
248
+ _notifyStatus() {
249
+ var _a, _b;
250
+ (_a = this._onStatusChange) === null || _a === void 0 ? void 0 : _a.call(this, {
251
+ state: this._state,
252
+ currentTime: this._playHead ? new Date(this._playHead).toISOString() : '',
253
+ speed: this._speed,
254
+ bufferedRanges: (_b = this._buffer) === null || _b === void 0 ? void 0 : _b.getBufferedRanges()
255
+ });
256
+ }
257
+ }
258
+ //# sourceMappingURL=playback-subscription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playback-subscription.js","sourceRoot":"","sources":["../../../src/graphql/playback-subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, DataSubscriptionProvider } from '@hatiolab/things-scene'\nimport gql from 'graphql-tag'\nimport { PlaybackBuffer, type PlaybackSnapshot, type ChunkFetcher } from './playback-buffer.js'\n\nexport type PlaybackState = 'idle' | 'playing' | 'paused' | 'stopped'\n\nexport interface PlaybackStatus {\n state: PlaybackState\n currentTime: string\n speed: number\n bufferedRanges?: { from: number; to: number }[]\n}\n\nexport interface PlaybackConfig {\n /** 플레이백 가능한 시간 범위 */\n timeRange?: { from: Date; to: Date }\n}\n\n/**\n * PlaybackProvider — YouTube 스트리밍 방식의 청크 기반 플레이백\n *\n * - 10분 단위로 데이터를 fetch하여 버퍼에 적재\n * - 남은 데이터 1분 이하 시 다음 10분 prefetch\n * - seek 시 기존 버퍼 전체 폐기 후 fresh fetch\n * - requestAnimationFrame 기반 재생 루프\n */\nexport class PlaybackProvider implements DataSubscriptionProvider {\n private _components: Map<string, Set<Component>> = new Map()\n private _buffer: PlaybackBuffer | null = null\n private _state: PlaybackState = 'idle'\n private _speed: number = 1\n private _playHead: number = 0\n private _lastFrameTime: number = 0\n private _rafId: number = 0\n private _lastDistributedSnapshot: PlaybackSnapshot | null = null\n private _onStatusChange?: (status: PlaybackStatus) => void\n private _totalRange: { from: Date; to: Date } | null = null\n\n constructor(onStatusChange?: (status: PlaybackStatus) => void) {\n this._onStatusChange = onStatusChange\n }\n\n get state() { return this._state }\n get speed() { return this._speed }\n get currentTime() { return this._playHead ? new Date(this._playHead).toISOString() : '' }\n\n /**\n * DataSubscriptionProvider.subscribe 구현\n */\n async subscribe(tag: string, component: Component) {\n if (!this._components.has(tag)) {\n this._components.set(tag, new Set())\n }\n this._components.get(tag)!.add(component)\n\n return {\n unsubscribe: () => {\n const components = this._components.get(tag)\n if (components) {\n components.delete(component)\n if (components.size === 0) {\n this._components.delete(tag)\n }\n }\n }\n }\n }\n\n /**\n * 플레이백 시작\n */\n async start(fromTime: Date, speed: number = 1, totalRange?: { from: Date; to: Date }) {\n this.stop()\n\n this._speed = speed\n this._totalRange = totalRange || { from: fromTime, to: new Date(fromTime.getTime() + 3600000) }\n\n // 버퍼 생성 + 초기 로드\n this._buffer = new PlaybackBuffer(this._createFetcher(), this._totalRange)\n await this._buffer.loadInitial(fromTime.getTime())\n\n this._playHead = fromTime.getTime()\n this._state = 'playing'\n this._lastFrameTime = performance.now()\n\n this._startPlayLoop()\n this._notifyStatus()\n }\n\n /**\n * 일시정지\n */\n pause() {\n if (this._state !== 'playing') return\n this._state = 'paused'\n this._stopPlayLoop()\n this._notifyStatus()\n }\n\n /**\n * 재개\n */\n resume() {\n if (this._state !== 'paused') return\n this._state = 'playing'\n this._lastFrameTime = performance.now()\n this._startPlayLoop()\n this._notifyStatus()\n }\n\n /**\n * seek — 기존 버퍼 폐기 + 새 위치에서 fresh fetch\n */\n async seek(toTime: Date) {\n if (!this._buffer) return\n\n const wasPlaying = this._state === 'playing'\n this._stopPlayLoop()\n\n // 버퍼 전체 폐기 + 새 위치 로드\n await this._buffer.seek(toTime.getTime())\n this._playHead = toTime.getTime()\n this._lastDistributedSnapshot = null\n\n // 새 위치의 첫 스냅샷 즉시 배포\n const snap = this._buffer.getSnapshotAt(this._playHead)\n if (snap) {\n this._distributeSnapshot(snap.data)\n this._lastDistributedSnapshot = snap\n }\n\n if (wasPlaying) {\n this._state = 'playing'\n this._lastFrameTime = performance.now()\n this._startPlayLoop()\n }\n\n this._notifyStatus()\n }\n\n /**\n * 배속 변경\n */\n setSpeed(speed: number) {\n this._speed = speed\n this._notifyStatus()\n }\n\n /**\n * 중지\n */\n stop() {\n this._stopPlayLoop()\n this._buffer?.clear()\n this._buffer = null\n this._state = 'stopped'\n this._playHead = 0\n this._lastDistributedSnapshot = null\n this._notifyStatus()\n }\n\n /**\n * DataSubscriptionProvider.dispose 구현\n */\n dispose() {\n this.stop()\n this._state = 'idle'\n this._components.clear()\n }\n\n // --- 재생 루프 ---\n\n private _startPlayLoop() {\n this._rafId = requestAnimationFrame(this._playLoop)\n }\n\n private _stopPlayLoop() {\n if (this._rafId) {\n cancelAnimationFrame(this._rafId)\n this._rafId = 0\n }\n }\n\n private _playLoop = (now: number) => {\n if (this._state !== 'playing' || !this._buffer) return\n\n const delta = (now - this._lastFrameTime) * this._speed\n this._lastFrameTime = now\n this._playHead += delta\n\n // 전체 범위 끝 도달 체크\n if (this._totalRange && this._playHead >= this._totalRange.to.getTime()) {\n this._playHead = this._totalRange.to.getTime()\n this._state = 'stopped'\n this._notifyStatus()\n return\n }\n\n // 현재 스냅샷 배포\n const snap = this._buffer.getSnapshotAt(this._playHead)\n if (snap && snap !== this._lastDistributedSnapshot) {\n this._distributeSnapshot(snap.data)\n this._lastDistributedSnapshot = snap\n }\n\n // prefetch 체크 (비동기, 루프 블로킹 안 함)\n this._buffer.checkPrefetch(this._playHead)\n\n this._notifyStatus()\n\n // 다음 프레임\n this._rafId = requestAnimationFrame(this._playLoop)\n }\n\n // --- 데이터 배포 ---\n\n private _distributeSnapshot(snapshotData: any) {\n if (!snapshotData || typeof snapshotData !== 'object') return\n\n for (const [tag, value] of Object.entries(snapshotData)) {\n const components = this._components.get(tag)\n if (components) {\n for (const component of components) {\n component.data = value\n }\n }\n }\n }\n\n // --- 데이터 fetcher ---\n\n private _createFetcher(): ChunkFetcher {\n return async (fromTime: Date, toTime: Date): Promise<PlaybackSnapshot[]> => {\n try {\n const { client } = await import('@operato/graphql')\n const response = await client.query({\n query: gql`\n query PlaybackChunk($startTime: String!, $endTime: String!) {\n playback(startTime: $startTime, endTime: $endTime) {\n status\n playback {\n scenarios {\n scenarioName\n snapshots {\n type\n time\n data\n }\n }\n }\n }\n }\n `,\n variables: {\n startTime: fromTime.toISOString().replace('T', ' ').replace(/\\.\\d+Z$/, ''),\n endTime: toTime.toISOString().replace('T', ' ').replace(/\\.\\d+Z$/, '')\n },\n fetchPolicy: 'no-cache'\n })\n\n return this._parsePlaybackResponse(response.data?.playback)\n } catch (e) {\n console.error('[PlaybackProvider] chunk fetch error:', e)\n return []\n }\n }\n }\n\n /**\n * 백엔드 응답을 PlaybackSnapshot[] 형식으로 변환\n */\n private _parsePlaybackResponse(response: any): PlaybackSnapshot[] {\n if (!response?.status || !response?.playback?.scenarios) return []\n\n const snapshotMap = new Map<number, Record<string, any>>()\n\n for (const scenario of response.playback.scenarios) {\n for (const record of scenario.snapshots) {\n const time = new Date(record.time.replace(' ', 'T')).getTime()\n const data = typeof record.data === 'string' ? JSON.parse(record.data) : record.data\n\n if (!snapshotMap.has(time)) {\n snapshotMap.set(time, {})\n }\n const merged = snapshotMap.get(time)!\n // scenarioName을 tag로 사용\n merged[scenario.scenarioName] = data\n }\n }\n\n return Array.from(snapshotMap.entries())\n .map(([timestamp, data]) => ({ timestamp, data }))\n .sort((a, b) => a.timestamp - b.timestamp)\n }\n\n // --- 상태 통보 ---\n\n private _notifyStatus() {\n this._onStatusChange?.({\n state: this._state,\n currentTime: this._playHead ? new Date(this._playHead).toISOString() : '',\n speed: this._speed,\n bufferedRanges: this._buffer?.getBufferedRanges()\n })\n }\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  export * from './types.js';
2
2
  export { registerDefaultGroups } from './component/register-default-groups.js';
3
3
  export { BoardDataStorage, PlaylistStorage } from './data-storage/data-storage.js';
4
+ export { BoardModelCache } from './data-storage/board-model-cache.js';
5
+ export type { CachedBoard } from './data-storage/board-model-cache.js';
4
6
  export * from './ox-board-viewer.js';
5
7
  export * from './ox-board-player.js';
6
8
  export * from './ox-board-modeller.js';
package/dist/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './types.js';
2
2
  export { registerDefaultGroups } from './component/register-default-groups.js';
3
3
  export { BoardDataStorage, PlaylistStorage } from './data-storage/data-storage.js';
4
+ export { BoardModelCache } from './data-storage/board-model-cache.js';
4
5
  export * from './ox-board-viewer.js';
5
6
  export * from './ox-board-player.js';
6
7
  export * from './ox-board-modeller.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAE1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAElF,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,+BAA+B,CAAA","sourcesContent":["export * from './types.js'\n\nexport { registerDefaultGroups } from './component/register-default-groups.js'\nexport { BoardDataStorage, PlaylistStorage } from './data-storage/data-storage.js'\n\nexport * from './ox-board-viewer.js'\nexport * from './ox-board-player.js'\nexport * from './ox-board-modeller.js'\nexport * from './ox-board-list.js'\nexport * from './ox-board-template-viewer.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAE1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAGrE,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,+BAA+B,CAAA","sourcesContent":["export * from './types.js'\n\nexport { registerDefaultGroups } from './component/register-default-groups.js'\nexport { BoardDataStorage, PlaylistStorage } from './data-storage/data-storage.js'\nexport { BoardModelCache } from './data-storage/board-model-cache.js'\nexport type { CachedBoard } from './data-storage/board-model-cache.js'\n\nexport * from './ox-board-viewer.js'\nexport * from './ox-board-player.js'\nexport * from './ox-board-modeller.js'\nexport * from './ox-board-list.js'\nexport * from './ox-board-template-viewer.js'\n"]}
@@ -12,7 +12,7 @@ export const style = css `
12
12
  [tools] {
13
13
  display: flex;
14
14
  align-items: center;
15
- overflow: none;
15
+ overflow: hidden;
16
16
  padding: 0px 10px;
17
17
  }
18
18
 
@@ -201,6 +201,43 @@ export const style = css `
201
201
  background-position-y: -1593px;
202
202
  }
203
203
 
204
+ #align-z-front {
205
+ background-position-y: -142px;
206
+ filter: hue-rotate(180deg);
207
+ }
208
+
209
+ #align-z-middle {
210
+ background-position-y: -192px;
211
+ filter: hue-rotate(180deg);
212
+ }
213
+
214
+ #align-z-back {
215
+ background-position-y: -242px;
216
+ filter: hue-rotate(180deg);
217
+ }
218
+
219
+ #distribute-z {
220
+ background-position-y: -1593px;
221
+ filter: hue-rotate(180deg);
222
+ }
223
+
224
+ .gizmo-btn {
225
+ background: none !important;
226
+ color: rgba(255, 255, 255, 0.5);
227
+ min-width: 24px;
228
+ display: inline-flex;
229
+ align-items: center;
230
+ justify-content: center;
231
+ cursor: pointer;
232
+ border-radius: 3px;
233
+ padding: 2px;
234
+ }
235
+
236
+ .gizmo-btn[disabled] {
237
+ opacity: 0.3;
238
+ pointer-events: none;
239
+ }
240
+
204
241
  #toggle-property {
205
242
  background-position-y: -1392px;
206
243
  }
@@ -1 +1 @@
1
- {"version":3,"file":"edit-toolbar-style.js","sourceRoot":"","sources":["../../../src/modeller/edit-toolbar-style.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiOvB,CAAA","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css } from 'lit'\n\nexport const style = css`\n :host {\n background-color: var(--edit-toolbar-background-color, var(--md-sys-color-secondary, #394e64));\n\n overflow-x: hidden;\n }\n\n [tools] {\n display: flex;\n align-items: center;\n overflow: none;\n padding: 0px 10px;\n }\n\n [tools] > * {\n padding: 0px;\n }\n\n [tools] > span[button] {\n min-width: 30px;\n }\n\n [tools] > span[padding] {\n flex: 1;\n }\n\n [tools] > .vline {\n display: block;\n flex: none;\n border-left: 1px solid rgba(255, 255, 255, 0.2);\n border-right: 1px solid rgba(0, 0, 0, 0.15);\n width: 0px;\n height: 18px;\n margin: 0 3px;\n }\n\n span[button] {\n min-height: 35px;\n\n background: var(--url-icon-htoolbar) no-repeat;\n background-position-x: 50%;\n opacity: 0.8;\n }\n span[button]:hover {\n opacity: 1;\n background-color: rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n\n #fullscreen,\n #toggle-property {\n flex: none;\n }\n\n #align-left {\n background-position-y: 8px;\n }\n\n #align-center {\n background-position-y: -42px;\n }\n\n #align-right {\n background-position-y: -92px;\n }\n\n #align-top {\n background-position-y: -142px;\n }\n\n #align-middle {\n background-position-y: -192px;\n }\n\n #align-bottom {\n background-position-y: -242px;\n }\n\n #undo {\n background-position-y: -592px;\n }\n\n #redo {\n background-position-y: -642px;\n }\n\n #front {\n background-position-y: -292px;\n }\n\n #back {\n background-position-y: -342px;\n }\n\n #forward {\n background-position-y: -392px;\n }\n\n #backward {\n background-position-y: -442px;\n }\n\n #symmetry-x {\n background-position-y: -492px;\n }\n\n #symmetry-y {\n background-position-y: -542px;\n }\n\n #group {\n background-position-y: -492px;\n }\n\n #ungroup {\n background-position-y: -542px;\n }\n\n #fullscreen {\n background-position-y: -692px;\n }\n\n #toggle-property {\n background-position-y: -692px;\n float: right;\n }\n\n #zoomin {\n background-position-y: -742px;\n }\n\n #zoomout {\n background-position-y: -792px;\n }\n\n #fit-scene {\n background-position-y: -1492px;\n }\n\n #cut {\n background-position-y: -842px;\n }\n\n #copy {\n background-position-y: -892px;\n }\n\n #paste {\n background-position-y: -942px;\n }\n\n #delete {\n background-position-y: -992px;\n }\n\n #font-increase {\n background-position-y: -1042px;\n }\n\n #font-decrease {\n background-position-y: -1092px;\n }\n\n #style-copy {\n background-position-y: -1142px;\n }\n\n #databind-copy {\n background-position-y: -1692px;\n }\n\n #context-menu {\n background-position-y: -692px;\n }\n\n #symmetry-x {\n background-position-y: -1192px;\n }\n\n #symmetry-y {\n background-position-y: -1242px;\n }\n\n #rotate-cw {\n background-position-y: -1292px;\n }\n\n #rotate-ccw {\n background-position-y: -1342px;\n }\n\n #distribute-horizontal {\n background-position-y: -1542px;\n }\n\n #distribute-vertical {\n background-position-y: -1593px;\n }\n\n #toggle-property {\n background-position-y: -1392px;\n }\n\n #preview {\n background-position-y: -1640px;\n }\n\n /* bigger buttons */\n #fullscreen {\n background: var(--url-icon-fullscreen) 50% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property {\n background: var(--url-icon-collapse) 80% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property[active] {\n background: var(--url-icon-collapse-active) 80% 10px no-repeat;\n }\n`\n"]}
1
+ {"version":3,"file":"edit-toolbar-style.js","sourceRoot":"","sources":["../../../src/modeller/edit-toolbar-style.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsQvB,CAAA","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css } from 'lit'\n\nexport const style = css`\n :host {\n background-color: var(--edit-toolbar-background-color, var(--md-sys-color-secondary, #394e64));\n\n overflow-x: hidden;\n }\n\n [tools] {\n display: flex;\n align-items: center;\n overflow: hidden;\n padding: 0px 10px;\n }\n\n [tools] > * {\n padding: 0px;\n }\n\n [tools] > span[button] {\n min-width: 30px;\n }\n\n [tools] > span[padding] {\n flex: 1;\n }\n\n [tools] > .vline {\n display: block;\n flex: none;\n border-left: 1px solid rgba(255, 255, 255, 0.2);\n border-right: 1px solid rgba(0, 0, 0, 0.15);\n width: 0px;\n height: 18px;\n margin: 0 3px;\n }\n\n span[button] {\n min-height: 35px;\n\n background: var(--url-icon-htoolbar) no-repeat;\n background-position-x: 50%;\n opacity: 0.8;\n }\n span[button]:hover {\n opacity: 1;\n background-color: rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n\n #fullscreen,\n #toggle-property {\n flex: none;\n }\n\n #align-left {\n background-position-y: 8px;\n }\n\n #align-center {\n background-position-y: -42px;\n }\n\n #align-right {\n background-position-y: -92px;\n }\n\n #align-top {\n background-position-y: -142px;\n }\n\n #align-middle {\n background-position-y: -192px;\n }\n\n #align-bottom {\n background-position-y: -242px;\n }\n\n #undo {\n background-position-y: -592px;\n }\n\n #redo {\n background-position-y: -642px;\n }\n\n #front {\n background-position-y: -292px;\n }\n\n #back {\n background-position-y: -342px;\n }\n\n #forward {\n background-position-y: -392px;\n }\n\n #backward {\n background-position-y: -442px;\n }\n\n #symmetry-x {\n background-position-y: -492px;\n }\n\n #symmetry-y {\n background-position-y: -542px;\n }\n\n #group {\n background-position-y: -492px;\n }\n\n #ungroup {\n background-position-y: -542px;\n }\n\n #fullscreen {\n background-position-y: -692px;\n }\n\n #toggle-property {\n background-position-y: -692px;\n float: right;\n }\n\n #zoomin {\n background-position-y: -742px;\n }\n\n #zoomout {\n background-position-y: -792px;\n }\n\n #fit-scene {\n background-position-y: -1492px;\n }\n\n #cut {\n background-position-y: -842px;\n }\n\n #copy {\n background-position-y: -892px;\n }\n\n #paste {\n background-position-y: -942px;\n }\n\n #delete {\n background-position-y: -992px;\n }\n\n #font-increase {\n background-position-y: -1042px;\n }\n\n #font-decrease {\n background-position-y: -1092px;\n }\n\n #style-copy {\n background-position-y: -1142px;\n }\n\n #databind-copy {\n background-position-y: -1692px;\n }\n\n #context-menu {\n background-position-y: -692px;\n }\n\n #symmetry-x {\n background-position-y: -1192px;\n }\n\n #symmetry-y {\n background-position-y: -1242px;\n }\n\n #rotate-cw {\n background-position-y: -1292px;\n }\n\n #rotate-ccw {\n background-position-y: -1342px;\n }\n\n #distribute-horizontal {\n background-position-y: -1542px;\n }\n\n #distribute-vertical {\n background-position-y: -1593px;\n }\n\n #align-z-front {\n background-position-y: -142px;\n filter: hue-rotate(180deg);\n }\n\n #align-z-middle {\n background-position-y: -192px;\n filter: hue-rotate(180deg);\n }\n\n #align-z-back {\n background-position-y: -242px;\n filter: hue-rotate(180deg);\n }\n\n #distribute-z {\n background-position-y: -1593px;\n filter: hue-rotate(180deg);\n }\n\n .gizmo-btn {\n background: none !important;\n color: rgba(255, 255, 255, 0.5);\n min-width: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n border-radius: 3px;\n padding: 2px;\n }\n\n .gizmo-btn[disabled] {\n opacity: 0.3;\n pointer-events: none;\n }\n\n #toggle-property {\n background-position-y: -1392px;\n }\n\n #preview {\n background-position-y: -1640px;\n }\n\n /* bigger buttons */\n #fullscreen {\n background: var(--url-icon-fullscreen) 50% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property {\n background: var(--url-icon-collapse) 80% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property[active] {\n background: var(--url-icon-collapse-active) 80% 10px no-repeat;\n }\n`\n"]}
@@ -8,23 +8,9 @@ export declare class EditToolbar extends LitElement {
8
8
  scene?: Scene;
9
9
  selected: any[];
10
10
  hideProperty: boolean;
11
+ private _dimension;
12
+ private _gizmoAttached;
11
13
  private cliped?;
12
- private redo;
13
- private undo;
14
- private fullscreen;
15
- private styleCopy;
16
- private databindCopy;
17
- private cut;
18
- private copy;
19
- private paste;
20
- private delete;
21
- private forward;
22
- private backward;
23
- private front;
24
- private back;
25
- private aligners;
26
- private zorders;
27
- private distributes;
28
14
  firstUpdated(): void;
29
15
  updated(changes: PropertyValues<this>): void;
30
16
  render(): import("lit-html").TemplateResult<1>;
@@ -32,10 +18,14 @@ export declare class EditToolbar extends LitElement {
32
18
  getSymbol(key: string): string;
33
19
  private getShortcutString;
34
20
  onShortcut(e: KeyboardEvent): boolean;
21
+ private _setDisabled;
35
22
  onExecute(command: string, undoable: boolean, redoable: boolean): void;
36
23
  onUndo(undoable: boolean, redoable: boolean): void;
37
24
  onRedo(undoable: boolean, redoable: boolean): void;
38
25
  onSceneChanged(after?: Scene, before?: Scene): void;
26
+ private _onDimensionChanged;
27
+ private _onGizmoAttachChanged;
28
+ private _setGizmoMode;
39
29
  onSelectedChanged(after: Component[], before: Component[]): void;
40
30
  onTapUndo(): void;
41
31
  onTapRedo(): void;
@@ -56,6 +46,8 @@ export declare class EditToolbar extends LitElement {
56
46
  onTapToggle(): void;
57
47
  onTapFitScene(): void;
58
48
  onTapPreview(): void;
49
+ onTapDataBinding(): void;
59
50
  onTapDownloadModel(): void;
51
+ onTapEditModel(): void;
60
52
  onTapDistribute(e: TouchEvent | string): void;
61
53
  }