quackscape/assets/js/scene.js
Kumi e9e8719a93
feat: improve sidebar UX with toggle functionality
Introduced new styles for the sidebar's open and close buttons to
enhance visibility and user interaction. Updated the sidebar logic in
the JavaScript to include a toggle functionality, allowing users to
easily open and close the sidebar. This improves the user experience by
making navigation more intuitive and the UI cleaner. The 'Close Sidebar'
button now directly closes the sidebar, and a new 'Open Sidebar' button
appears when the sidebar is closed, providing a clear action cue to the
users.
2024-03-16 07:58:14 +01:00

300 lines
9 KiB
JavaScript

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 <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("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 };