function initXopatScripts() {
$.extend($.scrollTo.defaults, {axis: 'y'});
if (APPLICATION_CONTEXT.getOption("debugMode")) {
(function(){var script=document.createElement('script');script.onload=function(){var stats=new Stats();document.body.appendChild(stats.dom);stats.showPanel(1);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src=APPLICATION_CONTEXT.url+'src/external/stats.js';document.head.appendChild(script);})()
}
$("#global-opacity input").on("input", function() {
let val = $(this).val();
VIEWER.world.getItemAt(VIEWER.bridge.getWorldIndex()).setOpacity(val);
});
$(VIEWER.element).on('contextmenu', function(event) {
event.preventDefault();
});
if (APPLICATION_CONTEXT.getOption("isStaticPreview")) {
const shareBtn = document.getElementById("copy-url");
const staticDisclaimer = document.getElementById("static-file-disclaimer");
shareBtn.style.display = "none";
staticDisclaimer.style.display = "grid";
}
let focusOnViewer = true;
VIEWER.addHandler('canvas-enter', function() {
focusOnViewer = true;
});
VIEWER.addHandler('canvas-exit', function() {
focusOnViewer = false;
});
VIEWER.addHandler('canvas-key', function(e) {
focusOnViewer = true;
e.preventDefaultAction = true;
});
function getIsViewerFocused() {
const focusedElement = document.activeElement;
const focusTyping = focusedElement.tagName === 'INPUT' ||
focusedElement.tagName === 'TEXTAREA' ||
focusedElement.isContentEditable;
return focusOnViewer && !focusTyping;
}
UTILITIES.setIsCanvasFocused = function(focused) {
focusOnViewer = focused;
};
document.addEventListener('keydown', function(e) {
e.focusCanvas = getIsViewerFocused();
VIEWER.raiseEvent('key-down', e);
});
document.addEventListener('keyup', function(e) {
e.focusCanvas = getIsViewerFocused();
VIEWER.raiseEvent('key-up', e);
});
let failCount = new WeakMap();
VIEWER.addHandler('tile-load-failed', function(e) {
if (e.message === "Image load aborted") return;
let index = VIEWER.world.getIndexOfItem(e.tiledImage);
let failed = failCount[index];
if (!failed || failed != e.tiledImage) {
failCount[index] = e.tiledImage;
e.tiledImage._failedCount = 1;
} else {
let d = e.time - e.tiledImage._failedDate;
if (d < 500) {
e.tiledImage._failedCount++;
} else {
e.tiledImage._failedCount = 1;
}
if (e.tiledImage._failedCount > 5) {
e.tiledImage._failedCount = 1;
e.worldIndex = index;
VIEWER.raiseEvent('tiled-image-problematic', e);
}
}
e.tiledImage._failedDate = e.time;
});
let _lastScroll = Date.now(), _scrollCount = 0, _currentScroll;
window.VIEWER.addHandler('navigator-scroll', function(e) {
VIEWER.viewport.zoomBy(e.scroll / 2 + 1);
VIEWER.viewport.applyConstraints();
});
if (!APPLICATION_CONTEXT.getOption("preventNavigationShortcuts")) {
function adjustBounds(speedX, speedY) {
let bounds = VIEWER.viewport.getBounds();
bounds.x += speedX*bounds.width;
bounds.y += speedY*bounds.height;
VIEWER.viewport.fitBounds(bounds);
}
VIEWER.addHandler('key-up', function(e) {
if (e.focusCanvas) {
let zoom = null,
speed = 0.3;
switch (e.key) {
case "Down":
case "ArrowDown":
adjustBounds(0, speed);
break;
case "Up":
case "ArrowUp":
adjustBounds(0, -speed);
break;
case "Left":
case "ArrowLeft":
adjustBounds(-speed, 0);
break;
case "Right":
case "ArrowRight":
adjustBounds(speed, 0);
break;
case "+":
zoom = VIEWER.viewport.getZoom();
VIEWER.viewport.zoomTo(zoom + zoom * speed * 3);
return;
case "-":
zoom = VIEWER.viewport.getZoom();
VIEWER.viewport.zoomTo(zoom - zoom * speed * 2);
return;
default:
return;
}
}
if (e.key === 'Escape') {
USER_INTERFACE.AdvancedMenu.close();
USER_INTERFACE.Tutorials.hide();
USER_INTERFACE.DropDown.hide();
}
});
}
window.UTILITIES.todayISO = function(separator="/") {
return new Date().toJSON().slice(0,10).split('-').reverse().join(separator);
};
window.UTILITIES.todayISOReversed = function(separator="/") {
return new Date().toJSON().slice(0,10).split('-').join(separator);
};
window.UTILITIES.isJSONBoolean = function(value, defaultValue) {
return (defaultValue && value === undefined) || (value && (typeof value !== "string" || value.trim().toLocaleLowerCase() !== "false"));
};
window.UTILITIES.makeThrottled = function (callback, delay) {
let lastCallTime = 0;
let timeoutId = null;
let pendingArgs = null;
const invoke = () => {
timeoutId = null;
lastCallTime = Date.now();
if (pendingArgs) {
callback(...pendingArgs);
pendingArgs = null;
}
};
const wrapper = (...args) => {
const now = Date.now();
if (!lastCallTime || now - lastCallTime >= delay) {
lastCallTime = now;
callback(...args);
} else {
pendingArgs = args;
if (!timeoutId) {
timeoutId = setTimeout(invoke, delay - (now - lastCallTime));
}
}
};
wrapper.finish = () => {
if (timeoutId) {
clearTimeout(timeoutId);
invoke();
}
};
return wrapper;
}
window.UTILITIES.sleep = async function(ms=undefined) {
return new Promise(resolve => setTimeout(resolve, ms));
}
window.UTILITIES.updateTheme = function(theme=undefined) {
theme = theme || APPLICATION_CONTEXT.getOption("theme");
if (!["dark", "dark_dimmed", "light", "auto"].some(t => t === theme)) theme = APPLICATION_CONTEXT.config.defaultParams.theme;
const isStatic = APPLICATION_CONTEXT.getOption("isStaticPreview");
if (theme === "dark_dimmed") {
document.documentElement.dataset['darkTheme'] = "dark_dimmed";
document.documentElement.dataset['colorMode'] = "dark";
document.body.setAttribute("data-theme", isStatic ? "blood-moon" : "catppuccin-mocha");
} else if (theme === "auto" && isStatic) {
const isLight = window.matchMedia('(prefers-color-scheme: light)').matches;
if (isLight) {
document.documentElement.dataset['colorMode'] = "light";
document.body.setAttribute("data-theme", "crimson-dawn");
}
else {
document.documentElement.dataset['colorMode'] = "dark";
document.documentElement.dataset['darkTheme'] = "dark";
document.body.setAttribute("data-theme", "blood-moon");
}
} else {
document.documentElement.dataset['darkTheme'] = "dark";
document.documentElement.dataset['colorMode'] = theme;
if (theme === "dark") {
document.body.setAttribute("data-theme", isStatic ? "blood-moon" : "catppuccin-mocha");
} else if (isStatic) {
document.body.setAttribute("data-theme", "crimson-dawn");
} else {
document.body.removeAttribute("data-theme");
}
}
};
window.UTILITIES.serializeAppConfig = function(withCookies=false, staticPreview = false) {
const data = {...APPLICATION_CONTEXT.config};
data.params = {...APPLICATION_CONTEXT.config.params};
delete data.defaultParams;
if (staticPreview) data.params.isStaticPreview = true;
if (!withCookies) data.params.bypassCookies = true;
data.params.bypassCacheLoadTime = true;
data.params.viewport = {
zoomLevel: VIEWER.viewport.getZoom(),
point: VIEWER.viewport.getCenter()
};
return APPLICATION_CONTEXT.layersAvailable && window.WebGLModule
? JSON.stringify(data, WebGLModule.jsonReplacer)
: JSON.stringify(data, (key, value) => key.startsWith("_") ? undefined : value);
};
window.UTILITIES.getForm = async function(customAttributes="", includedPluginsList=undefined, withCookies=false) {
const url = (APPLICATION_CONTEXT.url.startsWith('http') ? "" : "http://") + APPLICATION_CONTEXT.url;
if (! APPLICATION_CONTEXT.env.serverStatus.supportsPost) {
return `
<form method="POST" id="redirect" action="${url}#${encodeURI(UTILITIES.serializeAppConfig(withCookies, true))}">
<input type="hidden" id="visualization" name="visualization">
${customAttributes}
<input type="submit" value="">
</form>
<script type="text/javascript">const form = document.getElementById("redirect").submit();<\/script>`;
}
const {app, data} = await window.UTILITIES.serializeApp(includedPluginsList, withCookies, true);
data.visualization = app;
let form = `
<form method="POST" id="redirect" action="${url}">
${customAttributes}
<input type="submit" value="">
</form>
<script type="text/javascript">
const form = document.getElementById("redirect");
let node;`;
function addExport(key, data) {
form += `node = document.createElement("input");
node.setAttribute("type", "hidden");
node.setAttribute("name", "${key}");
node.setAttribute("value", JSON.stringify(${JSON.stringify(data)}));
form.appendChild(node);`;
}
for (let id in data) {
const sets = id.split('.'), dataItem = data[id];
if (sets.length === 1) {
if (id === "visualization") {
addExport(id, dataItem);
} else if (id === "module" || id === "plugin") {
if (typeof dataItem === "object") {
for (let nId in dataItem) addExport(`${id}[${nId}]`, dataItem[nId]);
} else {
addExport(id, dataItem);
}
} else {
console.error("Only 'visualization', 'module' and 'plugin' is allowed top-level object. Not included in export. Used:", id);
}
} else if (sets.length > 1) {
addExport(`${sets.shift()}[${sets.join('.')}]`, dataItem);
}
}
return `${form}
form.submit();
<\/script>`;
}
window.UTILITIES.copyToClipboard = function(content, alert=true) {
let $temp = $("<input>");
$("body").append($temp);
$temp.val(content).select();
document.execCommand("copy");
$temp.remove();
if (alert) Dialogs.show($.t('messages.valueCopied'), 3000, Dialogs.MSG_INFO);
};
window.UTILITIES.copyUrlToClipboard = function() {
let baseUrl = APPLICATION_CONTEXT.getOption("redirectUrl", "");
if (!baseUrl.match(/^https?:\/\//)) {
baseUrl = APPLICATION_CONTEXT.url + baseUrl;
}
const data = UTILITIES.serializeAppConfig();
UTILITIES.copyToClipboard(baseUrl + "#" + encodeURIComponent(data));
};
window.UTILITIES.makeScreenshot = function() {
const canvas = document.createElement("canvas"),
viewportCanvas = VIEWER.drawer.canvas, width = viewportCanvas.width, height = viewportCanvas.height;
canvas.width = width;
canvas.height = height;
const context = canvas.getContext("2d");
context.drawImage(viewportCanvas, 0, 0);
VIEWER.raiseEvent('screenshot', {
context2D: context,
width: width,
height: height
});
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
URL.revokeObjectURL(url);
});
};
window.UTILITIES.export = async function() {
let doc = `<!DOCTYPE html>
<html lang="en" dir="ltr">
<head><meta charset="utf-8"><title>Visualization export</title></head>
<body><!--Todo errors might fail to be stringified - cyclic structures!-->
<div>Errors (if any): <pre>${console.appTrace.join("")}</pre></div>
${await UTILITIES.getForm()}
</body></html>`;
UTILITIES.downloadAsFile("export.html", doc);
APPLICATION_CONTEXT.__cache.dirty = false;
};
window.UTILITIES.clone = async function() {
if (window.opener) {
return;
}
let ctx = Dialogs.getModalContext('synchronized-view');
if (ctx) {
ctx.window.focus();
return;
}
let x = window.innerWidth / 2, y = window.innerHeight;
window.resizeTo(x, y);
Dialogs._showCustomModalImpl('synchronized-view', "Loading...",
await UTILITIES.getForm(), `width=${x},height=${y}`);
};
window.UTILITIES.setDirty = () => APPLICATION_CONTEXT.__cache.dirty = true;
window.UTILITIES.refreshPage = async function(includedPluginsList=undefined) {
if (APPLICATION_CONTEXT.__cache.dirty) {
Dialogs.show($.t('messages.warnPageReload', {
onExport: "UTILITIES.export();",
onRefresh: "APPLICATION_CONTEXT.__cache.dirty = false; UTILITIES.refreshPage();"
}), 15000, Dialogs.MSG_WARN);
return;
}
if (!UTILITIES.storePageState(includedPluginsList)) {
Dialogs.show($.t('messages.warnPageReloadFailed'), 4000, Dialogs.MSG_WARN);
USER_INTERFACE.Loading.show(true);
await UTILITIES.sleep(3800);
}
window.location.replace(APPLICATION_CONTEXT.url);
};
window.UTILITIES.downloadAsFile = function(filename, content) {
let data = new Blob([content], { type: 'text/plain' });
let downloadURL = window.URL.createObjectURL(data);
let elem = document.getElementById('link-download-helper');
elem.href = downloadURL;
elem.setAttribute('download', filename);
elem.click();
URL.revokeObjectURL(downloadURL);
};
window.UTILITIES.uploadFile = async function(onUploaded, accept=".json", mode="text") {
const uploader = $("#file-upload-helper");
uploader.attr('accept', accept);
uploader.on('change', () => {
UTILITIES.readFileUploadEvent(event, mode).then(onUploaded).catch(onUploaded);
uploader.val('');
uploader.off('change');
});
uploader.trigger("click");
}
window.UTILITIES.readFileUploadEvent = function(e, mode="text") {
return new Promise((resolve, reject) => {
let file = e.target.files[0];
if (!file) return reject("Invalid input file: no file.");
let fileReader = new FileReader();
fileReader.onload = e => resolve(e.target.result);
if (mode === "text") fileReader.readAsText(file);
else if (mode === "bytes") fileReader.readAsArrayBuffer(file);
else throw "Invalid read file mode " + mode;
});
};
$("body")
.append("<a id='link-download-helper' class='d-none'></a>")
.parent().append("<input id='file-upload-helper' type='file' style='visibility: hidden !important; width: 1px; height: 1px'/>");
UTILITIES.updateTheme();
VIEWER.addOnceHandler('open', () => {
const DELAY = 90;
let last = 0;
new OpenSeadragon.MouseTracker({
userData: 'pixelTracker',
element: "viewer-container",
moveHandler: function(e) {
const now = Date.now();
if (now - last < DELAY) return;
last = now;
const image = VIEWER.scalebar.getReferencedTiledImage() || VIEWER.world.getItemAt(0);
if (!image) return;
const screen = new OpenSeadragon.Point(e.originalEvent.x, e.originalEvent.y);
const position = image.windowToImageCoordinates(screen);
let result = [`${Math.round(position.x)}, ${Math.round(position.y)} px`];
const vis = VIEWER.bridge && VIEWER.bridge.visualization(),
hasBg = APPLICATION_CONTEXT.config.background.length > 0;
let tidx = 0;
const viewport = VIEWER.viewport.windowToViewportCoordinates(screen);
if (hasBg) {
const pixel = getPixelData(screen, viewport, tidx);
if (pixel) {
result.push(`tissue: R${pixel[0]} G${pixel[1]} B${pixel[2]}`)
} else {
result.push(`tissue: -`)
}
tidx++;
}
if (vis) {
const pixel = getPixelData(screen, viewport, tidx);
if (pixel) {
result.push(`overlay: R${pixel[0]} G${pixel[1]} B${pixel[2]}`)
} else {
result.push(`overlay: -`)
}
}
USER_INTERFACE.Status.show(result.join("<br>"));
}
});
function getPixelData(screen, viewportPosition, tiledImage) {
function changeTile() {
let tiles = tiledImage.lastDrawn;
for (let i = 0; i < tiles.length; i++) {
if (tiles[i].bounds.containsPoint(viewportPosition)) {
return tiles[i];
}
}
return undefined;
}
if (Number.isInteger(tiledImage)) {
tiledImage = VIEWER.world.getItemAt(tiledImage);
if (!tiledImage) {
return undefined;
}
}
let tile;
tile = changeTile();
if (!tile) return undefined;
let x = screen.x - tile.position.x;
let y = screen.y - tile.position.y;
let canvasCtx = tile.getCanvasContext();
let relative_x = Math.round((x / tile.size.x) * canvasCtx.canvas.width);
let relative_y = Math.round((y / tile.size.y) * canvasCtx.canvas.height);
return canvasCtx.getImageData(relative_x, relative_y, 1, 1).data;
}
});
}