aframe icon indicating copy to clipboard operation
aframe copied to clipboard

How to effeciently Store/Load the entire structure of A-Scene into/from the DB?

Open hazho opened this issue 5 months ago • 3 comments

I have tried my best to store and load the same structure that I visually see into the DB, but the component's schema keys and values are not being included, also, even if I store the HTML content into the DB, when loaded, most of the entities' component values lost, the following are simplified examples tried with:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>A-Frame Scene Saver</title>
    <script src="https://aframe.io/releases/1.7.1/aframe.min.js"></script>
</head>
<body>
		<div class="html">
			<h2>Scene Management</h2>
			<button onclick="createScene()">Add a Scene</button>
			<select id="sceneSelect">
					<option value="">Select a saved scene...</option>
			</select>
			<button onclick="loadScene()">Load Scene</button>
			<button onclick="saveScene()">Save Scene</button>
			<input type="text" id="sceneName" placeholder="Enter scene name to save">
		</div>
    <div id="sceneContainer"></div>

    <script>
        document.addEventListener("DOMContentLoaded", populateScenes);

        function createScene() {
            const sceneContainer = document.getElementById("sceneContainer");
            if (!document.querySelector("a-scene")) {
                sceneContainer.innerHTML = `
                    <a-scene>
                        <a-entity camera look-controls position="0 1.6 0"></a-entity>
                    </a-scene>
                `;
            }
        }

        function populateScenes() {
            const sceneSelect = document.getElementById("sceneSelect");
            const scenes = JSON.parse(localStorage.getItem("scenes") || "{}");

            sceneSelect.innerHTML = '<option value="">Select a saved scene...</option>';
            for (let name in scenes) {
                let option = document.createElement("option");
                option.value = name;
                option.textContent = name;
                sceneSelect.appendChild(option);
            }
        }

        function saveScene() {
            const sceneElement = document.querySelector("a-scene");
            const sceneName = document.getElementById("sceneName").value.trim();
            if (!sceneElement || !sceneName) return alert("No scene or name provided!");

            let scenes = JSON.parse(localStorage.getItem("scenes") || "{}");
            scenes[sceneName] = sceneElement.innerHTML;
            localStorage.setItem("scenes", JSON.stringify(scenes));

            populateScenes();
            alert("Scene saved!");
        }

        function loadScene() {
            const sceneName = document.getElementById("sceneSelect").value;
            if (!sceneName) return alert("Select a scene!");

            const scenes = JSON.parse(localStorage.getItem("scenes") || "{}");
            const sceneContent = scenes[sceneName];

            if (sceneContent) {
                const sceneContainer = document.getElementById("sceneContainer");
                sceneContainer.innerHTML = `<a-scene>${sceneContent}</a-scene>`;
            }
        }
    </script>
    <style>.html{z-index:999999999999;position:fixed;left:40%}</style>
</body>
</html>

and

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>A-Frame Scene Saver</title>
  <script src="https://aframe.io/releases/1.7.1/aframe.min.js"></script>
  <style>
    #controls {
      position: fixed;
      top: 10px;
      left: 10px;
      z-index: 1000;
      background: white;
      padding: 10px;
      border-radius: 5px;
    }
  </style>
</head>
<body>

<div id="controls">
  <label for="sceneSelector">Choose Scene:</label>
  <select id="sceneSelector"></select>
  <button onclick="saveCurrentScene()">Save Current Scene</button>
</div>

<a-scene id="mainScene">
  <!-- Default scene -->
  <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
  <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
  <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
  <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
  <a-sky color="#ECECEC"></a-sky>
</a-scene>

<script>
  const DB_NAME = "AFRAME_SCENE_DB";
  const STORE_NAME = "scenes";

  let db;

  // Open IndexedDB
  function openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, 1);
      request.onupgradeneeded = function (event) {
        db = event.target.result;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          db.createObjectStore(STORE_NAME, { keyPath: "name" });
        }
      };
      request.onsuccess = function (event) {
        db = event.target.result;
        resolve(db);
      };
      request.onerror = function (event) {
        reject("Error opening IndexedDB");
      };
    });
  }

  // Save scene to IndexedDB
  function saveScene(name, sceneHTML) {
    const transaction = db.transaction([STORE_NAME], "readwrite");
    const store = transaction.objectStore(STORE_NAME);
    store.put({ name, sceneHTML });
  }

  // Load all scenes from IndexedDB
  function loadScenes() {
    const selector = document.getElementById("sceneSelector");
    const transaction = db.transaction([STORE_NAME], "readonly");
    const store = transaction.objectStore(STORE_NAME);
    const getAllRequest = store.getAll();

    getAllRequest.onsuccess = function () {
      const scenes = getAllRequest.result;
      selector.innerHTML = ""; // Clear existing options

      // Add default option
      let defaultOption = document.createElement("option");
      defaultOption.value = "default";
      defaultOption.text = "Default Scene";
      selector.appendChild(defaultOption);

      scenes.forEach((scene) => {
        let option = document.createElement("option");
        option.value = scene.name;
        option.text = scene.name;
        selector.appendChild(option);
      });
    };
  }

  // Load a specific scene by name
  function loadSceneByName(name) {
		const mainScene = document.getElementById("mainScene");

		if (name === "default") {
			const defaultHTML = getDefaultSceneHTML();
			replaceSceneHTML(mainScene, defaultHTML);
			return;
		}

		const transaction = db.transaction([STORE_NAME], "readonly");
		const store = transaction.objectStore(STORE_NAME);
		const getRequest = store.get(name);

		getRequest.onsuccess = function () {
			const result = getRequest.result;
			if (result) {
				replaceSceneHTML(mainScene, result.sceneHTML);
			}
		};
	}
  // Get default scene HTML as string (only children)
  function getDefaultSceneHTML() {
    return `
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    `;
  }

  // Event listener for select
  document.getElementById("sceneSelector").addEventListener("change", function (e) {
    loadSceneByName(e.target.value);
  });

  // Save current scene
  window.saveCurrentScene = function () {
    const sceneName = prompt("Enter scene name:");
    if (!sceneName) return;

    const mainScene = document.getElementById("mainScene");

    // Only save child elements, not the <a-scene> itself
    const sceneHTML = Array.from(mainScene.children)
      .map(el => el.outerHTML)
      .join("");

    saveScene(sceneName, sceneHTML);
    loadScenes(); // Refresh selector
  };

  // Initialize
  openDB().then(() => {
    loadScenes();
  });
	function replaceSceneHTML(sceneEl, htmlString) {
		// Clear existing scene content
		while (sceneEl.firstChild) {
			sceneEl.removeChild(sceneEl.firstChild);
		}

		// Convert HTML string to DOM elements
		const div = document.createElement('div');
		div.innerHTML = htmlString.trim();

		// Append each child to <a-scene>
		Array.from(div.children).forEach(child => {
			sceneEl.appendChild(child);
		});
	}
</script>

</body>
</html>

hazho avatar May 06 '25 13:05 hazho