223 lines
6.0 KiB
JavaScript
223 lines
6.0 KiB
JavaScript
import { debounce } from "./dom-utils.js";
|
|
import { emitEvent, listenTo } from "./events.js";
|
|
import { registerLoadCallback } from "./main.js";
|
|
import { makeAtlasImage } from "./atlas.js";
|
|
import { DevError } from "./errors.js";
|
|
import { getCellTooltip } from "./planner.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", retainTooltipHover));
|
|
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);
|
|
|
|
listenTo("dev:suppress-tooltips", () => {
|
|
suppressTooltips = !suppressTooltips;
|
|
console.log("suppressing tooltips", suppressTooltips);
|
|
});
|
|
});
|
|
|
|
export function retainTooltipHover(e) {
|
|
if (lastCoordinates !== null) {
|
|
e.stopPropagation();
|
|
lastCoordinates = { x: e.pageX, y: e.pageY };
|
|
emitEvent("tooltips:show");
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
let target = document.elementFromPoint(lastCoordinates.x, lastCoordinates.y);
|
|
while (target && !target.dataset.tooltip) {
|
|
target = target.parentElement;
|
|
}
|
|
if (!target) return;
|
|
|
|
if (!target.isSameNode(activeTooltipTarget)) hideTooltip();
|
|
else return;
|
|
|
|
const dom = target.dataset.tooltip === "auto"
|
|
? createAutoTooltipDom(target)
|
|
: createTooltipDom(tooltips[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(data) {
|
|
if (!Array.isArray(data)) {
|
|
throw new DevError(`tooltip is not an array: ${data}`);
|
|
}
|
|
if (data.length === 0) {
|
|
return null;
|
|
}
|
|
const div = document.createElement("div");
|
|
div.classList.add("tooltip");
|
|
for (const row of data) {
|
|
const rowElem = document.createElement("div");
|
|
rowElem.appendChild(createTooltipNode(row));
|
|
div.appendChild(rowElem);
|
|
}
|
|
return div;
|
|
}
|
|
|
|
function createTooltipNode(tooltip) {
|
|
if (typeof tooltip === "string") {
|
|
return document.createTextNode(tooltip);
|
|
}
|
|
|
|
if (!tooltip.type) {
|
|
throw tooltip;
|
|
}
|
|
|
|
switch (tooltip.type) {
|
|
case "plain":
|
|
return createTooltipNode(tooltip.text);
|
|
|
|
case "italics": {
|
|
const elem = document.createElement("span");
|
|
elem.classList.add("italics");
|
|
elem.appendChild(createTooltipNode(tooltip.text));
|
|
return elem;
|
|
}
|
|
|
|
case "bold": {
|
|
const elem = document.createElement("span");
|
|
elem.classList.add("bold");
|
|
elem.appendChild(createTooltipNode(tooltip.text));
|
|
return elem;
|
|
}
|
|
|
|
case "item": {
|
|
const elem = document.createElement("div");
|
|
if (tooltip.prefix) {
|
|
elem.appendChild(createTooltipNode(tooltip.prefix));
|
|
}
|
|
elem.classList.add("tooltip--item");
|
|
elem.appendChild(makeAtlasImage(tooltip.atlasIndex));
|
|
elem.appendChild(createTooltipNode(tooltip.name));
|
|
break;
|
|
}
|
|
|
|
case "compound": {
|
|
const fragment = document.createDocumentFragment();
|
|
for (const item of tooltip.items) {
|
|
fragment.appendChild(createTooltipNode(item));
|
|
}
|
|
return fragment;
|
|
}
|
|
|
|
case "meta":
|
|
break;
|
|
|
|
default:
|
|
console.warn("unknown tooltip type", tooltip);
|
|
break;
|
|
}
|
|
return document.createDocumentFragment();
|
|
}
|
|
|
|
function createAutoTooltipDom(element) {
|
|
if (element.dataset.x && element.dataset.tile) {
|
|
return createTooltipDom([...getCellTooltip(element)]);
|
|
}
|
|
throw new DevError(`unknown element for auto tooltip: ${element}`);
|
|
}
|
|
|
|
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;
|
|
if (row.type === "meta") return [];
|
|
return [];
|
|
})
|
|
}
|