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 CHANGED
@@ -280,13 +280,21 @@ new SWT.Tour(aframeSceneElement, tourConfiguration);
280
280
 
281
281
  #### Configuration Structure
282
282
 
283
+ Both the editor and viewer use the same data format. Scenes are stored as an **array**:
284
+
283
285
  ```javascript
284
286
  {
285
287
  initialScene: "scene-id", // Required: Starting scene ID
286
- scenes: { // Required: Object (not array!)
287
- "scene-id": { // Key = scene ID
288
+ scenes: [ // Required: Array of scenes
289
+ {
290
+ id: "scene-id", // Required: Scene identifier
288
291
  name: "Scene Name", // Required: Display name
289
292
  panorama: "url-or-dataurl", // Required: Image URL or base64
293
+ thumbnail: "url", // Optional: Thumbnail for editor
294
+ startingPosition: { // Optional: Initial camera orientation
295
+ pitch: 0.1, // Vertical angle (radians)
296
+ yaw: 1.5 // Horizontal angle (radians)
297
+ },
290
298
  hotspots: [ // Optional: Array of hotspots
291
299
  {
292
300
  id: "hotspot-1", // Optional: Auto-generated if omitted
@@ -301,25 +309,29 @@ new SWT.Tour(aframeSceneElement, tourConfiguration);
301
309
  },
302
310
  appearance: { // Optional: Visual customization
303
311
  color: "#00ff00", // Default: "#00ff00"
304
- scale: 1.5, // Default: 1.0 (number or "x y z" string)
305
- icon: "arrow" // Default: sphere (future: custom icons)
312
+ scale: "1 1 1", // Default: "1 1 1"
313
+ icon: "arrow" // Optional: Icon name or URL
306
314
  },
307
315
  tooltip: { // Optional: Hover/focus text
308
- text: "Click to navigate" // Tooltip content
316
+ text: "Click here", // Tooltip title
317
+ description: "Details" // Optional: Extended description
318
+ },
319
+ cameraOrientation: { // Optional: Camera direction when created
320
+ pitch: -0.02, // Vertical angle (radians)
321
+ yaw: 2.06 // Horizontal angle (radians)
309
322
  }
310
323
  }
311
324
  ]
312
325
  }
313
- }
326
+ ]
314
327
  }
315
328
  ```
316
329
 
317
330
  **Important Notes:**
318
331
 
319
- - `scenes` is an **object** (keys are scene IDs), not an array
332
+ - `scenes` is an **array** (not an object)
333
+ - The library also accepts `scenes` as an object keyed by ID for backward compatibility
320
334
  - Hotspot `position` is in 3D space (typically on 10-unit sphere surface)
321
- - Editor stores `imageUrl`, library expects `panorama` (export handles conversion)
322
- - Images can be URLs or Data URLs (base64) for offline tours
323
335
 
324
336
  ---
325
337
 
@@ -300,8 +300,10 @@
300
300
  return false;
301
301
  }
302
302
 
303
- // imageUrl is required for scenes to be valid
304
- if (!scene.imageUrl || typeof scene.imageUrl !== "string") {
303
+ // panorama or imageUrl is required for scenes to be valid (support both formats)
304
+ const hasImage = (scene.panorama && typeof scene.panorama === "string") ||
305
+ (scene.imageUrl && typeof scene.imageUrl === "string");
306
+ if (!hasImage) {
305
307
  return false;
306
308
  }
307
309
 
@@ -426,7 +428,7 @@
426
428
  * @param {File|Object} fileOrConfig - Either a File object or a scene config object
427
429
  * @param {string} fileOrConfig.id - Scene ID (if config object)
428
430
  * @param {string} fileOrConfig.name - Scene name (if config object)
429
- * @param {string} fileOrConfig.imageUrl - Image URL (if config object)
431
+ * @param {string} fileOrConfig.panorama - Panorama URL (if config object)
430
432
  * @param {string} fileOrConfig.thumbnail - Thumbnail URL (if config object)
431
433
  * @param {Array} fileOrConfig.hotspots - Hotspots array (if config object)
432
434
  */
@@ -443,17 +445,17 @@
443
445
  scene = {
444
446
  id: sanitizeId$1(fileOrConfig.name.replace(/\.[^/.]+$/, "")),
445
447
  name: fileOrConfig.name.replace(/\.[^/.]+$/, ""),
446
- imageUrl: imageDataUrl,
448
+ panorama: imageDataUrl,
447
449
  thumbnail: thumbnail,
448
450
  hotspots: [],
449
451
  };
450
452
  } else if (typeof fileOrConfig === "object" && fileOrConfig !== null) {
451
- // Handle config object with URL strings
453
+ // Handle config object - support both panorama and imageUrl for backward compatibility
452
454
  scene = {
453
455
  id: fileOrConfig.id || sanitizeId$1(`scene-${Date.now()}`),
454
456
  name: fileOrConfig.name || "Untitled Scene",
455
- imageUrl: fileOrConfig.imageUrl || "",
456
- thumbnail: fileOrConfig.thumbnail || fileOrConfig.imageUrl || "",
457
+ panorama: fileOrConfig.panorama || fileOrConfig.imageUrl || "",
458
+ thumbnail: fileOrConfig.thumbnail || fileOrConfig.panorama || fileOrConfig.imageUrl || "",
457
459
  hotspots: fileOrConfig.hotspots || [],
458
460
  ...(fileOrConfig.startingPosition && { startingPosition: fileOrConfig.startingPosition }),
459
461
  };
@@ -523,12 +525,13 @@
523
525
  if (index >= 0 && index < this.scenes.length) {
524
526
  this.scenes[index][property] = value;
525
527
 
526
- // If updating ID, update all hotspot target references
528
+ // If updating ID, update all hotspot target references (uses nested action.target)
527
529
  if (property === "id") {
530
+ const oldId = this.scenes[index].id;
528
531
  this.scenes.forEach((scene) => {
529
532
  scene.hotspots.forEach((hotspot) => {
530
- if (hotspot.targetSceneId === this.scenes[index].id) {
531
- hotspot.targetSceneId = value;
533
+ if (hotspot.action?.target === oldId) {
534
+ hotspot.action.target = value;
532
535
  }
533
536
  });
534
537
  });
@@ -649,17 +652,24 @@
649
652
  return null;
650
653
  }
651
654
 
655
+ // Use unified library format with nested structure
652
656
  const hotspot = {
653
657
  id: generateId('hotspot'),
654
- type: 'navigation',
655
658
  position: position,
656
- cameraOrientation: cameraOrientation, // Store camera pitch/yaw for reliable pointing
657
- targetSceneId: targetSceneId,
658
- title: 'New Hotspot',
659
- description: '',
660
- color: '#00ff00',
661
- icon: '',
662
- scale: '1 1 1'
659
+ cameraOrientation: cameraOrientation,
660
+ action: {
661
+ type: 'navigateTo',
662
+ target: targetSceneId
663
+ },
664
+ appearance: {
665
+ color: '#00ff00',
666
+ scale: '1 1 1',
667
+ icon: ''
668
+ },
669
+ tooltip: {
670
+ text: 'New Hotspot',
671
+ description: ''
672
+ }
663
673
  };
664
674
 
665
675
  scene.hotspots.push(hotspot);
@@ -698,6 +708,7 @@
698
708
 
699
709
  /**
700
710
  * Update hotspot property
711
+ * Supports nested properties like 'appearance.color', 'action.target', 'tooltip.text'
701
712
  */
702
713
  updateHotspot(index, property, value) {
703
714
  const scene = this.editor.sceneManager.getCurrentScene();
@@ -705,7 +716,23 @@
705
716
  return false;
706
717
  }
707
718
 
708
- scene.hotspots[index][property] = value;
719
+ const hotspot = scene.hotspots[index];
720
+
721
+ // Handle nested properties (e.g., 'appearance.color')
722
+ if (property.includes('.')) {
723
+ const parts = property.split('.');
724
+ let obj = hotspot;
725
+ for (let i = 0; i < parts.length - 1; i++) {
726
+ if (!obj[parts[i]]) {
727
+ obj[parts[i]] = {};
728
+ }
729
+ obj = obj[parts[i]];
730
+ }
731
+ obj[parts[parts.length - 1]] = value;
732
+ } else {
733
+ hotspot[property] = value;
734
+ }
735
+
709
736
  return true;
710
737
  }
711
738
 
@@ -772,7 +799,9 @@
772
799
  const original = scene.hotspots[index];
773
800
  const duplicate = deepClone(original);
774
801
  duplicate.id = generateId('hotspot');
775
- duplicate.title = original.title + ' (Copy)';
802
+ if (duplicate.tooltip) {
803
+ duplicate.tooltip.text = (original.tooltip?.text || 'Hotspot') + ' (Copy)';
804
+ }
776
805
 
777
806
  // Offset position slightly
778
807
  duplicate.position = {
@@ -816,6 +845,49 @@
816
845
  }
817
846
  };
818
847
 
848
+ /**
849
+ * Data Transform Utilities
850
+ *
851
+ * With the unified format, editor and library use the same structure:
852
+ * - scenes: Array of scene objects with `panorama`
853
+ * - hotspots: nested properties (action, appearance, tooltip)
854
+ *
855
+ * These utilities handle building the tour config for the SWT library.
856
+ */
857
+
858
+ /**
859
+ * Build a complete tour configuration for the SWT library
860
+ * Since unified format is used, this just wraps the scenes array with config
861
+ * @param {Object} config - Config with initialSceneId, etc.
862
+ * @param {Array} scenes - Array of scenes (already in unified format)
863
+ * @returns {Object} Complete tour config
864
+ */
865
+ function buildTourConfig(config, scenes) {
866
+ // Determine initial scene
867
+ let initialScene = config.initialSceneId;
868
+ if (!initialScene && scenes && scenes.length > 0) {
869
+ initialScene = scenes[0].id;
870
+ }
871
+
872
+ return {
873
+ initialScene: initialScene,
874
+ scenes: scenes || [],
875
+ };
876
+ }
877
+
878
+ /**
879
+ * Build tour config for preview (sets initial scene to current scene)
880
+ * @param {Object} currentScene - The scene to show initially
881
+ * @param {Array} allScenes - All scenes for navigation support
882
+ * @returns {Object} Complete tour config
883
+ */
884
+ function buildPreviewTourConfig(currentScene, allScenes) {
885
+ return buildTourConfig(
886
+ { initialSceneId: currentScene.id },
887
+ allScenes
888
+ );
889
+ }
890
+
819
891
  // Preview Controller - Manages A-Frame preview integration using SWT library
820
892
 
821
893
  let PreviewController$1 = class PreviewController {
@@ -866,14 +938,16 @@
866
938
 
867
939
  /**
868
940
  * Load scene into preview using SWT library
941
+ * Scenes and hotspots now use unified library format directly
869
942
  */
870
943
  async loadScene(scene, preserveCameraRotation = true) {
871
944
  if (!this.isInitialized || !scene) {
872
945
  return;
873
946
  }
874
947
 
875
- // Validate scene has required data
876
- if (!scene.imageUrl || !scene.id) {
948
+ // Validate scene has required data (panorama or imageUrl for backward compatibility)
949
+ const panoramaUrl = scene.panorama || scene.imageUrl;
950
+ if (!panoramaUrl || !scene.id) {
877
951
  console.error("Invalid scene data:", scene);
878
952
  return;
879
953
  }
@@ -939,65 +1013,10 @@
939
1013
  // Give A-Frame a moment to start initializing before we proceed
940
1014
  await new Promise((resolve) => setTimeout(resolve, 100));
941
1015
 
942
- // Build tour config for this single scene
943
- // Transform editor scene format to library format
944
- (scene.hotspots || []).map((h) => ({
945
- id: h.id,
946
- position: h.position,
947
- action: {
948
- type: h.type === "navigation" ? "navigateTo" : h.type,
949
- target: h.targetSceneId,
950
- },
951
- appearance: {
952
- color: h.color || "#00ff00",
953
- icon: h.icon || null,
954
- scale: h.scale || "1 1 1",
955
- },
956
- tooltip: {
957
- text: h.title || "Hotspot",
958
- },
959
- }));
960
-
961
- ({
962
- id: scene.id,
963
- name: scene.name,
964
- panorama: scene.imageUrl});
965
-
966
- // Build scenes object with ALL scenes (for navigation to work)
967
- const allScenes = {};
1016
+ // Build tour config using shared transform utilities
1017
+ // This includes ALL scenes for navigation support
968
1018
  const editorScenes = this.editor.sceneManager.scenes || [];
969
- editorScenes.forEach((s) => {
970
- const sceneHotspots = (s.hotspots || []).map((h) => ({
971
- id: h.id,
972
- position: h.position,
973
- action: {
974
- type: h.type === "navigation" ? "navigateTo" : h.type,
975
- target: h.targetSceneId,
976
- },
977
- appearance: {
978
- color: h.color || "#00ff00",
979
- icon: h.icon || null,
980
- scale: h.scale || "1 1 1",
981
- },
982
- tooltip: {
983
- text: h.title || "Hotspot",
984
- },
985
- }));
986
-
987
- allScenes[s.id] = {
988
- id: s.id,
989
- name: s.name,
990
- panorama: s.imageUrl,
991
- hotspots: sceneHotspots,
992
- startingPosition: s.startingPosition || null,
993
- };
994
- });
995
-
996
- const tourConfig = {
997
- title: scene.name,
998
- initialScene: scene.id,
999
- scenes: allScenes,
1000
- };
1019
+ const tourConfig = buildPreviewTourConfig(scene, editorScenes);
1001
1020
 
1002
1021
  try {
1003
1022
  // Create new tour instance
@@ -4022,9 +4041,9 @@
4022
4041
  dragHandle.innerHTML =
4023
4042
  '<ss-icon icon="arrow-up-down-left-right" thickness="2.2"></ss-icon>';
4024
4043
 
4025
- // Thumbnail
4044
+ // Thumbnail - use thumbnail, panorama, or imageUrl (backward compatibility)
4026
4045
  const thumbnail = document.createElement("img");
4027
- thumbnail.src = scene.thumbnail || scene.imageUrl;
4046
+ thumbnail.src = scene.thumbnail || scene.panorama || scene.imageUrl;
4028
4047
  thumbnail.alt = scene.name;
4029
4048
 
4030
4049
  // Info
@@ -4156,18 +4175,25 @@
4156
4175
 
4157
4176
  /**
4158
4177
  * Create hotspot list item
4178
+ * Uses unified format with nested appearance, action, and tooltip
4159
4179
  */
4160
4180
  createHotspotItem(hotspot, index, isActive) {
4161
4181
  const item = document.createElement("div");
4162
4182
  item.className = "hotspot-item" + (isActive ? " active" : "");
4163
4183
 
4184
+ // Get values from nested structure
4185
+ const color = hotspot.appearance?.color || "#00ff00";
4186
+ const icon = hotspot.appearance?.icon || "";
4187
+ const title = hotspot.tooltip?.text || "Untitled Hotspot";
4188
+ const targetSceneId = hotspot.action?.target || "";
4189
+
4164
4190
  const colorIndicator = document.createElement("div");
4165
4191
  colorIndicator.className = "hotspot-color";
4166
- colorIndicator.style.backgroundColor = hotspot.color;
4192
+ colorIndicator.style.backgroundColor = color;
4167
4193
 
4168
4194
  // If hotspot has an icon, show it with the color applied
4169
- if (hotspot.icon) {
4170
- colorIndicator.innerHTML = `<ss-icon icon="${hotspot.icon}" thickness="2.2" style="color: ${hotspot.color}; width: 20px; height: 20px;"></ss-icon>`;
4195
+ if (icon) {
4196
+ colorIndicator.innerHTML = `<ss-icon icon="${icon}" thickness="2.2" style="color: ${color}; width: 20px; height: 20px;"></ss-icon>`;
4171
4197
  colorIndicator.style.backgroundColor = "transparent";
4172
4198
  colorIndicator.style.display = "flex";
4173
4199
  colorIndicator.style.alignItems = "center";
@@ -4177,24 +4203,24 @@
4177
4203
  const info = document.createElement("div");
4178
4204
  info.className = "hotspot-info";
4179
4205
 
4180
- const title = document.createElement("div");
4181
- title.className = "hotspot-title";
4182
- title.textContent = hotspot.title || "Untitled Hotspot";
4206
+ const titleEl = document.createElement("div");
4207
+ titleEl.className = "hotspot-title";
4208
+ titleEl.textContent = title;
4183
4209
 
4184
4210
  const target = document.createElement("div");
4185
4211
  target.className = "hotspot-target";
4186
- if (hotspot.targetSceneId) {
4212
+ if (targetSceneId) {
4187
4213
  const targetScene = this.editor.sceneManager.getSceneById(
4188
- hotspot.targetSceneId
4214
+ targetSceneId
4189
4215
  );
4190
4216
  target.textContent = targetScene
4191
4217
  ? `→ ${targetScene.name}`
4192
- : `→ ${hotspot.targetSceneId}`;
4218
+ : `→ ${targetSceneId}`;
4193
4219
  } else {
4194
4220
  target.textContent = "No target";
4195
4221
  }
4196
4222
 
4197
- info.appendChild(title);
4223
+ info.appendChild(titleEl);
4198
4224
  info.appendChild(target);
4199
4225
 
4200
4226
  const actions = document.createElement("div");
@@ -4276,6 +4302,7 @@
4276
4302
 
4277
4303
  /**
4278
4304
  * Update properties panel for hotspot
4305
+ * Uses unified format with nested appearance, action, and tooltip
4279
4306
  */
4280
4307
  updateHotspotProperties(hotspot) {
4281
4308
  const hotspotAll = document.getElementById("hotspotAll");
@@ -4305,21 +4332,26 @@
4305
4332
  if (hotspotAll) hotspotAll.style.display = "block";
4306
4333
  if (hotspotProperties) hotspotProperties.style.display = "block";
4307
4334
 
4308
- document.getElementById("hotspotTitle").value = hotspot.title || "";
4309
- document.getElementById("hotspotDescription").value =
4310
- hotspot.description || "";
4311
- document.getElementById("hotspotTarget").value =
4312
- hotspot.targetSceneId || "";
4313
- document.getElementById("hotspotColor").value = hotspot.color || "#00ff00";
4335
+ // Get values from nested structure
4336
+ const title = hotspot.tooltip?.text || "";
4337
+ const description = hotspot.tooltip?.description || "";
4338
+ const targetSceneId = hotspot.action?.target || "";
4339
+ const color = hotspot.appearance?.color || "#00ff00";
4340
+ const icon = hotspot.appearance?.icon || "";
4341
+
4342
+ document.getElementById("hotspotTitle").value = title;
4343
+ document.getElementById("hotspotDescription").value = description;
4344
+ document.getElementById("hotspotTarget").value = targetSceneId;
4345
+ document.getElementById("hotspotColor").value = color;
4314
4346
 
4315
4347
  // Update color text input if it exists
4316
4348
  const colorText = document.getElementById("hotspotColorText");
4317
4349
  if (colorText) {
4318
- colorText.value = hotspot.color || "#00ff00";
4350
+ colorText.value = color;
4319
4351
  }
4320
4352
 
4321
4353
  // Update icon grid active button
4322
- this.setActiveIconButton(hotspot.icon || "");
4354
+ this.setActiveIconButton(icon);
4323
4355
 
4324
4356
  // Update position inputs
4325
4357
  const pos = hotspot.position || { x: 0, y: 0, z: 0 };
@@ -4349,7 +4381,8 @@
4349
4381
 
4350
4382
  document.getElementById("sceneId").value = scene.id || "";
4351
4383
  document.getElementById("sceneName").value = scene.name || "";
4352
- document.getElementById("sceneImageUrl").value = scene.imageUrl || "";
4384
+ // Support both panorama (unified) and imageUrl (legacy)
4385
+ document.getElementById("sceneImageUrl").value = scene.panorama || scene.imageUrl || "";
4353
4386
 
4354
4387
  // Update starting position display
4355
4388
  if (startingPosDisplay) {
@@ -4586,75 +4619,13 @@
4586
4619
  /**
4587
4620
  * Generate JSON compatible with SWT library
4588
4621
  * Follows the tourConfig structure from example-simple.html
4622
+ * Uses shared data-transform utilities for consistent transformation
4589
4623
  */
4590
4624
  generateJSON() {
4591
4625
  const scenes = this.editor.sceneManager.getAllScenes();
4592
4626
  const config = this.editor.config;
4593
4627
 
4594
- // Build scenes object (keyed by scene ID)
4595
- const scenesData = {};
4596
- scenes.forEach((scene) => {
4597
- scenesData[scene.id] = {
4598
- name: scene.name,
4599
- panorama: scene.imageUrl,
4600
- hotspots: scene.hotspots.map((hotspot) => {
4601
- const hotspotData = {
4602
- position: hotspot.position,
4603
- };
4604
-
4605
- // Add action based on hotspot type
4606
- if (hotspot.type === "navigation" && hotspot.targetSceneId) {
4607
- hotspotData.action = {
4608
- type: "navigateTo",
4609
- target: hotspot.targetSceneId,
4610
- };
4611
- } else if (hotspot.type === "info") {
4612
- hotspotData.action = {
4613
- type: "showInfo",
4614
- };
4615
- }
4616
-
4617
- // Add appearance
4618
- hotspotData.appearance = {
4619
- color: hotspot.color || "#FF6B6B",
4620
- scale: hotspot.scale || "2 2 2",
4621
- };
4622
-
4623
- // Add icon if set
4624
- if (hotspot.icon) {
4625
- hotspotData.appearance.icon = hotspot.icon;
4626
- }
4627
-
4628
- // Add tooltip if title exists
4629
- if (hotspot.title) {
4630
- hotspotData.tooltip = {
4631
- text: hotspot.title,
4632
- };
4633
- }
4634
-
4635
- return hotspotData;
4636
- }),
4637
- };
4638
-
4639
- // Add starting position if set
4640
- if (scene.startingPosition) {
4641
- scenesData[scene.id].startingPosition = scene.startingPosition;
4642
- }
4643
- });
4644
-
4645
- // Determine initial scene
4646
- let initialScene = config.initialSceneId;
4647
- if (!initialScene && scenes.length > 0) {
4648
- initialScene = scenes[0].id;
4649
- }
4650
-
4651
- // Build final JSON matching SWT tourConfig format
4652
- const jsonData = {
4653
- initialScene: initialScene,
4654
- scenes: scenesData,
4655
- };
4656
-
4657
- return jsonData;
4628
+ return buildTourConfig(config, scenes);
4658
4629
  }
4659
4630
 
4660
4631
  /**
@@ -4665,9 +4636,8 @@
4665
4636
  const jsonData = this.generateJSON();
4666
4637
 
4667
4638
  // Process all scenes and convert icon names to data URLs
4668
- for (const sceneId of Object.keys(jsonData.scenes)) {
4669
- const scene = jsonData.scenes[sceneId];
4670
-
4639
+ // Scenes are an array in unified format
4640
+ for (const scene of jsonData.scenes) {
4671
4641
  for (let i = 0; i < scene.hotspots.length; i++) {
4672
4642
  const hotspot = scene.hotspots[i];
4673
4643
  const icon = hotspot.appearance?.icon;
@@ -4968,19 +4938,19 @@
4968
4938
  });
4969
4939
 
4970
4940
  document.getElementById('hotspotTitle')?.addEventListener('input', debounce((e) => {
4971
- this.updateCurrentHotspot('title', e.target.value);
4941
+ this.updateCurrentHotspot('tooltip.text', e.target.value);
4972
4942
  }, 300));
4973
4943
 
4974
4944
  document.getElementById('hotspotDescription')?.addEventListener('input', debounce((e) => {
4975
- this.updateCurrentHotspot('description', e.target.value);
4945
+ this.updateCurrentHotspot('tooltip.description', e.target.value);
4976
4946
  }, 300));
4977
4947
 
4978
4948
  document.getElementById('hotspotTarget')?.addEventListener('change', (e) => {
4979
- this.updateCurrentHotspot('targetSceneId', e.target.value);
4949
+ this.updateCurrentHotspot('action.target', e.target.value);
4980
4950
  });
4981
4951
 
4982
4952
  document.getElementById('hotspotColor')?.addEventListener('input', (e) => {
4983
- this.updateCurrentHotspot('color', e.target.value);
4953
+ this.updateCurrentHotspot('appearance.color', e.target.value);
4984
4954
  });
4985
4955
 
4986
4956
  // Icon grid button clicks
@@ -4992,7 +4962,7 @@
4992
4962
  document.querySelectorAll('#hotspotIconGrid .icon-btn').forEach(b => b.classList.remove('active'));
4993
4963
  btn.classList.add('active');
4994
4964
  // Update hotspot
4995
- this.updateCurrentHotspot('icon', iconValue);
4965
+ this.updateCurrentHotspot('appearance.icon', iconValue);
4996
4966
  }
4997
4967
  });
4998
4968
 
@@ -5321,7 +5291,8 @@
5321
5291
  const index = this.sceneManager.currentSceneIndex;
5322
5292
  if (index < 0) return;
5323
5293
 
5324
- if (this.sceneManager.updateScene(index, 'imageUrl', imageUrl)) {
5294
+ // Use panorama for unified format
5295
+ if (this.sceneManager.updateScene(index, 'panorama', imageUrl)) {
5325
5296
  const scene = this.sceneManager.getCurrentScene();
5326
5297
  if (scene) {
5327
5298
  scene.thumbnail = imageUrl;