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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senangwebs-tour",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "description": "VR 360° virtual tour system with visual editor and viewer library",
6
6
  "main": "dist/swt.js",
@@ -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';
@@ -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
@@ -325,7 +325,7 @@ class UIController {
325
325
  grid.appendChild(btn);
326
326
  });
327
327
 
328
- console.log(`Loaded ${iconsData.length} icons`);
328
+ // console.log(`Loaded ${iconsData.length} icons`);
329
329
  }
330
330
 
331
331
  /**
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", "true");
96
- cursor.setAttribute("fuse-timeout", "1500");
95
+ cursor.setAttribute("fuse", "false");
97
96
  camera.appendChild(cursor);
98
97
  }
99
98
  }