senangwebs-tour 1.0.6 → 1.0.7
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 +145 -174
- 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 +7 -6
- 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 +35 -21
- package/src/index.js +31 -2
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Transform Utilities
|
|
3
|
+
*
|
|
4
|
+
* With the unified format, editor and library use the same structure:
|
|
5
|
+
* - scenes: Array of scene objects with `panorama`
|
|
6
|
+
* - hotspots: nested properties (action, appearance, tooltip)
|
|
7
|
+
*
|
|
8
|
+
* These utilities handle building the tour config for the SWT library.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build a complete tour configuration for the SWT library
|
|
13
|
+
* Since unified format is used, this just wraps the scenes array with config
|
|
14
|
+
* @param {Object} config - Config with initialSceneId, etc.
|
|
15
|
+
* @param {Array} scenes - Array of scenes (already in unified format)
|
|
16
|
+
* @returns {Object} Complete tour config
|
|
17
|
+
*/
|
|
18
|
+
export function buildTourConfig(config, scenes) {
|
|
19
|
+
// Determine initial scene
|
|
20
|
+
let initialScene = config.initialSceneId;
|
|
21
|
+
if (!initialScene && scenes && scenes.length > 0) {
|
|
22
|
+
initialScene = scenes[0].id;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
initialScene: initialScene,
|
|
27
|
+
scenes: scenes || [],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build tour config for preview (sets initial scene to current scene)
|
|
33
|
+
* @param {Object} currentScene - The scene to show initially
|
|
34
|
+
* @param {Array} allScenes - All scenes for navigation support
|
|
35
|
+
* @returns {Object} Complete tour config
|
|
36
|
+
*/
|
|
37
|
+
export function buildPreviewTourConfig(currentScene, allScenes) {
|
|
38
|
+
return buildTourConfig(
|
|
39
|
+
{ initialSceneId: currentScene.id },
|
|
40
|
+
allScenes
|
|
41
|
+
);
|
|
42
|
+
}
|
package/src/editor/js/editor.js
CHANGED
|
@@ -149,19 +149,19 @@ class TourEditor {
|
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
document.getElementById('hotspotTitle')?.addEventListener('input', debounce((e) => {
|
|
152
|
-
this.updateCurrentHotspot('
|
|
152
|
+
this.updateCurrentHotspot('tooltip.text', e.target.value);
|
|
153
153
|
}, 300));
|
|
154
154
|
|
|
155
155
|
document.getElementById('hotspotDescription')?.addEventListener('input', debounce((e) => {
|
|
156
|
-
this.updateCurrentHotspot('description', e.target.value);
|
|
156
|
+
this.updateCurrentHotspot('tooltip.description', e.target.value);
|
|
157
157
|
}, 300));
|
|
158
158
|
|
|
159
159
|
document.getElementById('hotspotTarget')?.addEventListener('change', (e) => {
|
|
160
|
-
this.updateCurrentHotspot('
|
|
160
|
+
this.updateCurrentHotspot('action.target', e.target.value);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
document.getElementById('hotspotColor')?.addEventListener('input', (e) => {
|
|
164
|
-
this.updateCurrentHotspot('color', e.target.value);
|
|
164
|
+
this.updateCurrentHotspot('appearance.color', e.target.value);
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
// Icon grid button clicks
|
|
@@ -173,7 +173,7 @@ class TourEditor {
|
|
|
173
173
|
document.querySelectorAll('#hotspotIconGrid .icon-btn').forEach(b => b.classList.remove('active'));
|
|
174
174
|
btn.classList.add('active');
|
|
175
175
|
// Update hotspot
|
|
176
|
-
this.updateCurrentHotspot('icon', iconValue);
|
|
176
|
+
this.updateCurrentHotspot('appearance.icon', iconValue);
|
|
177
177
|
}
|
|
178
178
|
});
|
|
179
179
|
|
|
@@ -502,7 +502,8 @@ class TourEditor {
|
|
|
502
502
|
const index = this.sceneManager.currentSceneIndex;
|
|
503
503
|
if (index < 0) return;
|
|
504
504
|
|
|
505
|
-
|
|
505
|
+
// Use panorama for unified format
|
|
506
|
+
if (this.sceneManager.updateScene(index, 'panorama', imageUrl)) {
|
|
506
507
|
const scene = this.sceneManager.getCurrentScene();
|
|
507
508
|
if (scene) {
|
|
508
509
|
scene.thumbnail = imageUrl;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Export Manager - Handles JSON generation for SWT library
|
|
2
2
|
import { downloadTextAsFile, showModal, copyToClipboard } from "./utils.js";
|
|
3
3
|
import { IconRenderer } from "../../IconRenderer.js";
|
|
4
|
+
import { buildTourConfig } from "./data-transform.js";
|
|
4
5
|
|
|
5
6
|
class ExportManager {
|
|
6
7
|
constructor(editor) {
|
|
@@ -11,75 +12,13 @@ class ExportManager {
|
|
|
11
12
|
/**
|
|
12
13
|
* Generate JSON compatible with SWT library
|
|
13
14
|
* Follows the tourConfig structure from example-simple.html
|
|
15
|
+
* Uses shared data-transform utilities for consistent transformation
|
|
14
16
|
*/
|
|
15
17
|
generateJSON() {
|
|
16
18
|
const scenes = this.editor.sceneManager.getAllScenes();
|
|
17
19
|
const config = this.editor.config;
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
const scenesData = {};
|
|
21
|
-
scenes.forEach((scene) => {
|
|
22
|
-
scenesData[scene.id] = {
|
|
23
|
-
name: scene.name,
|
|
24
|
-
panorama: scene.imageUrl,
|
|
25
|
-
hotspots: scene.hotspots.map((hotspot) => {
|
|
26
|
-
const hotspotData = {
|
|
27
|
-
position: hotspot.position,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Add action based on hotspot type
|
|
31
|
-
if (hotspot.type === "navigation" && hotspot.targetSceneId) {
|
|
32
|
-
hotspotData.action = {
|
|
33
|
-
type: "navigateTo",
|
|
34
|
-
target: hotspot.targetSceneId,
|
|
35
|
-
};
|
|
36
|
-
} else if (hotspot.type === "info") {
|
|
37
|
-
hotspotData.action = {
|
|
38
|
-
type: "showInfo",
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Add appearance
|
|
43
|
-
hotspotData.appearance = {
|
|
44
|
-
color: hotspot.color || "#FF6B6B",
|
|
45
|
-
scale: hotspot.scale || "2 2 2",
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Add icon if set
|
|
49
|
-
if (hotspot.icon) {
|
|
50
|
-
hotspotData.appearance.icon = hotspot.icon;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Add tooltip if title exists
|
|
54
|
-
if (hotspot.title) {
|
|
55
|
-
hotspotData.tooltip = {
|
|
56
|
-
text: hotspot.title,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return hotspotData;
|
|
61
|
-
}),
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Add starting position if set
|
|
65
|
-
if (scene.startingPosition) {
|
|
66
|
-
scenesData[scene.id].startingPosition = scene.startingPosition;
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Determine initial scene
|
|
71
|
-
let initialScene = config.initialSceneId;
|
|
72
|
-
if (!initialScene && scenes.length > 0) {
|
|
73
|
-
initialScene = scenes[0].id;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Build final JSON matching SWT tourConfig format
|
|
77
|
-
const jsonData = {
|
|
78
|
-
initialScene: initialScene,
|
|
79
|
-
scenes: scenesData,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
return jsonData;
|
|
21
|
+
return buildTourConfig(config, scenes);
|
|
83
22
|
}
|
|
84
23
|
|
|
85
24
|
/**
|
|
@@ -90,9 +29,8 @@ class ExportManager {
|
|
|
90
29
|
const jsonData = this.generateJSON();
|
|
91
30
|
|
|
92
31
|
// Process all scenes and convert icon names to data URLs
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
32
|
+
// Scenes are an array in unified format
|
|
33
|
+
for (const scene of jsonData.scenes) {
|
|
96
34
|
for (let i = 0; i < scene.hotspots.length; i++) {
|
|
97
35
|
const hotspot = scene.hotspots[i];
|
|
98
36
|
const icon = hotspot.appearance?.icon;
|
|
@@ -20,17 +20,24 @@ class HotspotEditor {
|
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Use unified library format with nested structure
|
|
23
24
|
const hotspot = {
|
|
24
25
|
id: generateId('hotspot'),
|
|
25
|
-
type: 'navigation',
|
|
26
26
|
position: position,
|
|
27
|
-
cameraOrientation: cameraOrientation,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
cameraOrientation: cameraOrientation,
|
|
28
|
+
action: {
|
|
29
|
+
type: 'navigateTo',
|
|
30
|
+
target: targetSceneId
|
|
31
|
+
},
|
|
32
|
+
appearance: {
|
|
33
|
+
color: '#00ff00',
|
|
34
|
+
scale: '1 1 1',
|
|
35
|
+
icon: ''
|
|
36
|
+
},
|
|
37
|
+
tooltip: {
|
|
38
|
+
text: 'New Hotspot',
|
|
39
|
+
description: ''
|
|
40
|
+
}
|
|
34
41
|
};
|
|
35
42
|
|
|
36
43
|
scene.hotspots.push(hotspot);
|
|
@@ -69,6 +76,7 @@ class HotspotEditor {
|
|
|
69
76
|
|
|
70
77
|
/**
|
|
71
78
|
* Update hotspot property
|
|
79
|
+
* Supports nested properties like 'appearance.color', 'action.target', 'tooltip.text'
|
|
72
80
|
*/
|
|
73
81
|
updateHotspot(index, property, value) {
|
|
74
82
|
const scene = this.editor.sceneManager.getCurrentScene();
|
|
@@ -76,7 +84,23 @@ class HotspotEditor {
|
|
|
76
84
|
return false;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
scene.hotspots[index]
|
|
87
|
+
const hotspot = scene.hotspots[index];
|
|
88
|
+
|
|
89
|
+
// Handle nested properties (e.g., 'appearance.color')
|
|
90
|
+
if (property.includes('.')) {
|
|
91
|
+
const parts = property.split('.');
|
|
92
|
+
let obj = hotspot;
|
|
93
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
94
|
+
if (!obj[parts[i]]) {
|
|
95
|
+
obj[parts[i]] = {};
|
|
96
|
+
}
|
|
97
|
+
obj = obj[parts[i]];
|
|
98
|
+
}
|
|
99
|
+
obj[parts[parts.length - 1]] = value;
|
|
100
|
+
} else {
|
|
101
|
+
hotspot[property] = value;
|
|
102
|
+
}
|
|
103
|
+
|
|
80
104
|
return true;
|
|
81
105
|
}
|
|
82
106
|
|
|
@@ -143,7 +167,9 @@ class HotspotEditor {
|
|
|
143
167
|
const original = scene.hotspots[index];
|
|
144
168
|
const duplicate = deepClone(original);
|
|
145
169
|
duplicate.id = generateId('hotspot');
|
|
146
|
-
duplicate.
|
|
170
|
+
if (duplicate.tooltip) {
|
|
171
|
+
duplicate.tooltip.text = (original.tooltip?.text || 'Hotspot') + ' (Copy)';
|
|
172
|
+
}
|
|
147
173
|
|
|
148
174
|
// Offset position slightly
|
|
149
175
|
duplicate.position = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Preview Controller - Manages A-Frame preview integration using SWT library
|
|
2
2
|
import { showToast } from "./utils.js";
|
|
3
|
+
import { buildPreviewTourConfig } from "./data-transform.js";
|
|
3
4
|
|
|
4
5
|
class PreviewController {
|
|
5
6
|
constructor(editor) {
|
|
@@ -49,14 +50,16 @@ class PreviewController {
|
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Load scene into preview using SWT library
|
|
53
|
+
* Scenes and hotspots now use unified library format directly
|
|
52
54
|
*/
|
|
53
55
|
async loadScene(scene, preserveCameraRotation = true) {
|
|
54
56
|
if (!this.isInitialized || !scene) {
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
// Validate scene has required data
|
|
59
|
-
|
|
60
|
+
// Validate scene has required data (panorama or imageUrl for backward compatibility)
|
|
61
|
+
const panoramaUrl = scene.panorama || scene.imageUrl;
|
|
62
|
+
if (!panoramaUrl || !scene.id) {
|
|
60
63
|
console.error("Invalid scene data:", scene);
|
|
61
64
|
return;
|
|
62
65
|
}
|
|
@@ -122,67 +125,10 @@ class PreviewController {
|
|
|
122
125
|
// Give A-Frame a moment to start initializing before we proceed
|
|
123
126
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
124
127
|
|
|
125
|
-
// Build tour config
|
|
126
|
-
//
|
|
127
|
-
const transformedHotspots = (scene.hotspots || []).map((h) => ({
|
|
128
|
-
id: h.id,
|
|
129
|
-
position: h.position,
|
|
130
|
-
action: {
|
|
131
|
-
type: h.type === "navigation" ? "navigateTo" : h.type,
|
|
132
|
-
target: h.targetSceneId,
|
|
133
|
-
},
|
|
134
|
-
appearance: {
|
|
135
|
-
color: h.color || "#00ff00",
|
|
136
|
-
icon: h.icon || null,
|
|
137
|
-
scale: h.scale || "1 1 1",
|
|
138
|
-
},
|
|
139
|
-
tooltip: {
|
|
140
|
-
text: h.title || "Hotspot",
|
|
141
|
-
},
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
const libraryScene = {
|
|
145
|
-
id: scene.id,
|
|
146
|
-
name: scene.name,
|
|
147
|
-
panorama: scene.imageUrl, // Editor uses 'imageUrl', library expects 'panorama'
|
|
148
|
-
hotspots: transformedHotspots,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// Build scenes object with ALL scenes (for navigation to work)
|
|
152
|
-
const allScenes = {};
|
|
128
|
+
// Build tour config using shared transform utilities
|
|
129
|
+
// This includes ALL scenes for navigation support
|
|
153
130
|
const editorScenes = this.editor.sceneManager.scenes || [];
|
|
154
|
-
editorScenes
|
|
155
|
-
const sceneHotspots = (s.hotspots || []).map((h) => ({
|
|
156
|
-
id: h.id,
|
|
157
|
-
position: h.position,
|
|
158
|
-
action: {
|
|
159
|
-
type: h.type === "navigation" ? "navigateTo" : h.type,
|
|
160
|
-
target: h.targetSceneId,
|
|
161
|
-
},
|
|
162
|
-
appearance: {
|
|
163
|
-
color: h.color || "#00ff00",
|
|
164
|
-
icon: h.icon || null,
|
|
165
|
-
scale: h.scale || "1 1 1",
|
|
166
|
-
},
|
|
167
|
-
tooltip: {
|
|
168
|
-
text: h.title || "Hotspot",
|
|
169
|
-
},
|
|
170
|
-
}));
|
|
171
|
-
|
|
172
|
-
allScenes[s.id] = {
|
|
173
|
-
id: s.id,
|
|
174
|
-
name: s.name,
|
|
175
|
-
panorama: s.imageUrl,
|
|
176
|
-
hotspots: sceneHotspots,
|
|
177
|
-
startingPosition: s.startingPosition || null,
|
|
178
|
-
};
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const tourConfig = {
|
|
182
|
-
title: scene.name,
|
|
183
|
-
initialScene: scene.id,
|
|
184
|
-
scenes: allScenes,
|
|
185
|
-
};
|
|
131
|
+
const tourConfig = buildPreviewTourConfig(scene, editorScenes);
|
|
186
132
|
|
|
187
133
|
try {
|
|
188
134
|
// Create new tour instance
|
|
@@ -18,7 +18,7 @@ class SceneManagerEditor {
|
|
|
18
18
|
* @param {File|Object} fileOrConfig - Either a File object or a scene config object
|
|
19
19
|
* @param {string} fileOrConfig.id - Scene ID (if config object)
|
|
20
20
|
* @param {string} fileOrConfig.name - Scene name (if config object)
|
|
21
|
-
* @param {string} fileOrConfig.
|
|
21
|
+
* @param {string} fileOrConfig.panorama - Panorama URL (if config object)
|
|
22
22
|
* @param {string} fileOrConfig.thumbnail - Thumbnail URL (if config object)
|
|
23
23
|
* @param {Array} fileOrConfig.hotspots - Hotspots array (if config object)
|
|
24
24
|
*/
|
|
@@ -35,17 +35,17 @@ class SceneManagerEditor {
|
|
|
35
35
|
scene = {
|
|
36
36
|
id: sanitizeId(fileOrConfig.name.replace(/\.[^/.]+$/, "")),
|
|
37
37
|
name: fileOrConfig.name.replace(/\.[^/.]+$/, ""),
|
|
38
|
-
|
|
38
|
+
panorama: imageDataUrl,
|
|
39
39
|
thumbnail: thumbnail,
|
|
40
40
|
hotspots: [],
|
|
41
41
|
};
|
|
42
42
|
} else if (typeof fileOrConfig === "object" && fileOrConfig !== null) {
|
|
43
|
-
// Handle config object
|
|
43
|
+
// Handle config object - support both panorama and imageUrl for backward compatibility
|
|
44
44
|
scene = {
|
|
45
45
|
id: fileOrConfig.id || sanitizeId(`scene-${Date.now()}`),
|
|
46
46
|
name: fileOrConfig.name || "Untitled Scene",
|
|
47
|
-
|
|
48
|
-
thumbnail: fileOrConfig.thumbnail || fileOrConfig.imageUrl || "",
|
|
47
|
+
panorama: fileOrConfig.panorama || fileOrConfig.imageUrl || "",
|
|
48
|
+
thumbnail: fileOrConfig.thumbnail || fileOrConfig.panorama || fileOrConfig.imageUrl || "",
|
|
49
49
|
hotspots: fileOrConfig.hotspots || [],
|
|
50
50
|
...(fileOrConfig.startingPosition && { startingPosition: fileOrConfig.startingPosition }),
|
|
51
51
|
};
|
|
@@ -115,12 +115,13 @@ class SceneManagerEditor {
|
|
|
115
115
|
if (index >= 0 && index < this.scenes.length) {
|
|
116
116
|
this.scenes[index][property] = value;
|
|
117
117
|
|
|
118
|
-
// If updating ID, update all hotspot target references
|
|
118
|
+
// If updating ID, update all hotspot target references (uses nested action.target)
|
|
119
119
|
if (property === "id") {
|
|
120
|
+
const oldId = this.scenes[index].id;
|
|
120
121
|
this.scenes.forEach((scene) => {
|
|
121
122
|
scene.hotspots.forEach((hotspot) => {
|
|
122
|
-
if (hotspot.
|
|
123
|
-
hotspot.
|
|
123
|
+
if (hotspot.action?.target === oldId) {
|
|
124
|
+
hotspot.action.target = value;
|
|
124
125
|
}
|
|
125
126
|
});
|
|
126
127
|
});
|
|
@@ -78,8 +78,10 @@ class ProjectStorageManager {
|
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// imageUrl is required for scenes to be valid
|
|
82
|
-
|
|
81
|
+
// panorama or imageUrl is required for scenes to be valid (support both formats)
|
|
82
|
+
const hasImage = (scene.panorama && typeof scene.panorama === "string") ||
|
|
83
|
+
(scene.imageUrl && typeof scene.imageUrl === "string");
|
|
84
|
+
if (!hasImage) {
|
|
83
85
|
return false;
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -53,9 +53,9 @@ class UIController {
|
|
|
53
53
|
dragHandle.innerHTML =
|
|
54
54
|
'<ss-icon icon="arrow-up-down-left-right" thickness="2.2"></ss-icon>';
|
|
55
55
|
|
|
56
|
-
// Thumbnail
|
|
56
|
+
// Thumbnail - use thumbnail, panorama, or imageUrl (backward compatibility)
|
|
57
57
|
const thumbnail = document.createElement("img");
|
|
58
|
-
thumbnail.src = scene.thumbnail || scene.imageUrl;
|
|
58
|
+
thumbnail.src = scene.thumbnail || scene.panorama || scene.imageUrl;
|
|
59
59
|
thumbnail.alt = scene.name;
|
|
60
60
|
|
|
61
61
|
// Info
|
|
@@ -187,18 +187,25 @@ class UIController {
|
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* Create hotspot list item
|
|
190
|
+
* Uses unified format with nested appearance, action, and tooltip
|
|
190
191
|
*/
|
|
191
192
|
createHotspotItem(hotspot, index, isActive) {
|
|
192
193
|
const item = document.createElement("div");
|
|
193
194
|
item.className = "hotspot-item" + (isActive ? " active" : "");
|
|
194
195
|
|
|
196
|
+
// Get values from nested structure
|
|
197
|
+
const color = hotspot.appearance?.color || "#00ff00";
|
|
198
|
+
const icon = hotspot.appearance?.icon || "";
|
|
199
|
+
const title = hotspot.tooltip?.text || "Untitled Hotspot";
|
|
200
|
+
const targetSceneId = hotspot.action?.target || "";
|
|
201
|
+
|
|
195
202
|
const colorIndicator = document.createElement("div");
|
|
196
203
|
colorIndicator.className = "hotspot-color";
|
|
197
|
-
colorIndicator.style.backgroundColor =
|
|
204
|
+
colorIndicator.style.backgroundColor = color;
|
|
198
205
|
|
|
199
206
|
// If hotspot has an icon, show it with the color applied
|
|
200
|
-
if (
|
|
201
|
-
colorIndicator.innerHTML = `<ss-icon icon="${
|
|
207
|
+
if (icon) {
|
|
208
|
+
colorIndicator.innerHTML = `<ss-icon icon="${icon}" thickness="2.2" style="color: ${color}; width: 20px; height: 20px;"></ss-icon>`;
|
|
202
209
|
colorIndicator.style.backgroundColor = "transparent";
|
|
203
210
|
colorIndicator.style.display = "flex";
|
|
204
211
|
colorIndicator.style.alignItems = "center";
|
|
@@ -208,24 +215,24 @@ class UIController {
|
|
|
208
215
|
const info = document.createElement("div");
|
|
209
216
|
info.className = "hotspot-info";
|
|
210
217
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
const titleEl = document.createElement("div");
|
|
219
|
+
titleEl.className = "hotspot-title";
|
|
220
|
+
titleEl.textContent = title;
|
|
214
221
|
|
|
215
222
|
const target = document.createElement("div");
|
|
216
223
|
target.className = "hotspot-target";
|
|
217
|
-
if (
|
|
224
|
+
if (targetSceneId) {
|
|
218
225
|
const targetScene = this.editor.sceneManager.getSceneById(
|
|
219
|
-
|
|
226
|
+
targetSceneId
|
|
220
227
|
);
|
|
221
228
|
target.textContent = targetScene
|
|
222
229
|
? `→ ${targetScene.name}`
|
|
223
|
-
: `→ ${
|
|
230
|
+
: `→ ${targetSceneId}`;
|
|
224
231
|
} else {
|
|
225
232
|
target.textContent = "No target";
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
info.appendChild(
|
|
235
|
+
info.appendChild(titleEl);
|
|
229
236
|
info.appendChild(target);
|
|
230
237
|
|
|
231
238
|
const actions = document.createElement("div");
|
|
@@ -307,6 +314,7 @@ class UIController {
|
|
|
307
314
|
|
|
308
315
|
/**
|
|
309
316
|
* Update properties panel for hotspot
|
|
317
|
+
* Uses unified format with nested appearance, action, and tooltip
|
|
310
318
|
*/
|
|
311
319
|
updateHotspotProperties(hotspot) {
|
|
312
320
|
const hotspotAll = document.getElementById("hotspotAll");
|
|
@@ -336,21 +344,26 @@ class UIController {
|
|
|
336
344
|
if (hotspotAll) hotspotAll.style.display = "block";
|
|
337
345
|
if (hotspotProperties) hotspotProperties.style.display = "block";
|
|
338
346
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
347
|
+
// Get values from nested structure
|
|
348
|
+
const title = hotspot.tooltip?.text || "";
|
|
349
|
+
const description = hotspot.tooltip?.description || "";
|
|
350
|
+
const targetSceneId = hotspot.action?.target || "";
|
|
351
|
+
const color = hotspot.appearance?.color || "#00ff00";
|
|
352
|
+
const icon = hotspot.appearance?.icon || "";
|
|
353
|
+
|
|
354
|
+
document.getElementById("hotspotTitle").value = title;
|
|
355
|
+
document.getElementById("hotspotDescription").value = description;
|
|
356
|
+
document.getElementById("hotspotTarget").value = targetSceneId;
|
|
357
|
+
document.getElementById("hotspotColor").value = color;
|
|
345
358
|
|
|
346
359
|
// Update color text input if it exists
|
|
347
360
|
const colorText = document.getElementById("hotspotColorText");
|
|
348
361
|
if (colorText) {
|
|
349
|
-
colorText.value =
|
|
362
|
+
colorText.value = color;
|
|
350
363
|
}
|
|
351
364
|
|
|
352
365
|
// Update icon grid active button
|
|
353
|
-
this.setActiveIconButton(
|
|
366
|
+
this.setActiveIconButton(icon);
|
|
354
367
|
|
|
355
368
|
// Update position inputs
|
|
356
369
|
const pos = hotspot.position || { x: 0, y: 0, z: 0 };
|
|
@@ -380,7 +393,8 @@ class UIController {
|
|
|
380
393
|
|
|
381
394
|
document.getElementById("sceneId").value = scene.id || "";
|
|
382
395
|
document.getElementById("sceneName").value = scene.name || "";
|
|
383
|
-
|
|
396
|
+
// Support both panorama (unified) and imageUrl (legacy)
|
|
397
|
+
document.getElementById("sceneImageUrl").value = scene.panorama || scene.imageUrl || "";
|
|
384
398
|
|
|
385
399
|
// Update starting position display
|
|
386
400
|
if (startingPosDisplay) {
|
package/src/index.js
CHANGED
|
@@ -30,6 +30,9 @@ class Tour {
|
|
|
30
30
|
this.config = tourConfig;
|
|
31
31
|
this.isStarted = false;
|
|
32
32
|
|
|
33
|
+
// Normalize scenes to array format (backward compatibility with object format)
|
|
34
|
+
this.scenesArray = this.normalizeScenesArray(tourConfig.scenes);
|
|
35
|
+
|
|
33
36
|
// Initialize managers
|
|
34
37
|
this.assetManager = new AssetManager(this.sceneEl);
|
|
35
38
|
this.sceneManager = new SceneManager(this.sceneEl, this.assetManager);
|
|
@@ -54,6 +57,32 @@ class Tour {
|
|
|
54
57
|
this.ensureCursor();
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Normalize scenes to array format
|
|
62
|
+
* Supports both array and object (keyed by ID) formats for backward compatibility
|
|
63
|
+
* @param {Array|Object} scenes - Scenes in either format
|
|
64
|
+
* @returns {Array} - Scenes as array with id property
|
|
65
|
+
*/
|
|
66
|
+
normalizeScenesArray(scenes) {
|
|
67
|
+
if (Array.isArray(scenes)) {
|
|
68
|
+
return scenes;
|
|
69
|
+
}
|
|
70
|
+
// Convert object format to array format
|
|
71
|
+
return Object.entries(scenes).map(([id, sceneData]) => ({
|
|
72
|
+
id,
|
|
73
|
+
...sceneData,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get scene data by ID
|
|
79
|
+
* @param {string} sceneId - The scene ID to find
|
|
80
|
+
* @returns {Object|undefined} - Scene data or undefined if not found
|
|
81
|
+
*/
|
|
82
|
+
getSceneById(sceneId) {
|
|
83
|
+
return this.scenesArray.find((scene) => scene.id === sceneId);
|
|
84
|
+
}
|
|
85
|
+
|
|
57
86
|
/**
|
|
58
87
|
* Ensure the scene has a cursor for interaction
|
|
59
88
|
*/
|
|
@@ -81,7 +110,7 @@ class Tour {
|
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
const initialSceneId = this.config.initialScene;
|
|
84
|
-
const initialSceneData = this.
|
|
113
|
+
const initialSceneData = this.getSceneById(initialSceneId);
|
|
85
114
|
|
|
86
115
|
if (!initialSceneData) {
|
|
87
116
|
throw new Error(
|
|
@@ -123,7 +152,7 @@ class Tour {
|
|
|
123
152
|
* @returns {Promise}
|
|
124
153
|
*/
|
|
125
154
|
async navigateTo(sceneId) {
|
|
126
|
-
const sceneData = this.
|
|
155
|
+
const sceneData = this.getSceneById(sceneId);
|
|
127
156
|
|
|
128
157
|
if (!sceneData) {
|
|
129
158
|
throw new Error(`Scene "${sceneId}" not found in tour configuration`);
|