import { getScene, getCategory } from "./api"; import { Dropdown, Collapse } from "bootstrap"; import "../scss/frontend.scss"; import "../css/scene.css"; require("aframe"); // Detect iOS devices // There is probably a better way to handle issues there, but... var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent); // Define the element class QuackscapeScene extends HTMLElement { connectedCallback() { // When one is created, automatically load the scene this.scene = this.getAttribute("scene"); this.embedded = this.getAttribute("embedded") != undefined; this.x = this.getAttribute("x") | 0; this.y = this.getAttribute("y") | 0; this.z = this.getAttribute("z") | 0; this.navbar = this.getAttribute("navbar") != undefined; this.sidebar = this.getAttribute("sidebar") != undefined; loadScene(this.scene, this.x, this.y, this.z, this, this.embedded); if (this.navbar) { loadNavbar(this.scene, this); } if (this.sidebar) { loadSidebar(this.scene, this); } } } document.addEventListener("contextmenu", function (event) { event.preventDefault(); }); customElements.define("quackscape-scene", QuackscapeScene); // Function to add a sidebar to the destination object async function loadSidebar(scene_id, destination) { getScene(scene_id).then((scene) => { getCategory(scene.obj.category).then((category) => { var sidebar = document.createElement("div"); sidebar.setAttribute("class", "collapse show"); sidebar.setAttribute("id", "sceneSidebar"); var p = document.createElement("div"); p.setAttribute("class", "p-4"); var h5 = document.createElement("h5"); h5.setAttribute("class", "font-weight-bold"); h5.textContent = "Scenes"; var ul = document.createElement("ul"); ul.setAttribute("class", "nav flex-column"); category.obj.scenes.forEach((scene) => { var li = document.createElement("li"); li.setAttribute("class", "nav-item"); var a = document.createElement("a"); a.setAttribute("href", "#"); a.setAttribute("class", "nav-link"); a.textContent = scene.title; a.addEventListener("click", function () { loadScene(scene.id, -1, -1, -1, destination); }); li.appendChild(a); ul.appendChild(li); }); p.appendChild(h5); p.appendChild(ul); var button = document.createElement("button"); button.setAttribute("class", "btn-close-sidebar"); button.setAttribute("type", "button"); button.setAttribute("id", "btnCloseSidebar"); button.textContent = "Close Sidebar"; button.addEventListener("click", function () { toggleSidebar(); }); sidebar.appendChild(button); // TODO: Make it prettier before enabling. sidebar.appendChild(p); var reopenButton = document.createElement("button"); reopenButton.setAttribute("class", "btn-open-sidebar hide"); reopenButton.textContent = "Open Sidebar"; reopenButton.addEventListener("click", function () { toggleSidebar(); }); destination.prepend(reopenButton); destination.prepend(sidebar); }); }); } // Function to add a navbar to the destination object async function loadNavbar(scene_id, destination) { getScene(scene_id).then((scene) => { getCategory(scene.obj.category).then((category) => { var nav = document.createElement("nav"); nav.setAttribute("class", "navbar navbar-expand-lg navbar-dark bg-dark"); nav.setAttribute("id", "sceneNavbar"); var container = document.createElement("div"); container.setAttribute("class", "container-fluid"); var dropdown = document.createElement("div"); dropdown.setAttribute("class", "dropdown"); var sceneSelector = document.createElement("button"); sceneSelector.setAttribute("class", "btn btn-secondary dropdown-toggle"); sceneSelector.setAttribute("type", "button"); sceneSelector.setAttribute("id", "sceneSelector"); sceneSelector.setAttribute("data-bs-toggle", "dropdown"); sceneSelector.setAttribute("aria-expanded", "true"); sceneSelector.textContent = "Select Scene"; var dropdownMenu = document.createElement("ul"); dropdownMenu.setAttribute("class", "dropdown-menu show"); dropdownMenu.setAttribute("aria-labelledby", "sceneSelector"); console.log(dropdownMenu); category.obj.scenes.forEach((scene) => { console.log(scene); var sceneLink = document.createElement("a"); sceneLink.setAttribute("class", "dropdown-item"); sceneLink.setAttribute("href", "#"); sceneLink.textContent = scene.title; sceneLink.addEventListener("click", function () { loadScene(scene.id, -1, -1, -1, destination); }); dropdownMenu.appendChild(sceneLink); }); var sceneTitle = document.createElement("a"); sceneTitle.setAttribute("class", "navbar-brand"); sceneTitle.setAttribute("href", "#"); sceneTitle.setAttribute("id", "sceneTitle"); sceneTitle.textContent = scene.obj.title; dropdown.appendChild(sceneSelector); dropdown.appendChild(dropdownMenu); console.log(dropdownMenu); container.appendChild(dropdown); container.appendChild(sceneTitle); nav.appendChild(container); destination.prepend(nav); }); }); } // Function to load a scene into a destination object // x and y signify the initial looking direction, -1 for the scene's default async function loadScene( scene_id, x = -1, y = -1, z = -1, destination = null, embedded = false ) { // Get WebGL maximum texture size var canvas = document.createElement("canvas"); var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); if (!gl) { alert("Unable to initialize WebGL. Your browser may not support it."); return; } var maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); if (iOS) { maxTextureSize = Math.min(8192, maxTextureSize); } // Get scene information from API getScene(scene_id).then((response) => { var scene = response.obj; // Get largest image that will fit in the texture size from scene.resolutions // First, order the resolutions by width var resolutions = scene.base_content.resolutions.sort( (a, b) => b.width - a.width ); // Then, find the first resolution that is less than or equal to the max texture size var content = resolutions.find( (resolution) => resolution.width <= maxTextureSize ); // Select a destination element if not specified if (!destination) { destination = document.getElementsByTagName("quackscape-scene")[0]; } destination.setAttribute("id", scene_id); // Start by removing any existing scenes var scenes = document.getElementsByTagName("a-scene"); if (scenes.length > 0) { scenes[0].remove(); } // Now, build the scene element var a_scene = document.createElement("a-scene"); a_scene.setAttribute("cursor", "rayOrigin: mouse"); if (embedded) { a_scene.setAttribute("embedded", "embedded"); } // Create a-camera element var rig = document.createElement("a-entity"); rig.setAttribute("id", "rig"); // Rotate camera if requested if (x != -1 && y != -1 && z != -1) { rig.setAttribute("rotation", x + " " + y + " " + z); } var camera = document.createElement("a-camera"); camera.setAttribute("wasd-controls-enabled", "false"); // TODO: Find a more elegant way to disable z axis movement rig.appendChild(camera); a_scene.appendChild(rig); // Create a-assets element var assets = document.createElement("a-assets"); a_scene.appendChild(assets); // Add background image as sky var sky = document.createElement("a-sky"); sky.setAttribute("src", "#" + content.id); a_scene.appendChild(sky); // Add img element to assets var background = document.createElement("img"); background.setAttribute("id", content.id); background.setAttribute("src", content.file); assets.appendChild(background); // Add elements to scene scene.elements.forEach((element) => { var node = document.createElement(element.data.tag); node.setAttribute("id", element.data.id); for (const [key, value] of Object.entries(element.data.attributes)) { node.setAttribute(key, value); } // Add node to scene a_scene.appendChild(node); }); destination.appendChild(a_scene); // Dispatch a signal for the editor to pick up const loaded_event = new CustomEvent("loadedQuackscapeScene"); document.dispatchEvent(loaded_event); }); } function toggleSidebar() { const sidebar = document.getElementById("sceneSidebar"); const reopenButton = document.querySelector(".btn-open-sidebar"); if (!sidebar.classList.contains("show")) { sidebar.classList.add("show"); reopenButton.classList.add("hide"); } else { sidebar.classList.remove("show"); reopenButton.classList.remove("hide"); } } window.loadScene = loadScene; export { loadScene };