nc-planner/scripts/tooltip.js

180 lines
4.9 KiB
JavaScript

import { debounce } from "./dom-utils.js";
import { emitEvent, listenTo } from "./events.js";
import { registerLoadCallback } from "./main.js";
import { makeAtlasImage } from "./atlas.js";
const tooltips = {};
let tooltipsCounter = 0;
let lastCoordinates = null;
let suppressTooltips = false;
let activeTooltipTarget = null;
registerLoadCallback(() => {
document.querySelectorAll("[data-retain-tooltip-hover]").forEach(elem =>
elem.addEventListener("mousemove", e => {
if (lastCoordinates !== null) {
e.stopPropagation();
lastCoordinates = { x: e.pageX, y: e.pageY };
emitEvent("tooltips:show");
}
}));
window.addEventListener("mousemove", () => {
if (lastCoordinates !== null) {
emitEvent("tooltips:hide");
}
lastCoordinates = null;
});
window.addEventListener("mousemove", debounce(e => {
lastCoordinates = { x: e.pageX, y: e.pageY };
emitEvent("tooltips:show");
}, 500));
window.addEventListener("wheel", () => {
if (lastCoordinates !== null) {
setTimeout(() => emitEvent("tooltips:show"), 200);
}
});
listenTo("tooltips:show", showTooltip);
listenTo("tooltips:hide", hideTooltip);
document.addEventListener("keydown", e => {
if (e.key === "T") {
suppressTooltips = !suppressTooltips;
console.log("suppressing tooltips", suppressTooltips);
}
});
});
export function createTooltip(data) {
const id = tooltipsCounter;
tooltips[id] = data;
tooltipsCounter++;
return id;
}
export function getTooltip(id) {
if (typeof id === "object") {
// assume DOM element
if (id.dataset && id.dataset.tooltip) {
return getTooltip(id.dataset.tooltip);
}
return null;
}
return tooltips[id];
}
export function deleteTooltip(id) {
delete tooltips[id];
}
export function setTooltip(element, data) {
if (data.length === 0) return;
if (element.dataset.tooltip) {
tooltips[Number(element.dataset.tooltip)] = data;
return;
}
element.dataset.tooltip = createTooltip(data).toString(10);
}
export function positionAsTooltip(mainElement, tooltipElement, { horizontal = "left", vertical = "bottom" } = {}) {
const targetRect = mainElement.getBoundingClientRect();
const domRect = tooltipElement.getBoundingClientRect();
const windowWidth = window.innerWidth, windowHeight = window.innerHeight;
tooltipElement.style.left = `${Math.min(targetRect[horizontal], windowWidth - domRect.width)}px`;
tooltipElement.style.top = `${Math.min(targetRect[vertical], windowHeight - domRect.height)}px`;
}
function showTooltip() {
if (suppressTooltips) return;
if (!lastCoordinates) return;
if (activeTooltipTarget) hideTooltip();
let target = document.elementFromPoint(lastCoordinates.x, lastCoordinates.y);
while (target && !target.dataset.tooltip) {
target = target.parentElement;
}
if (!target) return;
const dom = createTooltipDom(target.dataset.tooltip);
if (!dom) return;
const targetRect = target.getBoundingClientRect();
dom.style.left = `${targetRect.left}px`;
dom.style.top = `${targetRect.bottom}px`;
target.appendChild(dom);
positionAsTooltip(target, dom);
activeTooltipTarget = target;
}
function hideTooltip() {
if (suppressTooltips) return;
if (!activeTooltipTarget) return;
const tooltip = activeTooltipTarget.querySelector(".tooltip");
if (!tooltip) return;
activeTooltipTarget.removeChild(tooltip);
activeTooltipTarget = null;
}
function createTooltipDom(id) {
const data = tooltips[id];
if (!Array.isArray(data)) {
console.warn("tooltip %s is not an array: %o", id, data);
return null;
}
const div = document.createElement("div");
div.classList.add("tooltip");
for (const row of data) {
const rowElem = document.createElement("div");
if (typeof row === "string") {
rowElem.innerText = row;
} else {
if (!row.type) {
throw row;
}
// TODO: support richer tooltips
switch (row.type) {
case "italics":
rowElem.innerText = row.text;
rowElem.classList.add("italics");
break;
case "item":
if (row.prefix) {
rowElem.appendChild(document.createTextNode(row.prefix));
}
rowElem.appendChild(makeAtlasImage(row.atlasIndex));
rowElem.appendChild(document.createTextNode(row.name));
rowElem.classList.add("tooltip--item");
break;
default:
console.warn("unknown tooltip type", row);
break;
}
}
div.appendChild(rowElem);
}
return div;
}
export function tooltipDataToPlain(data) {
if (!Array.isArray(data)) {
console.warn("tooltip data is not an array: %o", data);
return [];
}
return data.flatMap(row => {
if (typeof row === "string") return row;
if (!row.type) return [];
if (row.text) return row.text;
if (row.type === "item") return row.name;
return [];
})
}