//@ts-check /* * Copyright 2016 Google Inc, 2020 Georg-Cantor-Gymnasium Halle (Saale), All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ "use strict"; import APP_DATA from "./data"; (($) => { $(window).on("load", () => { $(".preloader").fadeOut(100); }); // @ts-ignore var Marzipano = window.Marzipano; // mdi icons var svgs = { "chevron-up-circle-outline": '', "play-circle-outline": '', "information-outline": '', "pause-circle-outline": '', }; // Grab elements from DOM. var panoElement = document.querySelector("#pano"); var sceneNameElement = document.querySelector("#titleBar .sceneName"); var sceneListElement = document.querySelector("#sceneList"); var sceneElements = document.querySelectorAll("#sceneList .scene"); var sceneListToggleElement = document.querySelector("#sceneListToggle"); var autorotateToggleElement = document.querySelector("#autorotateToggle"); var fullscreenToggleElement = document.querySelector("#fullscreenToggle"); // Detect desktop or mobile mode. const setMode = () => { if (mql.matches) { document.body.classList.remove("desktop"); document.body.classList.add("mobile"); } else { document.body.classList.remove("mobile"); document.body.classList.add("desktop"); } }; var mql = matchMedia("(max-width: 768px), (max-height: 500px)"); setMode(); mql.onchange = setMode; // Viewer options. var viewerOpts = { controls: { mouseViewMode: APP_DATA.settings.mouseViewMode, }, }; // Initialize viewer. var viewer = new Marzipano.Viewer(panoElement, viewerOpts); // create list for audio elements var audioList = []; // Create scenes. var scenes = APP_DATA.scenes.map((data) => { var urlPrefix = "tiles"; var source = Marzipano.ImageUrlSource.fromString(urlPrefix + "/" + data.id + "/{z}/{f}/{y}/{x}.jpg", { cubeMapPreviewUrl: urlPrefix + "/" + data.id + "/preview.jpg", }); var geometry = new Marzipano.CubeGeometry(data.levels); var limiter = Marzipano.RectilinearView.limit.traditional( data.faceSize, (100 * Math.PI) / 180, (120 * Math.PI) / 180 ); var view = new Marzipano.RectilinearView(data.initialViewParameters, limiter); var scene = viewer.createScene({ source: source, geometry: geometry, view: view, pinFirstLevel: true, }); // Create link hotspots. data.linkHotspots.forEach(function (hotspot) { var element = createLinkHotspotElement(hotspot); scene.hotspotContainer().createHotspot(element, { yaw: hotspot.yaw, pitch: hotspot.pitch }); }); // Create info hotspots. data.infoHotspots.forEach(function (hotspot) { var element = createInfoHotspotElement(hotspot, data.id); scene.hotspotContainer().createHotspot(element, { yaw: hotspot.yaw, pitch: hotspot.pitch }); }); return { data: data, scene: scene, view: view, }; }); // Set up autorotate, if enabled. var autorotate = Marzipano.autorotate({ yawSpeed: 0.03, targetPitch: 0, targetFov: Math.PI / 2, }); if (APP_DATA.settings.autorotateEnabled) { autorotateToggleElement?.classList.add("enabled"); } // Start with the scene list open on desktop. if (!document.body.classList.contains("mobile")) { showSceneList(); } // Set handler for autorotate toggle. autorotateToggleElement?.addEventListener("click", toggleAutorotate); // Set up fullscreen mode if (APP_DATA.settings.fullscreenButton) { document.body.classList.add("fullscreen-enabled"); fullscreenToggleElement?.addEventListener("click", function () { if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); } else if (document.exitFullscreen) { document.exitFullscreen(); } }); document.addEventListener("fullscreenchange", () => { if (document.fullscreenElement) { fullscreenToggleElement?.classList.add("enabled"); } else { fullscreenToggleElement?.classList.remove("enabled"); } }); } else { document.body.classList.add("fullscreen-disabled"); } // Set handler for scene list toggle. sceneListToggleElement?.addEventListener("click", toggleSceneList); // Set handler for scene switch. scenes.forEach(function (scene) { var el = document.querySelector('#sceneList .scene[data-id="' + scene.data.id + '"]'); el?.addEventListener("click", function () { switchScene(scene); showSceneList(); // On mobile, hide scene list after selecting a scene. if (document.body.classList.contains("mobile")) { hideSceneList(); } }); }); // DOM elements for view controls. var viewUpElement = document.querySelector("#viewUp"); var viewDownElement = document.querySelector("#viewDown"); var viewLeftElement = document.querySelector("#viewLeft"); var viewRightElement = document.querySelector("#viewRight"); var viewInElement = document.querySelector("#viewIn"); var viewOutElement = document.querySelector("#viewOut"); // Dynamic parameters for controls. var velocity = 0.7; var friction = 3; // Associate view controls with elements. var controls = viewer.controls(); controls.registerMethod( "upElement", new Marzipano.ElementPressControlMethod(viewUpElement, "y", -velocity, friction), true ); controls.registerMethod( "downElement", new Marzipano.ElementPressControlMethod(viewDownElement, "y", velocity, friction), true ); controls.registerMethod( "leftElement", new Marzipano.ElementPressControlMethod(viewLeftElement, "x", -velocity, friction), true ); controls.registerMethod( "rightElement", new Marzipano.ElementPressControlMethod(viewRightElement, "x", velocity, friction), true ); controls.registerMethod( "inElement", new Marzipano.ElementPressControlMethod(viewInElement, "zoom", -velocity, friction), true ); controls.registerMethod( "outElement", new Marzipano.ElementPressControlMethod(viewOutElement, "zoom", velocity, friction), true ); function sanitize(s) { return s.replace("&", "&").replace("<", "<").replace(">", ">"); } function switchScene(scene) { stopPlayback(); stopAutorotate(); scene.view.setParameters(scene.data.initialViewParameters); scene.scene.switchTo(); startAutorotate(); updateSceneName(scene); updateSceneList(scene); } function updateSceneName(scene) { if (sceneNameElement) { sceneNameElement.innerHTML = sanitize(scene.data.name); } } function updateSceneList(scene) { for (var i = 0; i < sceneElements.length; i++) { var el = sceneElements[i]; if (el.getAttribute("data-id") === scene.data.id) { el.classList.add("current"); } else { el.classList.remove("current"); } } } function showSceneList() { sceneListElement?.classList.add("enabled"); sceneListToggleElement?.classList.add("enabled"); } function hideSceneList() { sceneListElement?.classList.remove("enabled"); sceneListToggleElement?.classList.remove("enabled"); } function toggleSceneList() { sceneListElement?.classList.toggle("enabled"); sceneListToggleElement?.classList.toggle("enabled"); } function startAutorotate() { if (!autorotateToggleElement?.classList.contains("enabled")) { return; } viewer.startMovement(autorotate); viewer.setIdleMovement(3000, autorotate); } function stopAutorotate() { viewer.stopMovement(); viewer.setIdleMovement(Infinity); } function stopPlayback() { audioList.forEach((a) => { a.pause(); a.currentTime = 0; }); const x = document.querySelectorAll("video"); for (let i = 0; i < x.length; i++) { if (!x[i].paused) { x[i].pause(); } } } function toggleAutorotate() { if (autorotateToggleElement?.classList.contains("enabled")) { autorotateToggleElement?.classList.remove("enabled"); stopAutorotate(); } else { autorotateToggleElement?.classList.add("enabled"); startAutorotate(); } } function createLinkHotspotElement(hotspot) { // Create wrapper element to hold icon and tooltip. var wrapper = document.createElement("div"); wrapper.classList.add("hotspot"); wrapper.classList.add("link-hotspot"); // Create image element. const icon = document.createElement("span"); icon.innerHTML = svgs["chevron-up-circle-outline"]; // Set rotation transform. var transformProperties = ["-webkit-transform", "transform"]; for (var i = 0; i < transformProperties.length; i++) { var property = transformProperties[i]; icon.style[property] = "rotate(" + hotspot.rotation + "rad)"; } // Add click event handler. wrapper.addEventListener("click", function () { switchScene(findSceneById(hotspot.target)); }); // Prevent touch and scroll events from reaching the parent element. // This prevents the view control logic from interfering with the hotspot. stopTouchAndScrollEventPropagation(wrapper); // Create tooltip element. var tooltip = document.createElement("div"); tooltip.classList.add("hotspot-tooltip"); tooltip.classList.add("link-hotspot-tooltip"); tooltip.innerHTML = findSceneDataById(hotspot.target)?.name ?? ""; wrapper.appendChild(icon); wrapper.appendChild(tooltip); return wrapper; } /** * * @param {*} hotspot * @param {string} sceneId * @returns {HTMLDivElement} */ function createInfoHotspotElement(hotspot, sceneId) { // Create wrapper element to hold icon and tooltip. var wrapper = document.createElement("div"); wrapper.classList.add("hotspot"); wrapper.classList.add("info-hotspot"); // Create hotspot/tooltip header. var header = document.createElement("div"); header.classList.add("info-hotspot-header"); // Create image element. var iconWrapper = document.createElement("div"); iconWrapper.classList.add("info-hotspot-icon-wrapper"); // Create title element. var title = document.createElement("div"); title.classList.add("info-hotspot-title"); title.innerHTML = hotspot.title; // Create close element. var closeWrapper = document.createElement("div"); closeWrapper.classList.add("info-hotspot-close-wrapper"); var closeIcon = document.createElement("i"); closeIcon.classList.add("info-hotspot-close-icon", "fa-solid", "fa-circle-xmark"); closeWrapper.appendChild(closeIcon); // Construct header element. header.appendChild(iconWrapper); header.appendChild(title); // Place header and text into wrapper element. wrapper.appendChild(header); var toggle; switch (hotspot.type) { case "audio": case "audio-room": { iconWrapper.innerHTML = svgs["play-circle-outline"]; if (hotspot.type == "audio-room") { wrapper.classList.add("audio-room"); } const player = new Audio(); player.preload = "none"; player.src = hotspot.src; player.addEventListener("play", () => { iconWrapper.innerHTML = svgs["pause-circle-outline"]; }); player.addEventListener("pause", () => { iconWrapper.innerHTML = svgs["play-circle-outline"]; }); player.addEventListener("ended", () => { iconWrapper.innerHTML = svgs["play-circle-outline"]; }); audioList.push(player); toggle = () => { if (player.paused) { player.play(); } else { player.pause(); } }; break; } case "title": iconWrapper.innerHTML = svgs["information-outline"]; toggle = () => {}; break; default: iconWrapper.innerHTML = svgs["information-outline"]; const modal = document.createElement("div"); modal.classList.add("modal"); const modalDialog = document.createElement("div"); modalDialog.classList.add("modal-dialog", "modal-dialog-centered", "modal-lg"); const modalContent = document.createElement("div"); modalContent.classList.add("modal-content"); const modalHeader = document.createElement("div"); modalHeader.classList.add("modal-header"); const modalTitle = document.createElement("h5"); modalTitle.classList.add("modal-title"); modalTitle.innerHTML = hotspot.title; modalHeader.appendChild(modalTitle); const buttonClose = document.createElement("button"); buttonClose.type = "button"; buttonClose.classList.add("btn-close"); buttonClose.setAttribute("data-bs-dismiss", "modal"); modalHeader.appendChild(buttonClose); modalContent.appendChild(modalHeader); switch (hotspot.type) { case "iframe": var iframe = document.createElement("iframe"); modalContent.appendChild(iframe); break; case "video": var video = document.createElement("video"); video.controls = true; video.classList.add("video-js", "vjs-fluid", "w-100"); modalContent.appendChild(video); break; case "image": var image = document.createElement("img"); modalContent.appendChild(image); break; default: var modalBody = document.createElement("div"); modalBody.classList.add("modal-body"); modalContent.appendChild(modalBody); } modalDialog.appendChild(modalContent); modal.appendChild(modalDialog); document.body.appendChild(modal); // @ts-ignore const bsModal = new bootstrap.Modal(modal); var videoJS = () => modalContent.querySelectorAll("video.video-js").forEach((video) => { // @ts-ignore videojs(video, {}); }); toggle = () => { switch (hotspot.type) { case "iframe": if (iframe.src == "") iframe.src = hotspot.src; break; case "video": if (video.src == "") video.src = hotspot.src; // @ts-ignore videojs(video, {}); break; case "image": if (image.src == "") image.src = hotspot.src; break; case "embed": if (modalBody.innerHTML == "") { fetch(`/tour/content/${sceneId}/${hotspot.name}/index.html`) .then((response) => response.text()) .then((text) => { modalBody.innerHTML = text; // @ts-ignore new VenoBox({ selector: ".vb-gallery", numeration: true, infinigall: true, share: true, shareStyle: "block", spinner: "grid", fitView: true, navTouch: true, }); videoJS(); }); } break; default: if (modalBody.innerHTML == "") modalBody.innerHTML = hotspot.text; videoJS(); break; } stopPlayback(); bsModal.show(); }; } // Show content when hotspot is clicked. wrapper.querySelector(".info-hotspot-header")?.addEventListener("click", toggle); // Prevent touch and scroll events from reaching the parent element. // This prevents the view control logic from interfering with the hotspot. stopTouchAndScrollEventPropagation(wrapper); return wrapper; } // Prevent touch and scroll events from reaching the parent element. function stopTouchAndScrollEventPropagation(element) { var eventList = ["touchstart", "touchmove", "touchend", "touchcancel", "wheel", "mousewheel"]; for (var i = 0; i < eventList.length; i++) { element.addEventListener(eventList[i], function (event) { event.stopPropagation(); }); } } function findSceneById(id) { for (var i = 0; i < scenes.length; i++) { if (scenes[i].data.id === id) { return scenes[i]; } } return null; } function findSceneDataById(id) { for (var i = 0; i < APP_DATA.scenes.length; i++) { if (APP_DATA.scenes[i].id === id) { return APP_DATA.scenes[i]; } } return null; } // Display the initial scene. switchScene(scenes[0]); // enable matomo analytics // @ts-ignore var _paq = (window._paq = window._paq || []); _paq.push(["setDoNotTrack", true]); _paq.push(["disableCookies"]); _paq.push(["trackPageView"]); _paq.push(["enableLinkTracking"]); (function () { var u = "//analytics.cantorgymnasium.de/"; _paq.push(["setTrackerUrl", u + "matomo.php"]); _paq.push(["setSiteId", "2"]); var d = document, g = d.createElement("script"), s = d.getElementsByTagName("script")[0]; g.async = true; g.src = u + "matomo.js"; // @ts-ignore s.parentNode.insertBefore(g, s); })(); // show introduction modal const modal = document.getElementById("modal-intro"); // @ts-ignore new bootstrap.Modal(modal).show(); // @ts-ignore })(jQuery);