senangwebs-tour 1.0.2
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/LICENSE.md +21 -0
- package/README.md +653 -0
- package/dist/swt-editor.css +869 -0
- package/dist/swt-editor.css.map +1 -0
- package/dist/swt-editor.js +2853 -0
- package/dist/swt-editor.js.map +1 -0
- package/dist/swt-editor.min.css +1 -0
- package/dist/swt-editor.min.js +1 -0
- package/dist/swt.js +873 -0
- package/dist/swt.js.map +1 -0
- package/dist/swt.min.js +1 -0
- package/package.json +39 -0
- package/src/AssetManager.js +153 -0
- package/src/HotspotManager.js +193 -0
- package/src/SceneManager.js +162 -0
- package/src/components/hotspot-listener.js +114 -0
- package/src/editor/css/main.css +1002 -0
- package/src/editor/editor-entry.css +4 -0
- package/src/editor/editor-entry.js +30 -0
- package/src/editor/js/editor.js +629 -0
- package/src/editor/js/export-manager.js +286 -0
- package/src/editor/js/hotspot-editor.js +237 -0
- package/src/editor/js/preview-controller.js +556 -0
- package/src/editor/js/scene-manager.js +202 -0
- package/src/editor/js/storage-manager.js +193 -0
- package/src/editor/js/ui-controller.js +387 -0
- package/src/editor/js/ui-init.js +164 -0
- package/src/editor/js/utils.js +217 -0
- package/src/index.js +245 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// Export Manager - Handles JSON generation for SWT library
|
|
2
|
+
import { downloadTextAsFile, showModal } from './utils.js';
|
|
3
|
+
|
|
4
|
+
class ExportManager {
|
|
5
|
+
constructor(editor) {
|
|
6
|
+
this.editor = editor;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate JSON compatible with SWT library
|
|
11
|
+
*/
|
|
12
|
+
generateJSON() {
|
|
13
|
+
const scenes = this.editor.sceneManager.getAllScenes();
|
|
14
|
+
const config = this.editor.config;
|
|
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
|
+
}
|
|
36
|
+
|
|
37
|
+
// Build final JSON
|
|
38
|
+
const jsonData = {
|
|
39
|
+
title: config.title || 'Virtual Tour',
|
|
40
|
+
description: config.description || '',
|
|
41
|
+
initialSceneId: initialSceneId,
|
|
42
|
+
scenes: scenesData,
|
|
43
|
+
settings: {
|
|
44
|
+
autoRotate: config.autoRotate || false,
|
|
45
|
+
showCompass: config.showCompass || false
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return jsonData;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Export as JSON file
|
|
54
|
+
*/
|
|
55
|
+
exportJSON() {
|
|
56
|
+
try {
|
|
57
|
+
const jsonData = this.generateJSON();
|
|
58
|
+
const json = JSON.stringify(jsonData, null, 2);
|
|
59
|
+
|
|
60
|
+
const filename = sanitizeId(jsonData.title || 'tour') + '.json';
|
|
61
|
+
downloadTextAsFile(json, filename);
|
|
62
|
+
|
|
63
|
+
showToast('Tour exported successfully', 'success');
|
|
64
|
+
return true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Export failed:', error);
|
|
67
|
+
showToast('Export failed', 'error');
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Copy JSON to clipboard
|
|
74
|
+
*/
|
|
75
|
+
async copyJSON() {
|
|
76
|
+
try {
|
|
77
|
+
const jsonData = this.generateJSON();
|
|
78
|
+
const json = JSON.stringify(jsonData, null, 2);
|
|
79
|
+
|
|
80
|
+
const success = await copyToClipboard(json);
|
|
81
|
+
if (success) {
|
|
82
|
+
showToast('JSON copied to clipboard', 'success');
|
|
83
|
+
} else {
|
|
84
|
+
showToast('Failed to copy to clipboard', 'error');
|
|
85
|
+
}
|
|
86
|
+
return success;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Copy failed:', error);
|
|
89
|
+
showToast('Copy failed', 'error');
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate HTML viewer code
|
|
96
|
+
*/
|
|
97
|
+
generateViewerHTML() {
|
|
98
|
+
const jsonData = this.generateJSON();
|
|
99
|
+
|
|
100
|
+
return `<!DOCTYPE html>
|
|
101
|
+
<html lang="en">
|
|
102
|
+
<head>
|
|
103
|
+
<meta charset="UTF-8">
|
|
104
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
105
|
+
<title>${jsonData.title}</title>
|
|
106
|
+
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
|
|
107
|
+
<script src="dist/swt.min.js"></script>
|
|
108
|
+
<style>
|
|
109
|
+
body {
|
|
110
|
+
margin: 0;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
font-family: Arial, sans-serif;
|
|
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>
|
|
185
|
+
|
|
186
|
+
<script>
|
|
187
|
+
// Tour configuration
|
|
188
|
+
const tourConfig = ${JSON.stringify(jsonData, null, 8)};
|
|
189
|
+
|
|
190
|
+
// Initialize tour
|
|
191
|
+
let tour;
|
|
192
|
+
|
|
193
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
194
|
+
try {
|
|
195
|
+
// Create tour instance
|
|
196
|
+
tour = new SenangWebsTour('tour-container', tourConfig);
|
|
197
|
+
|
|
198
|
+
// Listen to events
|
|
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
|
+
}
|
|
238
|
+
</script>
|
|
239
|
+
</body>
|
|
240
|
+
</html>`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Export as standalone HTML viewer
|
|
245
|
+
*/
|
|
246
|
+
exportViewerHTML() {
|
|
247
|
+
try {
|
|
248
|
+
const html = this.generateViewerHTML();
|
|
249
|
+
const jsonData = this.generateJSON();
|
|
250
|
+
const filename = sanitizeId(jsonData.title || 'tour') + '-viewer.html';
|
|
251
|
+
|
|
252
|
+
downloadTextAsFile(html, filename);
|
|
253
|
+
|
|
254
|
+
showToast('Viewer HTML exported successfully', 'success');
|
|
255
|
+
return true;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error('Export viewer failed:', error);
|
|
258
|
+
showToast('Export viewer failed', 'error');
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Show export preview in modal
|
|
265
|
+
*/
|
|
266
|
+
showExportPreview() {
|
|
267
|
+
try {
|
|
268
|
+
const jsonData = this.generateJSON();
|
|
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
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export default ExportManager;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// Hotspot Editor - Handles hotspot placement and editing
|
|
2
|
+
import { generateId, showToast } from './utils.js';
|
|
3
|
+
|
|
4
|
+
class HotspotEditor {
|
|
5
|
+
constructor(editor) {
|
|
6
|
+
this.editor = editor;
|
|
7
|
+
this.currentHotspotIndex = -1;
|
|
8
|
+
this.placementMode = false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Enable hotspot placement mode
|
|
13
|
+
*/
|
|
14
|
+
enablePlacementMode() {
|
|
15
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
16
|
+
if (!scene) {
|
|
17
|
+
showToast('Please select a scene first', 'error');
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.placementMode = true;
|
|
22
|
+
|
|
23
|
+
// Visual feedback
|
|
24
|
+
document.body.style.cursor = 'crosshair';
|
|
25
|
+
const preview = document.getElementById('preview');
|
|
26
|
+
if (preview) {
|
|
27
|
+
preview.style.border = '3px solid #4CC3D9';
|
|
28
|
+
preview.style.boxShadow = '0 0 20px rgba(76, 195, 217, 0.5)';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Update button state
|
|
32
|
+
const btn = document.getElementById('addHotspotBtn');
|
|
33
|
+
if (btn) {
|
|
34
|
+
btn.textContent = 'Click on Preview...';
|
|
35
|
+
btn.classList.add('btn-active');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
showToast('Click on the 360° preview to place hotspot', 'info', 5000);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Disable hotspot placement mode
|
|
44
|
+
*/
|
|
45
|
+
disablePlacementMode() {
|
|
46
|
+
this.placementMode = false;
|
|
47
|
+
document.body.style.cursor = 'default';
|
|
48
|
+
|
|
49
|
+
// Remove visual feedback
|
|
50
|
+
const preview = document.getElementById('preview');
|
|
51
|
+
if (preview) {
|
|
52
|
+
preview.style.border = '';
|
|
53
|
+
preview.style.boxShadow = '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Reset button state
|
|
57
|
+
const btn = document.getElementById('addHotspotBtn');
|
|
58
|
+
if (btn) {
|
|
59
|
+
btn.textContent = '+ Add Hotspot';
|
|
60
|
+
btn.classList.remove('btn-active');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Add hotspot at position
|
|
66
|
+
*/
|
|
67
|
+
addHotspot(position, targetSceneId = '') {
|
|
68
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
69
|
+
if (!scene) {
|
|
70
|
+
showToast('No scene selected', 'error');
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const hotspot = {
|
|
75
|
+
id: generateId('hotspot'),
|
|
76
|
+
type: 'navigation',
|
|
77
|
+
position: position,
|
|
78
|
+
targetSceneId: targetSceneId,
|
|
79
|
+
title: 'New Hotspot',
|
|
80
|
+
description: '',
|
|
81
|
+
color: '#00ff00',
|
|
82
|
+
icon: ''
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
scene.hotspots.push(hotspot);
|
|
86
|
+
this.currentHotspotIndex = scene.hotspots.length - 1;
|
|
87
|
+
|
|
88
|
+
this.disablePlacementMode();
|
|
89
|
+
showToast('Hotspot added', 'success');
|
|
90
|
+
|
|
91
|
+
return hotspot;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Remove hotspot
|
|
96
|
+
*/
|
|
97
|
+
removeHotspot(index) {
|
|
98
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
99
|
+
if (!scene || index < 0 || index >= scene.hotspots.length) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!confirm('Are you sure you want to delete this hotspot?')) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
scene.hotspots.splice(index, 1);
|
|
108
|
+
|
|
109
|
+
// Update current index
|
|
110
|
+
if (this.currentHotspotIndex === index) {
|
|
111
|
+
this.currentHotspotIndex = -1;
|
|
112
|
+
} else if (this.currentHotspotIndex > index) {
|
|
113
|
+
this.currentHotspotIndex--;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
showToast('Hotspot removed', 'success');
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Update hotspot property
|
|
122
|
+
*/
|
|
123
|
+
updateHotspot(index, property, value) {
|
|
124
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
125
|
+
if (!scene || index < 0 || index >= scene.hotspots.length) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
scene.hotspots[index][property] = value;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get hotspot by index
|
|
135
|
+
*/
|
|
136
|
+
getHotspot(index) {
|
|
137
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
138
|
+
if (!scene || index < 0 || index >= scene.hotspots.length) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return scene.hotspots[index];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Update hotspot position
|
|
146
|
+
*/
|
|
147
|
+
updateHotspotPosition(index, position) {
|
|
148
|
+
return this.updateHotspot(index, 'position', position);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get current hotspot
|
|
153
|
+
*/
|
|
154
|
+
getCurrentHotspot() {
|
|
155
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
156
|
+
if (!scene || this.currentHotspotIndex < 0) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return scene.hotspots[this.currentHotspotIndex] || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Set current hotspot
|
|
164
|
+
*/
|
|
165
|
+
setCurrentHotspot(index) {
|
|
166
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
167
|
+
if (!scene || index < 0 || index >= scene.hotspots.length) {
|
|
168
|
+
this.currentHotspotIndex = -1;
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.currentHotspotIndex = index;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get all hotspots for current scene
|
|
178
|
+
*/
|
|
179
|
+
getAllHotspots() {
|
|
180
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
181
|
+
return scene ? scene.hotspots : [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Duplicate hotspot
|
|
186
|
+
*/
|
|
187
|
+
duplicateHotspot(index) {
|
|
188
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
189
|
+
if (!scene || index < 0 || index >= scene.hotspots.length) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const original = scene.hotspots[index];
|
|
194
|
+
const duplicate = deepClone(original);
|
|
195
|
+
duplicate.id = generateId('hotspot');
|
|
196
|
+
duplicate.title = original.title + ' (Copy)';
|
|
197
|
+
|
|
198
|
+
// Offset position slightly
|
|
199
|
+
duplicate.position = {
|
|
200
|
+
x: original.position.x + 0.5,
|
|
201
|
+
y: original.position.y,
|
|
202
|
+
z: original.position.z
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
scene.hotspots.push(duplicate);
|
|
206
|
+
this.currentHotspotIndex = scene.hotspots.length - 1;
|
|
207
|
+
|
|
208
|
+
showToast('Hotspot duplicated', 'success');
|
|
209
|
+
return duplicate;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Clear all hotspots
|
|
214
|
+
*/
|
|
215
|
+
clearAllHotspots() {
|
|
216
|
+
const scene = this.editor.sceneManager.getCurrentScene();
|
|
217
|
+
if (!scene) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (scene.hotspots.length === 0) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!confirm('Are you sure you want to remove all hotspots from this scene?')) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
scene.hotspots = [];
|
|
230
|
+
this.currentHotspotIndex = -1;
|
|
231
|
+
|
|
232
|
+
showToast('All hotspots removed', 'success');
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default HotspotEditor;
|