Kumi
e3cff5b9b7
Introduced a new CSS file for styling a fixed navbar in scenes, enhancing user navigation within the virtual environment. Modified scene.js to include fetching categories and generating a navbar with scene options dynamically based on the category of the current scene. This allows users to switch scenes more intuitively. The `nonavbar` attribute was added for scenes where the navbar should be omitted, providing flexibility in scene presentation. Additionally, streamlined CSS and SCSS imports across JS files for consistency and removed an unnecessary SCSS import from editor.js, optimizing load times and project structure. This update significantly improves user experience by facilitating easier navigation and scene exploration within the application.
216 lines
6.5 KiB
JavaScript
216 lines
6.5 KiB
JavaScript
import { getScene, getCategory } from "./api";
|
|
import { Dropdown } 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 <quackscape-scene> 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("nonavbar") == undefined;
|
|
|
|
loadScene(this.scene, this.x, this.y, this.z, this, this.embedded);
|
|
|
|
if (this.navbar) {
|
|
loadNavbar(this.scene, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
document.addEventListener("contextmenu", function (event) {
|
|
event.preventDefault();
|
|
});
|
|
|
|
customElements.define("quackscape-scene", QuackscapeScene);
|
|
|
|
// 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);
|
|
});
|
|
}
|
|
|
|
window.loadScene = loadScene;
|
|
|
|
export { loadScene };
|