UNPKG

@tindtechnologies/universalviewer

Version:

The Universal Viewer is a community-developed open source project on a mission to help you share your 📚📜📰📽️📻🗿 with the 🌎

827 lines (729 loc) 26.8 kB
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>Universal Viewer Examples</title> <!-- https://tailwindcss.com/docs/installation/play-cdn --> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com?plugins=forms"></script> <script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script> <!-- <script src="https://unpkg.com/@universalviewer/aleph@0.0.15/dist/collection/assets/aframe-1.0.3.min.js"></script> <script src="https://unpkg.com/@universalviewer/aleph@0.0.15/dist/collection/assets/ami.min.js"></script> --> <script type="text/javascript" src="umd/UV.js"></script> <style> /** First override so that desktop always visible */ /* #uv { width: 924px; height: 668px; --uv-mobile-display: none; --uv-mobile-visibility: hidden; --uv-desktop-display: block; --uv-desktop-visibility: visible; } */ /** Then set custom point to enable mobile / hide desktop */ /* @media (max-width: 340px) { #uv { --uv-mobile-display: block; --uv-mobile-visibility: visible; --uv-desktop-display: none; --uv-desktop-visibility: hidden; width: 100%; } } */ /* todo: replace with dynamic viewport units when available: https://caniuse.com/?search=svh */ @media only screen and (max-width: 768px) { .uv { /* https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ */ height: 100vh; /* Fallback for browsers that do not support Custom Properties */ height: calc(var(--vh, 1vh) * 100); } } </style> <style type="text/tailwindcss"> @layer utilities { .controls-button { @apply py-2 px-4 bg-black text-white whitespace-nowrap min-w-[10rem]; } .controls-group { @apply py-2; } .controls-title { @apply text-lg font-semibold pb-3; } .controls-tab { @apply bg-none px-6 py-3 font-medium text-xl; } .controls-tab.active { @apply bg-gray-200; } .controls-tab-content { @apply bg-gray-200 p-6; } } </style> <style type="text/tailwindcss"> @layer utilities { .controls-button { @apply py-2 px-4 bg-black text-white whitespace-nowrap min-w-[10rem]; } .controls-group { @apply py-2; } .controls-title { @apply text-lg font-semibold pb-3; } .controls-tab { @apply bg-none px-6 py-3 font-medium text-xl; } .controls-tab.active { @apply bg-gray-200; } .controls-tab-content { @apply bg-gray-200 p-6; } } </style> <!-- https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ --> <script> // First we get the viewport height and we multiple it by 1% to get a value for a vh unit let vh = window.innerHeight * 0.01; // Then we set the value in the --vh custom property to the root of the document document.documentElement.style.setProperty("--vh", `${vh}px`); window.addEventListener("resize", () => { // We execute the same script as before let vh = window.innerHeight * 0.01; document.documentElement.style.setProperty("--vh", `${vh}px`); }); </script> </head> <body> <div id="uv" class="uv w-full md:w-[90vw] lg:w-[65vw] md:mx-auto md:h-[80vh] md:mt-4" ></div> <div id="uv-controls" class="hidden md:!block md:w-[90vw] lg:w-[65vw] mx-auto my-8" > <div> <ul id="annotation-list"></ul> </div> <button id="iiifTabButton" class="controls-tab active"> IIIF </button> <button id="youTubeTabButton" class="controls-tab">YouTube</button> <div id="iiifTab" class="controls-tab-content"> <div class="controls-group"> <h2 class="controls-title">Manifest Id</h2> <div class="flex"> <select id="iiifManifestIdSelect" class="py-2 px-3 mr-2 flex-1" ></select> <input id="iiifManifestId" type="text" value="" class="flex-1" /> </div> <div class="mt-2"> <button id="setIIIFManifestIdButton" class="controls-button" href="#" > Set IIIF Manifest Id </button> </div> </div> <div class="controls-group"> <h2 class="controls-title">Target</h2> <div class="flex"> <input id="target" type="text" value="" class="flex-1" /> <button id="setTargetButton" class="controls-button flex-initial ml-2" > Set Target </button> </div> </div> <div class="controls-group"> <h2 class="controls-title">Rotation</h2> <div class="flex"> <input id="rotation" type="text" value="" class="flex-1" /> <button id="setRotationButton" class="controls-button flex-initial ml-2" > Set Rotation </button> </div> </div> <div class="controls-group"> <h2 class="controls-title">Annotations</h2> <textarea id="annotations" rows="5" class="w-full"></textarea> <div class="mt-2"> <button id="setAnnotationsButton" class="controls-button"> Set Annotations </button> <button id="clearAnnotationsButton" class="controls-button"> Clear Annotations </button> </div> </div> </div> <div id="youTubeTab" class="hidden controls-tab-content"> <div class="controls-group"> <h2 class="controls-title">Video Id</h2> <div class="flex"> <select id="youTubeVideoIdSelect" class="py-2 px-3 flex-1"></select> <input id="youTubeVideoId" type="text" value="" class="flex-1 ml-2" /> </div> <div class="mt-2"> <button id="setYouTubeVideoIdButton" class="controls-button"> Set YouTube Video Id </button> </div> </div> <div class="controls-group"> <h2 class="controls-title">Muted</h2> <input type="checkbox" id="mutedCheckbox" /> </div> <div class="controls-group"> <h2 class="controls-title">Current Time</h2> <div class="flex"> <input id="currentTime" type="text" value="" class="flex-1" /> <button id="setCurrentTimeButton" class="controls-button flex-initial ml-2" > Set Current Time </button> </div> </div> <div class="controls-group"> <h2 class="controls-title">Duration</h2> <div class="flex"> <input id="durationStart" type="number" value="0" class="flex-1" /> <input id="durationEnd" type="number" value="0" class="flex-1 ml-2" /> <button id="setDurationButton" class="controls-button flex-initial ml-2" > Set Duration </button> </div> </div> </div> </div> <!-- UV Controls --> <script type="text/javascript"> function createOptGroup(label) { var optGroup = document.createElement("optgroup"); optGroup.label = label; return optGroup; } function createOption(text, value) { var option = document.createElement("option"); option.text = text; option.value = value; return option; } document.addEventListener("DOMContentLoaded", function() { var iiifTabButton = document.getElementById("iiifTabButton"); var iiifTab = document.getElementById("iiifTab"); var youTubeTabButton = document.getElementById("youTubeTabButton"); var youTubeTab = document.getElementById("youTubeTab"); iiifTabButton.addEventListener("click", function(e) { e.preventDefault(); activateIIIFTab(); }); youTubeTabButton.addEventListener("click", function(e) { e.preventDefault(); activateYouTubeTab(); }); var uv, iiifManifestId, youTubeVideoId; var $iiifManifestId = document.getElementById("iiifManifestId"); var $iiifManifestIdSelect = document.getElementById( "iiifManifestIdSelect" ); var $target = document.getElementById("target"); var $rotation = document.getElementById("rotation"); var $setIIIFManifestIdButton = document.getElementById( "setIIIFManifestIdButton" ); var $setTargetButton = document.getElementById("setTargetButton"); var $setRotationButton = document.getElementById("setRotationButton"); var $annotations = document.getElementById("annotations"); var $setAnnotationsButton = document.getElementById( "setAnnotationsButton" ); var $clearAnnotationsButton = document.getElementById( "clearAnnotationsButton" ); var $setYouTubeVideoIdButton = document.getElementById( "setYouTubeVideoIdButton" ); var $youTubeVideoId = document.getElementById("youTubeVideoId"); var $youTubeVideoIdSelect = document.getElementById( "youTubeVideoIdSelect" ); var $currentTime = document.getElementById("currentTime"); var $setCurrentTimeButton = document.getElementById( "setCurrentTimeButton" ); var $mutedCheckbox = document.getElementById("mutedCheckbox"); var $durationStart = document.getElementById("durationStart"); var $durationEnd = document.getElementById("durationEnd"); var $setDurationButton = document.getElementById("setDurationButton"); var annotations = []; var urlAdapter; function parseIIIFCollection(manifests) { for (var i = 0; i < manifests.collections.length; i++) { var collection = manifests.collections[i]; if (collection.visible === false) { continue; } var optGroup = createOptGroup(collection.label); $iiifManifestIdSelect.appendChild(optGroup); for (var j = 0; j < collection.manifests.length; j++) { var manifest = collection.manifests[j]; if (manifest.visible !== false) { var option = createOption(manifest.label, manifest["@id"]); optGroup.appendChild(option); } } } } function parseYouTubeCollection(collection) { for (var i = 0; i < collection.length; i++) { var item = collection[i]; if (item.visible === false) { continue; } var option = createOption(item.label, item.id); $youTubeVideoIdSelect.appendChild(option); } } function loadCollections(cb) { Promise.all([ fetch("iiif-collection.json"), fetch("youtube-collection.json"), ]) .then(function(responses) { // Get a JSON object from each of the responses return Promise.all( responses.map(function(response) { return response.json(); }) ); }) .then(function(data) { parseIIIFCollection(data[0]); parseYouTubeCollection(data[1]); cb(); }) .catch(function(error) { // if there's an error, log it console.log(error); }); } function setSelectedIIIFManifest() { var iiifContent = urlAdapter.get("iiif-content"); var legacyIIIFManifestParam = urlAdapter.get("iiifManifestId") || urlAdapter.get("manifest"); if (iiifContent && iiifContent.indexOf("http") !== -1) { // if it's a url, not an encoded annotation iiifManifestId = iiifContent; } else if (legacyIIIFManifestParam) { iiifManifestId = legacyIIIFManifestParam; } else { // use the first one in the drop down box var options = document.querySelectorAll( "#iiifManifestIdSelect optgroup option" ); if (options.length) { iiifManifestId = options[0].value; } } $iiifManifestIdSelect.value = iiifManifestId; $iiifManifestId.value = iiifManifestId; } function setSelectedYouTubeVideoId() { youTubeVideoId = urlAdapter.get("youTubeVideoId"); if (youTubeVideoId) { $youTubeVideoIdSelect.value = youTubeVideoId; } else { var options = document.querySelectorAll( "#youTubeVideoIdSelect option" ); if (options.length) { youTubeVideoId = options[0].value; } } $youTubeVideoId.value = youTubeVideoId; } function setAnnotations() { annotations = JSON.parse($annotations.value); uv.set({ annotations: annotations, }); } $iiifManifestIdSelect.onchange = function() { var $selectedOption = document.querySelector( "#iiifManifestIdSelect option:checked" ); iiifManifestId = $selectedOption.value; $iiifManifestId.value = iiifManifestId; urlAdapter.set("iiifManifestId", iiifManifestId); }; $setIIIFManifestIdButton.onclick = function() { iiifManifestId = $iiifManifestId.value; urlAdapter.set("iiifManifestId", iiifManifestId); clearAnnotations(); uv.set({ iiifManifestId: iiifManifestId, youTubeVideoId: undefined, canvasIndex: 0, annotations: [], }); }; $youTubeVideoIdSelect.onchange = function() { var $selectedOption = document.querySelector( "#youTubeVideoIdSelect option:checked" ); youTubeVideoId = $selectedOption.value; $youTubeVideoId.value = youTubeVideoId; urlAdapter.set("youTubeVideoId", youTubeVideoId); }; $setYouTubeVideoIdButton.onclick = function() { clearAnnotations(); uv.set({ iiifManifestId: undefined, youTubeVideoId: youTubeVideoId, autoPlay: true, annotations: [], }); }; $setTargetButton.onclick = function() { var target = $target.value; uv.set({ target: target, }); }; $setRotationButton.onclick = function() { var rotation = $rotation.value; uv.set({ rotation: rotation, }); }; $setAnnotationsButton.onclick = function() { setAnnotations(); }; $clearAnnotationsButton.onclick = function() { clearAnnotations(); setAnnotations(); }; $mutedCheckbox.onclick = function(e) { muted = e.target.checked; uv.set({ muted: muted, }); }; $setCurrentTimeButton.onclick = function() { var currentTime = $currentTime.value; uv.set({ currentTime: currentTime, }); }; $setDurationButton.onclick = function() { var durationStart = $durationStart.value; var durationEnd = $durationEnd.value; uv.set({ youTubeVideoId: youTubeVideoId, autoPlay: true, duration: [durationStart, durationEnd], }); }; function clearAnnotations() { annotations = []; $annotations.value = JSON.stringify(annotations); } function activateIIIFTab() { iiifTabButton.classList.add("active"); youTubeTabButton.classList.remove("active"); iiifTab.classList.remove("hidden"); youTubeTab.classList.add("hidden"); // uv.dispose(); // uv = UV.init("uv", { // iiifManifestId: iiifManifestId, // }); // addUVEventHandlers(); urlAdapter = new UV.IIIFURLAdapter(); setSelectedIIIFManifest(); const data = urlAdapter.getInitialData({ iiifManifestId: iiifManifestId, debug: false, }); if (uv) { uv.dispose(); } uv = UV.init("uv", data); urlAdapter.bindTo(uv); uv.on("configure", function({ config, cb }) { const manifest = urlAdapter.get("iiifManifestId"); if ( manifest === "https://iiif-commons.github.io/iiif-av-component/examples/data/bl/sounds-tests/loose-ends/C1685_98_P3.json" ) { // Example of custom, inline config. cb({ options: { dropEnabled: true, footerPanelEnabled: true, headerPanelEnabled: false, // leftPanelEnabled: false, // rightPanelEnabled: false, limitLocales: false, overrideFullScreen: false, pagingEnabled: true, limitToRange: true, manifestExclude: true, }, modules: { footerPanel: { options: { fullscreenEnabled: false, }, }, headerPanel: { options: { localeToggleEnabled: false, }, }, moreInfoRightPanel: { options: { limitToRange: true, }, }, avCenterPanel: { options: { autoAdvanceRanges: false, limitToRange: true, enableFastRewind: true, enableFastForward: true, }, }, contentLeftPanel: { options: { defaultToTreeEnabled: true, }, }, }, }); return; } cb( fetch("uv-iiif-config.json").then(function(resp) { return resp.json(); }) ); }); // test inline config, enable doubleclick annotation uv.on("configure", function({ config, cb }) { cb({ modules: { modelViewerCenterPanel: { options: { doubleClickAnnotationEnabled: true, }, }, avCenterPanel: { options: { // autoAdvanceRanges: false, // limitToRange: true, enableFastRewind: true, enableFastForward: true, }, }, openSeadragonCenterPanel: { options: { doubleClickAnnotationEnabled: true, }, }, }, }); }); uv.on("targetChange", function(target) { $target.value = target; }); uv.on("openseadragonExtension.doubleClick", function(e) { annotations.push({ target: e.target, bodyValue: String(annotations.length + 1), }); $annotations.value = JSON.stringify(annotations); setAnnotations(); }); uv.on("modelviewerExtension.doubleClick", function(e) { annotations.push({ target: e.target, bodyValue: String(annotations.length + 1), }); $annotations.value = JSON.stringify(annotations); setAnnotations(); }); uv.on("multiSelectionMade", function(e) { console.log("multiSelectionMade", e); }); uv.on("pinpointAnnotationClicked", function(index) { console.log("pinpointAnnotationClicked", index); }); uv.on("clearAnnotations", function(e) { clearAnnotations(); }); // uv.on("load", function(e) { // console.log(e); // }); // uv.set({ // iiifManifestId: iiifManifestId, // youTubeVideoId: undefined, // }); } function activateYouTubeTab() { youTubeTabButton.classList.add("active"); iiifTabButton.classList.remove("active"); youTubeTab.classList.remove("hidden"); iiifTab.classList.add("hidden"); // todo: implement youtube url adapter urlAdapter = new UV.IIIFURLAdapter(); setSelectedYouTubeVideoId(); if (uv) { uv.dispose(); } uv = UV.init("uv", { youTubeVideoId: youTubeVideoId, }); uv.on("configure", function({ config, cb }) { cb( new Promise(function(resolve) { fetch("uv-youtube-config.json").then(function(response) { resolve(response.json()); }); }) ); }); uv.on("load", function(e) { console.log("load", e); }); uv.on("unstarted", function() { console.log("unstarted"); }); uv.on("ended", function() { console.log("ended"); }); uv.on("playing", function() { console.log("playing"); }); uv.on("paused", function() { console.log("paused"); }); uv.on("error", function(e) { console.log("error", e); }); // uv.set({ // iiifManifestId: undefined, // youTubeVideoId: youTubeVideoId, // }); } loadCollections(function() { activateIIIFTab(); }); // test stories let storyAnnotations, partOf, target, duration; let muted = false; // data for https://www.exhibit.so/api/exhibits/Zmtdg3cwREXzNGCuHgmm // const annotationsJson = // "http://localhost:3000/api/exhibits/dIVPgGD9ostBUU1ckIZZ"; const annotationsJson = "http://192.168.1.233:3000/api/exhibits/dIVPgGD9ostBUU1ckIZZ"; const annotationList = document.getElementById("annotation-list"); function handleAnnotationClick(e) { e.preventDefault(); const anno = storyAnnotations.find( (a) => a.id === e.currentTarget.dataset.annoId ); partOf = anno.partOf; target = anno.target; duration = anno.duration; const type = partOf.includes("youtube") ? "youtube" : "iiif"; if (uv) { uv.dispose(); } uv = UV.init("uv", { autoPlay: true, iiifManifestId: type === "iiif" ? partOf : undefined, youTubeVideoId: type === "youtube" ? partOf : undefined, duration: duration, target: target, muted: muted, }); uv.on("load", function(args) { // console.log("load", args); // args.player.mute(); }); uv.on("configure", function({ config, cb }) { cb( new Promise(function(resolve) { fetch(`uv-${type}-config.json`).then(function(response) { resolve(response.json()); }); }) ); }); // uv.set({ // autoPlay: true, // iiifManifestId: type === "iiif" ? partOf : undefined, // youTubeVideoId: type === "youtube" ? partOf : undefined, // duration: duration, // target: target, // muted: muted, // }); } function initStory() { fetch(annotationsJson).then((res) => { const data = res.json().then((data) => { storyAnnotations = data.annotations.filter( (anno) => anno.motivation === "framing" ); storyAnnotations.forEach((anno) => { const li = document.createElement("li"); const a = document.createElement("a"); a.dataset.annoId = anno.id; a.href = `#${anno.id}`; a.innerHTML = anno.bodyValue; a.onclick = handleAnnotationClick; li.appendChild(a); annotationList.appendChild(li); }); partOf = storyAnnotations[0].partOf; target = storyAnnotations[0].target; }); }); } // initStory(); }); </script> </body> </html>