senangwebs-tour 1.0.8 → 1.0.9
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/README.md +2 -1
- package/dist/swt-editor.js +425 -34
- package/dist/swt-editor.js.map +1 -1
- package/dist/swt-editor.min.js +1 -1
- package/package.json +1 -1
- package/src/editor/js/editor.js +92 -0
- package/src/editor/js/event-emitter.js +257 -0
- package/src/editor/js/export-manager.js +47 -1
- package/src/editor/js/ui-controller.js +1 -1
package/package.json
CHANGED
package/src/editor/js/editor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Main Editor Controller
|
|
2
2
|
import { debounce, showModal, showToast } from './utils.js';
|
|
3
|
+
import EventEmitter, { EditorEvents } from './event-emitter.js';
|
|
3
4
|
|
|
4
5
|
class TourEditor {
|
|
5
6
|
constructor(options = {}) {
|
|
@@ -29,6 +30,47 @@ class TourEditor {
|
|
|
29
30
|
this.hasUnsavedChanges = false;
|
|
30
31
|
this.lastRenderedSceneIndex = -1;
|
|
31
32
|
this.listenersSetup = false;
|
|
33
|
+
|
|
34
|
+
// Initialize event emitter
|
|
35
|
+
this.events = new EventEmitter();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to editor events
|
|
40
|
+
* @param {string} event - Event name (use EditorEvents constants)
|
|
41
|
+
* @param {Function} callback - Callback function
|
|
42
|
+
* @returns {Function} Unsubscribe function
|
|
43
|
+
*/
|
|
44
|
+
on(event, callback) {
|
|
45
|
+
return this.events.on(event, callback);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Subscribe to an event once
|
|
50
|
+
* @param {string} event - Event name
|
|
51
|
+
* @param {Function} callback - Callback function
|
|
52
|
+
* @returns {Function} Unsubscribe function
|
|
53
|
+
*/
|
|
54
|
+
once(event, callback) {
|
|
55
|
+
return this.events.once(event, callback);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Unsubscribe from an event
|
|
60
|
+
* @param {string} event - Event name
|
|
61
|
+
* @param {Function} callback - Callback to remove
|
|
62
|
+
*/
|
|
63
|
+
off(event, callback) {
|
|
64
|
+
this.events.off(event, callback);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Emit an event
|
|
69
|
+
* @param {string} event - Event name
|
|
70
|
+
* @param {Object} data - Event data
|
|
71
|
+
*/
|
|
72
|
+
emit(event, data = {}) {
|
|
73
|
+
this.events.emit(event, data);
|
|
32
74
|
}
|
|
33
75
|
|
|
34
76
|
/**
|
|
@@ -89,6 +131,9 @@ class TourEditor {
|
|
|
89
131
|
|
|
90
132
|
showToast('Editor ready', 'success');
|
|
91
133
|
|
|
134
|
+
// Emit ready event
|
|
135
|
+
this.emit(EditorEvents.READY, { config: this.options });
|
|
136
|
+
|
|
92
137
|
return true;
|
|
93
138
|
}
|
|
94
139
|
|
|
@@ -294,6 +339,9 @@ class TourEditor {
|
|
|
294
339
|
}
|
|
295
340
|
|
|
296
341
|
const scene = await this.sceneManager.addScene(file);
|
|
342
|
+
if (scene) {
|
|
343
|
+
this.emit(EditorEvents.SCENE_ADD, { scene, file });
|
|
344
|
+
}
|
|
297
345
|
}
|
|
298
346
|
this.uiController.setLoading(false);
|
|
299
347
|
this.render();
|
|
@@ -327,6 +375,11 @@ class TourEditor {
|
|
|
327
375
|
this.lastRenderedSceneIndex = -1;
|
|
328
376
|
this.render();
|
|
329
377
|
this.markUnsavedChanges();
|
|
378
|
+
this.emit(EditorEvents.HOTSPOT_ADD, {
|
|
379
|
+
hotspot,
|
|
380
|
+
position,
|
|
381
|
+
sceneId: this.sceneManager.getCurrentScene()?.id
|
|
382
|
+
});
|
|
330
383
|
} else {
|
|
331
384
|
console.error('Failed to add hotspot');
|
|
332
385
|
}
|
|
@@ -372,6 +425,8 @@ class TourEditor {
|
|
|
372
425
|
this.uiController.updateHotspotProperties(null);
|
|
373
426
|
this.uiController.updateInitialSceneOptions();
|
|
374
427
|
this.uiController.updateTargetSceneOptions();
|
|
428
|
+
|
|
429
|
+
this.emit(EditorEvents.SCENE_SELECT, { scene, index });
|
|
375
430
|
}
|
|
376
431
|
}
|
|
377
432
|
|
|
@@ -390,6 +445,8 @@ class TourEditor {
|
|
|
390
445
|
if (hotspot) {
|
|
391
446
|
this.previewController.pointCameraToHotspot(hotspot);
|
|
392
447
|
}
|
|
448
|
+
|
|
449
|
+
this.emit(EditorEvents.HOTSPOT_SELECT, { hotspot, index });
|
|
393
450
|
}
|
|
394
451
|
}
|
|
395
452
|
|
|
@@ -397,9 +454,11 @@ class TourEditor {
|
|
|
397
454
|
* Remove scene
|
|
398
455
|
*/
|
|
399
456
|
removeScene(index) {
|
|
457
|
+
const scene = this.sceneManager.getScene(index);
|
|
400
458
|
if (this.sceneManager.removeScene(index)) {
|
|
401
459
|
this.render();
|
|
402
460
|
this.markUnsavedChanges();
|
|
461
|
+
this.emit(EditorEvents.SCENE_REMOVE, { scene, index });
|
|
403
462
|
}
|
|
404
463
|
}
|
|
405
464
|
|
|
@@ -407,10 +466,12 @@ class TourEditor {
|
|
|
407
466
|
* Remove hotspot
|
|
408
467
|
*/
|
|
409
468
|
removeHotspot(index) {
|
|
469
|
+
const hotspot = this.hotspotEditor.getHotspot(index);
|
|
410
470
|
if (this.hotspotEditor.removeHotspot(index)) {
|
|
411
471
|
this.lastRenderedSceneIndex = -1;
|
|
412
472
|
this.render();
|
|
413
473
|
this.markUnsavedChanges();
|
|
474
|
+
this.emit(EditorEvents.HOTSPOT_REMOVE, { hotspot, index });
|
|
414
475
|
}
|
|
415
476
|
}
|
|
416
477
|
|
|
@@ -423,6 +484,7 @@ class TourEditor {
|
|
|
423
484
|
this.lastRenderedSceneIndex = -1;
|
|
424
485
|
this.render();
|
|
425
486
|
this.markUnsavedChanges();
|
|
487
|
+
this.emit(EditorEvents.HOTSPOT_DUPLICATE, { hotspot, originalIndex: index });
|
|
426
488
|
}
|
|
427
489
|
}
|
|
428
490
|
|
|
@@ -433,6 +495,7 @@ class TourEditor {
|
|
|
433
495
|
if (this.sceneManager.reorderScenes(fromIndex, toIndex)) {
|
|
434
496
|
this.render();
|
|
435
497
|
this.markUnsavedChanges();
|
|
498
|
+
this.emit(EditorEvents.SCENE_REORDER, { fromIndex, toIndex });
|
|
436
499
|
}
|
|
437
500
|
}
|
|
438
501
|
|
|
@@ -445,6 +508,12 @@ class TourEditor {
|
|
|
445
508
|
await this.previewController.updateHotspotMarker(index);
|
|
446
509
|
this.uiController.renderHotspotList();
|
|
447
510
|
this.markUnsavedChanges();
|
|
511
|
+
this.emit(EditorEvents.HOTSPOT_UPDATE, {
|
|
512
|
+
hotspot: this.hotspotEditor.getHotspot(index),
|
|
513
|
+
index,
|
|
514
|
+
property,
|
|
515
|
+
value
|
|
516
|
+
});
|
|
448
517
|
}
|
|
449
518
|
}
|
|
450
519
|
|
|
@@ -481,6 +550,13 @@ class TourEditor {
|
|
|
481
550
|
await this.previewController.updateHotspotMarker(index);
|
|
482
551
|
this.uiController.renderHotspotList();
|
|
483
552
|
this.markUnsavedChanges();
|
|
553
|
+
this.emit(EditorEvents.HOTSPOT_POSITION_CHANGE, {
|
|
554
|
+
hotspot,
|
|
555
|
+
index,
|
|
556
|
+
axis,
|
|
557
|
+
value,
|
|
558
|
+
position: hotspot.position
|
|
559
|
+
});
|
|
484
560
|
}
|
|
485
561
|
}
|
|
486
562
|
|
|
@@ -492,6 +568,12 @@ class TourEditor {
|
|
|
492
568
|
if (this.sceneManager.updateScene(index, property, value)) {
|
|
493
569
|
this.uiController.renderSceneList();
|
|
494
570
|
this.markUnsavedChanges();
|
|
571
|
+
this.emit(EditorEvents.SCENE_UPDATE, {
|
|
572
|
+
scene: this.sceneManager.getScene(index),
|
|
573
|
+
index,
|
|
574
|
+
property,
|
|
575
|
+
value
|
|
576
|
+
});
|
|
495
577
|
}
|
|
496
578
|
}
|
|
497
579
|
|
|
@@ -518,6 +600,7 @@ class TourEditor {
|
|
|
518
600
|
showToast('Scene image updated', 'success');
|
|
519
601
|
}
|
|
520
602
|
this.markUnsavedChanges();
|
|
603
|
+
this.emit(EditorEvents.SCENE_IMAGE_CHANGE, { scene, index, imageUrl });
|
|
521
604
|
}
|
|
522
605
|
}
|
|
523
606
|
|
|
@@ -545,6 +628,7 @@ class TourEditor {
|
|
|
545
628
|
this.uiController.updateSceneProperties(scene);
|
|
546
629
|
this.markUnsavedChanges();
|
|
547
630
|
showToast('Starting position set', 'success');
|
|
631
|
+
this.emit(EditorEvents.SCENE_STARTING_POSITION_SET, { scene, startingPosition: scene.startingPosition });
|
|
548
632
|
}
|
|
549
633
|
|
|
550
634
|
/**
|
|
@@ -562,6 +646,7 @@ class TourEditor {
|
|
|
562
646
|
this.uiController.updateSceneProperties(scene);
|
|
563
647
|
this.markUnsavedChanges();
|
|
564
648
|
showToast('Starting position cleared', 'success');
|
|
649
|
+
this.emit(EditorEvents.SCENE_STARTING_POSITION_CLEAR, { scene });
|
|
565
650
|
}
|
|
566
651
|
|
|
567
652
|
/**
|
|
@@ -603,6 +688,8 @@ class TourEditor {
|
|
|
603
688
|
}
|
|
604
689
|
this.lastRenderedSceneIndex = -1;
|
|
605
690
|
}
|
|
691
|
+
|
|
692
|
+
this.emit(EditorEvents.UI_RENDER);
|
|
606
693
|
}
|
|
607
694
|
|
|
608
695
|
/**
|
|
@@ -617,6 +704,7 @@ class TourEditor {
|
|
|
617
704
|
if (this.storageManager.saveProject(projectData)) {
|
|
618
705
|
this.hasUnsavedChanges = false;
|
|
619
706
|
showToast('Project saved', 'success');
|
|
707
|
+
this.emit(EditorEvents.PROJECT_SAVE, { projectData });
|
|
620
708
|
return true;
|
|
621
709
|
}
|
|
622
710
|
return false;
|
|
@@ -633,6 +721,7 @@ class TourEditor {
|
|
|
633
721
|
this.hasUnsavedChanges = false;
|
|
634
722
|
this.render();
|
|
635
723
|
showToast('Project loaded', 'success');
|
|
724
|
+
this.emit(EditorEvents.PROJECT_LOAD, { projectData });
|
|
636
725
|
return true;
|
|
637
726
|
}
|
|
638
727
|
return false;
|
|
@@ -659,6 +748,7 @@ class TourEditor {
|
|
|
659
748
|
this.render();
|
|
660
749
|
|
|
661
750
|
showToast('New project created', 'success');
|
|
751
|
+
this.emit(EditorEvents.PROJECT_NEW, { config: this.config });
|
|
662
752
|
return true;
|
|
663
753
|
}
|
|
664
754
|
|
|
@@ -689,6 +779,7 @@ const importUpload = document.getElementById('importUpload');
|
|
|
689
779
|
this.uiController.setLoading(false);
|
|
690
780
|
|
|
691
781
|
showToast('Project imported successfully', 'success');
|
|
782
|
+
this.emit(EditorEvents.PROJECT_IMPORT, { projectData, file });
|
|
692
783
|
} catch (error) {
|
|
693
784
|
this.uiController.setLoading(false);
|
|
694
785
|
console.error('Import failed:', error);
|
|
@@ -714,3 +805,4 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
714
805
|
});
|
|
715
806
|
|
|
716
807
|
export default TourEditor;
|
|
808
|
+
export { EditorEvents } from './event-emitter.js';
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Emitter for TourEditor
|
|
3
|
+
*
|
|
4
|
+
* Provides a comprehensive event system for the editor with:
|
|
5
|
+
* - Specific events for all editor operations
|
|
6
|
+
* - A unified 'change' event that fires for any modification
|
|
7
|
+
* - Support for wildcards and namespaced events
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Event types for the Tour Editor
|
|
12
|
+
* These are the available events that can be listened to
|
|
13
|
+
*/
|
|
14
|
+
export const EditorEvents = {
|
|
15
|
+
// Lifecycle
|
|
16
|
+
INIT: 'init',
|
|
17
|
+
READY: 'ready',
|
|
18
|
+
DESTROY: 'destroy',
|
|
19
|
+
|
|
20
|
+
// Scene events
|
|
21
|
+
SCENE_ADD: 'scene:add',
|
|
22
|
+
SCENE_REMOVE: 'scene:remove',
|
|
23
|
+
SCENE_SELECT: 'scene:select',
|
|
24
|
+
SCENE_UPDATE: 'scene:update',
|
|
25
|
+
SCENE_REORDER: 'scene:reorder',
|
|
26
|
+
SCENE_CLEAR: 'scene:clear',
|
|
27
|
+
SCENE_IMAGE_CHANGE: 'scene:imageChange',
|
|
28
|
+
SCENE_STARTING_POSITION_SET: 'scene:startingPositionSet',
|
|
29
|
+
SCENE_STARTING_POSITION_CLEAR: 'scene:startingPositionClear',
|
|
30
|
+
|
|
31
|
+
// Hotspot events
|
|
32
|
+
HOTSPOT_ADD: 'hotspot:add',
|
|
33
|
+
HOTSPOT_REMOVE: 'hotspot:remove',
|
|
34
|
+
HOTSPOT_SELECT: 'hotspot:select',
|
|
35
|
+
HOTSPOT_UPDATE: 'hotspot:update',
|
|
36
|
+
HOTSPOT_DUPLICATE: 'hotspot:duplicate',
|
|
37
|
+
HOTSPOT_POSITION_CHANGE: 'hotspot:positionChange',
|
|
38
|
+
|
|
39
|
+
// Project events
|
|
40
|
+
PROJECT_NEW: 'project:new',
|
|
41
|
+
PROJECT_SAVE: 'project:save',
|
|
42
|
+
PROJECT_LOAD: 'project:load',
|
|
43
|
+
PROJECT_IMPORT: 'project:import',
|
|
44
|
+
PROJECT_EXPORT: 'project:export',
|
|
45
|
+
|
|
46
|
+
// Config events
|
|
47
|
+
CONFIG_UPDATE: 'config:update',
|
|
48
|
+
INITIAL_SCENE_CHANGE: 'config:initialSceneChange',
|
|
49
|
+
|
|
50
|
+
// Preview events
|
|
51
|
+
PREVIEW_START: 'preview:start',
|
|
52
|
+
PREVIEW_STOP: 'preview:stop',
|
|
53
|
+
PREVIEW_SCENE_CHANGE: 'preview:sceneChange',
|
|
54
|
+
|
|
55
|
+
// UI events
|
|
56
|
+
UI_RENDER: 'ui:render',
|
|
57
|
+
UI_LOADING_START: 'ui:loadingStart',
|
|
58
|
+
UI_LOADING_END: 'ui:loadingEnd',
|
|
59
|
+
MODAL_OPEN: 'ui:modalOpen',
|
|
60
|
+
MODAL_CLOSE: 'ui:modalClose',
|
|
61
|
+
|
|
62
|
+
// Data events
|
|
63
|
+
DATA_CHANGE: 'data:change', // Fires when any data changes
|
|
64
|
+
UNSAVED_CHANGES: 'data:unsavedChanges',
|
|
65
|
+
|
|
66
|
+
// Unified change event - fires for ANY modification
|
|
67
|
+
CHANGE: 'change'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Event Emitter class
|
|
72
|
+
* Provides pub/sub functionality for editor events
|
|
73
|
+
*/
|
|
74
|
+
class EventEmitter {
|
|
75
|
+
constructor() {
|
|
76
|
+
this.listeners = new Map();
|
|
77
|
+
this.onceListeners = new Map();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Register an event listener
|
|
82
|
+
* @param {string} event - Event name or 'change' for all changes
|
|
83
|
+
* @param {Function} callback - Function to call when event fires
|
|
84
|
+
* @returns {Function} Unsubscribe function
|
|
85
|
+
*/
|
|
86
|
+
on(event, callback) {
|
|
87
|
+
if (!this.listeners.has(event)) {
|
|
88
|
+
this.listeners.set(event, new Set());
|
|
89
|
+
}
|
|
90
|
+
this.listeners.get(event).add(callback);
|
|
91
|
+
|
|
92
|
+
// Return unsubscribe function
|
|
93
|
+
return () => this.off(event, callback);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Register a one-time event listener
|
|
98
|
+
* @param {string} event - Event name
|
|
99
|
+
* @param {Function} callback - Function to call once when event fires
|
|
100
|
+
* @returns {Function} Unsubscribe function
|
|
101
|
+
*/
|
|
102
|
+
once(event, callback) {
|
|
103
|
+
if (!this.onceListeners.has(event)) {
|
|
104
|
+
this.onceListeners.set(event, new Set());
|
|
105
|
+
}
|
|
106
|
+
this.onceListeners.get(event).add(callback);
|
|
107
|
+
|
|
108
|
+
return () => {
|
|
109
|
+
if (this.onceListeners.has(event)) {
|
|
110
|
+
this.onceListeners.get(event).delete(callback);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Remove an event listener
|
|
117
|
+
* @param {string} event - Event name
|
|
118
|
+
* @param {Function} callback - Function to remove
|
|
119
|
+
*/
|
|
120
|
+
off(event, callback) {
|
|
121
|
+
if (this.listeners.has(event)) {
|
|
122
|
+
this.listeners.get(event).delete(callback);
|
|
123
|
+
}
|
|
124
|
+
if (this.onceListeners.has(event)) {
|
|
125
|
+
this.onceListeners.get(event).delete(callback);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Remove all listeners for an event, or all listeners if no event specified
|
|
131
|
+
* @param {string} [event] - Optional event name
|
|
132
|
+
*/
|
|
133
|
+
removeAllListeners(event) {
|
|
134
|
+
if (event) {
|
|
135
|
+
this.listeners.delete(event);
|
|
136
|
+
this.onceListeners.delete(event);
|
|
137
|
+
} else {
|
|
138
|
+
this.listeners.clear();
|
|
139
|
+
this.onceListeners.clear();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Emit an event
|
|
145
|
+
* @param {string} event - Event name
|
|
146
|
+
* @param {Object} data - Event data
|
|
147
|
+
*/
|
|
148
|
+
emit(event, data = {}) {
|
|
149
|
+
const eventData = {
|
|
150
|
+
type: event,
|
|
151
|
+
timestamp: Date.now(),
|
|
152
|
+
...data
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Call specific event listeners
|
|
156
|
+
if (this.listeners.has(event)) {
|
|
157
|
+
this.listeners.get(event).forEach(callback => {
|
|
158
|
+
try {
|
|
159
|
+
callback(eventData);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error(`Error in event listener for "${event}":`, error);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Call once listeners and remove them
|
|
167
|
+
if (this.onceListeners.has(event)) {
|
|
168
|
+
const onceCallbacks = this.onceListeners.get(event);
|
|
169
|
+
this.onceListeners.delete(event);
|
|
170
|
+
onceCallbacks.forEach(callback => {
|
|
171
|
+
try {
|
|
172
|
+
callback(eventData);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`Error in once listener for "${event}":`, error);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Also emit to wildcard listeners (namespace:*)
|
|
180
|
+
const namespace = event.split(':')[0];
|
|
181
|
+
const wildcardEvent = `${namespace}:*`;
|
|
182
|
+
if (this.listeners.has(wildcardEvent)) {
|
|
183
|
+
this.listeners.get(wildcardEvent).forEach(callback => {
|
|
184
|
+
try {
|
|
185
|
+
callback(eventData);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`Error in wildcard listener for "${wildcardEvent}":`, error);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Emit unified 'change' event for data-modifying events
|
|
193
|
+
if (this.isDataModifyingEvent(event) && event !== EditorEvents.CHANGE) {
|
|
194
|
+
this.emit(EditorEvents.CHANGE, {
|
|
195
|
+
originalEvent: event,
|
|
196
|
+
...data
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check if an event modifies data (should trigger 'change' event)
|
|
203
|
+
* @param {string} event - Event name
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
isDataModifyingEvent(event) {
|
|
207
|
+
const dataEvents = [
|
|
208
|
+
EditorEvents.SCENE_ADD,
|
|
209
|
+
EditorEvents.SCENE_REMOVE,
|
|
210
|
+
EditorEvents.SCENE_UPDATE,
|
|
211
|
+
EditorEvents.SCENE_REORDER,
|
|
212
|
+
EditorEvents.SCENE_CLEAR,
|
|
213
|
+
EditorEvents.SCENE_IMAGE_CHANGE,
|
|
214
|
+
EditorEvents.SCENE_STARTING_POSITION_SET,
|
|
215
|
+
EditorEvents.SCENE_STARTING_POSITION_CLEAR,
|
|
216
|
+
EditorEvents.HOTSPOT_ADD,
|
|
217
|
+
EditorEvents.HOTSPOT_REMOVE,
|
|
218
|
+
EditorEvents.HOTSPOT_UPDATE,
|
|
219
|
+
EditorEvents.HOTSPOT_DUPLICATE,
|
|
220
|
+
EditorEvents.HOTSPOT_POSITION_CHANGE,
|
|
221
|
+
EditorEvents.CONFIG_UPDATE,
|
|
222
|
+
EditorEvents.INITIAL_SCENE_CHANGE,
|
|
223
|
+
EditorEvents.PROJECT_LOAD,
|
|
224
|
+
EditorEvents.PROJECT_IMPORT,
|
|
225
|
+
EditorEvents.PROJECT_NEW,
|
|
226
|
+
EditorEvents.DATA_CHANGE
|
|
227
|
+
];
|
|
228
|
+
return dataEvents.includes(event);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get the number of listeners for an event
|
|
233
|
+
* @param {string} event - Event name
|
|
234
|
+
* @returns {number}
|
|
235
|
+
*/
|
|
236
|
+
listenerCount(event) {
|
|
237
|
+
let count = 0;
|
|
238
|
+
if (this.listeners.has(event)) {
|
|
239
|
+
count += this.listeners.get(event).size;
|
|
240
|
+
}
|
|
241
|
+
if (this.onceListeners.has(event)) {
|
|
242
|
+
count += this.onceListeners.get(event).size;
|
|
243
|
+
}
|
|
244
|
+
return count;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get all event names that have listeners
|
|
249
|
+
* @returns {string[]}
|
|
250
|
+
*/
|
|
251
|
+
eventNames() {
|
|
252
|
+
const names = new Set([...this.listeners.keys(), ...this.onceListeners.keys()]);
|
|
253
|
+
return Array.from(names);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default EventEmitter;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Export Manager - Handles JSON generation for SWT library
|
|
2
|
-
import { downloadTextAsFile, showModal, copyToClipboard } from "./utils.js";
|
|
2
|
+
import { downloadTextAsFile, showModal, copyToClipboard, showToast } from "./utils.js";
|
|
3
3
|
import { IconRenderer } from "../../IconRenderer.js";
|
|
4
4
|
import { buildTourConfig } from "./data-transform.js";
|
|
5
|
+
import { EditorEvents } from "./event-emitter.js";
|
|
5
6
|
|
|
6
7
|
class ExportManager {
|
|
7
8
|
constructor(editor) {
|
|
@@ -21,6 +22,51 @@ class ExportManager {
|
|
|
21
22
|
return buildTourConfig(config, scenes);
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Load tour data from JSON (inverse of generateJSON)
|
|
27
|
+
* This loads the entire tour configuration including initialScene and all scenes
|
|
28
|
+
* @param {Object} tourData - Tour configuration object with initialScene and scenes
|
|
29
|
+
* @param {string} tourData.initialScene - Initial scene ID
|
|
30
|
+
* @param {Array} tourData.scenes - Array of scene objects
|
|
31
|
+
* @returns {boolean} Success status
|
|
32
|
+
*/
|
|
33
|
+
loadJSON(tourData) {
|
|
34
|
+
try {
|
|
35
|
+
if (!tourData || typeof tourData !== 'object') {
|
|
36
|
+
console.error('Invalid tour data: expected object');
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Load scenes into scene manager
|
|
41
|
+
const scenes = tourData.scenes || [];
|
|
42
|
+
this.editor.sceneManager.loadScenes(scenes);
|
|
43
|
+
|
|
44
|
+
// Set initial scene in config
|
|
45
|
+
if (tourData.initialScene) {
|
|
46
|
+
this.editor.config.initialSceneId = tourData.initialScene;
|
|
47
|
+
} else if (scenes.length > 0) {
|
|
48
|
+
this.editor.config.initialSceneId = scenes[0].id;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Mark as having unsaved changes
|
|
52
|
+
this.editor.hasUnsavedChanges = true;
|
|
53
|
+
|
|
54
|
+
// Re-render the editor UI
|
|
55
|
+
this.editor.render();
|
|
56
|
+
|
|
57
|
+
showToast('Tour loaded successfully', 'success');
|
|
58
|
+
|
|
59
|
+
// Emit event
|
|
60
|
+
this.editor.emit(EditorEvents.PROJECT_LOAD, { tourData, source: 'loadJSON' });
|
|
61
|
+
|
|
62
|
+
return true;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Failed to load tour data:', error);
|
|
65
|
+
showToast('Failed to load tour', 'error');
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
24
70
|
/**
|
|
25
71
|
* Generate JSON with icons baked in as SVG data URLs
|
|
26
72
|
* This ensures the exported HTML doesn't need the SenangStart icons library
|