senangwebs-tour 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/swt-editor.js +437 -35
- package/dist/swt-editor.js.map +1 -1
- package/dist/swt-editor.min.js +1 -1
- package/dist/swt.js +1 -2
- package/dist/swt.js.map +1 -1
- package/dist/swt.min.js +1 -1
- package/package.json +1 -1
- package/src/editor/editor-entry.js +2 -0
- package/src/editor/js/editor.js +96 -0
- package/src/editor/js/event-emitter.js +263 -0
- package/src/editor/js/export-manager.js +47 -1
- package/src/editor/js/ui-controller.js +1 -1
- package/src/index.js +1 -2
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,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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|