senangwebs-tour 1.0.8 → 1.0.10
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 +437 -35
- package/dist/swt-editor.js.map +1 -1
- package/dist/swt-editor.min.js +1 -1
- package/dist/swt.js +1 -2
- package/dist/swt.js.map +1 -1
- package/dist/swt.min.js +1 -1
- package/package.json +1 -1
- package/src/editor/editor-entry.js +2 -0
- package/src/editor/js/editor.js +96 -0
- package/src/editor/js/event-emitter.js +263 -0
- package/src/editor/js/export-manager.js +47 -1
- package/src/editor/js/ui-controller.js +1 -1
- package/src/index.js +1 -2
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import PreviewController from './js/preview-controller.js';
|
|
|
13
13
|
import UIController from './js/ui-controller.js';
|
|
14
14
|
import ExportManager from './js/export-manager.js';
|
|
15
15
|
import TourEditor from './js/editor.js';
|
|
16
|
+
import EditorEvents from './js/event-emitter.js';
|
|
16
17
|
|
|
17
18
|
// Attach classes to window for global access
|
|
18
19
|
window.ProjectStorageManager = ProjectStorageManager;
|
|
@@ -22,6 +23,7 @@ window.PreviewController = PreviewController;
|
|
|
22
23
|
window.UIController = UIController;
|
|
23
24
|
window.ExportManager = ExportManager;
|
|
24
25
|
window.TourEditor = TourEditor;
|
|
26
|
+
window.EditorEvents = EditorEvents;
|
|
25
27
|
|
|
26
28
|
// Import and execute UI initialization
|
|
27
29
|
import './js/ui-init.js';
|
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
|
|
|
@@ -212,6 +257,7 @@ class TourEditor {
|
|
|
212
257
|
document.getElementById('tourTitle')?.addEventListener('input', debounce((e) => {
|
|
213
258
|
this.config.title = e.target.value;
|
|
214
259
|
this.markUnsavedChanges();
|
|
260
|
+
this.emit(EditorEvents.TOUR_TITLE_CHANGE, { title: e.target.value });
|
|
215
261
|
const projectName = document.getElementById('project-name');
|
|
216
262
|
if (projectName && projectName.value !== e.target.value) {
|
|
217
263
|
projectName.value = e.target.value;
|
|
@@ -221,6 +267,7 @@ class TourEditor {
|
|
|
221
267
|
document.getElementById('project-name')?.addEventListener('input', debounce((e) => {
|
|
222
268
|
this.config.title = e.target.value;
|
|
223
269
|
this.markUnsavedChanges();
|
|
270
|
+
this.emit(EditorEvents.TOUR_TITLE_CHANGE, { title: e.target.value });
|
|
224
271
|
const tourTitle = document.getElementById('tourTitle');
|
|
225
272
|
if (tourTitle && tourTitle.value !== e.target.value) {
|
|
226
273
|
tourTitle.value = e.target.value;
|
|
@@ -230,11 +277,13 @@ class TourEditor {
|
|
|
230
277
|
document.getElementById('tourDescription')?.addEventListener('input', debounce((e) => {
|
|
231
278
|
this.config.description = e.target.value;
|
|
232
279
|
this.markUnsavedChanges();
|
|
280
|
+
this.emit(EditorEvents.TOUR_DESCRIPTION_CHANGE, { description: e.target.value });
|
|
233
281
|
}, 300));
|
|
234
282
|
|
|
235
283
|
document.getElementById('tourInitialScene')?.addEventListener('change', (e) => {
|
|
236
284
|
this.config.initialSceneId = e.target.value;
|
|
237
285
|
this.markUnsavedChanges();
|
|
286
|
+
this.emit(EditorEvents.INITIAL_SCENE_CHANGE, { initialSceneId: e.target.value });
|
|
238
287
|
});
|
|
239
288
|
|
|
240
289
|
document.getElementById('exportJsonBtn')?.addEventListener('click', () => {
|
|
@@ -294,6 +343,9 @@ class TourEditor {
|
|
|
294
343
|
}
|
|
295
344
|
|
|
296
345
|
const scene = await this.sceneManager.addScene(file);
|
|
346
|
+
if (scene) {
|
|
347
|
+
this.emit(EditorEvents.SCENE_ADD, { scene, file });
|
|
348
|
+
}
|
|
297
349
|
}
|
|
298
350
|
this.uiController.setLoading(false);
|
|
299
351
|
this.render();
|
|
@@ -327,6 +379,11 @@ class TourEditor {
|
|
|
327
379
|
this.lastRenderedSceneIndex = -1;
|
|
328
380
|
this.render();
|
|
329
381
|
this.markUnsavedChanges();
|
|
382
|
+
this.emit(EditorEvents.HOTSPOT_ADD, {
|
|
383
|
+
hotspot,
|
|
384
|
+
position,
|
|
385
|
+
sceneId: this.sceneManager.getCurrentScene()?.id
|
|
386
|
+
});
|
|
330
387
|
} else {
|
|
331
388
|
console.error('Failed to add hotspot');
|
|
332
389
|
}
|
|
@@ -372,6 +429,8 @@ class TourEditor {
|
|
|
372
429
|
this.uiController.updateHotspotProperties(null);
|
|
373
430
|
this.uiController.updateInitialSceneOptions();
|
|
374
431
|
this.uiController.updateTargetSceneOptions();
|
|
432
|
+
|
|
433
|
+
this.emit(EditorEvents.SCENE_SELECT, { scene, index });
|
|
375
434
|
}
|
|
376
435
|
}
|
|
377
436
|
|
|
@@ -390,6 +449,8 @@ class TourEditor {
|
|
|
390
449
|
if (hotspot) {
|
|
391
450
|
this.previewController.pointCameraToHotspot(hotspot);
|
|
392
451
|
}
|
|
452
|
+
|
|
453
|
+
this.emit(EditorEvents.HOTSPOT_SELECT, { hotspot, index });
|
|
393
454
|
}
|
|
394
455
|
}
|
|
395
456
|
|
|
@@ -397,9 +458,11 @@ class TourEditor {
|
|
|
397
458
|
* Remove scene
|
|
398
459
|
*/
|
|
399
460
|
removeScene(index) {
|
|
461
|
+
const scene = this.sceneManager.getScene(index);
|
|
400
462
|
if (this.sceneManager.removeScene(index)) {
|
|
401
463
|
this.render();
|
|
402
464
|
this.markUnsavedChanges();
|
|
465
|
+
this.emit(EditorEvents.SCENE_REMOVE, { scene, index });
|
|
403
466
|
}
|
|
404
467
|
}
|
|
405
468
|
|
|
@@ -407,10 +470,12 @@ class TourEditor {
|
|
|
407
470
|
* Remove hotspot
|
|
408
471
|
*/
|
|
409
472
|
removeHotspot(index) {
|
|
473
|
+
const hotspot = this.hotspotEditor.getHotspot(index);
|
|
410
474
|
if (this.hotspotEditor.removeHotspot(index)) {
|
|
411
475
|
this.lastRenderedSceneIndex = -1;
|
|
412
476
|
this.render();
|
|
413
477
|
this.markUnsavedChanges();
|
|
478
|
+
this.emit(EditorEvents.HOTSPOT_REMOVE, { hotspot, index });
|
|
414
479
|
}
|
|
415
480
|
}
|
|
416
481
|
|
|
@@ -423,6 +488,7 @@ class TourEditor {
|
|
|
423
488
|
this.lastRenderedSceneIndex = -1;
|
|
424
489
|
this.render();
|
|
425
490
|
this.markUnsavedChanges();
|
|
491
|
+
this.emit(EditorEvents.HOTSPOT_DUPLICATE, { hotspot, originalIndex: index });
|
|
426
492
|
}
|
|
427
493
|
}
|
|
428
494
|
|
|
@@ -433,6 +499,7 @@ class TourEditor {
|
|
|
433
499
|
if (this.sceneManager.reorderScenes(fromIndex, toIndex)) {
|
|
434
500
|
this.render();
|
|
435
501
|
this.markUnsavedChanges();
|
|
502
|
+
this.emit(EditorEvents.SCENE_REORDER, { fromIndex, toIndex });
|
|
436
503
|
}
|
|
437
504
|
}
|
|
438
505
|
|
|
@@ -445,6 +512,12 @@ class TourEditor {
|
|
|
445
512
|
await this.previewController.updateHotspotMarker(index);
|
|
446
513
|
this.uiController.renderHotspotList();
|
|
447
514
|
this.markUnsavedChanges();
|
|
515
|
+
this.emit(EditorEvents.HOTSPOT_UPDATE, {
|
|
516
|
+
hotspot: this.hotspotEditor.getHotspot(index),
|
|
517
|
+
index,
|
|
518
|
+
property,
|
|
519
|
+
value
|
|
520
|
+
});
|
|
448
521
|
}
|
|
449
522
|
}
|
|
450
523
|
|
|
@@ -481,6 +554,13 @@ class TourEditor {
|
|
|
481
554
|
await this.previewController.updateHotspotMarker(index);
|
|
482
555
|
this.uiController.renderHotspotList();
|
|
483
556
|
this.markUnsavedChanges();
|
|
557
|
+
this.emit(EditorEvents.HOTSPOT_POSITION_CHANGE, {
|
|
558
|
+
hotspot,
|
|
559
|
+
index,
|
|
560
|
+
axis,
|
|
561
|
+
value,
|
|
562
|
+
position: hotspot.position
|
|
563
|
+
});
|
|
484
564
|
}
|
|
485
565
|
}
|
|
486
566
|
|
|
@@ -492,6 +572,12 @@ class TourEditor {
|
|
|
492
572
|
if (this.sceneManager.updateScene(index, property, value)) {
|
|
493
573
|
this.uiController.renderSceneList();
|
|
494
574
|
this.markUnsavedChanges();
|
|
575
|
+
this.emit(EditorEvents.SCENE_UPDATE, {
|
|
576
|
+
scene: this.sceneManager.getScene(index),
|
|
577
|
+
index,
|
|
578
|
+
property,
|
|
579
|
+
value
|
|
580
|
+
});
|
|
495
581
|
}
|
|
496
582
|
}
|
|
497
583
|
|
|
@@ -518,6 +604,7 @@ class TourEditor {
|
|
|
518
604
|
showToast('Scene image updated', 'success');
|
|
519
605
|
}
|
|
520
606
|
this.markUnsavedChanges();
|
|
607
|
+
this.emit(EditorEvents.SCENE_IMAGE_CHANGE, { scene, index, imageUrl });
|
|
521
608
|
}
|
|
522
609
|
}
|
|
523
610
|
|
|
@@ -545,6 +632,7 @@ class TourEditor {
|
|
|
545
632
|
this.uiController.updateSceneProperties(scene);
|
|
546
633
|
this.markUnsavedChanges();
|
|
547
634
|
showToast('Starting position set', 'success');
|
|
635
|
+
this.emit(EditorEvents.SCENE_STARTING_POSITION_SET, { scene, startingPosition: scene.startingPosition });
|
|
548
636
|
}
|
|
549
637
|
|
|
550
638
|
/**
|
|
@@ -562,6 +650,7 @@ class TourEditor {
|
|
|
562
650
|
this.uiController.updateSceneProperties(scene);
|
|
563
651
|
this.markUnsavedChanges();
|
|
564
652
|
showToast('Starting position cleared', 'success');
|
|
653
|
+
this.emit(EditorEvents.SCENE_STARTING_POSITION_CLEAR, { scene });
|
|
565
654
|
}
|
|
566
655
|
|
|
567
656
|
/**
|
|
@@ -603,6 +692,8 @@ class TourEditor {
|
|
|
603
692
|
}
|
|
604
693
|
this.lastRenderedSceneIndex = -1;
|
|
605
694
|
}
|
|
695
|
+
|
|
696
|
+
this.emit(EditorEvents.UI_RENDER);
|
|
606
697
|
}
|
|
607
698
|
|
|
608
699
|
/**
|
|
@@ -617,6 +708,7 @@ class TourEditor {
|
|
|
617
708
|
if (this.storageManager.saveProject(projectData)) {
|
|
618
709
|
this.hasUnsavedChanges = false;
|
|
619
710
|
showToast('Project saved', 'success');
|
|
711
|
+
this.emit(EditorEvents.PROJECT_SAVE, { projectData });
|
|
620
712
|
return true;
|
|
621
713
|
}
|
|
622
714
|
return false;
|
|
@@ -633,6 +725,7 @@ class TourEditor {
|
|
|
633
725
|
this.hasUnsavedChanges = false;
|
|
634
726
|
this.render();
|
|
635
727
|
showToast('Project loaded', 'success');
|
|
728
|
+
this.emit(EditorEvents.PROJECT_LOAD, { projectData });
|
|
636
729
|
return true;
|
|
637
730
|
}
|
|
638
731
|
return false;
|
|
@@ -659,6 +752,7 @@ class TourEditor {
|
|
|
659
752
|
this.render();
|
|
660
753
|
|
|
661
754
|
showToast('New project created', 'success');
|
|
755
|
+
this.emit(EditorEvents.PROJECT_NEW, { config: this.config });
|
|
662
756
|
return true;
|
|
663
757
|
}
|
|
664
758
|
|
|
@@ -689,6 +783,7 @@ const importUpload = document.getElementById('importUpload');
|
|
|
689
783
|
this.uiController.setLoading(false);
|
|
690
784
|
|
|
691
785
|
showToast('Project imported successfully', 'success');
|
|
786
|
+
this.emit(EditorEvents.PROJECT_IMPORT, { projectData, file });
|
|
692
787
|
} catch (error) {
|
|
693
788
|
this.uiController.setLoading(false);
|
|
694
789
|
console.error('Import failed:', error);
|
|
@@ -714,3 +809,4 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
714
809
|
});
|
|
715
810
|
|
|
716
811
|
export default TourEditor;
|
|
812
|
+
export { EditorEvents } from './event-emitter.js';
|
|
@@ -0,0 +1,263 @@
|
|
|
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/Tour events
|
|
47
|
+
CONFIG_UPDATE: 'config:update',
|
|
48
|
+
INITIAL_SCENE_CHANGE: 'config:initialSceneChange',
|
|
49
|
+
TOUR_TITLE_CHANGE: 'tour:titleChange',
|
|
50
|
+
TOUR_DESCRIPTION_CHANGE: 'tour:descriptionChange',
|
|
51
|
+
|
|
52
|
+
// Preview events
|
|
53
|
+
PREVIEW_START: 'preview:start',
|
|
54
|
+
PREVIEW_STOP: 'preview:stop',
|
|
55
|
+
PREVIEW_SCENE_CHANGE: 'preview:sceneChange',
|
|
56
|
+
|
|
57
|
+
// UI events
|
|
58
|
+
UI_RENDER: 'ui:render',
|
|
59
|
+
UI_LOADING_START: 'ui:loadingStart',
|
|
60
|
+
UI_LOADING_END: 'ui:loadingEnd',
|
|
61
|
+
MODAL_OPEN: 'ui:modalOpen',
|
|
62
|
+
MODAL_CLOSE: 'ui:modalClose',
|
|
63
|
+
|
|
64
|
+
// Data events
|
|
65
|
+
DATA_CHANGE: 'data:change', // Fires when any data changes
|
|
66
|
+
UNSAVED_CHANGES: 'data:unsavedChanges',
|
|
67
|
+
|
|
68
|
+
// Unified change event - fires for ANY modification
|
|
69
|
+
CHANGE: 'change'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Event Emitter class
|
|
74
|
+
* Provides pub/sub functionality for editor events
|
|
75
|
+
*/
|
|
76
|
+
class EventEmitter {
|
|
77
|
+
constructor() {
|
|
78
|
+
this.listeners = new Map();
|
|
79
|
+
this.onceListeners = new Map();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register an event listener
|
|
84
|
+
* @param {string} event - Event name or 'change' for all changes
|
|
85
|
+
* @param {Function} callback - Function to call when event fires
|
|
86
|
+
* @returns {Function} Unsubscribe function
|
|
87
|
+
*/
|
|
88
|
+
on(event, callback) {
|
|
89
|
+
if (!this.listeners.has(event)) {
|
|
90
|
+
this.listeners.set(event, new Set());
|
|
91
|
+
}
|
|
92
|
+
this.listeners.get(event).add(callback);
|
|
93
|
+
|
|
94
|
+
// Return unsubscribe function
|
|
95
|
+
return () => this.off(event, callback);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Register a one-time event listener
|
|
100
|
+
* @param {string} event - Event name
|
|
101
|
+
* @param {Function} callback - Function to call once when event fires
|
|
102
|
+
* @returns {Function} Unsubscribe function
|
|
103
|
+
*/
|
|
104
|
+
once(event, callback) {
|
|
105
|
+
if (!this.onceListeners.has(event)) {
|
|
106
|
+
this.onceListeners.set(event, new Set());
|
|
107
|
+
}
|
|
108
|
+
this.onceListeners.get(event).add(callback);
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
if (this.onceListeners.has(event)) {
|
|
112
|
+
this.onceListeners.get(event).delete(callback);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Remove an event listener
|
|
119
|
+
* @param {string} event - Event name
|
|
120
|
+
* @param {Function} callback - Function to remove
|
|
121
|
+
*/
|
|
122
|
+
off(event, callback) {
|
|
123
|
+
if (this.listeners.has(event)) {
|
|
124
|
+
this.listeners.get(event).delete(callback);
|
|
125
|
+
}
|
|
126
|
+
if (this.onceListeners.has(event)) {
|
|
127
|
+
this.onceListeners.get(event).delete(callback);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove all listeners for an event, or all listeners if no event specified
|
|
133
|
+
* @param {string} [event] - Optional event name
|
|
134
|
+
*/
|
|
135
|
+
removeAllListeners(event) {
|
|
136
|
+
if (event) {
|
|
137
|
+
this.listeners.delete(event);
|
|
138
|
+
this.onceListeners.delete(event);
|
|
139
|
+
} else {
|
|
140
|
+
this.listeners.clear();
|
|
141
|
+
this.onceListeners.clear();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Emit an event
|
|
147
|
+
* @param {string} event - Event name
|
|
148
|
+
* @param {Object} data - Event data
|
|
149
|
+
*/
|
|
150
|
+
emit(event, data = {}) {
|
|
151
|
+
const eventData = {
|
|
152
|
+
type: event,
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
...data
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Call specific event listeners
|
|
158
|
+
if (this.listeners.has(event)) {
|
|
159
|
+
this.listeners.get(event).forEach(callback => {
|
|
160
|
+
try {
|
|
161
|
+
callback(eventData);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error(`Error in event listener for "${event}":`, error);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Call once listeners and remove them
|
|
169
|
+
if (this.onceListeners.has(event)) {
|
|
170
|
+
const onceCallbacks = this.onceListeners.get(event);
|
|
171
|
+
this.onceListeners.delete(event);
|
|
172
|
+
onceCallbacks.forEach(callback => {
|
|
173
|
+
try {
|
|
174
|
+
callback(eventData);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`Error in once listener for "${event}":`, error);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Also emit to wildcard listeners (namespace:*)
|
|
182
|
+
const namespace = event.split(':')[0];
|
|
183
|
+
const wildcardEvent = `${namespace}:*`;
|
|
184
|
+
if (this.listeners.has(wildcardEvent)) {
|
|
185
|
+
this.listeners.get(wildcardEvent).forEach(callback => {
|
|
186
|
+
try {
|
|
187
|
+
callback(eventData);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error(`Error in wildcard listener for "${wildcardEvent}":`, error);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Emit unified 'change' event for data-modifying events
|
|
195
|
+
if (this.isDataModifyingEvent(event) && event !== EditorEvents.CHANGE) {
|
|
196
|
+
this.emit(EditorEvents.CHANGE, {
|
|
197
|
+
originalEvent: event,
|
|
198
|
+
...data
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if an event modifies data (should trigger 'change' event)
|
|
205
|
+
* @param {string} event - Event name
|
|
206
|
+
* @returns {boolean}
|
|
207
|
+
*/
|
|
208
|
+
isDataModifyingEvent(event) {
|
|
209
|
+
const dataEvents = [
|
|
210
|
+
EditorEvents.SCENE_ADD,
|
|
211
|
+
EditorEvents.SCENE_REMOVE,
|
|
212
|
+
EditorEvents.SCENE_SELECT,
|
|
213
|
+
EditorEvents.SCENE_UPDATE,
|
|
214
|
+
EditorEvents.SCENE_REORDER,
|
|
215
|
+
EditorEvents.SCENE_CLEAR,
|
|
216
|
+
EditorEvents.SCENE_IMAGE_CHANGE,
|
|
217
|
+
EditorEvents.SCENE_STARTING_POSITION_SET,
|
|
218
|
+
EditorEvents.SCENE_STARTING_POSITION_CLEAR,
|
|
219
|
+
EditorEvents.HOTSPOT_ADD,
|
|
220
|
+
EditorEvents.HOTSPOT_REMOVE,
|
|
221
|
+
EditorEvents.HOTSPOT_SELECT,
|
|
222
|
+
EditorEvents.HOTSPOT_UPDATE,
|
|
223
|
+
EditorEvents.HOTSPOT_DUPLICATE,
|
|
224
|
+
EditorEvents.HOTSPOT_POSITION_CHANGE,
|
|
225
|
+
EditorEvents.CONFIG_UPDATE,
|
|
226
|
+
EditorEvents.INITIAL_SCENE_CHANGE,
|
|
227
|
+
EditorEvents.TOUR_TITLE_CHANGE,
|
|
228
|
+
EditorEvents.TOUR_DESCRIPTION_CHANGE,
|
|
229
|
+
EditorEvents.PROJECT_LOAD,
|
|
230
|
+
EditorEvents.PROJECT_IMPORT,
|
|
231
|
+
EditorEvents.PROJECT_NEW,
|
|
232
|
+
EditorEvents.DATA_CHANGE
|
|
233
|
+
];
|
|
234
|
+
return dataEvents.includes(event);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get the number of listeners for an event
|
|
239
|
+
* @param {string} event - Event name
|
|
240
|
+
* @returns {number}
|
|
241
|
+
*/
|
|
242
|
+
listenerCount(event) {
|
|
243
|
+
let count = 0;
|
|
244
|
+
if (this.listeners.has(event)) {
|
|
245
|
+
count += this.listeners.get(event).size;
|
|
246
|
+
}
|
|
247
|
+
if (this.onceListeners.has(event)) {
|
|
248
|
+
count += this.onceListeners.get(event).size;
|
|
249
|
+
}
|
|
250
|
+
return count;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get all event names that have listeners
|
|
255
|
+
* @returns {string[]}
|
|
256
|
+
*/
|
|
257
|
+
eventNames() {
|
|
258
|
+
const names = new Set([...this.listeners.keys(), ...this.onceListeners.keys()]);
|
|
259
|
+
return Array.from(names);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
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
|
package/src/index.js
CHANGED
|
@@ -92,8 +92,7 @@ class Tour {
|
|
|
92
92
|
let cursor = camera.querySelector("[cursor]");
|
|
93
93
|
if (!cursor) {
|
|
94
94
|
cursor = document.createElement("a-cursor");
|
|
95
|
-
cursor.setAttribute("fuse", "
|
|
96
|
-
cursor.setAttribute("fuse-timeout", "1500");
|
|
95
|
+
cursor.setAttribute("fuse", "false");
|
|
97
96
|
camera.appendChild(cursor);
|
|
98
97
|
}
|
|
99
98
|
}
|