UNPKG

universalviewer

Version:

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

936 lines (816 loc) 28.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> <script type="text/javascript" src="umd/UV.js"></script> <link rel="stylesheet" href="https://unpkg.com/modern-normalize@3.0.1/modern-normalize.css" /> <style> body { color-scheme: light dark; background-color: Canvas; } /* @apply uv w-full */ #uv { width: 100%; height: 100svh; } /* @apply hidden mx-auto my-8 */ #uv-controls { display: none; margin-inline: auto; margin-block: 2rem; } @media (min-width: 768px) { /* @apply md:w-[90vw] md:mx-auto md:h-[80vh] md:mt-4 */ #uv { width: 90vw; height: 80vh; margin-inline: auto; margin-block: 1rem; } /* @apply md:!block md:w-[90vw] */ #uv-controls { display: block; width: 90vw; margin-block: 1rem; } } @media (min-width: 1024px) { /* @apply lg:w-[65vw] */ #uv { width: 65vw; } /* @apply lg:w-[65vw] */ #uv-controls { width: 65vw; } } #uv-controls { button, input[type="text"], select, textarea { appearance: none; padding-inline: 0.75rem; /* px-3 */ padding-block: 0.5rem; /* py-2 */ font: inherit; line-height: 24px; border: 1px solid; background-color: Canvas; } select { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); background-position: right 0.5rem center; background-repeat: no-repeat; background-size: 1.5em 1.5em; } textarea { width: 100%; } /* Controls */ .controls-button { appearance: button; min-width: 10rem; padding-inline: 1rem; padding-block: 0.5rem; line-height: 26px; white-space: nowrap; color: white; border: none; background-color: black; } .controls-group { padding-block: 0.5rem; } .controls-title { margin-bottom: 0; padding-bottom: 0.75rem; font-size: 1.125rem; font-weight: 600; } .controls-tab { padding-inline: 1.5rem; padding-block: 0.75rem; font-weight: 500; font-size: 1.25rem; border: none; background: none; cursor: pointer; } .controls-tab.active { background-color: ButtonFace; } .controls-tab-content { padding: 1.5rem; color: ButtonText; background-color: ButtonFace; } } /* Utilities */ .flex { display: flex; align-items: center; gap: 0.5rem; } .flex-1 { flex: 1 1 0%; } .flex-initial { flex: 0 1 auto; } .mt-2 { margin-top: 0.5rem; } </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="iiif_target" type="text" value="" class="flex-1" /> <button id="iiif_setTargetButton" class="controls-button flex-initial ml-2" > Set Target </button> </div> </div> <div class="controls-group"> <h2 class="controls-title">Muted</h2> <input type="checkbox" id="iiif_mutedCheckbox" /> </div> <div class="controls-group"> <h2 class="controls-title">Rotation</h2> <div class="flex"> <input id="iiif_rotation" type="text" value="" class="flex-1" /> <button id="iiif_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="iiif_annotations" rows="5" class="w-full"></textarea> <div class="mt-2"> <button id="iiif_setAnnotationsButton" class="controls-button"> Set Annotations </button> <button id="iiif_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="youtube_mutedCheckbox" /> </div> <div class="controls-group"> <h2 class="controls-title">Current Time</h2> <div class="flex"> <input id="youtube_currentTime" type="text" value="" class="flex-1" /> <button id="youtube_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="youtube_durationStart" type="number" value="0" class="flex-1" /> <input id="youtube_durationEnd" type="number" value="0" class="flex-1 ml-2" /> <button id="youtube_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; // iiif tab var $iiifManifestId = iiifTab.querySelector("#iiifManifestId"); var $iiifManifestIdSelect = iiifTab.querySelector( "#iiifManifestIdSelect" ); var $iiif_target = iiifTab.querySelector("#iiif_target"); var $iiif_rotation = iiifTab.querySelector("#iiif_rotation"); var $setIIIFManifestIdButton = iiifTab.querySelector( "#setIIIFManifestIdButton" ); var $iiif_setTargetButton = iiifTab.querySelector( "#iiif_setTargetButton" ); var $iiif_mutedCheckbox = iiifTab.querySelector("#iiif_mutedCheckbox"); var $iiif_setRotationButton = iiifTab.querySelector( "#iiif_setRotationButton" ); var $iiif_annotations = iiifTab.querySelector("#iiif_annotations"); var $iiif_setAnnotationsButton = iiifTab.querySelector( "#iiif_setAnnotationsButton" ); var $iiif_clearAnnotationsButton = iiifTab.querySelector( "#iiif_clearAnnotationsButton" ); // youtube tab var $setYouTubeVideoIdButton = youTubeTab.querySelector( "#setYouTubeVideoIdButton" ); var $youTubeVideoId = youTubeTab.querySelector("#youTubeVideoId"); var $youTubeVideoIdSelect = youTubeTab.querySelector( "#youTubeVideoIdSelect" ); var $youtube_currentTime = youTubeTab.querySelector( "#youtube_currentTime" ); var $youtube_setCurrentTimeButton = youTubeTab.querySelector( "#youtube_setCurrentTimeButton" ); var $youtube_mutedCheckbox = youTubeTab.querySelector( "#youtube_mutedCheckbox" ); var $youtube_durationStart = youTubeTab.querySelector( "#youtube_durationStart" ); var $youtube_durationEnd = youTubeTab.querySelector( "#youtube_durationEnd" ); var $youtube_setDurationButton = youTubeTab.querySelector( "#youtube_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($iiif_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: [], }); }; // iiif inputs $iiif_setTargetButton.onclick = function() { var target = $iiif_target.value; uv.set({ target: target, }); }; $iiif_mutedCheckbox.onclick = function(e) { muted = e.target.checked; uv.set({ muted: muted, }); }; $iiif_setRotationButton.onclick = function() { var rotation = parseInt($iiif_rotation.value); uv.set({ rotation: rotation, }); }; $iiif_setAnnotationsButton.onclick = function() { setAnnotations(); }; $iiif_clearAnnotationsButton.onclick = function() { clearAnnotations(); setAnnotations(); }; // youtube inputs $youtube_mutedCheckbox.onclick = function(e) { muted = e.target.checked; uv.set({ muted: muted, }); }; $youtube_setCurrentTimeButton.onclick = function() { var currentTime = $youtube_currentTime.value; uv.set({ currentTime: currentTime, }); }; $youtube_setDurationButton.onclick = function() { var durationStart = $youtube_durationStart.value; var durationEnd = $youtube_durationEnd.value; uv.set({ youTubeVideoId: youTubeVideoId, autoPlay: true, duration: [durationStart, durationEnd], }); }; function clearAnnotations() { annotations = []; $iiif_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) { $iiif_target.value = target; }); uv.on("openseadragonExtension.doubleClick", function(e) { annotations.push({ target: e.target, bodyValue: String(annotations.length + 1), }); $iiif_annotations.value = JSON.stringify(annotations); setAnnotations(); }); uv.on("modelviewerExtension.doubleClick", function(e) { annotations.push({ target: e.target, bodyValue: String(annotations.length + 1), }); $iiif_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("mediaelementExtension.mediaMuted", function() { $iiif_mutedCheckbox.checked = true; }); uv.on("mediaelementExtension.mediaUnmuted", function() { $iiif_mutedCheckbox.checked = false; }); // 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>