senangwebs-tour 1.0.7 → 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
  }
@@ -4269,11 +4269,27 @@
4269
4269
 
4270
4270
  /**
4271
4271
  * Populate icon grid from SenangStart icons (baked in at build time)
4272
+ * Waits for ss-icon custom element to be defined to avoid race conditions
4272
4273
  */
4273
- populateIconGrid() {
4274
+ async populateIconGrid() {
4274
4275
  const grid = document.getElementById("hotspotIconGrid");
4275
4276
  if (!grid) return;
4276
4277
 
4278
+ // Wait for ss-icon custom element to be defined before populating
4279
+ // This prevents race conditions where icons don't render if the
4280
+ // custom element isn't registered yet when this method runs
4281
+ try {
4282
+ if (customElements.get('ss-icon') === undefined) {
4283
+ // Give a reasonable timeout to avoid infinite waiting
4284
+ await Promise.race([
4285
+ customElements.whenDefined('ss-icon'),
4286
+ new Promise((_, reject) => setTimeout(() => reject(new Error('ss-icon timeout')), 5000))
4287
+ ]);
4288
+ }
4289
+ } catch (err) {
4290
+ console.warn('ss-icon custom element not available, icon grid may not render properly:', err.message);
4291
+ }
4292
+
4277
4293
  // Clear existing content
4278
4294
  grid.innerHTML = "";
4279
4295
 
@@ -4297,7 +4313,7 @@
4297
4313
  grid.appendChild(btn);
4298
4314
  });
4299
4315
 
4300
- console.log(`Loaded ${iconsData.length} icons`);
4316
+ // console.log(`Loaded ${iconsData.length} icons`);
4301
4317
  }
4302
4318
 
4303
4319
  /**
@@ -4608,6 +4624,262 @@
4608
4624
  }
4609
4625
  }
4610
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
+
4611
4883
  // Export Manager - Handles JSON generation for SWT library
4612
4884
 
4613
4885
  let ExportManager$1 = class ExportManager {
@@ -4628,6 +4900,51 @@
4628
4900
  return buildTourConfig(config, scenes);
4629
4901
  }
4630
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
+
4631
4948
  /**
4632
4949
  * Generate JSON with icons baked in as SVG data URLs
4633
4950
  * This ensures the exported HTML doesn't need the SenangStart icons library
@@ -4818,6 +5135,47 @@
4818
5135
  this.hasUnsavedChanges = false;
4819
5136
  this.lastRenderedSceneIndex = -1;
4820
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);
4821
5179
  }
4822
5180
 
4823
5181
  /**
@@ -4837,15 +5195,15 @@
4837
5195
  const previewInit = await this.previewController.init();
4838
5196
  if (!previewInit) {
4839
5197
  console.error('Failed to initialize preview controller');
4840
- showToast$1('Failed to initialize preview', 'error');
5198
+ showToast('Failed to initialize preview', 'error');
4841
5199
  return false;
4842
5200
  }
4843
5201
 
4844
5202
  // Setup event listeners
4845
5203
  this.setupEventListeners();
4846
5204
 
4847
- // Populate icon grid
4848
- this.uiController.populateIconGrid();
5205
+ // Populate icon grid (async to wait for custom element registration)
5206
+ await this.uiController.populateIconGrid();
4849
5207
 
4850
5208
  // Load saved project if exists (but only if it has valid data)
4851
5209
  if (this.storageManager.hasProject()) {
@@ -4876,7 +5234,10 @@
4876
5234
  this.render();
4877
5235
  }
4878
5236
 
4879
- showToast$1('Editor ready', 'success');
5237
+ showToast('Editor ready', 'success');
5238
+
5239
+ // Emit ready event
5240
+ this.emit(EditorEvents.READY, { config: this.options });
4880
5241
 
4881
5242
  return true;
4882
5243
  }
@@ -5078,11 +5439,14 @@
5078
5439
 
5079
5440
  for (const file of files) {
5080
5441
  if (!file.type.startsWith('image/')) {
5081
- showToast$1(`${file.name} is not an image`, 'error');
5442
+ showToast(`${file.name} is not an image`, 'error');
5082
5443
  continue;
5083
5444
  }
5084
5445
 
5085
- 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
+ }
5086
5450
  }
5087
5451
  this.uiController.setLoading(false);
5088
5452
  this.render();
@@ -5116,6 +5480,11 @@
5116
5480
  this.lastRenderedSceneIndex = -1;
5117
5481
  this.render();
5118
5482
  this.markUnsavedChanges();
5483
+ this.emit(EditorEvents.HOTSPOT_ADD, {
5484
+ hotspot,
5485
+ position,
5486
+ sceneId: this.sceneManager.getCurrentScene()?.id
5487
+ });
5119
5488
  } else {
5120
5489
  console.error('Failed to add hotspot');
5121
5490
  }
@@ -5128,13 +5497,13 @@
5128
5497
  addHotspotAtCursor() {
5129
5498
  const scene = this.sceneManager.getCurrentScene();
5130
5499
  if (!scene) {
5131
- showToast$1('Please select a scene first', 'error');
5500
+ showToast('Please select a scene first', 'error');
5132
5501
  return;
5133
5502
  }
5134
5503
 
5135
5504
  const position = this.previewController.getCursorIntersection();
5136
5505
  if (!position) {
5137
- 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');
5138
5507
  return;
5139
5508
  }
5140
5509
 
@@ -5161,6 +5530,8 @@
5161
5530
  this.uiController.updateHotspotProperties(null);
5162
5531
  this.uiController.updateInitialSceneOptions();
5163
5532
  this.uiController.updateTargetSceneOptions();
5533
+
5534
+ this.emit(EditorEvents.SCENE_SELECT, { scene, index });
5164
5535
  }
5165
5536
  }
5166
5537
 
@@ -5179,6 +5550,8 @@
5179
5550
  if (hotspot) {
5180
5551
  this.previewController.pointCameraToHotspot(hotspot);
5181
5552
  }
5553
+
5554
+ this.emit(EditorEvents.HOTSPOT_SELECT, { hotspot, index });
5182
5555
  }
5183
5556
  }
5184
5557
 
@@ -5186,9 +5559,11 @@
5186
5559
  * Remove scene
5187
5560
  */
5188
5561
  removeScene(index) {
5562
+ const scene = this.sceneManager.getScene(index);
5189
5563
  if (this.sceneManager.removeScene(index)) {
5190
5564
  this.render();
5191
5565
  this.markUnsavedChanges();
5566
+ this.emit(EditorEvents.SCENE_REMOVE, { scene, index });
5192
5567
  }
5193
5568
  }
5194
5569
 
@@ -5196,10 +5571,12 @@
5196
5571
  * Remove hotspot
5197
5572
  */
5198
5573
  removeHotspot(index) {
5574
+ const hotspot = this.hotspotEditor.getHotspot(index);
5199
5575
  if (this.hotspotEditor.removeHotspot(index)) {
5200
5576
  this.lastRenderedSceneIndex = -1;
5201
5577
  this.render();
5202
5578
  this.markUnsavedChanges();
5579
+ this.emit(EditorEvents.HOTSPOT_REMOVE, { hotspot, index });
5203
5580
  }
5204
5581
  }
5205
5582
 
@@ -5212,6 +5589,7 @@
5212
5589
  this.lastRenderedSceneIndex = -1;
5213
5590
  this.render();
5214
5591
  this.markUnsavedChanges();
5592
+ this.emit(EditorEvents.HOTSPOT_DUPLICATE, { hotspot, originalIndex: index });
5215
5593
  }
5216
5594
  }
5217
5595
 
@@ -5222,6 +5600,7 @@
5222
5600
  if (this.sceneManager.reorderScenes(fromIndex, toIndex)) {
5223
5601
  this.render();
5224
5602
  this.markUnsavedChanges();
5603
+ this.emit(EditorEvents.SCENE_REORDER, { fromIndex, toIndex });
5225
5604
  }
5226
5605
  }
5227
5606
 
@@ -5234,6 +5613,12 @@
5234
5613
  await this.previewController.updateHotspotMarker(index);
5235
5614
  this.uiController.renderHotspotList();
5236
5615
  this.markUnsavedChanges();
5616
+ this.emit(EditorEvents.HOTSPOT_UPDATE, {
5617
+ hotspot: this.hotspotEditor.getHotspot(index),
5618
+ index,
5619
+ property,
5620
+ value
5621
+ });
5237
5622
  }
5238
5623
  }
5239
5624
 
@@ -5264,12 +5649,19 @@
5264
5649
  pos.z *= scale;
5265
5650
 
5266
5651
  document.getElementById(`hotspotPos${axis.toUpperCase()}`).value = pos[axis].toFixed(2);
5267
- showToast$1('Position clamped to 10-unit radius', 'info');
5652
+ showToast('Position clamped to 10-unit radius', 'info');
5268
5653
  }
5269
5654
 
5270
5655
  await this.previewController.updateHotspotMarker(index);
5271
5656
  this.uiController.renderHotspotList();
5272
5657
  this.markUnsavedChanges();
5658
+ this.emit(EditorEvents.HOTSPOT_POSITION_CHANGE, {
5659
+ hotspot,
5660
+ index,
5661
+ axis,
5662
+ value,
5663
+ position: hotspot.position
5664
+ });
5273
5665
  }
5274
5666
  }
5275
5667
 
@@ -5281,6 +5673,12 @@
5281
5673
  if (this.sceneManager.updateScene(index, property, value)) {
5282
5674
  this.uiController.renderSceneList();
5283
5675
  this.markUnsavedChanges();
5676
+ this.emit(EditorEvents.SCENE_UPDATE, {
5677
+ scene: this.sceneManager.getScene(index),
5678
+ index,
5679
+ property,
5680
+ value
5681
+ });
5284
5682
  }
5285
5683
  }
5286
5684
 
@@ -5304,9 +5702,10 @@
5304
5702
  if (scene) {
5305
5703
  await this.previewController.loadScene(scene);
5306
5704
  this.lastRenderedSceneIndex = index;
5307
- showToast$1('Scene image updated', 'success');
5705
+ showToast('Scene image updated', 'success');
5308
5706
  }
5309
5707
  this.markUnsavedChanges();
5708
+ this.emit(EditorEvents.SCENE_IMAGE_CHANGE, { scene, index, imageUrl });
5310
5709
  }
5311
5710
  }
5312
5711
 
@@ -5316,13 +5715,13 @@
5316
5715
  setSceneStartingPosition() {
5317
5716
  const scene = this.sceneManager.getCurrentScene();
5318
5717
  if (!scene) {
5319
- showToast$1('No scene selected', 'error');
5718
+ showToast('No scene selected', 'error');
5320
5719
  return;
5321
5720
  }
5322
5721
 
5323
5722
  const rotation = this.previewController.getCameraRotation();
5324
5723
  if (!rotation) {
5325
- showToast$1('Could not get camera rotation', 'error');
5724
+ showToast('Could not get camera rotation', 'error');
5326
5725
  return;
5327
5726
  }
5328
5727
 
@@ -5333,7 +5732,8 @@
5333
5732
 
5334
5733
  this.uiController.updateSceneProperties(scene);
5335
5734
  this.markUnsavedChanges();
5336
- showToast$1('Starting position set', 'success');
5735
+ showToast('Starting position set', 'success');
5736
+ this.emit(EditorEvents.SCENE_STARTING_POSITION_SET, { scene, startingPosition: scene.startingPosition });
5337
5737
  }
5338
5738
 
5339
5739
  /**
@@ -5342,7 +5742,7 @@
5342
5742
  clearSceneStartingPosition() {
5343
5743
  const scene = this.sceneManager.getCurrentScene();
5344
5744
  if (!scene) {
5345
- showToast$1('No scene selected', 'error');
5745
+ showToast('No scene selected', 'error');
5346
5746
  return;
5347
5747
  }
5348
5748
 
@@ -5350,7 +5750,8 @@
5350
5750
 
5351
5751
  this.uiController.updateSceneProperties(scene);
5352
5752
  this.markUnsavedChanges();
5353
- showToast$1('Starting position cleared', 'success');
5753
+ showToast('Starting position cleared', 'success');
5754
+ this.emit(EditorEvents.SCENE_STARTING_POSITION_CLEAR, { scene });
5354
5755
  }
5355
5756
 
5356
5757
  /**
@@ -5359,6 +5760,7 @@
5359
5760
  render() {
5360
5761
  this.uiController.renderSceneList();
5361
5762
  this.uiController.renderHotspotList();
5763
+ this.uiController.populateIconGrid(); // Re-render icon grid to ensure icons display
5362
5764
 
5363
5765
  const currentScene = this.sceneManager.getCurrentScene();
5364
5766
  const currentHotspot = this.hotspotEditor.getCurrentHotspot();
@@ -5391,6 +5793,8 @@
5391
5793
  }
5392
5794
  this.lastRenderedSceneIndex = -1;
5393
5795
  }
5796
+
5797
+ this.emit(EditorEvents.UI_RENDER);
5394
5798
  }
5395
5799
 
5396
5800
  /**
@@ -5404,7 +5808,8 @@
5404
5808
 
5405
5809
  if (this.storageManager.saveProject(projectData)) {
5406
5810
  this.hasUnsavedChanges = false;
5407
- showToast$1('Project saved', 'success');
5811
+ showToast('Project saved', 'success');
5812
+ this.emit(EditorEvents.PROJECT_SAVE, { projectData });
5408
5813
  return true;
5409
5814
  }
5410
5815
  return false;
@@ -5420,7 +5825,8 @@
5420
5825
  this.sceneManager.loadScenes(projectData.scenes || []);
5421
5826
  this.hasUnsavedChanges = false;
5422
5827
  this.render();
5423
- showToast$1('Project loaded', 'success');
5828
+ showToast('Project loaded', 'success');
5829
+ this.emit(EditorEvents.PROJECT_LOAD, { projectData });
5424
5830
  return true;
5425
5831
  }
5426
5832
  return false;
@@ -5446,7 +5852,8 @@
5446
5852
  this.hasUnsavedChanges = false;
5447
5853
  this.render();
5448
5854
 
5449
- showToast$1('New project created', 'success');
5855
+ showToast('New project created', 'success');
5856
+ this.emit(EditorEvents.PROJECT_NEW, { config: this.config });
5450
5857
  return true;
5451
5858
  }
5452
5859
 
@@ -5476,7 +5883,8 @@
5476
5883
  this.render();
5477
5884
  this.uiController.setLoading(false);
5478
5885
 
5479
- showToast$1('Project imported successfully', 'success');
5886
+ showToast('Project imported successfully', 'success');
5887
+ this.emit(EditorEvents.PROJECT_IMPORT, { projectData, file });
5480
5888
  } catch (error) {
5481
5889
  this.uiController.setLoading(false);
5482
5890
  console.error('Import failed:', error);