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.
@@ -913,7 +913,7 @@
913
913
  console.error("Error removing scene:", error);
914
914
  }
915
915
  }
916
-
916
+
917
917
  // Clear any remaining children (loading overlays, empty state, etc)
918
918
  while (this.previewContainer.firstChild) {
919
919
  this.previewContainer.removeChild(this.previewContainer.firstChild);
@@ -921,9 +921,9 @@
921
921
  } else {
922
922
  // First load - only remove non-A-Frame elements (like empty state divs)
923
923
  const children = Array.from(this.previewContainer.children);
924
- children.forEach(child => {
924
+ children.forEach((child) => {
925
925
  // Only remove if it's NOT an a-scene (shouldn't be any, but be safe)
926
- if (child.tagName.toLowerCase() !== 'a-scene') {
926
+ if (child.tagName.toLowerCase() !== "a-scene") {
927
927
  this.previewContainer.removeChild(child);
928
928
  }
929
929
  });
@@ -1132,6 +1132,7 @@
1132
1132
 
1133
1133
  /**
1134
1134
  * Get current camera rotation
1135
+ * Works with A-Frame's look-controls component by reading its internal state
1135
1136
  */
1136
1137
  getCameraRotation() {
1137
1138
  const aframeScene = this.previewContainer?.querySelector("a-scene");
@@ -1144,7 +1145,18 @@
1144
1145
  return null;
1145
1146
  }
1146
1147
 
1147
- // Get rotation from object3D which is more reliable
1148
+ // Try to get rotation from look-controls internal state (more reliable)
1149
+ const lookControls = camera.components?.["look-controls"];
1150
+ if (lookControls && lookControls.pitchObject && lookControls.yawObject) {
1151
+ const savedRotation = {
1152
+ x: lookControls.pitchObject.rotation.x,
1153
+ y: lookControls.yawObject.rotation.y,
1154
+ z: 0,
1155
+ };
1156
+ return savedRotation;
1157
+ }
1158
+
1159
+ // Fallback to object3D rotation
1148
1160
  const rotation = camera.object3D.rotation;
1149
1161
  const savedRotation = {
1150
1162
  x: rotation.x,
@@ -1156,6 +1168,7 @@
1156
1168
 
1157
1169
  /**
1158
1170
  * Set camera rotation
1171
+ * Works with A-Frame's look-controls component by setting its internal state
1159
1172
  */
1160
1173
  setCameraRotation(rotation) {
1161
1174
  if (!rotation) {
@@ -1172,17 +1185,30 @@
1172
1185
  return;
1173
1186
  }
1174
1187
 
1175
- // Set rotation on object3D directly
1188
+ // Set rotation via look-controls internal pitchObject/yawObject
1189
+ // This is required because look-controls overrides object3D.rotation on each tick
1176
1190
  const setRotation = () => {
1177
- if (camera.object3D) {
1178
- camera.object3D.rotation.set(rotation.x, rotation.y, rotation.z);
1191
+ const cam = this.previewContainer?.querySelector("[camera]");
1192
+ if (!cam) return;
1193
+
1194
+ const lookControls = cam.components?.["look-controls"];
1195
+ if (lookControls && lookControls.pitchObject && lookControls.yawObject) {
1196
+ // Set pitch (x rotation) on pitchObject
1197
+ lookControls.pitchObject.rotation.x = rotation.x;
1198
+ // Set yaw (y rotation) on yawObject
1199
+ lookControls.yawObject.rotation.y = rotation.y;
1200
+ } else if (cam.object3D) {
1201
+ // Fallback to direct object3D if look-controls not ready
1202
+ cam.object3D.rotation.set(rotation.x, rotation.y, rotation.z);
1179
1203
  }
1180
1204
  };
1181
1205
 
1182
- // Try immediately and also after a delay to ensure it sticks
1206
+ // Try immediately and also after delays to ensure it sticks
1207
+ // A-Frame look-controls may not be fully initialized immediately
1183
1208
  setRotation();
1184
1209
  setTimeout(setRotation, 100);
1185
1210
  setTimeout(setRotation, 300);
1211
+ setTimeout(setRotation, 500);
1186
1212
  }
1187
1213
 
1188
1214
  /**
@@ -1191,15 +1217,8 @@
1191
1217
  async refresh() {
1192
1218
  const scene = this.editor.sceneManager.getCurrentScene();
1193
1219
  if (scene) {
1194
- // Save current camera rotation
1195
- const savedRotation = this.getCameraRotation();
1196
- // Reload scene
1197
- await this.loadScene(scene);
1198
-
1199
- // Restore camera rotation
1200
- if (savedRotation) {
1201
- this.setCameraRotation(savedRotation);
1202
- }
1220
+ // loadScene with preserveCameraRotation=true handles save/restore internally
1221
+ await this.loadScene(scene, true);
1203
1222
  }
1204
1223
  }
1205
1224
 
@@ -1375,670 +1394,599 @@
1375
1394
  // UI Controller - Handles DOM manipulation and rendering
1376
1395
 
1377
1396
  let UIController$1 = class UIController {
1378
- constructor(editor) {
1379
- this.editor = editor;
1380
- this.sceneList = document.getElementById('sceneList');
1381
- this.hotspotList = document.getElementById('hotspotList');
1382
- this.draggedElement = null;
1383
- }
1397
+ constructor(editor) {
1398
+ this.editor = editor;
1399
+ this.sceneList = document.getElementById("sceneList");
1400
+ this.hotspotList = document.getElementById("hotspotList");
1401
+ this.draggedElement = null;
1402
+ }
1384
1403
 
1385
- /**
1386
- * Render scene list
1387
- */
1388
- renderSceneList() {
1389
- if (!this.sceneList) return;
1404
+ /**
1405
+ * Render scene list
1406
+ */
1407
+ renderSceneList() {
1408
+ if (!this.sceneList) return;
1390
1409
 
1391
- this.sceneList.innerHTML = '';
1392
- const scenes = this.editor.sceneManager.getAllScenes();
1393
- const currentIndex = this.editor.sceneManager.currentSceneIndex;
1410
+ this.sceneList.innerHTML = "";
1411
+ const scenes = this.editor.sceneManager.getAllScenes();
1412
+ const currentIndex = this.editor.sceneManager.currentSceneIndex;
1394
1413
 
1395
- if (scenes.length === 0) {
1396
- const empty = document.createElement('div');
1397
- empty.className = 'empty-state';
1398
- empty.innerHTML = `
1414
+ if (scenes.length === 0) {
1415
+ const empty = document.createElement("div");
1416
+ empty.className = "empty-state";
1417
+ empty.innerHTML = `
1399
1418
  <p>No scenes yet</p>
1400
1419
  <p class="hint">Click "Add Scene" to upload a 360° panorama</p>
1401
1420
  `;
1402
- this.sceneList.appendChild(empty);
1403
- return;
1404
- }
1405
-
1406
- scenes.forEach((scene, index) => {
1407
- const card = this.createSceneCard(scene, index, index === currentIndex);
1408
- this.sceneList.appendChild(card);
1409
- });
1421
+ this.sceneList.appendChild(empty);
1422
+ return;
1410
1423
  }
1411
1424
 
1412
- /**
1413
- * Create scene card element
1414
- */
1415
- createSceneCard(scene, index, isActive) {
1416
- const card = document.createElement('div');
1417
- card.className = 'scene-card' + (isActive ? ' active' : '');
1418
- card.draggable = true;
1419
- card.dataset.index = index;
1420
-
1421
- // Drag handle
1422
- const dragHandle = document.createElement('div');
1423
- dragHandle.className = 'drag-handle';
1424
- dragHandle.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free v6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"/></svg>';
1425
-
1426
- // Thumbnail
1427
- const thumbnail = document.createElement('img');
1428
- thumbnail.src = scene.thumbnail || scene.imageUrl;
1429
- thumbnail.alt = scene.name;
1430
-
1431
- // Info
1432
- const info = document.createElement('div');
1433
- info.className = 'scene-info';
1434
-
1435
- const name = document.createElement('div');
1436
- name.className = 'scene-name';
1437
- name.textContent = scene.name;
1438
-
1439
- const meta = document.createElement('div');
1440
- meta.className = 'scene-meta';
1441
- meta.textContent = `${scene.hotspots.length} hotspot${scene.hotspots.length !== 1 ? 's' : ''}`;
1442
-
1443
- info.appendChild(name);
1444
- info.appendChild(meta);
1445
-
1446
- // Actions
1447
- const actions = document.createElement('div');
1448
- actions.className = 'scene-actions';
1449
-
1450
- const deleteBtn = document.createElement('button');
1451
- deleteBtn.className = 'btn-icon';
1452
- deleteBtn.innerHTML = '🗑️';
1453
- deleteBtn.title = 'Delete scene';
1454
- deleteBtn.onclick = (e) => {
1455
- e.stopPropagation();
1456
- this.editor.removeScene(index);
1457
- };
1425
+ scenes.forEach((scene, index) => {
1426
+ const card = this.createSceneCard(scene, index, index === currentIndex);
1427
+ this.sceneList.appendChild(card);
1428
+ });
1429
+ }
1458
1430
 
1459
- actions.appendChild(deleteBtn);
1431
+ /**
1432
+ * Create scene card element
1433
+ */
1434
+ createSceneCard(scene, index, isActive) {
1435
+ const card = document.createElement("div");
1436
+ card.className = "scene-card" + (isActive ? " active" : "");
1437
+ card.draggable = true;
1438
+ card.dataset.index = index;
1439
+
1440
+ // Drag handle
1441
+ const dragHandle = document.createElement("div");
1442
+ dragHandle.className = "drag-handle";
1443
+ dragHandle.innerHTML =
1444
+ '<ss-icon icon="arrow-up-down-left-right" thickness="2.2"></ss-icon>';
1445
+
1446
+ // Thumbnail
1447
+ const thumbnail = document.createElement("img");
1448
+ thumbnail.src = scene.thumbnail || scene.imageUrl;
1449
+ thumbnail.alt = scene.name;
1450
+
1451
+ // Info
1452
+ const info = document.createElement("div");
1453
+ info.className = "scene-info";
1454
+
1455
+ const name = document.createElement("div");
1456
+ name.className = "scene-name";
1457
+ name.textContent = scene.name;
1458
+
1459
+ const meta = document.createElement("div");
1460
+ meta.className = "scene-meta";
1461
+ meta.textContent = `${scene.hotspots.length} hotspot${
1462
+ scene.hotspots.length !== 1 ? "s" : ""
1463
+ }`;
1464
+
1465
+ info.appendChild(name);
1466
+ info.appendChild(meta);
1467
+
1468
+ // Actions
1469
+ const actions = document.createElement("div");
1470
+ actions.className = "scene-actions";
1471
+
1472
+ const deleteBtn = document.createElement("button");
1473
+ deleteBtn.className = "btn-icon";
1474
+ deleteBtn.innerHTML = '<ss-icon icon="trash" thickness="2.2"></ss-icon>';
1475
+ deleteBtn.title = "Delete scene";
1476
+ deleteBtn.onclick = (e) => {
1477
+ e.stopPropagation();
1478
+ this.editor.removeScene(index);
1479
+ };
1460
1480
 
1461
- card.appendChild(dragHandle);
1462
- card.appendChild(thumbnail);
1463
- card.appendChild(info);
1464
- card.appendChild(actions);
1481
+ actions.appendChild(deleteBtn);
1465
1482
 
1466
- // Click handler
1467
- card.onclick = () => {
1468
- this.editor.selectScene(index);
1469
- };
1483
+ card.appendChild(dragHandle);
1484
+ card.appendChild(thumbnail);
1485
+ card.appendChild(info);
1486
+ card.appendChild(actions);
1470
1487
 
1471
- // Drag and drop handlers
1472
- this.setupDragAndDrop(card);
1488
+ // Click handler
1489
+ card.onclick = () => {
1490
+ this.editor.selectScene(index);
1491
+ };
1473
1492
 
1474
- return card;
1475
- }
1493
+ // Drag and drop handlers
1494
+ this.setupDragAndDrop(card);
1476
1495
 
1477
- /**
1478
- * Setup drag and drop for scene reordering
1479
- */
1480
- setupDragAndDrop(card) {
1481
- card.addEventListener('dragstart', (e) => {
1482
- this.draggedElement = card;
1483
- card.classList.add('dragging');
1484
- e.dataTransfer.effectAllowed = 'move';
1485
- });
1496
+ return card;
1497
+ }
1486
1498
 
1487
- card.addEventListener('dragend', () => {
1488
- card.classList.remove('dragging');
1489
- this.draggedElement = null;
1490
- });
1499
+ /**
1500
+ * Setup drag and drop for scene reordering
1501
+ */
1502
+ setupDragAndDrop(card) {
1503
+ card.addEventListener("dragstart", (e) => {
1504
+ this.draggedElement = card;
1505
+ card.classList.add("dragging");
1506
+ e.dataTransfer.effectAllowed = "move";
1507
+ });
1491
1508
 
1492
- card.addEventListener('dragover', (e) => {
1493
- e.preventDefault();
1494
- e.dataTransfer.dropEffect = 'move';
1509
+ card.addEventListener("dragend", () => {
1510
+ card.classList.remove("dragging");
1511
+ this.draggedElement = null;
1512
+ });
1495
1513
 
1496
- if (this.draggedElement && this.draggedElement !== card) {
1497
- const bounding = card.getBoundingClientRect();
1498
- const offset = bounding.y + bounding.height / 2;
1514
+ card.addEventListener("dragover", (e) => {
1515
+ e.preventDefault();
1516
+ e.dataTransfer.dropEffect = "move";
1499
1517
 
1500
- if (e.clientY - offset > 0) {
1501
- card.style.borderBottom = '2px solid var(--accent-color)';
1502
- card.style.borderTop = '';
1503
- } else {
1504
- card.style.borderTop = '2px solid var(--accent-color)';
1505
- card.style.borderBottom = '';
1506
- }
1507
- }
1508
- });
1518
+ if (this.draggedElement && this.draggedElement !== card) {
1519
+ const bounding = card.getBoundingClientRect();
1520
+ const offset = bounding.y + bounding.height / 2;
1509
1521
 
1510
- card.addEventListener('dragleave', () => {
1511
- card.style.borderTop = '';
1512
- card.style.borderBottom = '';
1513
- });
1522
+ if (e.clientY - offset > 0) {
1523
+ card.style.borderBottom = "2px solid var(--accent-color)";
1524
+ card.style.borderTop = "";
1525
+ } else {
1526
+ card.style.borderTop = "2px solid var(--accent-color)";
1527
+ card.style.borderBottom = "";
1528
+ }
1529
+ }
1530
+ });
1514
1531
 
1515
- card.addEventListener('drop', (e) => {
1516
- e.preventDefault();
1517
- card.style.borderTop = '';
1518
- card.style.borderBottom = '';
1532
+ card.addEventListener("dragleave", () => {
1533
+ card.style.borderTop = "";
1534
+ card.style.borderBottom = "";
1535
+ });
1519
1536
 
1520
- if (this.draggedElement && this.draggedElement !== card) {
1521
- const fromIndex = parseInt(this.draggedElement.dataset.index);
1522
- const toIndex = parseInt(card.dataset.index);
1523
- this.editor.reorderScenes(fromIndex, toIndex);
1524
- }
1525
- });
1537
+ card.addEventListener("drop", (e) => {
1538
+ e.preventDefault();
1539
+ card.style.borderTop = "";
1540
+ card.style.borderBottom = "";
1541
+
1542
+ if (this.draggedElement && this.draggedElement !== card) {
1543
+ const fromIndex = parseInt(this.draggedElement.dataset.index);
1544
+ const toIndex = parseInt(card.dataset.index);
1545
+ this.editor.reorderScenes(fromIndex, toIndex);
1546
+ }
1547
+ });
1548
+ }
1549
+
1550
+ /**
1551
+ * Render hotspot list
1552
+ */
1553
+ renderHotspotList() {
1554
+ if (!this.hotspotList) return;
1555
+
1556
+ this.hotspotList.innerHTML = "";
1557
+ const hotspots = this.editor.hotspotEditor.getAllHotspots();
1558
+ const currentIndex = this.editor.hotspotEditor.currentHotspotIndex;
1559
+
1560
+ if (hotspots.length === 0) {
1561
+ const empty = document.createElement("div");
1562
+ empty.className = "empty-state";
1563
+ empty.textContent = 'No hotspots. Click "Add Hotspot" to create one.';
1564
+ this.hotspotList.appendChild(empty);
1565
+ return;
1526
1566
  }
1527
1567
 
1528
- /**
1529
- * Render hotspot list
1530
- */
1531
- renderHotspotList() {
1532
- if (!this.hotspotList) return;
1533
-
1534
- this.hotspotList.innerHTML = '';
1535
- const hotspots = this.editor.hotspotEditor.getAllHotspots();
1536
- const currentIndex = this.editor.hotspotEditor.currentHotspotIndex;
1537
-
1538
- if (hotspots.length === 0) {
1539
- const empty = document.createElement('div');
1540
- empty.className = 'empty-state';
1541
- empty.textContent = 'No hotspots. Click "Add Hotspot" to create one.';
1542
- this.hotspotList.appendChild(empty);
1543
- return;
1544
- }
1568
+ hotspots.forEach((hotspot, index) => {
1569
+ const item = this.createHotspotItem(
1570
+ hotspot,
1571
+ index,
1572
+ index === currentIndex
1573
+ );
1574
+ this.hotspotList.appendChild(item);
1575
+ });
1576
+ }
1545
1577
 
1546
- hotspots.forEach((hotspot, index) => {
1547
- const item = this.createHotspotItem(hotspot, index, index === currentIndex);
1548
- this.hotspotList.appendChild(item);
1549
- });
1578
+ /**
1579
+ * Create hotspot list item
1580
+ */
1581
+ createHotspotItem(hotspot, index, isActive) {
1582
+ const item = document.createElement("div");
1583
+ item.className = "hotspot-item" + (isActive ? " active" : "");
1584
+
1585
+ const color = document.createElement("div");
1586
+ color.className = "hotspot-color";
1587
+ color.style.backgroundColor = hotspot.color;
1588
+
1589
+ const info = document.createElement("div");
1590
+ info.className = "hotspot-info";
1591
+
1592
+ const title = document.createElement("div");
1593
+ title.className = "hotspot-title";
1594
+ title.textContent = hotspot.title || "Untitled Hotspot";
1595
+
1596
+ const target = document.createElement("div");
1597
+ target.className = "hotspot-target";
1598
+ if (hotspot.targetSceneId) {
1599
+ const targetScene = this.editor.sceneManager.getSceneById(
1600
+ hotspot.targetSceneId
1601
+ );
1602
+ target.textContent = targetScene
1603
+ ? `→ ${targetScene.name}`
1604
+ : `→ ${hotspot.targetSceneId}`;
1605
+ } else {
1606
+ target.textContent = "No target";
1550
1607
  }
1551
1608
 
1552
- /**
1553
- * Create hotspot list item
1554
- */
1555
- createHotspotItem(hotspot, index, isActive) {
1556
- const item = document.createElement('div');
1557
- item.className = 'hotspot-item' + (isActive ? ' active' : '');
1609
+ info.appendChild(title);
1610
+ info.appendChild(target);
1558
1611
 
1559
- const color = document.createElement('div');
1560
- color.className = 'hotspot-color';
1561
- color.style.backgroundColor = hotspot.color;
1612
+ const actions = document.createElement("div");
1613
+ actions.className = "hotspot-actions";
1562
1614
 
1563
- const info = document.createElement('div');
1564
- info.className = 'hotspot-info';
1565
-
1566
- const title = document.createElement('div');
1567
- title.className = 'hotspot-title';
1568
- title.textContent = hotspot.title || 'Untitled Hotspot';
1569
-
1570
- const target = document.createElement('div');
1571
- target.className = 'hotspot-target';
1572
- if (hotspot.targetSceneId) {
1573
- const targetScene = this.editor.sceneManager.getSceneById(hotspot.targetSceneId);
1574
- target.textContent = targetScene ? `→ ${targetScene.name}` : `→ ${hotspot.targetSceneId}`;
1575
- } else {
1576
- target.textContent = 'No target';
1577
- }
1578
-
1579
- info.appendChild(title);
1580
- info.appendChild(target);
1581
-
1582
- const actions = document.createElement('div');
1583
- actions.className = 'hotspot-actions';
1584
-
1585
- const deleteBtn = document.createElement('button');
1586
- deleteBtn.className = 'btn-delete';
1587
- deleteBtn.innerHTML = '🗑️';
1588
- deleteBtn.title = 'Delete';
1589
- deleteBtn.onclick = (e) => {
1590
- e.stopPropagation();
1591
- this.editor.removeHotspot(index);
1592
- };
1615
+ const deleteBtn = document.createElement("button");
1616
+ deleteBtn.className = "btn-delete";
1617
+ deleteBtn.innerHTML = '<ss-icon icon="trash" thickness="2.2"></ss-icon>';
1618
+ deleteBtn.title = "Delete";
1619
+ deleteBtn.onclick = (e) => {
1620
+ e.stopPropagation();
1621
+ this.editor.removeHotspot(index);
1622
+ };
1593
1623
 
1594
- actions.appendChild(deleteBtn);
1624
+ actions.appendChild(deleteBtn);
1595
1625
 
1596
- item.appendChild(color);
1597
- item.appendChild(info);
1598
- item.appendChild(actions);
1626
+ item.appendChild(color);
1627
+ item.appendChild(info);
1628
+ item.appendChild(actions);
1599
1629
 
1600
- item.onclick = () => {
1601
- this.editor.selectHotspot(index);
1602
- };
1630
+ item.onclick = () => {
1631
+ this.editor.selectHotspot(index);
1632
+ };
1603
1633
 
1604
- return item;
1605
- }
1634
+ return item;
1635
+ }
1606
1636
 
1607
- /**
1608
- * Update properties panel for hotspot
1609
- */
1610
- updateHotspotProperties(hotspot) {
1611
- const hotspotAll = document.getElementById('hotspotAll');
1612
- const hotspotProperties = document.getElementById('hotspotProperties');
1613
-
1614
- if (!hotspot) {
1615
- // No hotspot selected - show list, hide properties
1616
- if (hotspotAll) hotspotAll.style.display = 'block';
1617
- if (hotspotProperties) hotspotProperties.style.display = 'none';
1618
-
1619
- // Clear form
1620
- document.getElementById('hotspotTitle').value = '';
1621
- document.getElementById('hotspotDescription').value = '';
1622
- document.getElementById('hotspotTarget').value = '';
1623
- document.getElementById('hotspotColor').value = '#00ff00';
1624
- const colorText = document.getElementById('hotspotColorText');
1625
- if (colorText) colorText.value = '#00ff00';
1626
- document.getElementById('hotspotPosX').value = '';
1627
- document.getElementById('hotspotPosY').value = '';
1628
- document.getElementById('hotspotPosZ').value = '';
1629
- return;
1630
- }
1637
+ /**
1638
+ * Update properties panel for hotspot
1639
+ */
1640
+ updateHotspotProperties(hotspot) {
1641
+ const hotspotAll = document.getElementById("hotspotAll");
1642
+ const hotspotProperties = document.getElementById("hotspotProperties");
1643
+
1644
+ if (!hotspot) {
1645
+ // No hotspot selected - show list, hide properties
1646
+ if (hotspotAll) hotspotAll.style.display = "block";
1647
+ if (hotspotProperties) hotspotProperties.style.display = "none";
1648
+
1649
+ // Clear form
1650
+ document.getElementById("hotspotTitle").value = "";
1651
+ document.getElementById("hotspotDescription").value = "";
1652
+ document.getElementById("hotspotTarget").value = "";
1653
+ document.getElementById("hotspotColor").value = "#00ff00";
1654
+ const colorText = document.getElementById("hotspotColorText");
1655
+ if (colorText) colorText.value = "#00ff00";
1656
+ document.getElementById("hotspotPosX").value = "";
1657
+ document.getElementById("hotspotPosY").value = "";
1658
+ document.getElementById("hotspotPosZ").value = "";
1659
+ return;
1660
+ }
1631
1661
 
1632
- // Hotspot selected - show both list and properties
1633
- if (hotspotAll) hotspotAll.style.display = 'block';
1634
- if (hotspotProperties) hotspotProperties.style.display = 'block';
1662
+ // Hotspot selected - show both list and properties
1663
+ if (hotspotAll) hotspotAll.style.display = "block";
1664
+ if (hotspotProperties) hotspotProperties.style.display = "block";
1635
1665
 
1636
- document.getElementById('hotspotTitle').value = hotspot.title || '';
1637
- document.getElementById('hotspotDescription').value = hotspot.description || '';
1638
- document.getElementById('hotspotTarget').value = hotspot.targetSceneId || '';
1639
- document.getElementById('hotspotColor').value = hotspot.color || '#00ff00';
1640
-
1641
- // Update color text input if it exists
1642
- const colorText = document.getElementById('hotspotColorText');
1643
- if (colorText) {
1644
- colorText.value = hotspot.color || '#00ff00';
1645
- }
1646
-
1647
- // Update position inputs
1648
- const pos = hotspot.position || { x: 0, y: 0, z: 0 };
1649
- document.getElementById('hotspotPosX').value = pos.x;
1650
- document.getElementById('hotspotPosY').value = pos.y;
1651
- document.getElementById('hotspotPosZ').value = pos.z;
1666
+ document.getElementById("hotspotTitle").value = hotspot.title || "";
1667
+ document.getElementById("hotspotDescription").value =
1668
+ hotspot.description || "";
1669
+ document.getElementById("hotspotTarget").value =
1670
+ hotspot.targetSceneId || "";
1671
+ document.getElementById("hotspotColor").value = hotspot.color || "#00ff00";
1652
1672
 
1653
- // Update target dropdown
1654
- this.updateTargetSceneOptions();
1673
+ // Update color text input if it exists
1674
+ const colorText = document.getElementById("hotspotColorText");
1675
+ if (colorText) {
1676
+ colorText.value = hotspot.color || "#00ff00";
1655
1677
  }
1656
1678
 
1657
- /**
1658
- * Update properties panel for scene
1659
- */
1660
- updateSceneProperties(scene) {
1661
- if (!scene) {
1662
- document.getElementById('sceneId').value = '';
1663
- document.getElementById('sceneName').value = '';
1664
- document.getElementById('sceneImageUrl').value = '';
1665
- return;
1666
- }
1679
+ // Update position inputs
1680
+ const pos = hotspot.position || { x: 0, y: 0, z: 0 };
1681
+ document.getElementById("hotspotPosX").value = pos.x;
1682
+ document.getElementById("hotspotPosY").value = pos.y;
1683
+ document.getElementById("hotspotPosZ").value = pos.z;
1667
1684
 
1668
- document.getElementById('sceneId').value = scene.id || '';
1669
- document.getElementById('sceneName').value = scene.name || '';
1670
- document.getElementById('sceneImageUrl').value = scene.imageUrl || '';
1685
+ // Update target dropdown
1686
+ this.updateTargetSceneOptions();
1687
+ }
1688
+
1689
+ /**
1690
+ * Update properties panel for scene
1691
+ */
1692
+ updateSceneProperties(scene) {
1693
+ if (!scene) {
1694
+ document.getElementById("sceneId").value = "";
1695
+ document.getElementById("sceneName").value = "";
1696
+ document.getElementById("sceneImageUrl").value = "";
1697
+ return;
1671
1698
  }
1672
1699
 
1673
- /**
1674
- * Update properties panel for tour
1675
- */
1676
- updateTourProperties(config) {
1677
- document.getElementById('tourTitle').value = config.title || '';
1678
- document.getElementById('tourDescription').value = config.description || '';
1679
- document.getElementById('tourInitialScene').value = config.initialSceneId || '';
1680
- document.getElementById('tourAutoRotate').checked = config.autoRotate || false;
1681
- document.getElementById('tourShowCompass').checked = config.showCompass || false;
1682
-
1683
- // Also update project name in header if it exists
1684
- const projectName = document.getElementById('project-name');
1685
- if (projectName) {
1686
- projectName.value = config.title || '';
1687
- }
1700
+ document.getElementById("sceneId").value = scene.id || "";
1701
+ document.getElementById("sceneName").value = scene.name || "";
1702
+ document.getElementById("sceneImageUrl").value = scene.imageUrl || "";
1703
+ }
1704
+
1705
+ /**
1706
+ * Update properties panel for tour
1707
+ */
1708
+ updateTourProperties(config) {
1709
+ document.getElementById("tourTitle").value = config.title || "";
1710
+ document.getElementById("tourDescription").value = config.description || "";
1711
+ document.getElementById("tourInitialScene").value =
1712
+ config.initialSceneId || "";
1713
+ document.getElementById("tourAutoRotate").checked =
1714
+ config.autoRotate || false;
1715
+ document.getElementById("tourShowCompass").checked =
1716
+ config.showCompass || false;
1717
+
1718
+ // Also update project name in header if it exists
1719
+ const projectName = document.getElementById("project-name");
1720
+ if (projectName) {
1721
+ projectName.value = config.title || "";
1688
1722
  }
1723
+ }
1689
1724
 
1690
- /**
1691
- * Update target scene options in hotspot properties
1692
- */
1693
- updateTargetSceneOptions() {
1694
- const select = document.getElementById('hotspotTarget');
1695
- if (!select) return;
1725
+ /**
1726
+ * Update target scene options in hotspot properties
1727
+ */
1728
+ updateTargetSceneOptions() {
1729
+ const select = document.getElementById("hotspotTarget");
1730
+ if (!select) return;
1696
1731
 
1697
- const scenes = this.editor.sceneManager.getAllScenes();
1698
- const currentValue = select.value;
1732
+ const scenes = this.editor.sceneManager.getAllScenes();
1733
+ const currentValue = select.value;
1699
1734
 
1700
- select.innerHTML = '<option value="">Select target scene...</option>';
1735
+ select.innerHTML = '<option value="">Select target scene...</option>';
1701
1736
 
1702
- scenes.forEach(scene => {
1703
- const option = document.createElement('option');
1704
- option.value = scene.id;
1705
- option.textContent = scene.name;
1706
- select.appendChild(option);
1707
- });
1737
+ scenes.forEach((scene) => {
1738
+ const option = document.createElement("option");
1739
+ option.value = scene.id;
1740
+ option.textContent = scene.name;
1741
+ select.appendChild(option);
1742
+ });
1708
1743
 
1709
- select.value = currentValue;
1710
- }
1744
+ select.value = currentValue;
1745
+ }
1711
1746
 
1712
- /**
1713
- * Update initial scene options in tour properties
1714
- */
1715
- updateInitialSceneOptions() {
1716
- const select = document.getElementById('tourInitialScene');
1717
- if (!select) return;
1747
+ /**
1748
+ * Update initial scene options in tour properties
1749
+ */
1750
+ updateInitialSceneOptions() {
1751
+ const select = document.getElementById("tourInitialScene");
1752
+ if (!select) return;
1718
1753
 
1719
- const scenes = this.editor.sceneManager.getAllScenes();
1720
- const currentValue = select.value;
1754
+ const scenes = this.editor.sceneManager.getAllScenes();
1755
+ const currentValue = select.value;
1721
1756
 
1722
- select.innerHTML = '<option value="">Select initial scene...</option>';
1757
+ select.innerHTML = '<option value="">Select initial scene...</option>';
1723
1758
 
1724
- scenes.forEach(scene => {
1725
- const option = document.createElement('option');
1726
- option.value = scene.id;
1727
- option.textContent = scene.name;
1728
- select.appendChild(option);
1729
- });
1759
+ scenes.forEach((scene) => {
1760
+ const option = document.createElement("option");
1761
+ option.value = scene.id;
1762
+ option.textContent = scene.name;
1763
+ select.appendChild(option);
1764
+ });
1730
1765
 
1731
- select.value = currentValue;
1732
- }
1766
+ select.value = currentValue;
1767
+ }
1733
1768
 
1734
- /**
1735
- * Show/hide loading indicator
1736
- */
1737
- setLoading(isLoading) {
1738
- const indicator = document.querySelector('.loading-indicator');
1739
- if (indicator) {
1740
- indicator.style.display = isLoading ? 'block' : 'none';
1741
- }
1769
+ /**
1770
+ * Show/hide loading indicator
1771
+ */
1772
+ setLoading(isLoading) {
1773
+ const indicator = document.querySelector(".loading-indicator");
1774
+ if (indicator) {
1775
+ indicator.style.display = isLoading ? "block" : "none";
1742
1776
  }
1777
+ }
1743
1778
 
1744
- /**
1745
- * Switch properties tab
1746
- */
1747
- switchTab(tabName) {
1748
- // Update tab buttons
1749
- document.querySelectorAll('.tab-btn').forEach(btn => {
1750
- btn.classList.toggle('active', btn.dataset.tab === tabName);
1751
- });
1779
+ /**
1780
+ * Switch properties tab
1781
+ */
1782
+ switchTab(tabName) {
1783
+ // Update tab buttons
1784
+ document.querySelectorAll(".tab-btn").forEach((btn) => {
1785
+ btn.classList.toggle("active", btn.dataset.tab === tabName);
1786
+ });
1752
1787
 
1753
- // Update tab content
1754
- document.querySelectorAll('.tab-content').forEach(content => {
1755
- content.classList.toggle('active', content.id === tabName + 'Tab');
1756
- });
1757
- }
1788
+ // Update tab content
1789
+ document.querySelectorAll(".tab-content").forEach((content) => {
1790
+ content.classList.toggle("active", content.id === tabName + "Tab");
1791
+ });
1792
+ }
1758
1793
  };
1759
1794
 
1760
1795
  // Export Manager - Handles JSON generation for SWT library
1761
1796
 
1762
1797
  let ExportManager$1 = class ExportManager {
1763
- constructor(editor) {
1764
- this.editor = editor;
1765
- }
1798
+ constructor(editor) {
1799
+ this.editor = editor;
1800
+ }
1766
1801
 
1767
- /**
1768
- * Generate JSON compatible with SWT library
1769
- */
1770
- generateJSON() {
1771
- const scenes = this.editor.sceneManager.getAllScenes();
1772
- const config = this.editor.config;
1773
- // Build scenes array
1774
- const scenesData = scenes.map(scene => ({
1775
- id: scene.id,
1776
- name: scene.name,
1777
- imageUrl: scene.imageUrl,
1778
- hotspots: scene.hotspots.map(hotspot => ({
1779
- id: hotspot.id,
1780
- type: hotspot.type || 'navigation',
1781
- position: hotspot.position,
1782
- targetSceneId: hotspot.targetSceneId || '',
1783
- title: hotspot.title || '',
1784
- description: hotspot.description || '',
1785
- color: hotspot.color || '#00ff00',
1786
- icon: hotspot.icon || ''
1787
- }))
1788
- }));
1789
- // Determine initial scene
1790
- let initialSceneId = config.initialSceneId;
1791
- if (!initialSceneId && scenes.length > 0) {
1792
- initialSceneId = scenes[0].id;
1793
- }
1802
+ /**
1803
+ * Generate JSON compatible with SWT library
1804
+ * Follows the tourConfig structure from example-simple.html
1805
+ */
1806
+ generateJSON() {
1807
+ const scenes = this.editor.sceneManager.getAllScenes();
1808
+ const config = this.editor.config;
1809
+
1810
+ // Build scenes object (keyed by scene ID)
1811
+ const scenesData = {};
1812
+ scenes.forEach((scene) => {
1813
+ scenesData[scene.id] = {
1814
+ name: scene.name,
1815
+ panorama: scene.imageUrl,
1816
+ hotspots: scene.hotspots.map((hotspot) => {
1817
+ const hotspotData = {
1818
+ position: hotspot.position,
1819
+ };
1820
+
1821
+ // Add action based on hotspot type
1822
+ if (hotspot.type === "navigation" && hotspot.targetSceneId) {
1823
+ hotspotData.action = {
1824
+ type: "navigateTo",
1825
+ target: hotspot.targetSceneId,
1826
+ };
1827
+ } else if (hotspot.type === "info") {
1828
+ hotspotData.action = {
1829
+ type: "showInfo",
1830
+ };
1831
+ }
1794
1832
 
1795
- // Build final JSON
1796
- const jsonData = {
1797
- title: config.title || 'Virtual Tour',
1798
- description: config.description || '',
1799
- initialSceneId: initialSceneId,
1800
- scenes: scenesData,
1801
- settings: {
1802
- autoRotate: config.autoRotate || false,
1803
- showCompass: config.showCompass || false
1804
- }
1805
- };
1833
+ // Add appearance
1834
+ hotspotData.appearance = {
1835
+ color: hotspot.color || "#FF6B6B",
1836
+ scale: "2 2 2",
1837
+ };
1838
+
1839
+ // Add tooltip if title exists
1840
+ if (hotspot.title) {
1841
+ hotspotData.tooltip = {
1842
+ text: hotspot.title,
1843
+ };
1844
+ }
1845
+
1846
+ return hotspotData;
1847
+ }),
1848
+ };
1849
+ });
1806
1850
 
1807
- return jsonData;
1851
+ // Determine initial scene
1852
+ let initialScene = config.initialSceneId;
1853
+ if (!initialScene && scenes.length > 0) {
1854
+ initialScene = scenes[0].id;
1808
1855
  }
1809
1856
 
1810
- /**
1811
- * Export as JSON file
1812
- */
1813
- exportJSON() {
1814
- try {
1815
- const jsonData = this.generateJSON();
1816
- const json = JSON.stringify(jsonData, null, 2);
1817
-
1818
- const filename = sanitizeId(jsonData.title || 'tour') + '.json';
1819
- downloadTextAsFile(json, filename);
1820
-
1821
- showToast('Tour exported successfully', 'success');
1822
- return true;
1823
- } catch (error) {
1824
- console.error('Export failed:', error);
1825
- showToast('Export failed', 'error');
1826
- return false;
1827
- }
1857
+ // Build final JSON matching SWT tourConfig format
1858
+ const jsonData = {
1859
+ initialScene: initialScene,
1860
+ scenes: scenesData,
1861
+ };
1862
+
1863
+ return jsonData;
1864
+ }
1865
+
1866
+ /**
1867
+ * Export as JSON file
1868
+ */
1869
+ exportJSON() {
1870
+ try {
1871
+ const jsonData = this.generateJSON();
1872
+ const json = JSON.stringify(jsonData, null, 2);
1873
+
1874
+ const title = this.editor.config.title || "tour";
1875
+ const filename = sanitizeId(title) + ".json";
1876
+ downloadTextAsFile(json, filename);
1877
+
1878
+ showToast("Tour exported successfully", "success");
1879
+ return true;
1880
+ } catch (error) {
1881
+ console.error("Export failed:", error);
1882
+ showToast("Export failed", "error");
1883
+ return false;
1828
1884
  }
1885
+ }
1829
1886
 
1830
- /**
1831
- * Copy JSON to clipboard
1832
- */
1833
- async copyJSON() {
1834
- try {
1835
- const jsonData = this.generateJSON();
1836
- const json = JSON.stringify(jsonData, null, 2);
1837
-
1838
- const success = await copyToClipboard(json);
1839
- if (success) {
1840
- showToast('JSON copied to clipboard', 'success');
1841
- } else {
1842
- showToast('Failed to copy to clipboard', 'error');
1843
- }
1844
- return success;
1845
- } catch (error) {
1846
- console.error('Copy failed:', error);
1847
- showToast('Copy failed', 'error');
1848
- return false;
1849
- }
1887
+ /**
1888
+ * Copy JSON to clipboard
1889
+ */
1890
+ async copyJSON() {
1891
+ try {
1892
+ const jsonData = this.generateJSON();
1893
+ const json = JSON.stringify(jsonData, null, 2);
1894
+
1895
+ const success = await copyToClipboard(json);
1896
+ if (success) {
1897
+ showToast("JSON copied to clipboard", "success");
1898
+ } else {
1899
+ showToast("Failed to copy to clipboard", "error");
1900
+ }
1901
+ return success;
1902
+ } catch (error) {
1903
+ console.error("Copy failed:", error);
1904
+ showToast("Copy failed", "error");
1905
+ return false;
1850
1906
  }
1907
+ }
1851
1908
 
1852
- /**
1853
- * Generate HTML viewer code
1854
- */
1855
- generateViewerHTML() {
1856
- const jsonData = this.generateJSON();
1857
-
1858
- return `<!DOCTYPE html>
1909
+ /**
1910
+ * Generate HTML viewer code
1911
+ */
1912
+ generateViewerHTML() {
1913
+ const jsonData = this.generateJSON();
1914
+ const title = this.editor.config.title || "Virtual Tour";
1915
+
1916
+ return `<!DOCTYPE html>
1859
1917
  <html lang="en">
1860
- <head>
1861
- <meta charset="UTF-8">
1862
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1863
- <title>${jsonData.title}</title>
1864
- <script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
1865
- <script src="dist/swt.min.js"></script>
1866
- <style>
1867
- body {
1868
- margin: 0;
1869
- overflow: hidden;
1870
- font-family: Arial, sans-serif;
1871
- }
1872
-
1873
- #loading {
1874
- position: fixed;
1875
- top: 0;
1876
- left: 0;
1877
- width: 100%;
1878
- height: 100%;
1879
- background: #000;
1880
- display: flex;
1881
- align-items: center;
1882
- justify-content: center;
1883
- color: #fff;
1884
- z-index: 1000;
1885
- }
1886
-
1887
- #loading.hidden {
1888
- display: none;
1889
- }
1890
-
1891
- .spinner {
1892
- border: 4px solid rgba(255,255,255,0.3);
1893
- border-top: 4px solid #fff;
1894
- border-radius: 50%;
1895
- width: 40px;
1896
- height: 40px;
1897
- animation: spin 1s linear infinite;
1898
- margin-right: 15px;
1899
- }
1900
-
1901
- @keyframes spin {
1902
- 0% { transform: rotate(0deg); }
1903
- 100% { transform: rotate(360deg); }
1904
- }
1905
-
1906
- #ui {
1907
- position: fixed;
1908
- bottom: 20px;
1909
- left: 50%;
1910
- transform: translateX(-50%);
1911
- z-index: 100;
1912
- display: flex;
1913
- gap: 10px;
1914
- }
1915
-
1916
- .btn {
1917
- background: rgba(0,0,0,0.7);
1918
- color: #fff;
1919
- border: none;
1920
- padding: 10px 20px;
1921
- border-radius: 5px;
1922
- cursor: pointer;
1923
- font-size: 14px;
1924
- }
1925
-
1926
- .btn:hover {
1927
- background: rgba(0,0,0,0.9);
1928
- }
1929
- </style>
1930
- </head>
1931
- <body>
1932
- <div id="loading">
1933
- <div class="spinner"></div>
1934
- <span>Loading Tour...</span>
1935
- </div>
1936
-
1937
- <div id="tour-container"></div>
1938
-
1939
- <div id="ui" style="display: none;">
1940
- <button class="btn" id="resetBtn">Reset View</button>
1941
- <span class="btn" id="sceneInfo"></span>
1942
- </div>
1918
+ <head>
1919
+ <meta charset="UTF-8" />
1920
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1921
+ <title>${title}</title>
1922
+ <script src="https://unpkg.com/senangwebs-tour@latest/dist/swt.min.js"></script>
1923
+ </head>
1924
+ <body>
1925
+ <a-scene id="tour-container">
1926
+ <a-camera>
1927
+ <a-cursor></a-cursor>
1928
+ </a-camera>
1929
+ </a-scene>
1943
1930
 
1944
1931
  <script>
1945
- // Tour configuration
1946
- const tourConfig = ${JSON.stringify(jsonData, null, 8)};
1947
-
1948
- // Initialize tour
1949
- let tour;
1950
-
1951
- document.addEventListener('DOMContentLoaded', async () => {
1952
- try {
1953
- // Create tour instance
1954
- tour = new SenangWebsTour('tour-container', tourConfig);
1955
-
1956
- // Listen to events
1957
- tour.on('sceneChanged', (sceneId) => {
1958
- updateSceneInfo();
1959
- });
1960
-
1961
- tour.on('ready', () => {
1962
- document.getElementById('loading').classList.add('hidden');
1963
- document.getElementById('ui').style.display = 'flex';
1964
- updateSceneInfo();
1965
- });
1966
-
1967
- tour.on('error', (error) => {
1968
- console.error('Tour error:', error);
1969
- alert('Failed to load tour: ' + error.message);
1970
- });
1971
-
1972
- // Start tour
1973
- await tour.start();
1974
-
1975
- // Setup UI
1976
- document.getElementById('resetBtn').addEventListener('click', () => {
1977
- const camera = document.querySelector('[camera]');
1978
- if (camera) {
1979
- camera.setAttribute('rotation', '0 0 0');
1980
- }
1981
- });
1982
-
1983
- } catch (error) {
1984
- console.error('Failed to initialize tour:', error);
1985
- alert('Failed to initialize tour: ' + error.message);
1986
- }
1987
- });
1988
-
1989
- function updateSceneInfo() {
1990
- const sceneId = tour.getCurrentSceneId();
1991
- const scene = tourConfig.scenes.find(s => s.id === sceneId);
1992
- if (scene) {
1993
- document.getElementById('sceneInfo').textContent = scene.name;
1994
- }
1995
- }
1932
+ // Tour configuration
1933
+ const tourConfig = ${JSON.stringify(jsonData, null, 8)
1934
+ .split("\n")
1935
+ .map((line, i) => (i === 0 ? line : " " + line))
1936
+ .join("\n")};
1937
+
1938
+ // Initialize tour when scene is loaded
1939
+ const sceneEl = document.querySelector("#tour-container");
1940
+ sceneEl.addEventListener("loaded", () => {
1941
+ const tour = new SWT.Tour(sceneEl, tourConfig);
1942
+ tour.start();
1943
+ });
1996
1944
  </script>
1997
- </body>
1945
+ </body>
1998
1946
  </html>`;
1999
- }
1947
+ }
2000
1948
 
2001
- /**
2002
- * Export as standalone HTML viewer
2003
- */
2004
- exportViewerHTML() {
2005
- try {
2006
- const html = this.generateViewerHTML();
2007
- const jsonData = this.generateJSON();
2008
- const filename = sanitizeId(jsonData.title || 'tour') + '-viewer.html';
2009
-
2010
- downloadTextAsFile(html, filename);
2011
-
2012
- showToast('Viewer HTML exported successfully', 'success');
2013
- return true;
2014
- } catch (error) {
2015
- console.error('Export viewer failed:', error);
2016
- showToast('Export viewer failed', 'error');
2017
- return false;
2018
- }
1949
+ /**
1950
+ * Export as standalone HTML viewer
1951
+ */
1952
+ exportViewerHTML() {
1953
+ try {
1954
+ const html = this.generateViewerHTML();
1955
+ const title = this.editor.config.title || "tour";
1956
+ const filename = sanitizeId(title) + "-viewer.html";
1957
+
1958
+ downloadTextAsFile(html, filename);
1959
+
1960
+ showToast("Viewer HTML exported successfully", "success");
1961
+ return true;
1962
+ } catch (error) {
1963
+ console.error("Export viewer failed:", error);
1964
+ showToast("Export viewer failed", "error");
1965
+ return false;
2019
1966
  }
1967
+ }
2020
1968
 
2021
- /**
2022
- * Show export preview in modal
2023
- */
2024
- showExportPreview() {
2025
- try {
2026
- const jsonData = this.generateJSON();
2027
- const json = JSON.stringify(jsonData, null, 2);
2028
-
2029
- const preview = document.getElementById('exportPreview');
2030
- if (preview) {
2031
- preview.textContent = json;
2032
- }
2033
-
2034
- showModal('exportModal');
2035
- return true;
2036
- } catch (error) {
2037
- console.error('Failed to show export preview:', error);
2038
- showToast('Failed to generate preview', 'error');
2039
- return false;
2040
- }
1969
+ /**
1970
+ * Show export preview in modal
1971
+ */
1972
+ showExportPreview() {
1973
+ try {
1974
+ const jsonData = this.generateJSON();
1975
+ const json = JSON.stringify(jsonData, null, 2);
1976
+
1977
+ const preview = document.getElementById("exportPreview");
1978
+ if (preview) {
1979
+ preview.textContent = json;
1980
+ }
1981
+
1982
+ showModal("exportModal");
1983
+ return true;
1984
+ } catch (error) {
1985
+ console.error("Failed to show export preview:", error);
1986
+ showToast("Failed to generate preview", "error");
1987
+ return false;
2041
1988
  }
1989
+ }
2042
1990
  };
2043
1991
 
2044
1992
  // Main Editor Controller