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.
@@ -79,7 +79,7 @@
79
79
  /**
80
80
  * Show toast notification
81
81
  */
82
- function showToast$1(message, type = 'info', duration = 3000) {
82
+ function showToast(message, type = 'info', duration = 3000) {
83
83
  const toast = document.getElementById('toast');
84
84
  toast.textContent = message;
85
85
  toast.className = `toast ${type}`;
@@ -218,7 +218,7 @@
218
218
  positionToString: positionToString,
219
219
  sanitizeId: sanitizeId$1,
220
220
  showModal: showModal,
221
- showToast: showToast$1
221
+ showToast: showToast
222
222
  });
223
223
 
224
224
  // Storage Manager - Handles LocalStorage operations
@@ -245,7 +245,7 @@
245
245
  } catch (error) {
246
246
  console.error("Failed to save project:", error);
247
247
  if (error.name === "QuotaExceededError") {
248
- showToast$1("Storage quota exceeded. Project too large!", "error");
248
+ showToast("Storage quota exceeded. Project too large!", "error");
249
249
  }
250
250
  return false;
251
251
  }
@@ -271,7 +271,7 @@
271
271
  return null;
272
272
  } catch (error) {
273
273
  console.error("Failed to load project:", error);
274
- showToast$1("Failed to load project", "error");
274
+ showToast("Failed to load project", "error");
275
275
  return null;
276
276
  }
277
277
  }
@@ -380,7 +380,7 @@
380
380
  return true;
381
381
  } catch (error) {
382
382
  console.error("Failed to export project:", error);
383
- showToast$1("Failed to export project", "error");
383
+ showToast("Failed to export project", "error");
384
384
  return false;
385
385
  }
386
386
  }
@@ -398,14 +398,14 @@
398
398
  resolve(projectData);
399
399
  } catch (error) {
400
400
  console.error("Failed to parse project file:", error);
401
- showToast$1("Invalid project file", "error");
401
+ showToast("Invalid project file", "error");
402
402
  reject(error);
403
403
  }
404
404
  };
405
405
 
406
406
  reader.onerror = () => {
407
407
  console.error("Failed to read file:", reader.error);
408
- showToast$1("Failed to read file", "error");
408
+ showToast("Failed to read file", "error");
409
409
  reject(reader.error);
410
410
  };
411
411
 
@@ -465,11 +465,11 @@
465
465
 
466
466
  this.scenes.push(scene);
467
467
  this.currentSceneIndex = this.scenes.length - 1;
468
- showToast$1(`Scene "${scene.name}" added successfully`, "success");
468
+ showToast(`Scene "${scene.name}" added successfully`, "success");
469
469
  return scene;
470
470
  } catch (error) {
471
471
  console.error("Failed to add scene:", error);
472
- showToast$1("Failed to add scene", "error");
472
+ showToast("Failed to add scene", "error");
473
473
  return null;
474
474
  }
475
475
  }
@@ -498,7 +498,7 @@
498
498
  this.currentSceneIndex--;
499
499
  }
500
500
 
501
- showToast$1(`Scene "${scene.name}" removed`, "success");
501
+ showToast(`Scene "${scene.name}" removed`, "success");
502
502
  return true;
503
503
  }
504
504
  return false;
@@ -648,7 +648,7 @@
648
648
  addHotspot(position, targetSceneId = '', cameraOrientation = null) {
649
649
  const scene = this.editor.sceneManager.getCurrentScene();
650
650
  if (!scene) {
651
- showToast$1('No scene selected', 'error');
651
+ showToast('No scene selected', 'error');
652
652
  return null;
653
653
  }
654
654
 
@@ -675,7 +675,7 @@
675
675
  scene.hotspots.push(hotspot);
676
676
  this.currentHotspotIndex = scene.hotspots.length - 1;
677
677
 
678
- showToast$1('Hotspot added', 'success');
678
+ showToast('Hotspot added', 'success');
679
679
 
680
680
  return hotspot;
681
681
  }
@@ -702,7 +702,7 @@
702
702
  this.currentHotspotIndex--;
703
703
  }
704
704
 
705
- showToast$1('Hotspot removed', 'success');
705
+ showToast('Hotspot removed', 'success');
706
706
  return true;
707
707
  }
708
708
 
@@ -816,7 +816,7 @@
816
816
  scene.hotspots.push(duplicate);
817
817
  this.currentHotspotIndex = scene.hotspots.length - 1;
818
818
 
819
- showToast$1('Hotspot duplicated', 'success');
819
+ showToast('Hotspot duplicated', 'success');
820
820
  return duplicate;
821
821
  }
822
822
 
@@ -840,7 +840,7 @@
840
840
  scene.hotspots = [];
841
841
  this.currentHotspotIndex = -1;
842
842
 
843
- showToast$1('All hotspots removed', 'success');
843
+ showToast('All hotspots removed', 'success');
844
844
  return true;
845
845
  }
846
846
  };
@@ -1066,7 +1066,7 @@
1066
1066
  }
1067
1067
  } catch (error) {
1068
1068
  console.error("Failed to load preview:", error);
1069
- showToast$1("Failed to load preview: " + error.message, "error");
1069
+ showToast("Failed to load preview: " + error.message, "error");
1070
1070
  // Hide loading on error
1071
1071
  this.hideLoading();
1072
1072
  }
@@ -4313,7 +4313,7 @@
4313
4313
  grid.appendChild(btn);
4314
4314
  });
4315
4315
 
4316
- console.log(`Loaded ${iconsData.length} icons`);
4316
+ // console.log(`Loaded ${iconsData.length} icons`);
4317
4317
  }
4318
4318
 
4319
4319
  /**
@@ -4624,6 +4624,268 @@
4624
4624
  }
4625
4625
  }
4626
4626
 
4627
+ /**
4628
+ * Event Emitter for TourEditor
4629
+ *
4630
+ * Provides a comprehensive event system for the editor with:
4631
+ * - Specific events for all editor operations
4632
+ * - A unified 'change' event that fires for any modification
4633
+ * - Support for wildcards and namespaced events
4634
+ */
4635
+
4636
+ /**
4637
+ * Event types for the Tour Editor
4638
+ * These are the available events that can be listened to
4639
+ */
4640
+ const EditorEvents = {
4641
+ // Lifecycle
4642
+ INIT: 'init',
4643
+ READY: 'ready',
4644
+ DESTROY: 'destroy',
4645
+
4646
+ // Scene events
4647
+ SCENE_ADD: 'scene:add',
4648
+ SCENE_REMOVE: 'scene:remove',
4649
+ SCENE_SELECT: 'scene:select',
4650
+ SCENE_UPDATE: 'scene:update',
4651
+ SCENE_REORDER: 'scene:reorder',
4652
+ SCENE_CLEAR: 'scene:clear',
4653
+ SCENE_IMAGE_CHANGE: 'scene:imageChange',
4654
+ SCENE_STARTING_POSITION_SET: 'scene:startingPositionSet',
4655
+ SCENE_STARTING_POSITION_CLEAR: 'scene:startingPositionClear',
4656
+
4657
+ // Hotspot events
4658
+ HOTSPOT_ADD: 'hotspot:add',
4659
+ HOTSPOT_REMOVE: 'hotspot:remove',
4660
+ HOTSPOT_SELECT: 'hotspot:select',
4661
+ HOTSPOT_UPDATE: 'hotspot:update',
4662
+ HOTSPOT_DUPLICATE: 'hotspot:duplicate',
4663
+ HOTSPOT_POSITION_CHANGE: 'hotspot:positionChange',
4664
+
4665
+ // Project events
4666
+ PROJECT_NEW: 'project:new',
4667
+ PROJECT_SAVE: 'project:save',
4668
+ PROJECT_LOAD: 'project:load',
4669
+ PROJECT_IMPORT: 'project:import',
4670
+ PROJECT_EXPORT: 'project:export',
4671
+
4672
+ // Config/Tour events
4673
+ CONFIG_UPDATE: 'config:update',
4674
+ INITIAL_SCENE_CHANGE: 'config:initialSceneChange',
4675
+ TOUR_TITLE_CHANGE: 'tour:titleChange',
4676
+ TOUR_DESCRIPTION_CHANGE: 'tour:descriptionChange',
4677
+
4678
+ // Preview events
4679
+ PREVIEW_START: 'preview:start',
4680
+ PREVIEW_STOP: 'preview:stop',
4681
+ PREVIEW_SCENE_CHANGE: 'preview:sceneChange',
4682
+
4683
+ // UI events
4684
+ UI_RENDER: 'ui:render',
4685
+ UI_LOADING_START: 'ui:loadingStart',
4686
+ UI_LOADING_END: 'ui:loadingEnd',
4687
+ MODAL_OPEN: 'ui:modalOpen',
4688
+ MODAL_CLOSE: 'ui:modalClose',
4689
+
4690
+ // Data events
4691
+ DATA_CHANGE: 'data:change', // Fires when any data changes
4692
+ UNSAVED_CHANGES: 'data:unsavedChanges',
4693
+
4694
+ // Unified change event - fires for ANY modification
4695
+ CHANGE: 'change'
4696
+ };
4697
+
4698
+ /**
4699
+ * Event Emitter class
4700
+ * Provides pub/sub functionality for editor events
4701
+ */
4702
+ class EventEmitter {
4703
+ constructor() {
4704
+ this.listeners = new Map();
4705
+ this.onceListeners = new Map();
4706
+ }
4707
+
4708
+ /**
4709
+ * Register an event listener
4710
+ * @param {string} event - Event name or 'change' for all changes
4711
+ * @param {Function} callback - Function to call when event fires
4712
+ * @returns {Function} Unsubscribe function
4713
+ */
4714
+ on(event, callback) {
4715
+ if (!this.listeners.has(event)) {
4716
+ this.listeners.set(event, new Set());
4717
+ }
4718
+ this.listeners.get(event).add(callback);
4719
+
4720
+ // Return unsubscribe function
4721
+ return () => this.off(event, callback);
4722
+ }
4723
+
4724
+ /**
4725
+ * Register a one-time event listener
4726
+ * @param {string} event - Event name
4727
+ * @param {Function} callback - Function to call once when event fires
4728
+ * @returns {Function} Unsubscribe function
4729
+ */
4730
+ once(event, callback) {
4731
+ if (!this.onceListeners.has(event)) {
4732
+ this.onceListeners.set(event, new Set());
4733
+ }
4734
+ this.onceListeners.get(event).add(callback);
4735
+
4736
+ return () => {
4737
+ if (this.onceListeners.has(event)) {
4738
+ this.onceListeners.get(event).delete(callback);
4739
+ }
4740
+ };
4741
+ }
4742
+
4743
+ /**
4744
+ * Remove an event listener
4745
+ * @param {string} event - Event name
4746
+ * @param {Function} callback - Function to remove
4747
+ */
4748
+ off(event, callback) {
4749
+ if (this.listeners.has(event)) {
4750
+ this.listeners.get(event).delete(callback);
4751
+ }
4752
+ if (this.onceListeners.has(event)) {
4753
+ this.onceListeners.get(event).delete(callback);
4754
+ }
4755
+ }
4756
+
4757
+ /**
4758
+ * Remove all listeners for an event, or all listeners if no event specified
4759
+ * @param {string} [event] - Optional event name
4760
+ */
4761
+ removeAllListeners(event) {
4762
+ if (event) {
4763
+ this.listeners.delete(event);
4764
+ this.onceListeners.delete(event);
4765
+ } else {
4766
+ this.listeners.clear();
4767
+ this.onceListeners.clear();
4768
+ }
4769
+ }
4770
+
4771
+ /**
4772
+ * Emit an event
4773
+ * @param {string} event - Event name
4774
+ * @param {Object} data - Event data
4775
+ */
4776
+ emit(event, data = {}) {
4777
+ const eventData = {
4778
+ type: event,
4779
+ timestamp: Date.now(),
4780
+ ...data
4781
+ };
4782
+
4783
+ // Call specific event listeners
4784
+ if (this.listeners.has(event)) {
4785
+ this.listeners.get(event).forEach(callback => {
4786
+ try {
4787
+ callback(eventData);
4788
+ } catch (error) {
4789
+ console.error(`Error in event listener for "${event}":`, error);
4790
+ }
4791
+ });
4792
+ }
4793
+
4794
+ // Call once listeners and remove them
4795
+ if (this.onceListeners.has(event)) {
4796
+ const onceCallbacks = this.onceListeners.get(event);
4797
+ this.onceListeners.delete(event);
4798
+ onceCallbacks.forEach(callback => {
4799
+ try {
4800
+ callback(eventData);
4801
+ } catch (error) {
4802
+ console.error(`Error in once listener for "${event}":`, error);
4803
+ }
4804
+ });
4805
+ }
4806
+
4807
+ // Also emit to wildcard listeners (namespace:*)
4808
+ const namespace = event.split(':')[0];
4809
+ const wildcardEvent = `${namespace}:*`;
4810
+ if (this.listeners.has(wildcardEvent)) {
4811
+ this.listeners.get(wildcardEvent).forEach(callback => {
4812
+ try {
4813
+ callback(eventData);
4814
+ } catch (error) {
4815
+ console.error(`Error in wildcard listener for "${wildcardEvent}":`, error);
4816
+ }
4817
+ });
4818
+ }
4819
+
4820
+ // Emit unified 'change' event for data-modifying events
4821
+ if (this.isDataModifyingEvent(event) && event !== EditorEvents.CHANGE) {
4822
+ this.emit(EditorEvents.CHANGE, {
4823
+ originalEvent: event,
4824
+ ...data
4825
+ });
4826
+ }
4827
+ }
4828
+
4829
+ /**
4830
+ * Check if an event modifies data (should trigger 'change' event)
4831
+ * @param {string} event - Event name
4832
+ * @returns {boolean}
4833
+ */
4834
+ isDataModifyingEvent(event) {
4835
+ const dataEvents = [
4836
+ EditorEvents.SCENE_ADD,
4837
+ EditorEvents.SCENE_REMOVE,
4838
+ EditorEvents.SCENE_SELECT,
4839
+ EditorEvents.SCENE_UPDATE,
4840
+ EditorEvents.SCENE_REORDER,
4841
+ EditorEvents.SCENE_CLEAR,
4842
+ EditorEvents.SCENE_IMAGE_CHANGE,
4843
+ EditorEvents.SCENE_STARTING_POSITION_SET,
4844
+ EditorEvents.SCENE_STARTING_POSITION_CLEAR,
4845
+ EditorEvents.HOTSPOT_ADD,
4846
+ EditorEvents.HOTSPOT_REMOVE,
4847
+ EditorEvents.HOTSPOT_SELECT,
4848
+ EditorEvents.HOTSPOT_UPDATE,
4849
+ EditorEvents.HOTSPOT_DUPLICATE,
4850
+ EditorEvents.HOTSPOT_POSITION_CHANGE,
4851
+ EditorEvents.CONFIG_UPDATE,
4852
+ EditorEvents.INITIAL_SCENE_CHANGE,
4853
+ EditorEvents.TOUR_TITLE_CHANGE,
4854
+ EditorEvents.TOUR_DESCRIPTION_CHANGE,
4855
+ EditorEvents.PROJECT_LOAD,
4856
+ EditorEvents.PROJECT_IMPORT,
4857
+ EditorEvents.PROJECT_NEW,
4858
+ EditorEvents.DATA_CHANGE
4859
+ ];
4860
+ return dataEvents.includes(event);
4861
+ }
4862
+
4863
+ /**
4864
+ * Get the number of listeners for an event
4865
+ * @param {string} event - Event name
4866
+ * @returns {number}
4867
+ */
4868
+ listenerCount(event) {
4869
+ let count = 0;
4870
+ if (this.listeners.has(event)) {
4871
+ count += this.listeners.get(event).size;
4872
+ }
4873
+ if (this.onceListeners.has(event)) {
4874
+ count += this.onceListeners.get(event).size;
4875
+ }
4876
+ return count;
4877
+ }
4878
+
4879
+ /**
4880
+ * Get all event names that have listeners
4881
+ * @returns {string[]}
4882
+ */
4883
+ eventNames() {
4884
+ const names = new Set([...this.listeners.keys(), ...this.onceListeners.keys()]);
4885
+ return Array.from(names);
4886
+ }
4887
+ }
4888
+
4627
4889
  // Export Manager - Handles JSON generation for SWT library
4628
4890
 
4629
4891
  let ExportManager$1 = class ExportManager {
@@ -4644,6 +4906,51 @@
4644
4906
  return buildTourConfig(config, scenes);
4645
4907
  }
4646
4908
 
4909
+ /**
4910
+ * Load tour data from JSON (inverse of generateJSON)
4911
+ * This loads the entire tour configuration including initialScene and all scenes
4912
+ * @param {Object} tourData - Tour configuration object with initialScene and scenes
4913
+ * @param {string} tourData.initialScene - Initial scene ID
4914
+ * @param {Array} tourData.scenes - Array of scene objects
4915
+ * @returns {boolean} Success status
4916
+ */
4917
+ loadJSON(tourData) {
4918
+ try {
4919
+ if (!tourData || typeof tourData !== 'object') {
4920
+ console.error('Invalid tour data: expected object');
4921
+ return false;
4922
+ }
4923
+
4924
+ // Load scenes into scene manager
4925
+ const scenes = tourData.scenes || [];
4926
+ this.editor.sceneManager.loadScenes(scenes);
4927
+
4928
+ // Set initial scene in config
4929
+ if (tourData.initialScene) {
4930
+ this.editor.config.initialSceneId = tourData.initialScene;
4931
+ } else if (scenes.length > 0) {
4932
+ this.editor.config.initialSceneId = scenes[0].id;
4933
+ }
4934
+
4935
+ // Mark as having unsaved changes
4936
+ this.editor.hasUnsavedChanges = true;
4937
+
4938
+ // Re-render the editor UI
4939
+ this.editor.render();
4940
+
4941
+ showToast('Tour loaded successfully', 'success');
4942
+
4943
+ // Emit event
4944
+ this.editor.emit(EditorEvents.PROJECT_LOAD, { tourData, source: 'loadJSON' });
4945
+
4946
+ return true;
4947
+ } catch (error) {
4948
+ console.error('Failed to load tour data:', error);
4949
+ showToast('Failed to load tour', 'error');
4950
+ return false;
4951
+ }
4952
+ }
4953
+
4647
4954
  /**
4648
4955
  * Generate JSON with icons baked in as SVG data URLs
4649
4956
  * This ensures the exported HTML doesn't need the SenangStart icons library
@@ -4834,6 +5141,47 @@
4834
5141
  this.hasUnsavedChanges = false;
4835
5142
  this.lastRenderedSceneIndex = -1;
4836
5143
  this.listenersSetup = false;
5144
+
5145
+ // Initialize event emitter
5146
+ this.events = new EventEmitter();
5147
+ }
5148
+
5149
+ /**
5150
+ * Subscribe to editor events
5151
+ * @param {string} event - Event name (use EditorEvents constants)
5152
+ * @param {Function} callback - Callback function
5153
+ * @returns {Function} Unsubscribe function
5154
+ */
5155
+ on(event, callback) {
5156
+ return this.events.on(event, callback);
5157
+ }
5158
+
5159
+ /**
5160
+ * Subscribe to an event once
5161
+ * @param {string} event - Event name
5162
+ * @param {Function} callback - Callback function
5163
+ * @returns {Function} Unsubscribe function
5164
+ */
5165
+ once(event, callback) {
5166
+ return this.events.once(event, callback);
5167
+ }
5168
+
5169
+ /**
5170
+ * Unsubscribe from an event
5171
+ * @param {string} event - Event name
5172
+ * @param {Function} callback - Callback to remove
5173
+ */
5174
+ off(event, callback) {
5175
+ this.events.off(event, callback);
5176
+ }
5177
+
5178
+ /**
5179
+ * Emit an event
5180
+ * @param {string} event - Event name
5181
+ * @param {Object} data - Event data
5182
+ */
5183
+ emit(event, data = {}) {
5184
+ this.events.emit(event, data);
4837
5185
  }
4838
5186
 
4839
5187
  /**
@@ -4853,7 +5201,7 @@
4853
5201
  const previewInit = await this.previewController.init();
4854
5202
  if (!previewInit) {
4855
5203
  console.error('Failed to initialize preview controller');
4856
- showToast$1('Failed to initialize preview', 'error');
5204
+ showToast('Failed to initialize preview', 'error');
4857
5205
  return false;
4858
5206
  }
4859
5207
 
@@ -4892,7 +5240,10 @@
4892
5240
  this.render();
4893
5241
  }
4894
5242
 
4895
- showToast$1('Editor ready', 'success');
5243
+ showToast('Editor ready', 'success');
5244
+
5245
+ // Emit ready event
5246
+ this.emit(EditorEvents.READY, { config: this.options });
4896
5247
 
4897
5248
  return true;
4898
5249
  }
@@ -5017,6 +5368,7 @@
5017
5368
  document.getElementById('tourTitle')?.addEventListener('input', debounce((e) => {
5018
5369
  this.config.title = e.target.value;
5019
5370
  this.markUnsavedChanges();
5371
+ this.emit(EditorEvents.TOUR_TITLE_CHANGE, { title: e.target.value });
5020
5372
  const projectName = document.getElementById('project-name');
5021
5373
  if (projectName && projectName.value !== e.target.value) {
5022
5374
  projectName.value = e.target.value;
@@ -5026,6 +5378,7 @@
5026
5378
  document.getElementById('project-name')?.addEventListener('input', debounce((e) => {
5027
5379
  this.config.title = e.target.value;
5028
5380
  this.markUnsavedChanges();
5381
+ this.emit(EditorEvents.TOUR_TITLE_CHANGE, { title: e.target.value });
5029
5382
  const tourTitle = document.getElementById('tourTitle');
5030
5383
  if (tourTitle && tourTitle.value !== e.target.value) {
5031
5384
  tourTitle.value = e.target.value;
@@ -5035,11 +5388,13 @@
5035
5388
  document.getElementById('tourDescription')?.addEventListener('input', debounce((e) => {
5036
5389
  this.config.description = e.target.value;
5037
5390
  this.markUnsavedChanges();
5391
+ this.emit(EditorEvents.TOUR_DESCRIPTION_CHANGE, { description: e.target.value });
5038
5392
  }, 300));
5039
5393
 
5040
5394
  document.getElementById('tourInitialScene')?.addEventListener('change', (e) => {
5041
5395
  this.config.initialSceneId = e.target.value;
5042
5396
  this.markUnsavedChanges();
5397
+ this.emit(EditorEvents.INITIAL_SCENE_CHANGE, { initialSceneId: e.target.value });
5043
5398
  });
5044
5399
 
5045
5400
  document.getElementById('exportJsonBtn')?.addEventListener('click', () => {
@@ -5094,11 +5449,14 @@
5094
5449
 
5095
5450
  for (const file of files) {
5096
5451
  if (!file.type.startsWith('image/')) {
5097
- showToast$1(`${file.name} is not an image`, 'error');
5452
+ showToast(`${file.name} is not an image`, 'error');
5098
5453
  continue;
5099
5454
  }
5100
5455
 
5101
- await this.sceneManager.addScene(file);
5456
+ const scene = await this.sceneManager.addScene(file);
5457
+ if (scene) {
5458
+ this.emit(EditorEvents.SCENE_ADD, { scene, file });
5459
+ }
5102
5460
  }
5103
5461
  this.uiController.setLoading(false);
5104
5462
  this.render();
@@ -5132,6 +5490,11 @@
5132
5490
  this.lastRenderedSceneIndex = -1;
5133
5491
  this.render();
5134
5492
  this.markUnsavedChanges();
5493
+ this.emit(EditorEvents.HOTSPOT_ADD, {
5494
+ hotspot,
5495
+ position,
5496
+ sceneId: this.sceneManager.getCurrentScene()?.id
5497
+ });
5135
5498
  } else {
5136
5499
  console.error('Failed to add hotspot');
5137
5500
  }
@@ -5144,13 +5507,13 @@
5144
5507
  addHotspotAtCursor() {
5145
5508
  const scene = this.sceneManager.getCurrentScene();
5146
5509
  if (!scene) {
5147
- showToast$1('Please select a scene first', 'error');
5510
+ showToast('Please select a scene first', 'error');
5148
5511
  return;
5149
5512
  }
5150
5513
 
5151
5514
  const position = this.previewController.getCursorIntersection();
5152
5515
  if (!position) {
5153
- showToast$1('Could not get cursor position. Please ensure the preview is loaded.', 'error');
5516
+ showToast('Could not get cursor position. Please ensure the preview is loaded.', 'error');
5154
5517
  return;
5155
5518
  }
5156
5519
 
@@ -5177,6 +5540,8 @@
5177
5540
  this.uiController.updateHotspotProperties(null);
5178
5541
  this.uiController.updateInitialSceneOptions();
5179
5542
  this.uiController.updateTargetSceneOptions();
5543
+
5544
+ this.emit(EditorEvents.SCENE_SELECT, { scene, index });
5180
5545
  }
5181
5546
  }
5182
5547
 
@@ -5195,6 +5560,8 @@
5195
5560
  if (hotspot) {
5196
5561
  this.previewController.pointCameraToHotspot(hotspot);
5197
5562
  }
5563
+
5564
+ this.emit(EditorEvents.HOTSPOT_SELECT, { hotspot, index });
5198
5565
  }
5199
5566
  }
5200
5567
 
@@ -5202,9 +5569,11 @@
5202
5569
  * Remove scene
5203
5570
  */
5204
5571
  removeScene(index) {
5572
+ const scene = this.sceneManager.getScene(index);
5205
5573
  if (this.sceneManager.removeScene(index)) {
5206
5574
  this.render();
5207
5575
  this.markUnsavedChanges();
5576
+ this.emit(EditorEvents.SCENE_REMOVE, { scene, index });
5208
5577
  }
5209
5578
  }
5210
5579
 
@@ -5212,10 +5581,12 @@
5212
5581
  * Remove hotspot
5213
5582
  */
5214
5583
  removeHotspot(index) {
5584
+ const hotspot = this.hotspotEditor.getHotspot(index);
5215
5585
  if (this.hotspotEditor.removeHotspot(index)) {
5216
5586
  this.lastRenderedSceneIndex = -1;
5217
5587
  this.render();
5218
5588
  this.markUnsavedChanges();
5589
+ this.emit(EditorEvents.HOTSPOT_REMOVE, { hotspot, index });
5219
5590
  }
5220
5591
  }
5221
5592
 
@@ -5228,6 +5599,7 @@
5228
5599
  this.lastRenderedSceneIndex = -1;
5229
5600
  this.render();
5230
5601
  this.markUnsavedChanges();
5602
+ this.emit(EditorEvents.HOTSPOT_DUPLICATE, { hotspot, originalIndex: index });
5231
5603
  }
5232
5604
  }
5233
5605
 
@@ -5238,6 +5610,7 @@
5238
5610
  if (this.sceneManager.reorderScenes(fromIndex, toIndex)) {
5239
5611
  this.render();
5240
5612
  this.markUnsavedChanges();
5613
+ this.emit(EditorEvents.SCENE_REORDER, { fromIndex, toIndex });
5241
5614
  }
5242
5615
  }
5243
5616
 
@@ -5250,6 +5623,12 @@
5250
5623
  await this.previewController.updateHotspotMarker(index);
5251
5624
  this.uiController.renderHotspotList();
5252
5625
  this.markUnsavedChanges();
5626
+ this.emit(EditorEvents.HOTSPOT_UPDATE, {
5627
+ hotspot: this.hotspotEditor.getHotspot(index),
5628
+ index,
5629
+ property,
5630
+ value
5631
+ });
5253
5632
  }
5254
5633
  }
5255
5634
 
@@ -5280,12 +5659,19 @@
5280
5659
  pos.z *= scale;
5281
5660
 
5282
5661
  document.getElementById(`hotspotPos${axis.toUpperCase()}`).value = pos[axis].toFixed(2);
5283
- showToast$1('Position clamped to 10-unit radius', 'info');
5662
+ showToast('Position clamped to 10-unit radius', 'info');
5284
5663
  }
5285
5664
 
5286
5665
  await this.previewController.updateHotspotMarker(index);
5287
5666
  this.uiController.renderHotspotList();
5288
5667
  this.markUnsavedChanges();
5668
+ this.emit(EditorEvents.HOTSPOT_POSITION_CHANGE, {
5669
+ hotspot,
5670
+ index,
5671
+ axis,
5672
+ value,
5673
+ position: hotspot.position
5674
+ });
5289
5675
  }
5290
5676
  }
5291
5677
 
@@ -5297,6 +5683,12 @@
5297
5683
  if (this.sceneManager.updateScene(index, property, value)) {
5298
5684
  this.uiController.renderSceneList();
5299
5685
  this.markUnsavedChanges();
5686
+ this.emit(EditorEvents.SCENE_UPDATE, {
5687
+ scene: this.sceneManager.getScene(index),
5688
+ index,
5689
+ property,
5690
+ value
5691
+ });
5300
5692
  }
5301
5693
  }
5302
5694
 
@@ -5320,9 +5712,10 @@
5320
5712
  if (scene) {
5321
5713
  await this.previewController.loadScene(scene);
5322
5714
  this.lastRenderedSceneIndex = index;
5323
- showToast$1('Scene image updated', 'success');
5715
+ showToast('Scene image updated', 'success');
5324
5716
  }
5325
5717
  this.markUnsavedChanges();
5718
+ this.emit(EditorEvents.SCENE_IMAGE_CHANGE, { scene, index, imageUrl });
5326
5719
  }
5327
5720
  }
5328
5721
 
@@ -5332,13 +5725,13 @@
5332
5725
  setSceneStartingPosition() {
5333
5726
  const scene = this.sceneManager.getCurrentScene();
5334
5727
  if (!scene) {
5335
- showToast$1('No scene selected', 'error');
5728
+ showToast('No scene selected', 'error');
5336
5729
  return;
5337
5730
  }
5338
5731
 
5339
5732
  const rotation = this.previewController.getCameraRotation();
5340
5733
  if (!rotation) {
5341
- showToast$1('Could not get camera rotation', 'error');
5734
+ showToast('Could not get camera rotation', 'error');
5342
5735
  return;
5343
5736
  }
5344
5737
 
@@ -5349,7 +5742,8 @@
5349
5742
 
5350
5743
  this.uiController.updateSceneProperties(scene);
5351
5744
  this.markUnsavedChanges();
5352
- showToast$1('Starting position set', 'success');
5745
+ showToast('Starting position set', 'success');
5746
+ this.emit(EditorEvents.SCENE_STARTING_POSITION_SET, { scene, startingPosition: scene.startingPosition });
5353
5747
  }
5354
5748
 
5355
5749
  /**
@@ -5358,7 +5752,7 @@
5358
5752
  clearSceneStartingPosition() {
5359
5753
  const scene = this.sceneManager.getCurrentScene();
5360
5754
  if (!scene) {
5361
- showToast$1('No scene selected', 'error');
5755
+ showToast('No scene selected', 'error');
5362
5756
  return;
5363
5757
  }
5364
5758
 
@@ -5366,7 +5760,8 @@
5366
5760
 
5367
5761
  this.uiController.updateSceneProperties(scene);
5368
5762
  this.markUnsavedChanges();
5369
- showToast$1('Starting position cleared', 'success');
5763
+ showToast('Starting position cleared', 'success');
5764
+ this.emit(EditorEvents.SCENE_STARTING_POSITION_CLEAR, { scene });
5370
5765
  }
5371
5766
 
5372
5767
  /**
@@ -5408,6 +5803,8 @@
5408
5803
  }
5409
5804
  this.lastRenderedSceneIndex = -1;
5410
5805
  }
5806
+
5807
+ this.emit(EditorEvents.UI_RENDER);
5411
5808
  }
5412
5809
 
5413
5810
  /**
@@ -5421,7 +5818,8 @@
5421
5818
 
5422
5819
  if (this.storageManager.saveProject(projectData)) {
5423
5820
  this.hasUnsavedChanges = false;
5424
- showToast$1('Project saved', 'success');
5821
+ showToast('Project saved', 'success');
5822
+ this.emit(EditorEvents.PROJECT_SAVE, { projectData });
5425
5823
  return true;
5426
5824
  }
5427
5825
  return false;
@@ -5437,7 +5835,8 @@
5437
5835
  this.sceneManager.loadScenes(projectData.scenes || []);
5438
5836
  this.hasUnsavedChanges = false;
5439
5837
  this.render();
5440
- showToast$1('Project loaded', 'success');
5838
+ showToast('Project loaded', 'success');
5839
+ this.emit(EditorEvents.PROJECT_LOAD, { projectData });
5441
5840
  return true;
5442
5841
  }
5443
5842
  return false;
@@ -5463,7 +5862,8 @@
5463
5862
  this.hasUnsavedChanges = false;
5464
5863
  this.render();
5465
5864
 
5466
- showToast$1('New project created', 'success');
5865
+ showToast('New project created', 'success');
5866
+ this.emit(EditorEvents.PROJECT_NEW, { config: this.config });
5467
5867
  return true;
5468
5868
  }
5469
5869
 
@@ -5493,7 +5893,8 @@
5493
5893
  this.render();
5494
5894
  this.uiController.setLoading(false);
5495
5895
 
5496
- showToast$1('Project imported successfully', 'success');
5896
+ showToast('Project imported successfully', 'success');
5897
+ this.emit(EditorEvents.PROJECT_IMPORT, { projectData, file });
5497
5898
  } catch (error) {
5498
5899
  this.uiController.setLoading(false);
5499
5900
  console.error('Import failed:', error);
@@ -5695,7 +6096,8 @@
5695
6096
  window.PreviewController = PreviewController$1;
5696
6097
  window.UIController = UIController$1;
5697
6098
  window.ExportManager = ExportManager$1;
5698
- window.TourEditor = TourEditor$1;
6099
+ window.TourEditor = TourEditor$1;
6100
+ window.EditorEvents = EventEmitter;
5699
6101
 
5700
6102
  return TourEditor$1;
5701
6103