senangwebs-tour 1.0.3 → 1.0.5

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.
@@ -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 color = document.createElement("div");
193
- color.className = "hotspot-color";
194
- color.style.backgroundColor = hotspot.color;
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(color);
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");
@@ -209,6 +209,7 @@ export {
209
209
  hideModal,
210
210
  formatFileSize,
211
211
  downloadTextAsFile,
212
+ copyToClipboard,
212
213
  debounce,
213
214
  positionToString,
214
215
  parsePosition,
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
  }