@operato/board 10.0.0-beta.5 → 10.0.0-beta.50

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 (86) hide show
  1. package/CHANGELOG.md +408 -0
  2. package/dist/src/component/container.js +18 -3
  3. package/dist/src/component/container.js.map +1 -1
  4. package/dist/src/component/conveyance.d.ts +2 -0
  5. package/dist/src/component/conveyance.js +38 -0
  6. package/dist/src/component/conveyance.js.map +1 -0
  7. package/dist/src/component/etc.js +2 -10
  8. package/dist/src/component/etc.js.map +1 -1
  9. package/dist/src/component/facility.d.ts +2 -0
  10. package/dist/src/component/facility.js +35 -0
  11. package/dist/src/component/facility.js.map +1 -0
  12. package/dist/src/component/index.d.ts +5 -0
  13. package/dist/src/component/index.js +5 -0
  14. package/dist/src/component/index.js.map +1 -1
  15. package/dist/src/component/line.js +4 -28
  16. package/dist/src/component/line.js.map +1 -1
  17. package/dist/src/component/manufacturing.d.ts +2 -0
  18. package/dist/src/component/manufacturing.js +41 -0
  19. package/dist/src/component/manufacturing.js.map +1 -0
  20. package/dist/src/component/register-default-groups.js +19 -2
  21. package/dist/src/component/register-default-groups.js.map +1 -1
  22. package/dist/src/component/shape.js +5 -29
  23. package/dist/src/component/shape.js.map +1 -1
  24. package/dist/src/component/storage.d.ts +2 -0
  25. package/dist/src/component/storage.js +27 -0
  26. package/dist/src/component/storage.js.map +1 -0
  27. package/dist/src/component/text-and-media.js +2 -25
  28. package/dist/src/component/text-and-media.js.map +1 -1
  29. package/dist/src/component/transport.d.ts +2 -0
  30. package/dist/src/component/transport.js +36 -0
  31. package/dist/src/component/transport.js.map +1 -0
  32. package/dist/src/component/warehouse.d.ts +1 -0
  33. package/dist/src/component/warehouse.js +8 -1
  34. package/dist/src/component/warehouse.js.map +1 -1
  35. package/dist/src/data-storage/board-model-cache.d.ts +30 -0
  36. package/dist/src/data-storage/board-model-cache.js +93 -0
  37. package/dist/src/data-storage/board-model-cache.js.map +1 -0
  38. package/dist/src/graphql/playback-buffer.d.ts +79 -0
  39. package/dist/src/graphql/playback-buffer.js +139 -0
  40. package/dist/src/graphql/playback-buffer.js.map +1 -0
  41. package/dist/src/graphql/playback-buffer.test.d.ts +1 -0
  42. package/dist/src/graphql/playback-buffer.test.js +261 -0
  43. package/dist/src/graphql/playback-buffer.test.js.map +1 -0
  44. package/dist/src/graphql/playback-subscription.d.ts +89 -0
  45. package/dist/src/graphql/playback-subscription.js +258 -0
  46. package/dist/src/graphql/playback-subscription.js.map +1 -0
  47. package/dist/src/index.d.ts +3 -0
  48. package/dist/src/index.js +2 -0
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  51. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  52. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  53. package/dist/src/modeller/component-toolbar/component-menu.js +8 -1
  54. package/dist/src/modeller/component-toolbar/component-menu.js.map +1 -1
  55. package/dist/src/modeller/edit-toolbar-style.js +38 -1
  56. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  57. package/dist/src/modeller/edit-toolbar.d.ts +21 -16
  58. package/dist/src/modeller/edit-toolbar.js +305 -201
  59. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  60. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  61. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -11
  62. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  63. package/dist/src/ox-board-modeller.d.ts +8 -1
  64. package/dist/src/ox-board-modeller.js +125 -6
  65. package/dist/src/ox-board-modeller.js.map +1 -1
  66. package/dist/src/ox-board-preview.d.ts +36 -0
  67. package/dist/src/ox-board-preview.js +114 -0
  68. package/dist/src/ox-board-preview.js.map +1 -0
  69. package/dist/src/ox-board-template-list.d.ts +1 -0
  70. package/dist/src/ox-board-template-list.js +19 -1
  71. package/dist/src/ox-board-template-list.js.map +1 -1
  72. package/dist/src/ox-board-viewer.d.ts +50 -1
  73. package/dist/src/ox-board-viewer.js +271 -28
  74. package/dist/src/ox-board-viewer.js.map +1 -1
  75. package/dist/src/ox-playback-controls.d.ts +56 -0
  76. package/dist/src/ox-playback-controls.js +515 -0
  77. package/dist/src/ox-playback-controls.js.map +1 -0
  78. package/dist/src/selector/ox-board-selector.js +11 -1
  79. package/dist/src/selector/ox-board-selector.js.map +1 -1
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/package.json +17 -12
  82. package/translations/en.json +19 -1
  83. package/translations/ja.json +19 -1
  84. package/translations/ko.json +19 -1
  85. package/translations/ms.json +19 -1
  86. package/translations/zh.json +19 -1
@@ -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,7 +1,10 @@
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';
7
+ export * from './ox-board-preview.js';
5
8
  export * from './ox-board-player.js';
6
9
  export * from './ox-board-modeller.js';
7
10
  export * from './ox-board-list.js';
package/dist/src/index.js CHANGED
@@ -1,7 +1,9 @@
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';
6
+ export * from './ox-board-preview.js';
5
7
  export * from './ox-board-player.js';
6
8
  export * from './ox-board-modeller.js';
7
9
  export * from './ox-board-list.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,uBAAuB,CAAA;AACrC,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-preview.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"]}
@@ -0,0 +1,51 @@
1
+ import { LitElement } from 'lit';
2
+ import { generateBulkComponents, type BulkCreateArea as Area } from '@hatiolab/things-scene';
3
+ export type ConflictPolicy = 'skip' | 'duplicate';
4
+ export interface BulkCreateDialogProps {
5
+ /** 템플릿 컴포넌트 state (read-only 표시용). */
6
+ template: Record<string, any>;
7
+ /** 초기 영역 (보드 좌표). 사용자가 dialog 내에서 조정 가능. */
8
+ initialArea: Area;
9
+ /** 기존 scene 의 모든 ID — 충돌 검사. */
10
+ existingIds?: Set<string>;
11
+ /** OK 클릭 시 콜백 — 생성할 model 배열 전달. */
12
+ onConfirm: (models: ReturnType<typeof generateBulkComponents>) => void;
13
+ /** 취소 / 닫기 콜백. */
14
+ onCancel?: () => void;
15
+ }
16
+ export declare class BulkCreateDialog extends LitElement {
17
+ static styles: import("lit").CSSResult;
18
+ template: Record<string, any>;
19
+ initialArea: Area;
20
+ existingIds: Set<string>;
21
+ onConfirm: BulkCreateDialogProps['onConfirm'];
22
+ onCancel?: BulkCreateDialogProps['onCancel'];
23
+ private pattern;
24
+ private count;
25
+ private rows;
26
+ private cols;
27
+ private idPattern;
28
+ private idStart;
29
+ private idPadding;
30
+ private area;
31
+ private conflictPolicy;
32
+ countInput: HTMLInputElement;
33
+ connectedCallback(): void;
34
+ /** rows/cols 가 미입력 (undefined) 이면 영역 종횡비 + count 로 자동 계산해 표시. */
35
+ private get displayRows();
36
+ private get displayCols();
37
+ firstUpdated(): void;
38
+ private updateArea;
39
+ private get previewModels();
40
+ private get conflicts();
41
+ /** 충돌 정책 적용 후 실제 생성될 model 배열. */
42
+ private get effectiveModels();
43
+ private onOk;
44
+ private onCancelClick;
45
+ render(): import("lit-html").TemplateResult<1>;
46
+ }
47
+ declare global {
48
+ interface HTMLElementTagNameMap {
49
+ 'bulk-create-dialog': BulkCreateDialog;
50
+ }
51
+ }