senangwebs-tour 1.0.2 → 1.0.3
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 +190 -217
- package/dist/swt-editor.css +759 -730
- package/dist/swt-editor.css.map +1 -1
- package/dist/swt-editor.js +550 -602
- package/dist/swt-editor.js.map +1 -1
- package/dist/swt-editor.min.css +1 -1
- package/dist/swt-editor.min.js +1 -1
- package/dist/swt.js +56531 -867
- package/dist/swt.js.map +1 -1
- package/dist/swt.min.js +12 -1
- package/package.json +6 -3
- package/src/editor/css/main.css +610 -581
- package/src/editor/js/export-manager.js +175 -262
- package/src/editor/js/preview-controller.js +36 -17
- package/src/editor/js/ui-controller.js +377 -362
- package/src/index.js +45 -33
|
@@ -1,286 +1,199 @@
|
|
|
1
1
|
// Export Manager - Handles JSON generation for SWT library
|
|
2
|
-
import { downloadTextAsFile, showModal } from
|
|
2
|
+
import { downloadTextAsFile, showModal } from "./utils.js";
|
|
3
3
|
|
|
4
4
|
class ExportManager {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
constructor(editor) {
|
|
6
|
+
this.editor = editor;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate JSON compatible with SWT library
|
|
11
|
+
* Follows the tourConfig structure from example-simple.html
|
|
12
|
+
*/
|
|
13
|
+
generateJSON() {
|
|
14
|
+
const scenes = this.editor.sceneManager.getAllScenes();
|
|
15
|
+
const config = this.editor.config;
|
|
16
|
+
|
|
17
|
+
// Build scenes object (keyed by scene ID)
|
|
18
|
+
const scenesData = {};
|
|
19
|
+
scenes.forEach((scene) => {
|
|
20
|
+
scenesData[scene.id] = {
|
|
21
|
+
name: scene.name,
|
|
22
|
+
panorama: scene.imageUrl,
|
|
23
|
+
hotspots: scene.hotspots.map((hotspot) => {
|
|
24
|
+
const hotspotData = {
|
|
25
|
+
position: hotspot.position,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Add action based on hotspot type
|
|
29
|
+
if (hotspot.type === "navigation" && hotspot.targetSceneId) {
|
|
30
|
+
hotspotData.action = {
|
|
31
|
+
type: "navigateTo",
|
|
32
|
+
target: hotspot.targetSceneId,
|
|
33
|
+
};
|
|
34
|
+
} else if (hotspot.type === "info") {
|
|
35
|
+
hotspotData.action = {
|
|
36
|
+
type: "showInfo",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add appearance
|
|
41
|
+
hotspotData.appearance = {
|
|
42
|
+
color: hotspot.color || "#FF6B6B",
|
|
43
|
+
scale: "2 2 2",
|
|
44
|
+
};
|
|
8
45
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Build scenes array
|
|
16
|
-
const scenesData = scenes.map(scene => ({
|
|
17
|
-
id: scene.id,
|
|
18
|
-
name: scene.name,
|
|
19
|
-
imageUrl: scene.imageUrl,
|
|
20
|
-
hotspots: scene.hotspots.map(hotspot => ({
|
|
21
|
-
id: hotspot.id,
|
|
22
|
-
type: hotspot.type || 'navigation',
|
|
23
|
-
position: hotspot.position,
|
|
24
|
-
targetSceneId: hotspot.targetSceneId || '',
|
|
25
|
-
title: hotspot.title || '',
|
|
26
|
-
description: hotspot.description || '',
|
|
27
|
-
color: hotspot.color || '#00ff00',
|
|
28
|
-
icon: hotspot.icon || ''
|
|
29
|
-
}))
|
|
30
|
-
}));
|
|
31
|
-
// Determine initial scene
|
|
32
|
-
let initialSceneId = config.initialSceneId;
|
|
33
|
-
if (!initialSceneId && scenes.length > 0) {
|
|
34
|
-
initialSceneId = scenes[0].id;
|
|
35
|
-
}
|
|
46
|
+
// Add tooltip if title exists
|
|
47
|
+
if (hotspot.title) {
|
|
48
|
+
hotspotData.tooltip = {
|
|
49
|
+
text: hotspot.title,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
initialSceneId: initialSceneId,
|
|
42
|
-
scenes: scenesData,
|
|
43
|
-
settings: {
|
|
44
|
-
autoRotate: config.autoRotate || false,
|
|
45
|
-
showCompass: config.showCompass || false
|
|
46
|
-
}
|
|
47
|
-
};
|
|
53
|
+
return hotspotData;
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
// Determine initial scene
|
|
59
|
+
let initialScene = config.initialSceneId;
|
|
60
|
+
if (!initialScene && scenes.length > 0) {
|
|
61
|
+
initialScene = scenes[0].id;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
// Build final JSON matching SWT tourConfig format
|
|
65
|
+
const jsonData = {
|
|
66
|
+
initialScene: initialScene,
|
|
67
|
+
scenes: scenesData,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return jsonData;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Export as JSON file
|
|
75
|
+
*/
|
|
76
|
+
exportJSON() {
|
|
77
|
+
try {
|
|
78
|
+
const jsonData = this.generateJSON();
|
|
79
|
+
const json = JSON.stringify(jsonData, null, 2);
|
|
80
|
+
|
|
81
|
+
const title = this.editor.config.title || "tour";
|
|
82
|
+
const filename = sanitizeId(title) + ".json";
|
|
83
|
+
downloadTextAsFile(json, filename);
|
|
84
|
+
|
|
85
|
+
showToast("Tour exported successfully", "success");
|
|
86
|
+
return true;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Export failed:", error);
|
|
89
|
+
showToast("Export failed", "error");
|
|
90
|
+
return false;
|
|
70
91
|
}
|
|
92
|
+
}
|
|
71
93
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
94
|
+
/**
|
|
95
|
+
* Copy JSON to clipboard
|
|
96
|
+
*/
|
|
97
|
+
async copyJSON() {
|
|
98
|
+
try {
|
|
99
|
+
const jsonData = this.generateJSON();
|
|
100
|
+
const json = JSON.stringify(jsonData, null, 2);
|
|
101
|
+
|
|
102
|
+
const success = await copyToClipboard(json);
|
|
103
|
+
if (success) {
|
|
104
|
+
showToast("JSON copied to clipboard", "success");
|
|
105
|
+
} else {
|
|
106
|
+
showToast("Failed to copy to clipboard", "error");
|
|
107
|
+
}
|
|
108
|
+
return success;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("Copy failed:", error);
|
|
111
|
+
showToast("Copy failed", "error");
|
|
112
|
+
return false;
|
|
92
113
|
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate HTML viewer code
|
|
118
|
+
*/
|
|
119
|
+
generateViewerHTML() {
|
|
120
|
+
const jsonData = this.generateJSON();
|
|
121
|
+
const title = this.editor.config.title || "Virtual Tour";
|
|
93
122
|
|
|
94
|
-
|
|
95
|
-
* Generate HTML viewer code
|
|
96
|
-
*/
|
|
97
|
-
generateViewerHTML() {
|
|
98
|
-
const jsonData = this.generateJSON();
|
|
99
|
-
|
|
100
|
-
return `<!DOCTYPE html>
|
|
123
|
+
return `<!DOCTYPE html>
|
|
101
124
|
<html lang="en">
|
|
102
|
-
<head>
|
|
103
|
-
<meta charset="UTF-8"
|
|
104
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"
|
|
105
|
-
<title>${
|
|
106
|
-
<script src="https://
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
#loading {
|
|
116
|
-
position: fixed;
|
|
117
|
-
top: 0;
|
|
118
|
-
left: 0;
|
|
119
|
-
width: 100%;
|
|
120
|
-
height: 100%;
|
|
121
|
-
background: #000;
|
|
122
|
-
display: flex;
|
|
123
|
-
align-items: center;
|
|
124
|
-
justify-content: center;
|
|
125
|
-
color: #fff;
|
|
126
|
-
z-index: 1000;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
#loading.hidden {
|
|
130
|
-
display: none;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.spinner {
|
|
134
|
-
border: 4px solid rgba(255,255,255,0.3);
|
|
135
|
-
border-top: 4px solid #fff;
|
|
136
|
-
border-radius: 50%;
|
|
137
|
-
width: 40px;
|
|
138
|
-
height: 40px;
|
|
139
|
-
animation: spin 1s linear infinite;
|
|
140
|
-
margin-right: 15px;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
@keyframes spin {
|
|
144
|
-
0% { transform: rotate(0deg); }
|
|
145
|
-
100% { transform: rotate(360deg); }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
#ui {
|
|
149
|
-
position: fixed;
|
|
150
|
-
bottom: 20px;
|
|
151
|
-
left: 50%;
|
|
152
|
-
transform: translateX(-50%);
|
|
153
|
-
z-index: 100;
|
|
154
|
-
display: flex;
|
|
155
|
-
gap: 10px;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.btn {
|
|
159
|
-
background: rgba(0,0,0,0.7);
|
|
160
|
-
color: #fff;
|
|
161
|
-
border: none;
|
|
162
|
-
padding: 10px 20px;
|
|
163
|
-
border-radius: 5px;
|
|
164
|
-
cursor: pointer;
|
|
165
|
-
font-size: 14px;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.btn:hover {
|
|
169
|
-
background: rgba(0,0,0,0.9);
|
|
170
|
-
}
|
|
171
|
-
</style>
|
|
172
|
-
</head>
|
|
173
|
-
<body>
|
|
174
|
-
<div id="loading">
|
|
175
|
-
<div class="spinner"></div>
|
|
176
|
-
<span>Loading Tour...</span>
|
|
177
|
-
</div>
|
|
178
|
-
|
|
179
|
-
<div id="tour-container"></div>
|
|
180
|
-
|
|
181
|
-
<div id="ui" style="display: none;">
|
|
182
|
-
<button class="btn" id="resetBtn">Reset View</button>
|
|
183
|
-
<span class="btn" id="sceneInfo"></span>
|
|
184
|
-
</div>
|
|
125
|
+
<head>
|
|
126
|
+
<meta charset="UTF-8" />
|
|
127
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
128
|
+
<title>${title}</title>
|
|
129
|
+
<script src="https://unpkg.com/senangwebs-tour@latest/dist/swt.min.js"></script>
|
|
130
|
+
</head>
|
|
131
|
+
<body>
|
|
132
|
+
<a-scene id="tour-container">
|
|
133
|
+
<a-camera>
|
|
134
|
+
<a-cursor></a-cursor>
|
|
135
|
+
</a-camera>
|
|
136
|
+
</a-scene>
|
|
185
137
|
|
|
186
138
|
<script>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
tour.on('sceneChanged', (sceneId) => {
|
|
200
|
-
updateSceneInfo();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
tour.on('ready', () => {
|
|
204
|
-
document.getElementById('loading').classList.add('hidden');
|
|
205
|
-
document.getElementById('ui').style.display = 'flex';
|
|
206
|
-
updateSceneInfo();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
tour.on('error', (error) => {
|
|
210
|
-
console.error('Tour error:', error);
|
|
211
|
-
alert('Failed to load tour: ' + error.message);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Start tour
|
|
215
|
-
await tour.start();
|
|
216
|
-
|
|
217
|
-
// Setup UI
|
|
218
|
-
document.getElementById('resetBtn').addEventListener('click', () => {
|
|
219
|
-
const camera = document.querySelector('[camera]');
|
|
220
|
-
if (camera) {
|
|
221
|
-
camera.setAttribute('rotation', '0 0 0');
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error('Failed to initialize tour:', error);
|
|
227
|
-
alert('Failed to initialize tour: ' + error.message);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
function updateSceneInfo() {
|
|
232
|
-
const sceneId = tour.getCurrentSceneId();
|
|
233
|
-
const scene = tourConfig.scenes.find(s => s.id === sceneId);
|
|
234
|
-
if (scene) {
|
|
235
|
-
document.getElementById('sceneInfo').textContent = scene.name;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
139
|
+
// Tour configuration
|
|
140
|
+
const tourConfig = ${JSON.stringify(jsonData, null, 8)
|
|
141
|
+
.split("\n")
|
|
142
|
+
.map((line, i) => (i === 0 ? line : " " + line))
|
|
143
|
+
.join("\n")};
|
|
144
|
+
|
|
145
|
+
// Initialize tour when scene is loaded
|
|
146
|
+
const sceneEl = document.querySelector("#tour-container");
|
|
147
|
+
sceneEl.addEventListener("loaded", () => {
|
|
148
|
+
const tour = new SWT.Tour(sceneEl, tourConfig);
|
|
149
|
+
tour.start();
|
|
150
|
+
});
|
|
238
151
|
</script>
|
|
239
|
-
</body>
|
|
152
|
+
</body>
|
|
240
153
|
</html>`;
|
|
241
|
-
|
|
154
|
+
}
|
|
242
155
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
156
|
+
/**
|
|
157
|
+
* Export as standalone HTML viewer
|
|
158
|
+
*/
|
|
159
|
+
exportViewerHTML() {
|
|
160
|
+
try {
|
|
161
|
+
const html = this.generateViewerHTML();
|
|
162
|
+
const title = this.editor.config.title || "tour";
|
|
163
|
+
const filename = sanitizeId(title) + "-viewer.html";
|
|
164
|
+
|
|
165
|
+
downloadTextAsFile(html, filename);
|
|
166
|
+
|
|
167
|
+
showToast("Viewer HTML exported successfully", "success");
|
|
168
|
+
return true;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error("Export viewer failed:", error);
|
|
171
|
+
showToast("Export viewer failed", "error");
|
|
172
|
+
return false;
|
|
261
173
|
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Show export preview in modal
|
|
178
|
+
*/
|
|
179
|
+
showExportPreview() {
|
|
180
|
+
try {
|
|
181
|
+
const jsonData = this.generateJSON();
|
|
182
|
+
const json = JSON.stringify(jsonData, null, 2);
|
|
183
|
+
|
|
184
|
+
const preview = document.getElementById("exportPreview");
|
|
185
|
+
if (preview) {
|
|
186
|
+
preview.textContent = json;
|
|
187
|
+
}
|
|
262
188
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const json = JSON.stringify(jsonData, null, 2);
|
|
270
|
-
|
|
271
|
-
const preview = document.getElementById('exportPreview');
|
|
272
|
-
if (preview) {
|
|
273
|
-
preview.textContent = json;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
showModal('exportModal');
|
|
277
|
-
return true;
|
|
278
|
-
} catch (error) {
|
|
279
|
-
console.error('Failed to show export preview:', error);
|
|
280
|
-
showToast('Failed to generate preview', 'error');
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
189
|
+
showModal("exportModal");
|
|
190
|
+
return true;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error("Failed to show export preview:", error);
|
|
193
|
+
showToast("Failed to generate preview", "error");
|
|
194
|
+
return false;
|
|
283
195
|
}
|
|
196
|
+
}
|
|
284
197
|
}
|
|
285
198
|
|
|
286
199
|
export default ExportManager;
|
|
@@ -92,7 +92,7 @@ class PreviewController {
|
|
|
92
92
|
console.error("Error removing scene:", error);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
// Clear any remaining children (loading overlays, empty state, etc)
|
|
97
97
|
while (this.previewContainer.firstChild) {
|
|
98
98
|
this.previewContainer.removeChild(this.previewContainer.firstChild);
|
|
@@ -100,9 +100,9 @@ class PreviewController {
|
|
|
100
100
|
} else {
|
|
101
101
|
// First load - only remove non-A-Frame elements (like empty state divs)
|
|
102
102
|
const children = Array.from(this.previewContainer.children);
|
|
103
|
-
children.forEach(child => {
|
|
103
|
+
children.forEach((child) => {
|
|
104
104
|
// Only remove if it's NOT an a-scene (shouldn't be any, but be safe)
|
|
105
|
-
if (child.tagName.toLowerCase() !==
|
|
105
|
+
if (child.tagName.toLowerCase() !== "a-scene") {
|
|
106
106
|
this.previewContainer.removeChild(child);
|
|
107
107
|
}
|
|
108
108
|
});
|
|
@@ -313,6 +313,7 @@ class PreviewController {
|
|
|
313
313
|
|
|
314
314
|
/**
|
|
315
315
|
* Get current camera rotation
|
|
316
|
+
* Works with A-Frame's look-controls component by reading its internal state
|
|
316
317
|
*/
|
|
317
318
|
getCameraRotation() {
|
|
318
319
|
const aframeScene = this.previewContainer?.querySelector("a-scene");
|
|
@@ -325,7 +326,18 @@ class PreviewController {
|
|
|
325
326
|
return null;
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
//
|
|
329
|
+
// Try to get rotation from look-controls internal state (more reliable)
|
|
330
|
+
const lookControls = camera.components?.["look-controls"];
|
|
331
|
+
if (lookControls && lookControls.pitchObject && lookControls.yawObject) {
|
|
332
|
+
const savedRotation = {
|
|
333
|
+
x: lookControls.pitchObject.rotation.x,
|
|
334
|
+
y: lookControls.yawObject.rotation.y,
|
|
335
|
+
z: 0,
|
|
336
|
+
};
|
|
337
|
+
return savedRotation;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Fallback to object3D rotation
|
|
329
341
|
const rotation = camera.object3D.rotation;
|
|
330
342
|
const savedRotation = {
|
|
331
343
|
x: rotation.x,
|
|
@@ -337,6 +349,7 @@ class PreviewController {
|
|
|
337
349
|
|
|
338
350
|
/**
|
|
339
351
|
* Set camera rotation
|
|
352
|
+
* Works with A-Frame's look-controls component by setting its internal state
|
|
340
353
|
*/
|
|
341
354
|
setCameraRotation(rotation) {
|
|
342
355
|
if (!rotation) {
|
|
@@ -353,17 +366,30 @@ class PreviewController {
|
|
|
353
366
|
return;
|
|
354
367
|
}
|
|
355
368
|
|
|
356
|
-
// Set rotation
|
|
369
|
+
// Set rotation via look-controls internal pitchObject/yawObject
|
|
370
|
+
// This is required because look-controls overrides object3D.rotation on each tick
|
|
357
371
|
const setRotation = () => {
|
|
358
|
-
|
|
359
|
-
|
|
372
|
+
const cam = this.previewContainer?.querySelector("[camera]");
|
|
373
|
+
if (!cam) return;
|
|
374
|
+
|
|
375
|
+
const lookControls = cam.components?.["look-controls"];
|
|
376
|
+
if (lookControls && lookControls.pitchObject && lookControls.yawObject) {
|
|
377
|
+
// Set pitch (x rotation) on pitchObject
|
|
378
|
+
lookControls.pitchObject.rotation.x = rotation.x;
|
|
379
|
+
// Set yaw (y rotation) on yawObject
|
|
380
|
+
lookControls.yawObject.rotation.y = rotation.y;
|
|
381
|
+
} else if (cam.object3D) {
|
|
382
|
+
// Fallback to direct object3D if look-controls not ready
|
|
383
|
+
cam.object3D.rotation.set(rotation.x, rotation.y, rotation.z);
|
|
360
384
|
}
|
|
361
385
|
};
|
|
362
386
|
|
|
363
|
-
// Try immediately and also after
|
|
387
|
+
// Try immediately and also after delays to ensure it sticks
|
|
388
|
+
// A-Frame look-controls may not be fully initialized immediately
|
|
364
389
|
setRotation();
|
|
365
390
|
setTimeout(setRotation, 100);
|
|
366
391
|
setTimeout(setRotation, 300);
|
|
392
|
+
setTimeout(setRotation, 500);
|
|
367
393
|
}
|
|
368
394
|
|
|
369
395
|
/**
|
|
@@ -372,15 +398,8 @@ class PreviewController {
|
|
|
372
398
|
async refresh() {
|
|
373
399
|
const scene = this.editor.sceneManager.getCurrentScene();
|
|
374
400
|
if (scene) {
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
// Reload scene
|
|
378
|
-
await this.loadScene(scene);
|
|
379
|
-
|
|
380
|
-
// Restore camera rotation
|
|
381
|
-
if (savedRotation) {
|
|
382
|
-
this.setCameraRotation(savedRotation);
|
|
383
|
-
}
|
|
401
|
+
// loadScene with preserveCameraRotation=true handles save/restore internally
|
|
402
|
+
await this.loadScene(scene, true);
|
|
384
403
|
}
|
|
385
404
|
}
|
|
386
405
|
|