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.
@@ -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,262 @@
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 events
4673
+ CONFIG_UPDATE: 'config:update',
4674
+ INITIAL_SCENE_CHANGE: 'config:initialSceneChange',
4675
+
4676
+ // Preview events
4677
+ PREVIEW_START: 'preview:start',
4678
+ PREVIEW_STOP: 'preview:stop',
4679
+ PREVIEW_SCENE_CHANGE: 'preview:sceneChange',
4680
+
4681
+ // UI events
4682
+ UI_RENDER: 'ui:render',
4683
+ UI_LOADING_START: 'ui:loadingStart',
4684
+ UI_LOADING_END: 'ui:loadingEnd',
4685
+ MODAL_OPEN: 'ui:modalOpen',
4686
+ MODAL_CLOSE: 'ui:modalClose',
4687
+
4688
+ // Data events
4689
+ DATA_CHANGE: 'data:change', // Fires when any data changes
4690
+ UNSAVED_CHANGES: 'data:unsavedChanges',
4691
+
4692
+ // Unified change event - fires for ANY modification
4693
+ CHANGE: 'change'
4694
+ };
4695
+
4696
+ /**
4697
+ * Event Emitter class
4698
+ * Provides pub/sub functionality for editor events
4699
+ */
4700
+ class EventEmitter {
4701
+ constructor() {
4702
+ this.listeners = new Map();
4703
+ this.onceListeners = new Map();
4704
+ }
4705
+
4706
+ /**
4707
+ * Register an event listener
4708
+ * @param {string} event - Event name or 'change' for all changes
4709
+ * @param {Function} callback - Function to call when event fires
4710
+ * @returns {Function} Unsubscribe function
4711
+ */
4712
+ on(event, callback) {
4713
+ if (!this.listeners.has(event)) {
4714
+ this.listeners.set(event, new Set());
4715
+ }
4716
+ this.listeners.get(event).add(callback);
4717
+
4718
+ // Return unsubscribe function
4719
+ return () => this.off(event, callback);
4720
+ }
4721
+
4722
+ /**
4723
+ * Register a one-time event listener
4724
+ * @param {string} event - Event name
4725
+ * @param {Function} callback - Function to call once when event fires
4726
+ * @returns {Function} Unsubscribe function
4727
+ */
4728
+ once(event, callback) {
4729
+ if (!this.onceListeners.has(event)) {
4730
+ this.onceListeners.set(event, new Set());
4731
+ }
4732
+ this.onceListeners.get(event).add(callback);
4733
+
4734
+ return () => {
4735
+ if (this.onceListeners.has(event)) {
4736
+ this.onceListeners.get(event).delete(callback);
4737
+ }
4738
+ };
4739
+ }
4740
+
4741
+ /**
4742
+ * Remove an event listener
4743
+ * @param {string} event - Event name
4744
+ * @param {Function} callback - Function to remove
4745
+ */
4746
+ off(event, callback) {
4747
+ if (this.listeners.has(event)) {
4748
+ this.listeners.get(event).delete(callback);
4749
+ }
4750
+ if (this.onceListeners.has(event)) {
4751
+ this.onceListeners.get(event).delete(callback);
4752
+ }
4753
+ }
4754
+
4755
+ /**
4756
+ * Remove all listeners for an event, or all listeners if no event specified
4757
+ * @param {string} [event] - Optional event name
4758
+ */
4759
+ removeAllListeners(event) {
4760
+ if (event) {
4761
+ this.listeners.delete(event);
4762
+ this.onceListeners.delete(event);
4763
+ } else {
4764
+ this.listeners.clear();
4765
+ this.onceListeners.clear();
4766
+ }
4767
+ }
4768
+
4769
+ /**
4770
+ * Emit an event
4771
+ * @param {string} event - Event name
4772
+ * @param {Object} data - Event data
4773
+ */
4774
+ emit(event, data = {}) {
4775
+ const eventData = {
4776
+ type: event,
4777
+ timestamp: Date.now(),
4778
+ ...data
4779
+ };
4780
+
4781
+ // Call specific event listeners
4782
+ if (this.listeners.has(event)) {
4783
+ this.listeners.get(event).forEach(callback => {
4784
+ try {
4785
+ callback(eventData);
4786
+ } catch (error) {
4787
+ console.error(`Error in event listener for "${event}":`, error);
4788
+ }
4789
+ });
4790
+ }
4791
+
4792
+ // Call once listeners and remove them
4793
+ if (this.onceListeners.has(event)) {
4794
+ const onceCallbacks = this.onceListeners.get(event);
4795
+ this.onceListeners.delete(event);
4796
+ onceCallbacks.forEach(callback => {
4797
+ try {
4798
+ callback(eventData);
4799
+ } catch (error) {
4800
+ console.error(`Error in once listener for "${event}":`, error);
4801
+ }
4802
+ });
4803
+ }
4804
+
4805
+ // Also emit to wildcard listeners (namespace:*)
4806
+ const namespace = event.split(':')[0];
4807
+ const wildcardEvent = `${namespace}:*`;
4808
+ if (this.listeners.has(wildcardEvent)) {
4809
+ this.listeners.get(wildcardEvent).forEach(callback => {
4810
+ try {
4811
+ callback(eventData);
4812
+ } catch (error) {
4813
+ console.error(`Error in wildcard listener for "${wildcardEvent}":`, error);
4814
+ }
4815
+ });
4816
+ }
4817
+
4818
+ // Emit unified 'change' event for data-modifying events
4819
+ if (this.isDataModifyingEvent(event) && event !== EditorEvents.CHANGE) {
4820
+ this.emit(EditorEvents.CHANGE, {
4821
+ originalEvent: event,
4822
+ ...data
4823
+ });
4824
+ }
4825
+ }
4826
+
4827
+ /**
4828
+ * Check if an event modifies data (should trigger 'change' event)
4829
+ * @param {string} event - Event name
4830
+ * @returns {boolean}
4831
+ */
4832
+ isDataModifyingEvent(event) {
4833
+ const dataEvents = [
4834
+ EditorEvents.SCENE_ADD,
4835
+ EditorEvents.SCENE_REMOVE,
4836
+ EditorEvents.SCENE_UPDATE,
4837
+ EditorEvents.SCENE_REORDER,
4838
+ EditorEvents.SCENE_CLEAR,
4839
+ EditorEvents.SCENE_IMAGE_CHANGE,
4840
+ EditorEvents.SCENE_STARTING_POSITION_SET,
4841
+ EditorEvents.SCENE_STARTING_POSITION_CLEAR,
4842
+ EditorEvents.HOTSPOT_ADD,
4843
+ EditorEvents.HOTSPOT_REMOVE,
4844
+ EditorEvents.HOTSPOT_UPDATE,
4845
+ EditorEvents.HOTSPOT_DUPLICATE,
4846
+ EditorEvents.HOTSPOT_POSITION_CHANGE,
4847
+ EditorEvents.CONFIG_UPDATE,
4848
+ EditorEvents.INITIAL_SCENE_CHANGE,
4849
+ EditorEvents.PROJECT_LOAD,
4850
+ EditorEvents.PROJECT_IMPORT,
4851
+ EditorEvents.PROJECT_NEW,
4852
+ EditorEvents.DATA_CHANGE
4853
+ ];
4854
+ return dataEvents.includes(event);
4855
+ }
4856
+
4857
+ /**
4858
+ * Get the number of listeners for an event
4859
+ * @param {string} event - Event name
4860
+ * @returns {number}
4861
+ */
4862
+ listenerCount(event) {
4863
+ let count = 0;
4864
+ if (this.listeners.has(event)) {
4865
+ count += this.listeners.get(event).size;
4866
+ }
4867
+ if (this.onceListeners.has(event)) {
4868
+ count += this.onceListeners.get(event).size;
4869
+ }
4870
+ return count;
4871
+ }
4872
+
4873
+ /**
4874
+ * Get all event names that have listeners
4875
+ * @returns {string[]}
4876
+ */
4877
+ eventNames() {
4878
+ const names = new Set([...this.listeners.keys(), ...this.onceListeners.keys()]);
4879
+ return Array.from(names);
4880
+ }
4881
+ }
4882
+
4627
4883
  // Export Manager - Handles JSON generation for SWT library
4628
4884
 
4629
4885
  let ExportManager$1 = class ExportManager {
@@ -4644,6 +4900,51 @@
4644
4900
  return buildTourConfig(config, scenes);
4645
4901
  }
4646
4902
 
4903
+ /**
4904
+ * Load tour data from JSON (inverse of generateJSON)
4905
+ * This loads the entire tour configuration including initialScene and all scenes
4906
+ * @param {Object} tourData - Tour configuration object with initialScene and scenes
4907
+ * @param {string} tourData.initialScene - Initial scene ID
4908
+ * @param {Array} tourData.scenes - Array of scene objects
4909
+ * @returns {boolean} Success status
4910
+ */
4911
+ loadJSON(tourData) {
4912
+ try {
4913
+ if (!tourData || typeof tourData !== 'object') {
4914
+ console.error('Invalid tour data: expected object');
4915
+ return false;
4916
+ }
4917
+
4918
+ // Load scenes into scene manager
4919
+ const scenes = tourData.scenes || [];
4920
+ this.editor.sceneManager.loadScenes(scenes);
4921
+
4922
+ // Set initial scene in config
4923
+ if (tourData.initialScene) {
4924
+ this.editor.config.initialSceneId = tourData.initialScene;
4925
+ } else if (scenes.length > 0) {
4926
+ this.editor.config.initialSceneId = scenes[0].id;
4927
+ }
4928
+
4929
+ // Mark as having unsaved changes
4930
+ this.editor.hasUnsavedChanges = true;
4931
+
4932
+ // Re-render the editor UI
4933
+ this.editor.render();
4934
+
4935
+ showToast('Tour loaded successfully', 'success');
4936
+
4937
+ // Emit event
4938
+ this.editor.emit(EditorEvents.PROJECT_LOAD, { tourData, source: 'loadJSON' });
4939
+
4940
+ return true;
4941
+ } catch (error) {
4942
+ console.error('Failed to load tour data:', error);
4943
+ showToast('Failed to load tour', 'error');
4944
+ return false;
4945
+ }
4946
+ }
4947
+
4647
4948
  /**
4648
4949
  * Generate JSON with icons baked in as SVG data URLs
4649
4950
  * This ensures the exported HTML doesn't need the SenangStart icons library
@@ -4834,6 +5135,47 @@
4834
5135
  this.hasUnsavedChanges = false;
4835
5136
  this.lastRenderedSceneIndex = -1;
4836
5137
  this.listenersSetup = false;
5138
+
5139
+ // Initialize event emitter
5140
+ this.events = new EventEmitter();
5141
+ }
5142
+
5143
+ /**
5144
+ * Subscribe to editor events
5145
+ * @param {string} event - Event name (use EditorEvents constants)
5146
+ * @param {Function} callback - Callback function
5147
+ * @returns {Function} Unsubscribe function
5148
+ */
5149
+ on(event, callback) {
5150
+ return this.events.on(event, callback);
5151
+ }
5152
+
5153
+ /**
5154
+ * Subscribe to an event once
5155
+ * @param {string} event - Event name
5156
+ * @param {Function} callback - Callback function
5157
+ * @returns {Function} Unsubscribe function
5158
+ */
5159
+ once(event, callback) {
5160
+ return this.events.once(event, callback);
5161
+ }
5162
+
5163
+ /**
5164
+ * Unsubscribe from an event
5165
+ * @param {string} event - Event name
5166
+ * @param {Function} callback - Callback to remove
5167
+ */
5168
+ off(event, callback) {
5169
+ this.events.off(event, callback);
5170
+ }
5171
+
5172
+ /**
5173
+ * Emit an event
5174
+ * @param {string} event - Event name
5175
+ * @param {Object} data - Event data
5176
+ */
5177
+ emit(event, data = {}) {
5178
+ this.events.emit(event, data);
4837
5179
  }
4838
5180
 
4839
5181
  /**
@@ -4853,7 +5195,7 @@
4853
5195
  const previewInit = await this.previewController.init();
4854
5196
  if (!previewInit) {
4855
5197
  console.error('Failed to initialize preview controller');
4856
- showToast$1('Failed to initialize preview', 'error');
5198
+ showToast('Failed to initialize preview', 'error');
4857
5199
  return false;
4858
5200
  }
4859
5201
 
@@ -4892,7 +5234,10 @@
4892
5234
  this.render();
4893
5235
  }
4894
5236
 
4895
- showToast$1('Editor ready', 'success');
5237
+ showToast('Editor ready', 'success');
5238
+
5239
+ // Emit ready event
5240
+ this.emit(EditorEvents.READY, { config: this.options });
4896
5241
 
4897
5242
  return true;
4898
5243
  }
@@ -5094,11 +5439,14 @@
5094
5439
 
5095
5440
  for (const file of files) {
5096
5441
  if (!file.type.startsWith('image/')) {
5097
- showToast$1(`${file.name} is not an image`, 'error');
5442
+ showToast(`${file.name} is not an image`, 'error');
5098
5443
  continue;
5099
5444
  }
5100
5445
 
5101
- await this.sceneManager.addScene(file);
5446
+ const scene = await this.sceneManager.addScene(file);
5447
+ if (scene) {
5448
+ this.emit(EditorEvents.SCENE_ADD, { scene, file });
5449
+ }
5102
5450
  }
5103
5451
  this.uiController.setLoading(false);
5104
5452
  this.render();
@@ -5132,6 +5480,11 @@
5132
5480
  this.lastRenderedSceneIndex = -1;
5133
5481
  this.render();
5134
5482
  this.markUnsavedChanges();
5483
+ this.emit(EditorEvents.HOTSPOT_ADD, {
5484
+ hotspot,
5485
+ position,
5486
+ sceneId: this.sceneManager.getCurrentScene()?.id
5487
+ });
5135
5488
  } else {
5136
5489
  console.error('Failed to add hotspot');
5137
5490
  }
@@ -5144,13 +5497,13 @@
5144
5497
  addHotspotAtCursor() {
5145
5498
  const scene = this.sceneManager.getCurrentScene();
5146
5499
  if (!scene) {
5147
- showToast$1('Please select a scene first', 'error');
5500
+ showToast('Please select a scene first', 'error');
5148
5501
  return;
5149
5502
  }
5150
5503
 
5151
5504
  const position = this.previewController.getCursorIntersection();
5152
5505
  if (!position) {
5153
- showToast$1('Could not get cursor position. Please ensure the preview is loaded.', 'error');
5506
+ showToast('Could not get cursor position. Please ensure the preview is loaded.', 'error');
5154
5507
  return;
5155
5508
  }
5156
5509
 
@@ -5177,6 +5530,8 @@
5177
5530
  this.uiController.updateHotspotProperties(null);
5178
5531
  this.uiController.updateInitialSceneOptions();
5179
5532
  this.uiController.updateTargetSceneOptions();
5533
+
5534
+ this.emit(EditorEvents.SCENE_SELECT, { scene, index });
5180
5535
  }
5181
5536
  }
5182
5537
 
@@ -5195,6 +5550,8 @@
5195
5550
  if (hotspot) {
5196
5551
  this.previewController.pointCameraToHotspot(hotspot);
5197
5552
  }
5553
+
5554
+ this.emit(EditorEvents.HOTSPOT_SELECT, { hotspot, index });
5198
5555
  }
5199
5556
  }
5200
5557
 
@@ -5202,9 +5559,11 @@
5202
5559
  * Remove scene
5203
5560
  */
5204
5561
  removeScene(index) {
5562
+ const scene = this.sceneManager.getScene(index);
5205
5563
  if (this.sceneManager.removeScene(index)) {
5206
5564
  this.render();
5207
5565
  this.markUnsavedChanges();
5566
+ this.emit(EditorEvents.SCENE_REMOVE, { scene, index });
5208
5567
  }
5209
5568
  }
5210
5569
 
@@ -5212,10 +5571,12 @@
5212
5571
  * Remove hotspot
5213
5572
  */
5214
5573
  removeHotspot(index) {
5574
+ const hotspot = this.hotspotEditor.getHotspot(index);
5215
5575
  if (this.hotspotEditor.removeHotspot(index)) {
5216
5576
  this.lastRenderedSceneIndex = -1;
5217
5577
  this.render();
5218
5578
  this.markUnsavedChanges();
5579
+ this.emit(EditorEvents.HOTSPOT_REMOVE, { hotspot, index });
5219
5580
  }
5220
5581
  }
5221
5582
 
@@ -5228,6 +5589,7 @@
5228
5589
  this.lastRenderedSceneIndex = -1;
5229
5590
  this.render();
5230
5591
  this.markUnsavedChanges();
5592
+ this.emit(EditorEvents.HOTSPOT_DUPLICATE, { hotspot, originalIndex: index });
5231
5593
  }
5232
5594
  }
5233
5595
 
@@ -5238,6 +5600,7 @@
5238
5600
  if (this.sceneManager.reorderScenes(fromIndex, toIndex)) {
5239
5601
  this.render();
5240
5602
  this.markUnsavedChanges();
5603
+ this.emit(EditorEvents.SCENE_REORDER, { fromIndex, toIndex });
5241
5604
  }
5242
5605
  }
5243
5606
 
@@ -5250,6 +5613,12 @@
5250
5613
  await this.previewController.updateHotspotMarker(index);
5251
5614
  this.uiController.renderHotspotList();
5252
5615
  this.markUnsavedChanges();
5616
+ this.emit(EditorEvents.HOTSPOT_UPDATE, {
5617
+ hotspot: this.hotspotEditor.getHotspot(index),
5618
+ index,
5619
+ property,
5620
+ value
5621
+ });
5253
5622
  }
5254
5623
  }
5255
5624
 
@@ -5280,12 +5649,19 @@
5280
5649
  pos.z *= scale;
5281
5650
 
5282
5651
  document.getElementById(`hotspotPos${axis.toUpperCase()}`).value = pos[axis].toFixed(2);
5283
- showToast$1('Position clamped to 10-unit radius', 'info');
5652
+ showToast('Position clamped to 10-unit radius', 'info');
5284
5653
  }
5285
5654
 
5286
5655
  await this.previewController.updateHotspotMarker(index);
5287
5656
  this.uiController.renderHotspotList();
5288
5657
  this.markUnsavedChanges();
5658
+ this.emit(EditorEvents.HOTSPOT_POSITION_CHANGE, {
5659
+ hotspot,
5660
+ index,
5661
+ axis,
5662
+ value,
5663
+ position: hotspot.position
5664
+ });
5289
5665
  }
5290
5666
  }
5291
5667
 
@@ -5297,6 +5673,12 @@
5297
5673
  if (this.sceneManager.updateScene(index, property, value)) {
5298
5674
  this.uiController.renderSceneList();
5299
5675
  this.markUnsavedChanges();
5676
+ this.emit(EditorEvents.SCENE_UPDATE, {
5677
+ scene: this.sceneManager.getScene(index),
5678
+ index,
5679
+ property,
5680
+ value
5681
+ });
5300
5682
  }
5301
5683
  }
5302
5684
 
@@ -5320,9 +5702,10 @@
5320
5702
  if (scene) {
5321
5703
  await this.previewController.loadScene(scene);
5322
5704
  this.lastRenderedSceneIndex = index;
5323
- showToast$1('Scene image updated', 'success');
5705
+ showToast('Scene image updated', 'success');
5324
5706
  }
5325
5707
  this.markUnsavedChanges();
5708
+ this.emit(EditorEvents.SCENE_IMAGE_CHANGE, { scene, index, imageUrl });
5326
5709
  }
5327
5710
  }
5328
5711
 
@@ -5332,13 +5715,13 @@
5332
5715
  setSceneStartingPosition() {
5333
5716
  const scene = this.sceneManager.getCurrentScene();
5334
5717
  if (!scene) {
5335
- showToast$1('No scene selected', 'error');
5718
+ showToast('No scene selected', 'error');
5336
5719
  return;
5337
5720
  }
5338
5721
 
5339
5722
  const rotation = this.previewController.getCameraRotation();
5340
5723
  if (!rotation) {
5341
- showToast$1('Could not get camera rotation', 'error');
5724
+ showToast('Could not get camera rotation', 'error');
5342
5725
  return;
5343
5726
  }
5344
5727
 
@@ -5349,7 +5732,8 @@
5349
5732
 
5350
5733
  this.uiController.updateSceneProperties(scene);
5351
5734
  this.markUnsavedChanges();
5352
- showToast$1('Starting position set', 'success');
5735
+ showToast('Starting position set', 'success');
5736
+ this.emit(EditorEvents.SCENE_STARTING_POSITION_SET, { scene, startingPosition: scene.startingPosition });
5353
5737
  }
5354
5738
 
5355
5739
  /**
@@ -5358,7 +5742,7 @@
5358
5742
  clearSceneStartingPosition() {
5359
5743
  const scene = this.sceneManager.getCurrentScene();
5360
5744
  if (!scene) {
5361
- showToast$1('No scene selected', 'error');
5745
+ showToast('No scene selected', 'error');
5362
5746
  return;
5363
5747
  }
5364
5748
 
@@ -5366,7 +5750,8 @@
5366
5750
 
5367
5751
  this.uiController.updateSceneProperties(scene);
5368
5752
  this.markUnsavedChanges();
5369
- showToast$1('Starting position cleared', 'success');
5753
+ showToast('Starting position cleared', 'success');
5754
+ this.emit(EditorEvents.SCENE_STARTING_POSITION_CLEAR, { scene });
5370
5755
  }
5371
5756
 
5372
5757
  /**
@@ -5408,6 +5793,8 @@
5408
5793
  }
5409
5794
  this.lastRenderedSceneIndex = -1;
5410
5795
  }
5796
+
5797
+ this.emit(EditorEvents.UI_RENDER);
5411
5798
  }
5412
5799
 
5413
5800
  /**
@@ -5421,7 +5808,8 @@
5421
5808
 
5422
5809
  if (this.storageManager.saveProject(projectData)) {
5423
5810
  this.hasUnsavedChanges = false;
5424
- showToast$1('Project saved', 'success');
5811
+ showToast('Project saved', 'success');
5812
+ this.emit(EditorEvents.PROJECT_SAVE, { projectData });
5425
5813
  return true;
5426
5814
  }
5427
5815
  return false;
@@ -5437,7 +5825,8 @@
5437
5825
  this.sceneManager.loadScenes(projectData.scenes || []);
5438
5826
  this.hasUnsavedChanges = false;
5439
5827
  this.render();
5440
- showToast$1('Project loaded', 'success');
5828
+ showToast('Project loaded', 'success');
5829
+ this.emit(EditorEvents.PROJECT_LOAD, { projectData });
5441
5830
  return true;
5442
5831
  }
5443
5832
  return false;
@@ -5463,7 +5852,8 @@
5463
5852
  this.hasUnsavedChanges = false;
5464
5853
  this.render();
5465
5854
 
5466
- showToast$1('New project created', 'success');
5855
+ showToast('New project created', 'success');
5856
+ this.emit(EditorEvents.PROJECT_NEW, { config: this.config });
5467
5857
  return true;
5468
5858
  }
5469
5859
 
@@ -5493,7 +5883,8 @@
5493
5883
  this.render();
5494
5884
  this.uiController.setLoading(false);
5495
5885
 
5496
- showToast$1('Project imported successfully', 'success');
5886
+ showToast('Project imported successfully', 'success');
5887
+ this.emit(EditorEvents.PROJECT_IMPORT, { projectData, file });
5497
5888
  } catch (error) {
5498
5889
  this.uiController.setLoading(false);
5499
5890
  console.error('Import failed:', error);