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.
- package/README.md +2 -1
- package/dist/swt-editor.js +425 -34
- package/dist/swt-editor.js.map +1 -1
- package/dist/swt-editor.min.js +1 -1
- package/package.json +1 -1
- package/src/editor/js/editor.js +92 -0
- package/src/editor/js/event-emitter.js +257 -0
- package/src/editor/js/export-manager.js +47 -1
- package/src/editor/js/ui-controller.js +1 -1
package/dist/swt-editor.js
CHANGED
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
/**
|
|
80
80
|
* Show toast notification
|
|
81
81
|
*/
|
|
82
|
-
function showToast
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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);
|