senangwebs-tour 1.0.6 → 1.0.8
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 +21 -9
- package/dist/swt-editor.js +165 -177
- package/dist/swt-editor.js.map +1 -1
- package/dist/swt-editor.min.js +1 -1
- package/dist/swt.js +31 -2
- package/dist/swt.js.map +1 -1
- package/dist/swt.min.js +1 -1
- package/package.json +1 -1
- package/src/editor/js/data-transform.js +42 -0
- package/src/editor/js/editor.js +10 -8
- package/src/editor/js/export-manager.js +5 -67
- package/src/editor/js/hotspot-editor.js +36 -10
- package/src/editor/js/preview-controller.js +8 -62
- package/src/editor/js/scene-manager.js +9 -8
- package/src/editor/js/storage-manager.js +4 -2
- package/src/editor/js/ui-controller.js +52 -22
- package/src/index.js +31 -2
package/README.md
CHANGED
|
@@ -280,13 +280,21 @@ new SWT.Tour(aframeSceneElement, tourConfiguration);
|
|
|
280
280
|
|
|
281
281
|
#### Configuration Structure
|
|
282
282
|
|
|
283
|
+
Both the editor and viewer use the same data format. Scenes are stored as an **array**:
|
|
284
|
+
|
|
283
285
|
```javascript
|
|
284
286
|
{
|
|
285
287
|
initialScene: "scene-id", // Required: Starting scene ID
|
|
286
|
-
scenes:
|
|
287
|
-
|
|
288
|
+
scenes: [ // Required: Array of scenes
|
|
289
|
+
{
|
|
290
|
+
id: "scene-id", // Required: Scene identifier
|
|
288
291
|
name: "Scene Name", // Required: Display name
|
|
289
292
|
panorama: "url-or-dataurl", // Required: Image URL or base64
|
|
293
|
+
thumbnail: "url", // Optional: Thumbnail for editor
|
|
294
|
+
startingPosition: { // Optional: Initial camera orientation
|
|
295
|
+
pitch: 0.1, // Vertical angle (radians)
|
|
296
|
+
yaw: 1.5 // Horizontal angle (radians)
|
|
297
|
+
},
|
|
290
298
|
hotspots: [ // Optional: Array of hotspots
|
|
291
299
|
{
|
|
292
300
|
id: "hotspot-1", // Optional: Auto-generated if omitted
|
|
@@ -301,25 +309,29 @@ new SWT.Tour(aframeSceneElement, tourConfiguration);
|
|
|
301
309
|
},
|
|
302
310
|
appearance: { // Optional: Visual customization
|
|
303
311
|
color: "#00ff00", // Default: "#00ff00"
|
|
304
|
-
scale: 1
|
|
305
|
-
icon: "arrow" //
|
|
312
|
+
scale: "1 1 1", // Default: "1 1 1"
|
|
313
|
+
icon: "arrow" // Optional: Icon name or URL
|
|
306
314
|
},
|
|
307
315
|
tooltip: { // Optional: Hover/focus text
|
|
308
|
-
text: "Click
|
|
316
|
+
text: "Click here", // Tooltip title
|
|
317
|
+
description: "Details" // Optional: Extended description
|
|
318
|
+
},
|
|
319
|
+
cameraOrientation: { // Optional: Camera direction when created
|
|
320
|
+
pitch: -0.02, // Vertical angle (radians)
|
|
321
|
+
yaw: 2.06 // Horizontal angle (radians)
|
|
309
322
|
}
|
|
310
323
|
}
|
|
311
324
|
]
|
|
312
325
|
}
|
|
313
|
-
|
|
326
|
+
]
|
|
314
327
|
}
|
|
315
328
|
```
|
|
316
329
|
|
|
317
330
|
**Important Notes:**
|
|
318
331
|
|
|
319
|
-
- `scenes` is an **
|
|
332
|
+
- `scenes` is an **array** (not an object)
|
|
333
|
+
- The library also accepts `scenes` as an object keyed by ID for backward compatibility
|
|
320
334
|
- Hotspot `position` is in 3D space (typically on 10-unit sphere surface)
|
|
321
|
-
- Editor stores `imageUrl`, library expects `panorama` (export handles conversion)
|
|
322
|
-
- Images can be URLs or Data URLs (base64) for offline tours
|
|
323
335
|
|
|
324
336
|
---
|
|
325
337
|
|
package/dist/swt-editor.js
CHANGED
|
@@ -300,8 +300,10 @@
|
|
|
300
300
|
return false;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
// imageUrl is required for scenes to be valid
|
|
304
|
-
|
|
303
|
+
// panorama or imageUrl is required for scenes to be valid (support both formats)
|
|
304
|
+
const hasImage = (scene.panorama && typeof scene.panorama === "string") ||
|
|
305
|
+
(scene.imageUrl && typeof scene.imageUrl === "string");
|
|
306
|
+
if (!hasImage) {
|
|
305
307
|
return false;
|
|
306
308
|
}
|
|
307
309
|
|
|
@@ -426,7 +428,7 @@
|
|
|
426
428
|
* @param {File|Object} fileOrConfig - Either a File object or a scene config object
|
|
427
429
|
* @param {string} fileOrConfig.id - Scene ID (if config object)
|
|
428
430
|
* @param {string} fileOrConfig.name - Scene name (if config object)
|
|
429
|
-
* @param {string} fileOrConfig.
|
|
431
|
+
* @param {string} fileOrConfig.panorama - Panorama URL (if config object)
|
|
430
432
|
* @param {string} fileOrConfig.thumbnail - Thumbnail URL (if config object)
|
|
431
433
|
* @param {Array} fileOrConfig.hotspots - Hotspots array (if config object)
|
|
432
434
|
*/
|
|
@@ -443,17 +445,17 @@
|
|
|
443
445
|
scene = {
|
|
444
446
|
id: sanitizeId$1(fileOrConfig.name.replace(/\.[^/.]+$/, "")),
|
|
445
447
|
name: fileOrConfig.name.replace(/\.[^/.]+$/, ""),
|
|
446
|
-
|
|
448
|
+
panorama: imageDataUrl,
|
|
447
449
|
thumbnail: thumbnail,
|
|
448
450
|
hotspots: [],
|
|
449
451
|
};
|
|
450
452
|
} else if (typeof fileOrConfig === "object" && fileOrConfig !== null) {
|
|
451
|
-
// Handle config object
|
|
453
|
+
// Handle config object - support both panorama and imageUrl for backward compatibility
|
|
452
454
|
scene = {
|
|
453
455
|
id: fileOrConfig.id || sanitizeId$1(`scene-${Date.now()}`),
|
|
454
456
|
name: fileOrConfig.name || "Untitled Scene",
|
|
455
|
-
|
|
456
|
-
thumbnail: fileOrConfig.thumbnail || fileOrConfig.imageUrl || "",
|
|
457
|
+
panorama: fileOrConfig.panorama || fileOrConfig.imageUrl || "",
|
|
458
|
+
thumbnail: fileOrConfig.thumbnail || fileOrConfig.panorama || fileOrConfig.imageUrl || "",
|
|
457
459
|
hotspots: fileOrConfig.hotspots || [],
|
|
458
460
|
...(fileOrConfig.startingPosition && { startingPosition: fileOrConfig.startingPosition }),
|
|
459
461
|
};
|
|
@@ -523,12 +525,13 @@
|
|
|
523
525
|
if (index >= 0 && index < this.scenes.length) {
|
|
524
526
|
this.scenes[index][property] = value;
|
|
525
527
|
|
|
526
|
-
// If updating ID, update all hotspot target references
|
|
528
|
+
// If updating ID, update all hotspot target references (uses nested action.target)
|
|
527
529
|
if (property === "id") {
|
|
530
|
+
const oldId = this.scenes[index].id;
|
|
528
531
|
this.scenes.forEach((scene) => {
|
|
529
532
|
scene.hotspots.forEach((hotspot) => {
|
|
530
|
-
if (hotspot.
|
|
531
|
-
hotspot.
|
|
533
|
+
if (hotspot.action?.target === oldId) {
|
|
534
|
+
hotspot.action.target = value;
|
|
532
535
|
}
|
|
533
536
|
});
|
|
534
537
|
});
|
|
@@ -649,17 +652,24 @@
|
|
|
649
652
|
return null;
|
|
650
653
|
}
|
|
651
654
|
|
|
655
|
+
// Use unified library format with nested structure
|
|
652
656
|
const hotspot = {
|
|
653
657
|
id: generateId('hotspot'),
|
|
654
|
-
type: 'navigation',
|
|
655
658
|
position: position,
|
|
656
|
-
cameraOrientation: cameraOrientation,
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
659
|
+
cameraOrientation: cameraOrientation,
|
|
660
|
+
action: {
|
|
661
|
+
type: 'navigateTo',
|
|
662
|
+
target: targetSceneId
|
|
663
|
+
},
|
|
664
|
+
appearance: {
|
|
665
|
+
color: '#00ff00',
|
|
666
|
+
scale: '1 1 1',
|
|
667
|
+
icon: ''
|
|
668
|
+
},
|
|
669
|
+
tooltip: {
|
|
670
|
+
text: 'New Hotspot',
|
|
671
|
+
description: ''
|
|
672
|
+
}
|
|
663
673
|
};
|
|
664
674
|
|
|
665
675
|
scene.hotspots.push(hotspot);
|
|
@@ -698,6 +708,7 @@
|
|
|
698
708
|
|
|
699
709
|
/**
|
|
700
710
|
* Update hotspot property
|
|
711
|
+
* Supports nested properties like 'appearance.color', 'action.target', 'tooltip.text'
|
|
701
712
|
*/
|
|
702
713
|
updateHotspot(index, property, value) {
|
|
703
714
|
const scene = this.editor.sceneManager.getCurrentScene();
|
|
@@ -705,7 +716,23 @@
|
|
|
705
716
|
return false;
|
|
706
717
|
}
|
|
707
718
|
|
|
708
|
-
scene.hotspots[index]
|
|
719
|
+
const hotspot = scene.hotspots[index];
|
|
720
|
+
|
|
721
|
+
// Handle nested properties (e.g., 'appearance.color')
|
|
722
|
+
if (property.includes('.')) {
|
|
723
|
+
const parts = property.split('.');
|
|
724
|
+
let obj = hotspot;
|
|
725
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
726
|
+
if (!obj[parts[i]]) {
|
|
727
|
+
obj[parts[i]] = {};
|
|
728
|
+
}
|
|
729
|
+
obj = obj[parts[i]];
|
|
730
|
+
}
|
|
731
|
+
obj[parts[parts.length - 1]] = value;
|
|
732
|
+
} else {
|
|
733
|
+
hotspot[property] = value;
|
|
734
|
+
}
|
|
735
|
+
|
|
709
736
|
return true;
|
|
710
737
|
}
|
|
711
738
|
|
|
@@ -772,7 +799,9 @@
|
|
|
772
799
|
const original = scene.hotspots[index];
|
|
773
800
|
const duplicate = deepClone(original);
|
|
774
801
|
duplicate.id = generateId('hotspot');
|
|
775
|
-
duplicate.
|
|
802
|
+
if (duplicate.tooltip) {
|
|
803
|
+
duplicate.tooltip.text = (original.tooltip?.text || 'Hotspot') + ' (Copy)';
|
|
804
|
+
}
|
|
776
805
|
|
|
777
806
|
// Offset position slightly
|
|
778
807
|
duplicate.position = {
|
|
@@ -816,6 +845,49 @@
|
|
|
816
845
|
}
|
|
817
846
|
};
|
|
818
847
|
|
|
848
|
+
/**
|
|
849
|
+
* Data Transform Utilities
|
|
850
|
+
*
|
|
851
|
+
* With the unified format, editor and library use the same structure:
|
|
852
|
+
* - scenes: Array of scene objects with `panorama`
|
|
853
|
+
* - hotspots: nested properties (action, appearance, tooltip)
|
|
854
|
+
*
|
|
855
|
+
* These utilities handle building the tour config for the SWT library.
|
|
856
|
+
*/
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Build a complete tour configuration for the SWT library
|
|
860
|
+
* Since unified format is used, this just wraps the scenes array with config
|
|
861
|
+
* @param {Object} config - Config with initialSceneId, etc.
|
|
862
|
+
* @param {Array} scenes - Array of scenes (already in unified format)
|
|
863
|
+
* @returns {Object} Complete tour config
|
|
864
|
+
*/
|
|
865
|
+
function buildTourConfig(config, scenes) {
|
|
866
|
+
// Determine initial scene
|
|
867
|
+
let initialScene = config.initialSceneId;
|
|
868
|
+
if (!initialScene && scenes && scenes.length > 0) {
|
|
869
|
+
initialScene = scenes[0].id;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
initialScene: initialScene,
|
|
874
|
+
scenes: scenes || [],
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Build tour config for preview (sets initial scene to current scene)
|
|
880
|
+
* @param {Object} currentScene - The scene to show initially
|
|
881
|
+
* @param {Array} allScenes - All scenes for navigation support
|
|
882
|
+
* @returns {Object} Complete tour config
|
|
883
|
+
*/
|
|
884
|
+
function buildPreviewTourConfig(currentScene, allScenes) {
|
|
885
|
+
return buildTourConfig(
|
|
886
|
+
{ initialSceneId: currentScene.id },
|
|
887
|
+
allScenes
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
819
891
|
// Preview Controller - Manages A-Frame preview integration using SWT library
|
|
820
892
|
|
|
821
893
|
let PreviewController$1 = class PreviewController {
|
|
@@ -866,14 +938,16 @@
|
|
|
866
938
|
|
|
867
939
|
/**
|
|
868
940
|
* Load scene into preview using SWT library
|
|
941
|
+
* Scenes and hotspots now use unified library format directly
|
|
869
942
|
*/
|
|
870
943
|
async loadScene(scene, preserveCameraRotation = true) {
|
|
871
944
|
if (!this.isInitialized || !scene) {
|
|
872
945
|
return;
|
|
873
946
|
}
|
|
874
947
|
|
|
875
|
-
// Validate scene has required data
|
|
876
|
-
|
|
948
|
+
// Validate scene has required data (panorama or imageUrl for backward compatibility)
|
|
949
|
+
const panoramaUrl = scene.panorama || scene.imageUrl;
|
|
950
|
+
if (!panoramaUrl || !scene.id) {
|
|
877
951
|
console.error("Invalid scene data:", scene);
|
|
878
952
|
return;
|
|
879
953
|
}
|
|
@@ -939,65 +1013,10 @@
|
|
|
939
1013
|
// Give A-Frame a moment to start initializing before we proceed
|
|
940
1014
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
941
1015
|
|
|
942
|
-
// Build tour config
|
|
943
|
-
//
|
|
944
|
-
(scene.hotspots || []).map((h) => ({
|
|
945
|
-
id: h.id,
|
|
946
|
-
position: h.position,
|
|
947
|
-
action: {
|
|
948
|
-
type: h.type === "navigation" ? "navigateTo" : h.type,
|
|
949
|
-
target: h.targetSceneId,
|
|
950
|
-
},
|
|
951
|
-
appearance: {
|
|
952
|
-
color: h.color || "#00ff00",
|
|
953
|
-
icon: h.icon || null,
|
|
954
|
-
scale: h.scale || "1 1 1",
|
|
955
|
-
},
|
|
956
|
-
tooltip: {
|
|
957
|
-
text: h.title || "Hotspot",
|
|
958
|
-
},
|
|
959
|
-
}));
|
|
960
|
-
|
|
961
|
-
({
|
|
962
|
-
id: scene.id,
|
|
963
|
-
name: scene.name,
|
|
964
|
-
panorama: scene.imageUrl});
|
|
965
|
-
|
|
966
|
-
// Build scenes object with ALL scenes (for navigation to work)
|
|
967
|
-
const allScenes = {};
|
|
1016
|
+
// Build tour config using shared transform utilities
|
|
1017
|
+
// This includes ALL scenes for navigation support
|
|
968
1018
|
const editorScenes = this.editor.sceneManager.scenes || [];
|
|
969
|
-
editorScenes
|
|
970
|
-
const sceneHotspots = (s.hotspots || []).map((h) => ({
|
|
971
|
-
id: h.id,
|
|
972
|
-
position: h.position,
|
|
973
|
-
action: {
|
|
974
|
-
type: h.type === "navigation" ? "navigateTo" : h.type,
|
|
975
|
-
target: h.targetSceneId,
|
|
976
|
-
},
|
|
977
|
-
appearance: {
|
|
978
|
-
color: h.color || "#00ff00",
|
|
979
|
-
icon: h.icon || null,
|
|
980
|
-
scale: h.scale || "1 1 1",
|
|
981
|
-
},
|
|
982
|
-
tooltip: {
|
|
983
|
-
text: h.title || "Hotspot",
|
|
984
|
-
},
|
|
985
|
-
}));
|
|
986
|
-
|
|
987
|
-
allScenes[s.id] = {
|
|
988
|
-
id: s.id,
|
|
989
|
-
name: s.name,
|
|
990
|
-
panorama: s.imageUrl,
|
|
991
|
-
hotspots: sceneHotspots,
|
|
992
|
-
startingPosition: s.startingPosition || null,
|
|
993
|
-
};
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
const tourConfig = {
|
|
997
|
-
title: scene.name,
|
|
998
|
-
initialScene: scene.id,
|
|
999
|
-
scenes: allScenes,
|
|
1000
|
-
};
|
|
1019
|
+
const tourConfig = buildPreviewTourConfig(scene, editorScenes);
|
|
1001
1020
|
|
|
1002
1021
|
try {
|
|
1003
1022
|
// Create new tour instance
|
|
@@ -4022,9 +4041,9 @@
|
|
|
4022
4041
|
dragHandle.innerHTML =
|
|
4023
4042
|
'<ss-icon icon="arrow-up-down-left-right" thickness="2.2"></ss-icon>';
|
|
4024
4043
|
|
|
4025
|
-
// Thumbnail
|
|
4044
|
+
// Thumbnail - use thumbnail, panorama, or imageUrl (backward compatibility)
|
|
4026
4045
|
const thumbnail = document.createElement("img");
|
|
4027
|
-
thumbnail.src = scene.thumbnail || scene.imageUrl;
|
|
4046
|
+
thumbnail.src = scene.thumbnail || scene.panorama || scene.imageUrl;
|
|
4028
4047
|
thumbnail.alt = scene.name;
|
|
4029
4048
|
|
|
4030
4049
|
// Info
|
|
@@ -4156,18 +4175,25 @@
|
|
|
4156
4175
|
|
|
4157
4176
|
/**
|
|
4158
4177
|
* Create hotspot list item
|
|
4178
|
+
* Uses unified format with nested appearance, action, and tooltip
|
|
4159
4179
|
*/
|
|
4160
4180
|
createHotspotItem(hotspot, index, isActive) {
|
|
4161
4181
|
const item = document.createElement("div");
|
|
4162
4182
|
item.className = "hotspot-item" + (isActive ? " active" : "");
|
|
4163
4183
|
|
|
4184
|
+
// Get values from nested structure
|
|
4185
|
+
const color = hotspot.appearance?.color || "#00ff00";
|
|
4186
|
+
const icon = hotspot.appearance?.icon || "";
|
|
4187
|
+
const title = hotspot.tooltip?.text || "Untitled Hotspot";
|
|
4188
|
+
const targetSceneId = hotspot.action?.target || "";
|
|
4189
|
+
|
|
4164
4190
|
const colorIndicator = document.createElement("div");
|
|
4165
4191
|
colorIndicator.className = "hotspot-color";
|
|
4166
|
-
colorIndicator.style.backgroundColor =
|
|
4192
|
+
colorIndicator.style.backgroundColor = color;
|
|
4167
4193
|
|
|
4168
4194
|
// If hotspot has an icon, show it with the color applied
|
|
4169
|
-
if (
|
|
4170
|
-
colorIndicator.innerHTML = `<ss-icon icon="${
|
|
4195
|
+
if (icon) {
|
|
4196
|
+
colorIndicator.innerHTML = `<ss-icon icon="${icon}" thickness="2.2" style="color: ${color}; width: 20px; height: 20px;"></ss-icon>`;
|
|
4171
4197
|
colorIndicator.style.backgroundColor = "transparent";
|
|
4172
4198
|
colorIndicator.style.display = "flex";
|
|
4173
4199
|
colorIndicator.style.alignItems = "center";
|
|
@@ -4177,24 +4203,24 @@
|
|
|
4177
4203
|
const info = document.createElement("div");
|
|
4178
4204
|
info.className = "hotspot-info";
|
|
4179
4205
|
|
|
4180
|
-
const
|
|
4181
|
-
|
|
4182
|
-
|
|
4206
|
+
const titleEl = document.createElement("div");
|
|
4207
|
+
titleEl.className = "hotspot-title";
|
|
4208
|
+
titleEl.textContent = title;
|
|
4183
4209
|
|
|
4184
4210
|
const target = document.createElement("div");
|
|
4185
4211
|
target.className = "hotspot-target";
|
|
4186
|
-
if (
|
|
4212
|
+
if (targetSceneId) {
|
|
4187
4213
|
const targetScene = this.editor.sceneManager.getSceneById(
|
|
4188
|
-
|
|
4214
|
+
targetSceneId
|
|
4189
4215
|
);
|
|
4190
4216
|
target.textContent = targetScene
|
|
4191
4217
|
? `→ ${targetScene.name}`
|
|
4192
|
-
: `→ ${
|
|
4218
|
+
: `→ ${targetSceneId}`;
|
|
4193
4219
|
} else {
|
|
4194
4220
|
target.textContent = "No target";
|
|
4195
4221
|
}
|
|
4196
4222
|
|
|
4197
|
-
info.appendChild(
|
|
4223
|
+
info.appendChild(titleEl);
|
|
4198
4224
|
info.appendChild(target);
|
|
4199
4225
|
|
|
4200
4226
|
const actions = document.createElement("div");
|
|
@@ -4243,11 +4269,27 @@
|
|
|
4243
4269
|
|
|
4244
4270
|
/**
|
|
4245
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
|
|
4246
4273
|
*/
|
|
4247
|
-
populateIconGrid() {
|
|
4274
|
+
async populateIconGrid() {
|
|
4248
4275
|
const grid = document.getElementById("hotspotIconGrid");
|
|
4249
4276
|
if (!grid) return;
|
|
4250
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
|
+
|
|
4251
4293
|
// Clear existing content
|
|
4252
4294
|
grid.innerHTML = "";
|
|
4253
4295
|
|
|
@@ -4276,6 +4318,7 @@
|
|
|
4276
4318
|
|
|
4277
4319
|
/**
|
|
4278
4320
|
* Update properties panel for hotspot
|
|
4321
|
+
* Uses unified format with nested appearance, action, and tooltip
|
|
4279
4322
|
*/
|
|
4280
4323
|
updateHotspotProperties(hotspot) {
|
|
4281
4324
|
const hotspotAll = document.getElementById("hotspotAll");
|
|
@@ -4305,21 +4348,26 @@
|
|
|
4305
4348
|
if (hotspotAll) hotspotAll.style.display = "block";
|
|
4306
4349
|
if (hotspotProperties) hotspotProperties.style.display = "block";
|
|
4307
4350
|
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4351
|
+
// Get values from nested structure
|
|
4352
|
+
const title = hotspot.tooltip?.text || "";
|
|
4353
|
+
const description = hotspot.tooltip?.description || "";
|
|
4354
|
+
const targetSceneId = hotspot.action?.target || "";
|
|
4355
|
+
const color = hotspot.appearance?.color || "#00ff00";
|
|
4356
|
+
const icon = hotspot.appearance?.icon || "";
|
|
4357
|
+
|
|
4358
|
+
document.getElementById("hotspotTitle").value = title;
|
|
4359
|
+
document.getElementById("hotspotDescription").value = description;
|
|
4360
|
+
document.getElementById("hotspotTarget").value = targetSceneId;
|
|
4361
|
+
document.getElementById("hotspotColor").value = color;
|
|
4314
4362
|
|
|
4315
4363
|
// Update color text input if it exists
|
|
4316
4364
|
const colorText = document.getElementById("hotspotColorText");
|
|
4317
4365
|
if (colorText) {
|
|
4318
|
-
colorText.value =
|
|
4366
|
+
colorText.value = color;
|
|
4319
4367
|
}
|
|
4320
4368
|
|
|
4321
4369
|
// Update icon grid active button
|
|
4322
|
-
this.setActiveIconButton(
|
|
4370
|
+
this.setActiveIconButton(icon);
|
|
4323
4371
|
|
|
4324
4372
|
// Update position inputs
|
|
4325
4373
|
const pos = hotspot.position || { x: 0, y: 0, z: 0 };
|
|
@@ -4349,7 +4397,8 @@
|
|
|
4349
4397
|
|
|
4350
4398
|
document.getElementById("sceneId").value = scene.id || "";
|
|
4351
4399
|
document.getElementById("sceneName").value = scene.name || "";
|
|
4352
|
-
|
|
4400
|
+
// Support both panorama (unified) and imageUrl (legacy)
|
|
4401
|
+
document.getElementById("sceneImageUrl").value = scene.panorama || scene.imageUrl || "";
|
|
4353
4402
|
|
|
4354
4403
|
// Update starting position display
|
|
4355
4404
|
if (startingPosDisplay) {
|
|
@@ -4586,75 +4635,13 @@
|
|
|
4586
4635
|
/**
|
|
4587
4636
|
* Generate JSON compatible with SWT library
|
|
4588
4637
|
* Follows the tourConfig structure from example-simple.html
|
|
4638
|
+
* Uses shared data-transform utilities for consistent transformation
|
|
4589
4639
|
*/
|
|
4590
4640
|
generateJSON() {
|
|
4591
4641
|
const scenes = this.editor.sceneManager.getAllScenes();
|
|
4592
4642
|
const config = this.editor.config;
|
|
4593
4643
|
|
|
4594
|
-
|
|
4595
|
-
const scenesData = {};
|
|
4596
|
-
scenes.forEach((scene) => {
|
|
4597
|
-
scenesData[scene.id] = {
|
|
4598
|
-
name: scene.name,
|
|
4599
|
-
panorama: scene.imageUrl,
|
|
4600
|
-
hotspots: scene.hotspots.map((hotspot) => {
|
|
4601
|
-
const hotspotData = {
|
|
4602
|
-
position: hotspot.position,
|
|
4603
|
-
};
|
|
4604
|
-
|
|
4605
|
-
// Add action based on hotspot type
|
|
4606
|
-
if (hotspot.type === "navigation" && hotspot.targetSceneId) {
|
|
4607
|
-
hotspotData.action = {
|
|
4608
|
-
type: "navigateTo",
|
|
4609
|
-
target: hotspot.targetSceneId,
|
|
4610
|
-
};
|
|
4611
|
-
} else if (hotspot.type === "info") {
|
|
4612
|
-
hotspotData.action = {
|
|
4613
|
-
type: "showInfo",
|
|
4614
|
-
};
|
|
4615
|
-
}
|
|
4616
|
-
|
|
4617
|
-
// Add appearance
|
|
4618
|
-
hotspotData.appearance = {
|
|
4619
|
-
color: hotspot.color || "#FF6B6B",
|
|
4620
|
-
scale: hotspot.scale || "2 2 2",
|
|
4621
|
-
};
|
|
4622
|
-
|
|
4623
|
-
// Add icon if set
|
|
4624
|
-
if (hotspot.icon) {
|
|
4625
|
-
hotspotData.appearance.icon = hotspot.icon;
|
|
4626
|
-
}
|
|
4627
|
-
|
|
4628
|
-
// Add tooltip if title exists
|
|
4629
|
-
if (hotspot.title) {
|
|
4630
|
-
hotspotData.tooltip = {
|
|
4631
|
-
text: hotspot.title,
|
|
4632
|
-
};
|
|
4633
|
-
}
|
|
4634
|
-
|
|
4635
|
-
return hotspotData;
|
|
4636
|
-
}),
|
|
4637
|
-
};
|
|
4638
|
-
|
|
4639
|
-
// Add starting position if set
|
|
4640
|
-
if (scene.startingPosition) {
|
|
4641
|
-
scenesData[scene.id].startingPosition = scene.startingPosition;
|
|
4642
|
-
}
|
|
4643
|
-
});
|
|
4644
|
-
|
|
4645
|
-
// Determine initial scene
|
|
4646
|
-
let initialScene = config.initialSceneId;
|
|
4647
|
-
if (!initialScene && scenes.length > 0) {
|
|
4648
|
-
initialScene = scenes[0].id;
|
|
4649
|
-
}
|
|
4650
|
-
|
|
4651
|
-
// Build final JSON matching SWT tourConfig format
|
|
4652
|
-
const jsonData = {
|
|
4653
|
-
initialScene: initialScene,
|
|
4654
|
-
scenes: scenesData,
|
|
4655
|
-
};
|
|
4656
|
-
|
|
4657
|
-
return jsonData;
|
|
4644
|
+
return buildTourConfig(config, scenes);
|
|
4658
4645
|
}
|
|
4659
4646
|
|
|
4660
4647
|
/**
|
|
@@ -4665,9 +4652,8 @@
|
|
|
4665
4652
|
const jsonData = this.generateJSON();
|
|
4666
4653
|
|
|
4667
4654
|
// Process all scenes and convert icon names to data URLs
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4655
|
+
// Scenes are an array in unified format
|
|
4656
|
+
for (const scene of jsonData.scenes) {
|
|
4671
4657
|
for (let i = 0; i < scene.hotspots.length; i++) {
|
|
4672
4658
|
const hotspot = scene.hotspots[i];
|
|
4673
4659
|
const icon = hotspot.appearance?.icon;
|
|
@@ -4874,8 +4860,8 @@
|
|
|
4874
4860
|
// Setup event listeners
|
|
4875
4861
|
this.setupEventListeners();
|
|
4876
4862
|
|
|
4877
|
-
// Populate icon grid
|
|
4878
|
-
this.uiController.populateIconGrid();
|
|
4863
|
+
// Populate icon grid (async to wait for custom element registration)
|
|
4864
|
+
await this.uiController.populateIconGrid();
|
|
4879
4865
|
|
|
4880
4866
|
// Load saved project if exists (but only if it has valid data)
|
|
4881
4867
|
if (this.storageManager.hasProject()) {
|
|
@@ -4968,19 +4954,19 @@
|
|
|
4968
4954
|
});
|
|
4969
4955
|
|
|
4970
4956
|
document.getElementById('hotspotTitle')?.addEventListener('input', debounce((e) => {
|
|
4971
|
-
this.updateCurrentHotspot('
|
|
4957
|
+
this.updateCurrentHotspot('tooltip.text', e.target.value);
|
|
4972
4958
|
}, 300));
|
|
4973
4959
|
|
|
4974
4960
|
document.getElementById('hotspotDescription')?.addEventListener('input', debounce((e) => {
|
|
4975
|
-
this.updateCurrentHotspot('description', e.target.value);
|
|
4961
|
+
this.updateCurrentHotspot('tooltip.description', e.target.value);
|
|
4976
4962
|
}, 300));
|
|
4977
4963
|
|
|
4978
4964
|
document.getElementById('hotspotTarget')?.addEventListener('change', (e) => {
|
|
4979
|
-
this.updateCurrentHotspot('
|
|
4965
|
+
this.updateCurrentHotspot('action.target', e.target.value);
|
|
4980
4966
|
});
|
|
4981
4967
|
|
|
4982
4968
|
document.getElementById('hotspotColor')?.addEventListener('input', (e) => {
|
|
4983
|
-
this.updateCurrentHotspot('color', e.target.value);
|
|
4969
|
+
this.updateCurrentHotspot('appearance.color', e.target.value);
|
|
4984
4970
|
});
|
|
4985
4971
|
|
|
4986
4972
|
// Icon grid button clicks
|
|
@@ -4992,7 +4978,7 @@
|
|
|
4992
4978
|
document.querySelectorAll('#hotspotIconGrid .icon-btn').forEach(b => b.classList.remove('active'));
|
|
4993
4979
|
btn.classList.add('active');
|
|
4994
4980
|
// Update hotspot
|
|
4995
|
-
this.updateCurrentHotspot('icon', iconValue);
|
|
4981
|
+
this.updateCurrentHotspot('appearance.icon', iconValue);
|
|
4996
4982
|
}
|
|
4997
4983
|
});
|
|
4998
4984
|
|
|
@@ -5321,7 +5307,8 @@
|
|
|
5321
5307
|
const index = this.sceneManager.currentSceneIndex;
|
|
5322
5308
|
if (index < 0) return;
|
|
5323
5309
|
|
|
5324
|
-
|
|
5310
|
+
// Use panorama for unified format
|
|
5311
|
+
if (this.sceneManager.updateScene(index, 'panorama', imageUrl)) {
|
|
5325
5312
|
const scene = this.sceneManager.getCurrentScene();
|
|
5326
5313
|
if (scene) {
|
|
5327
5314
|
scene.thumbnail = imageUrl;
|
|
@@ -5388,6 +5375,7 @@
|
|
|
5388
5375
|
render() {
|
|
5389
5376
|
this.uiController.renderSceneList();
|
|
5390
5377
|
this.uiController.renderHotspotList();
|
|
5378
|
+
this.uiController.populateIconGrid(); // Re-render icon grid to ensure icons display
|
|
5391
5379
|
|
|
5392
5380
|
const currentScene = this.sceneManager.getCurrentScene();
|
|
5393
5381
|
const currentHotspot = this.hotspotEditor.getCurrentHotspot();
|