senangwebs-tour 1.0.3 → 1.0.6
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/dist/swt-editor.css +36 -2
- package/dist/swt-editor.css.map +1 -1
- package/dist/swt-editor.js +3137 -223
- 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 +292 -26
- package/dist/swt.js.map +1 -1
- package/dist/swt.min.js +1 -1
- package/package.json +2 -1
- package/src/AssetManager.js +13 -2
- package/src/HotspotManager.js +93 -20
- package/src/IconRenderer.js +123 -0
- package/src/SceneManager.js +7 -1
- package/src/editor/css/main.css +41 -2
- package/src/editor/js/editor.js +108 -23
- package/src/editor/js/export-manager.js +56 -9
- package/src/editor/js/hotspot-editor.js +10 -57
- package/src/editor/js/preview-controller.js +132 -105
- package/src/editor/js/scene-manager.js +35 -14
- package/src/editor/js/ui-controller.js +91 -8
- package/src/editor/js/utils.js +1 -0
- package/src/index.js +56 -3
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// UI Controller - Handles DOM manipulation and rendering
|
|
2
2
|
|
|
3
|
+
// Import icons list from SenangStart icons package (baked into bundle at build time)
|
|
4
|
+
import iconsData from "@bookklik/senangstart-icons/src/icons.json";
|
|
5
|
+
|
|
3
6
|
class UIController {
|
|
4
7
|
constructor(editor) {
|
|
5
8
|
this.editor = editor;
|
|
@@ -189,9 +192,18 @@ class UIController {
|
|
|
189
192
|
const item = document.createElement("div");
|
|
190
193
|
item.className = "hotspot-item" + (isActive ? " active" : "");
|
|
191
194
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
const colorIndicator = document.createElement("div");
|
|
196
|
+
colorIndicator.className = "hotspot-color";
|
|
197
|
+
colorIndicator.style.backgroundColor = hotspot.color;
|
|
198
|
+
|
|
199
|
+
// If hotspot has an icon, show it with the color applied
|
|
200
|
+
if (hotspot.icon) {
|
|
201
|
+
colorIndicator.innerHTML = `<ss-icon icon="${hotspot.icon}" thickness="2.2" style="color: ${hotspot.color}; width: 20px; height: 20px;"></ss-icon>`;
|
|
202
|
+
colorIndicator.style.backgroundColor = "transparent";
|
|
203
|
+
colorIndicator.style.display = "flex";
|
|
204
|
+
colorIndicator.style.alignItems = "center";
|
|
205
|
+
colorIndicator.style.justifyContent = "center";
|
|
206
|
+
}
|
|
195
207
|
|
|
196
208
|
const info = document.createElement("div");
|
|
197
209
|
info.className = "hotspot-info";
|
|
@@ -230,7 +242,7 @@ class UIController {
|
|
|
230
242
|
|
|
231
243
|
actions.appendChild(deleteBtn);
|
|
232
244
|
|
|
233
|
-
item.appendChild(
|
|
245
|
+
item.appendChild(colorIndicator);
|
|
234
246
|
item.appendChild(info);
|
|
235
247
|
item.appendChild(actions);
|
|
236
248
|
|
|
@@ -241,6 +253,58 @@ class UIController {
|
|
|
241
253
|
return item;
|
|
242
254
|
}
|
|
243
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Set active state on icon grid button
|
|
258
|
+
*/
|
|
259
|
+
setActiveIconButton(iconName) {
|
|
260
|
+
const grid = document.getElementById("hotspotIconGrid");
|
|
261
|
+
if (!grid) return;
|
|
262
|
+
|
|
263
|
+
// Remove active from all buttons
|
|
264
|
+
grid.querySelectorAll(".icon-btn").forEach(btn => {
|
|
265
|
+
btn.classList.remove("active");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Find and activate the matching button
|
|
269
|
+
const activeBtn = grid.querySelector(`.icon-btn[data-icon="${iconName}"]`);
|
|
270
|
+
if (activeBtn) {
|
|
271
|
+
activeBtn.classList.add("active");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Populate icon grid from SenangStart icons (baked in at build time)
|
|
277
|
+
*/
|
|
278
|
+
populateIconGrid() {
|
|
279
|
+
const grid = document.getElementById("hotspotIconGrid");
|
|
280
|
+
if (!grid) return;
|
|
281
|
+
|
|
282
|
+
// Clear existing content
|
|
283
|
+
grid.innerHTML = "";
|
|
284
|
+
|
|
285
|
+
// Add "No icon" button first
|
|
286
|
+
const noIconBtn = document.createElement("button");
|
|
287
|
+
noIconBtn.type = "button";
|
|
288
|
+
noIconBtn.className = "icon-btn active";
|
|
289
|
+
noIconBtn.dataset.icon = "";
|
|
290
|
+
noIconBtn.title = "No icon";
|
|
291
|
+
noIconBtn.innerHTML = '<ss-icon icon="x-mark" thickness="1.5" style="opacity: 0.5;"></ss-icon>';
|
|
292
|
+
grid.appendChild(noIconBtn);
|
|
293
|
+
|
|
294
|
+
// Add buttons for each icon from imported JSON
|
|
295
|
+
iconsData.forEach(icon => {
|
|
296
|
+
const btn = document.createElement("button");
|
|
297
|
+
btn.type = "button";
|
|
298
|
+
btn.className = "icon-btn";
|
|
299
|
+
btn.dataset.icon = icon.slug;
|
|
300
|
+
btn.title = icon.name;
|
|
301
|
+
btn.innerHTML = `<ss-icon icon="${icon.slug}" thickness="2.2"></ss-icon>`;
|
|
302
|
+
grid.appendChild(btn);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
console.log(`Loaded ${iconsData.length} icons`);
|
|
306
|
+
}
|
|
307
|
+
|
|
244
308
|
/**
|
|
245
309
|
* Update properties panel for hotspot
|
|
246
310
|
*/
|
|
@@ -260,6 +324,8 @@ class UIController {
|
|
|
260
324
|
document.getElementById("hotspotColor").value = "#00ff00";
|
|
261
325
|
const colorText = document.getElementById("hotspotColorText");
|
|
262
326
|
if (colorText) colorText.value = "#00ff00";
|
|
327
|
+
// Reset icon grid to "no icon" button
|
|
328
|
+
this.setActiveIconButton("");
|
|
263
329
|
document.getElementById("hotspotPosX").value = "";
|
|
264
330
|
document.getElementById("hotspotPosY").value = "";
|
|
265
331
|
document.getElementById("hotspotPosZ").value = "";
|
|
@@ -283,6 +349,9 @@ class UIController {
|
|
|
283
349
|
colorText.value = hotspot.color || "#00ff00";
|
|
284
350
|
}
|
|
285
351
|
|
|
352
|
+
// Update icon grid active button
|
|
353
|
+
this.setActiveIconButton(hotspot.icon || "");
|
|
354
|
+
|
|
286
355
|
// Update position inputs
|
|
287
356
|
const pos = hotspot.position || { x: 0, y: 0, z: 0 };
|
|
288
357
|
document.getElementById("hotspotPosX").value = pos.x;
|
|
@@ -297,16 +366,34 @@ class UIController {
|
|
|
297
366
|
* Update properties panel for scene
|
|
298
367
|
*/
|
|
299
368
|
updateSceneProperties(scene) {
|
|
369
|
+
const startingPosDisplay = document.getElementById("startingPositionDisplay");
|
|
370
|
+
|
|
300
371
|
if (!scene) {
|
|
301
372
|
document.getElementById("sceneId").value = "";
|
|
302
373
|
document.getElementById("sceneName").value = "";
|
|
303
374
|
document.getElementById("sceneImageUrl").value = "";
|
|
375
|
+
if (startingPosDisplay) {
|
|
376
|
+
startingPosDisplay.textContent = "Not set (camera keeps current position)";
|
|
377
|
+
}
|
|
304
378
|
return;
|
|
305
379
|
}
|
|
306
380
|
|
|
307
381
|
document.getElementById("sceneId").value = scene.id || "";
|
|
308
382
|
document.getElementById("sceneName").value = scene.name || "";
|
|
309
383
|
document.getElementById("sceneImageUrl").value = scene.imageUrl || "";
|
|
384
|
+
|
|
385
|
+
// Update starting position display
|
|
386
|
+
if (startingPosDisplay) {
|
|
387
|
+
if (scene.startingPosition) {
|
|
388
|
+
const pitchDeg = (scene.startingPosition.pitch * 180 / Math.PI).toFixed(1);
|
|
389
|
+
const yawDeg = (scene.startingPosition.yaw * 180 / Math.PI).toFixed(1);
|
|
390
|
+
startingPosDisplay.textContent = `Pitch: ${pitchDeg}° | Yaw: ${yawDeg}°`;
|
|
391
|
+
startingPosDisplay.style.color = "var(--accent-primary)";
|
|
392
|
+
} else {
|
|
393
|
+
startingPosDisplay.textContent = "Not set (camera keeps current position)";
|
|
394
|
+
startingPosDisplay.style.color = "var(--text-muted)";
|
|
395
|
+
}
|
|
396
|
+
}
|
|
310
397
|
}
|
|
311
398
|
|
|
312
399
|
/**
|
|
@@ -317,10 +404,6 @@ class UIController {
|
|
|
317
404
|
document.getElementById("tourDescription").value = config.description || "";
|
|
318
405
|
document.getElementById("tourInitialScene").value =
|
|
319
406
|
config.initialSceneId || "";
|
|
320
|
-
document.getElementById("tourAutoRotate").checked =
|
|
321
|
-
config.autoRotate || false;
|
|
322
|
-
document.getElementById("tourShowCompass").checked =
|
|
323
|
-
config.showCompass || false;
|
|
324
407
|
|
|
325
408
|
// Also update project name in header if it exists
|
|
326
409
|
const projectName = document.getElementById("project-name");
|
package/src/editor/js/utils.js
CHANGED
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import "./components/hotspot-listener.js";
|
|
|
9
9
|
import { AssetManager } from "./AssetManager.js";
|
|
10
10
|
import { SceneManager } from "./SceneManager.js";
|
|
11
11
|
import { HotspotManager } from "./HotspotManager.js";
|
|
12
|
+
import { IconRenderer } from "./IconRenderer.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Main Tour class - The public API for the SWT library
|
|
@@ -32,12 +33,14 @@ class Tour {
|
|
|
32
33
|
// Initialize managers
|
|
33
34
|
this.assetManager = new AssetManager(this.sceneEl);
|
|
34
35
|
this.sceneManager = new SceneManager(this.sceneEl, this.assetManager);
|
|
36
|
+
this.iconRenderer = new IconRenderer();
|
|
35
37
|
|
|
36
38
|
const defaultHotspotSettings = this.config.settings?.defaultHotspot || {};
|
|
37
39
|
this.hotspotManager = new HotspotManager(
|
|
38
40
|
this.sceneEl,
|
|
39
41
|
this.assetManager,
|
|
40
|
-
defaultHotspotSettings
|
|
42
|
+
defaultHotspotSettings,
|
|
43
|
+
this.iconRenderer
|
|
41
44
|
);
|
|
42
45
|
|
|
43
46
|
// Event listeners
|
|
@@ -98,6 +101,11 @@ class Tour {
|
|
|
98
101
|
|
|
99
102
|
this.isStarted = true;
|
|
100
103
|
|
|
104
|
+
// Set camera to starting position if set
|
|
105
|
+
if (initialSceneData.startingPosition) {
|
|
106
|
+
this.setCameraToStartingPosition(initialSceneData.startingPosition);
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
// Emit events
|
|
102
110
|
this.emit("scene-loaded", { sceneId: initialSceneId });
|
|
103
111
|
this.emit("tour-started", { sceneId: initialSceneId });
|
|
@@ -133,8 +141,13 @@ class Tour {
|
|
|
133
141
|
// Remove old hotspots
|
|
134
142
|
this.hotspotManager.removeAllHotspots();
|
|
135
143
|
|
|
136
|
-
// Transition to new scene
|
|
137
|
-
await this.sceneManager.transitionTo(sceneId, sceneData)
|
|
144
|
+
// Transition to new scene with callback to set camera position while screen is black
|
|
145
|
+
await this.sceneManager.transitionTo(sceneId, sceneData, () => {
|
|
146
|
+
// Set camera to starting position during transition (while screen is still black)
|
|
147
|
+
if (sceneData.startingPosition) {
|
|
148
|
+
this.setCameraToStartingPosition(sceneData.startingPosition);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
138
151
|
|
|
139
152
|
// Create new hotspots
|
|
140
153
|
await this.hotspotManager.createHotspots(sceneData.hotspots || []);
|
|
@@ -188,6 +201,41 @@ class Tour {
|
|
|
188
201
|
}
|
|
189
202
|
}
|
|
190
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Set camera to a starting position immediately
|
|
206
|
+
* Uses look-controls internal pitchObject/yawObject for proper A-Frame compatibility
|
|
207
|
+
* @param {Object} startingPosition - Object with pitch and yaw in radians
|
|
208
|
+
*/
|
|
209
|
+
setCameraToStartingPosition(startingPosition) {
|
|
210
|
+
if (!startingPosition) return;
|
|
211
|
+
|
|
212
|
+
const camera = this.sceneEl.querySelector("[camera]");
|
|
213
|
+
if (!camera) return;
|
|
214
|
+
|
|
215
|
+
// Get look-controls component
|
|
216
|
+
const lookControls = camera.components?.["look-controls"];
|
|
217
|
+
|
|
218
|
+
// Set rotation using look-controls internal objects (already in radians)
|
|
219
|
+
const setRotation = () => {
|
|
220
|
+
const cam = this.sceneEl.querySelector("[camera]");
|
|
221
|
+
if (!cam) return;
|
|
222
|
+
|
|
223
|
+
const lc = cam.components?.["look-controls"];
|
|
224
|
+
if (lc && lc.pitchObject && lc.yawObject) {
|
|
225
|
+
lc.pitchObject.rotation.x = startingPosition.pitch;
|
|
226
|
+
lc.yawObject.rotation.y = startingPosition.yaw;
|
|
227
|
+
} else if (cam.object3D) {
|
|
228
|
+
cam.object3D.rotation.set(startingPosition.pitch, startingPosition.yaw, 0);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Set immediately and retry a few times to ensure it sticks
|
|
233
|
+
setRotation();
|
|
234
|
+
setTimeout(setRotation, 100);
|
|
235
|
+
setTimeout(setRotation, 300);
|
|
236
|
+
setTimeout(setRotation, 500);
|
|
237
|
+
}
|
|
238
|
+
|
|
191
239
|
/**
|
|
192
240
|
* Get the current scene ID
|
|
193
241
|
* @returns {string}
|
|
@@ -242,6 +290,11 @@ class Tour {
|
|
|
242
290
|
this.hotspotManager.destroy();
|
|
243
291
|
this.sceneManager.destroy();
|
|
244
292
|
this.assetManager.destroy();
|
|
293
|
+
|
|
294
|
+
// Clean up icon renderer
|
|
295
|
+
if (this.iconRenderer) {
|
|
296
|
+
this.iconRenderer.destroy();
|
|
297
|
+
}
|
|
245
298
|
|
|
246
299
|
this.isStarted = false;
|
|
247
300
|
}
|