//@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);