Denys Konovalov
9992e29ba0
All checks were successful
Marzipano Prod / Produktivumgebung (push) Successful in 1m2s
585 lines
19 KiB
JavaScript
585 lines
19 KiB
JavaScript
//@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":
|
|
'<svg xmlns="http://www.w3.org/2000/svg" id="mdi-chevron-up-circle-outline" viewBox="0 0 24 24" height="1em" width="1em" fill="white"><path d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M7.4,15.4L12,10.8L16.6,15.4L18,14L12,8L6,14L7.4,15.4Z" /></svg>',
|
|
"play-circle-outline":
|
|
'<svg xmlns="http://www.w3.org/2000/svg" id="mdi-play-circle-outline" viewBox="0 0 24 24" height="1em" width="1em" fill="white"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M10,16.5L16,12L10,7.5V16.5Z" /></svg>',
|
|
"information-outline":
|
|
'<svg xmlns="http://www.w3.org/2000/svg" id="mdi-information-outline" viewBox="0 0 24 24" height="1em" width="1em" fill="white"><path d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z" /></svg>',
|
|
"pause-circle-outline":
|
|
'<svg xmlns="http://www.w3.org/2000/svg" id="mdi-pause-circle-outline" viewBox="0 0 24 24" height="1em" width="1em" fill="white"><path d="M13,16V8H15V16H13M9,16V8H11V16H9M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></svg>',
|
|
};
|
|
|
|
// 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);
|