import "shiny";
import "jquery";
import { getDimensions } from "../dimensions";
import { hideRecalculate } from "../recalculate";
import { setWaiterHiddenInput, setWaiterShownInput } from "./callbacks";
import { createOverlay } from "./overlay";
import "./css/css-spinners.css";
import "./css/custom.css";
import "./css/devloop.css";
import "./css/loaders.css";
import "./css/spinbolt.css";
import "./css/spinkit.css";
import "./css/spinners.css";
import "./css/waiter.css";
// elements to hide on recomputed
var waiterToHideOnRender = new Map();
var waiterToFadeout = new Map();
var waiterToHideOnError = new Map();
var waiterToHideOnSilentError = new Map();
let defaultWaiter = {
id: null,
html: '<div class="container--box"><div class="boxxy"><div class="spinner spinner--1"></div></div></div>',
color: "#333e48",
hideOnRender: false,
hideOnError: false,
hideOnSilentError: false,
image: null,
fadeOut: false,
ns: null,
onShown: setWaiterShownInput,
onHidden: setWaiterHiddenInput,
};
/**
* Show a waiter screen
* @function
* @param {JSON} params - JSON object of options, see 'defaultWaiter'.
* @example
* // defaults
* show({
* id: null,
* html: '<div class="container--box"><div class="boxxy"><div class="spinner spinner--1"></div></div></div>',
* color: '#333e48',
* hideOnRender: false,
* hideOnError: false,
* hideOnSilentError: false,
* image: null,
* fadeOut: false,
* ns: null,
* onShown: setWaiterShownInput,
* onHidden: setWaiterHiddenInput
* });
*/
export const show = (params = defaultWaiter) => {
// declare
var dom,
selector = "body",
exists = false;
// get parent
if (params.id !== null) selector = "#" + params.id;
dom = document.querySelector(selector);
if (dom == undefined) {
console.error("Cannot find", params.id);
return;
}
// allow missing for testing
params.hideOnRender = params.hideOnRender || false;
// set in maps
waiterToHideOnRender.set(params.id, params);
waiterToFadeout.set(selector, params.fadeOut);
waiterToHideOnError.set(params.id, params.hideOnError);
waiterToHideOnSilentError.set(params.id, params.hideOnSilentError);
let el = getDimensions(dom); // get dimensions
if (
dom.classList.contains("leaflet") &&
document.getElementById("map").children.length > 1
) {
el.top = 0;
el.left = 0;
}
// no id = fll screen
if (params.id === null) {
el.height = window.innerHeight;
el.width = $("body").width();
}
// force static if position relative
// otherwise overlay is completely off
var pos = window.getComputedStyle(dom, null).position;
if (pos == "relative") dom.className += " staticParent";
// check if overlay exists
dom.childNodes.forEach((el) => {
if (el.className === "waiter-overlay") exists = true;
});
if (exists) {
console.error("waiter on", params.id, "already exists");
return;
}
hideRecalculate(params.id);
let overlay = createOverlay(params, el);
// append overlay to dom
dom.appendChild(overlay);
// set input
if (params.onShown != undefined) params.onShown(params.id);
};
/**
* @function
* @param {string} id - Id of element containing the waiter.
* if 'null' assumes the waiter is full screen.
* @param {Function} onHidden - A callback function to call
* when the waiter is hidden. Leave on 'null' to not use.
*/
export const hide = (id, onHidden = null) => {
var selector = "body";
if (id !== null) selector = "#" + id;
let overlay = $(selector).find(".waiter-overlay");
if (overlay.length == 0) return;
let timeout = 250;
if (waiterToFadeout.get(selector)) {
let value = waiterToFadeout.get(selector);
if (typeof value == "boolean") value = 500;
$(overlay).fadeOut(value);
timeout = timeout + value;
}
// this is to avoid the waiter screen from flashing
setTimeout(function () {
overlay.remove();
}, timeout);
if (onHidden != undefined && onHidden != null) onHidden(id);
};
/**
* Update the content of the waiter.
* @function
* @param {string} id - Id of element to update the waiter.
* If 'null' assumes the waiter is full screen.
* @param {string} html - An html string content to replace
* the waiter.
*/
export const update = (id, html) => {
var selector = "body";
if (id !== null) selector = "#" + id;
$(selector)
.find(".waiter-overlay-content")
.each((index, el) => {
$(el).html(html);
});
};
/**
* Show the recalculate effect from base shiny.
* Only useful if it was previously hidden.
* @function
* @param {string} id - Id of reactive element.
*/
export const showRecalculate = (id) => {
$(id + "-waiter-recalculating").remove();
};
// remove when output receives value
$(document).on("shiny:value", function (event) {
let w = waiterToHideOnRender.get(event.name);
if (w == undefined) return;
if (!w.hideOnRender) return;
hide(event.name, w.onHidden);
});
// remove when output errors
$(document).on("shiny:error", function (event) {
if (event.error.type == null && waiterToHideOnError.get(event.name)) {
hide(event.name, setWaiterHiddenInput);
return;
}
if (event.error.type != null && waiterToHideOnSilentError.get(event.name)) {
hide(event.name, setWaiterHiddenInput);
}
});
// On resize we need to resize the waiter screens too
window.addEventListener("resize", function () {
$(".waiter-local").each((index, el) => {
let dim = getDimensions($(el).parent()[0]);
$(el).css({
width: dim.width + "px",
height: dim.height + "px",
});
});
$(".waiter-fullscreen").css({
width: window.innerWidth + "px",
height: window.innerHeight + "px",
});
});
Shiny.addCustomMessageHandler("waiter-show", function (opts) {
show(opts);
Shiny.setInputValue("waiter_shown", true, { priority: "event" });
});
Shiny.addCustomMessageHandler("waiter-update", function (opts) {
update(opts.id, opts.html);
});
Shiny.addCustomMessageHandler("waiter-hide", function (opts) {
hide(opts.id, setWaiterHiddenInput);
});