BlueMapWeb/src/util/Utils.js

225 lines
6.4 KiB
JavaScript

/**
* Takes a base46 string and converts it into an image element
* @param string {string}
* @returns {HTMLElement}
*/
import {MathUtils} from "three";
export const stringToImage = string => {
let image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img');
image.src = string;
return image;
};
/**
* Creates an optimized path from x,z coordinates used by bluemap to save tiles
* @param x {number}
* @param z {number}
* @returns {string}
*/
export const pathFromCoords = (x, z) => {
let path = 'x';
path += splitNumberToPath(x);
path += 'z';
path += splitNumberToPath(z);
path = path.substring(0, path.length - 1);
return path;
};
/**
* Splits a number into an optimized folder-path used to save bluemap-tiles
* @param num {number}
* @returns {string}
*/
const splitNumberToPath = num => {
let path = '';
if (num < 0) {
num = -num;
path += '-';
}
let s = num.toString();
for (let i = 0; i < s.length; i++) {
path += s.charAt(i) + '/';
}
return path;
};
/**
* Hashes tile-coordinates to be saved in a map
* @param x {number}
* @param z {number}
* @returns {string}
*/
export const hashTile = (x, z) => `x${x}z${z}`;
/**
* Dispatches an event to the element of this map-viewer
* @param element {EventTarget} the element on that the event is dispatched
* @param event {string}
* @param detail {object}
* @returns {undefined|void|boolean}
*/
export const dispatchEvent = (element, event, detail = {}) => {
if (!element || !element.dispatchEvent) return;
return element.dispatchEvent(new CustomEvent(event, {
detail: detail
}));
}
/**
* Sends a "bluemapAlert" event with a message and a level.
* The level can be anything, but the app uses the levels
* - debug
* - fine
* - info
* - warning
* - error
* @param element {EventTarget} the element on that the event is dispatched
* @param message {string}
* @param level {string}
*/
export const alert = (element, message, level = "info") => {
// alert event
let printToConsole = dispatchEvent(element, "bluemapAlert", {
message: message,
level: level
});
// log alert to console
if (printToConsole) {
if (level === "info") {
console.log(`[BlueMap/${level}]`, message);
} else if (level === "warning") {
console.warn(`[BlueMap/${level}]`, message);
} else if (level === "error") {
console.error(`[BlueMap/${level}]`, message);
} else {
console.debug(`[BlueMap/${level}]`, message);
}
}
}
/**
* Source: https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
*
* @param html {string} representing a single element
* @return {Element}
*/
export const htmlToElement = html => {
let template = document.createElement('template');
template.innerHTML = html.trim();
return template.content.firstChild;
}
/**
* Source: https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
*
* @param html {string} representing any number of sibling elements
* @return {NodeList}
*/
export const htmlToElements = html => {
let template = document.createElement('template');
template.innerHTML = html;
return template.content.childNodes;
}
/**
* Schedules an animation
* @param durationMs {number} the duration of the animation in ms
* @param animationFrame {function(progress: number, deltaTime: number)} a function that is getting called each frame with the parameters (progress (0-1), deltaTime)
* @param postAnimation {function(finished: boolean)} a function that gets called once after the animation is finished or cancelled. The function accepts one bool-parameter whether the animation was finished (true) or canceled (false)
* @returns {{cancel: function()}} the animation object
*/
export const animate = function (animationFrame, durationMs = 1000, postAnimation = null) {
let animation = {
animationStart: -1,
lastFrame: -1,
cancelled: false,
frame: function (time) {
if (this.cancelled) return;
if (this.animationStart === -1) {
this.animationStart = time;
this.lastFrame = time;
}
let progress = MathUtils.clamp((time - this.animationStart) / durationMs, 0, 1);
let deltaTime = time - this.lastFrame;
animationFrame(progress, deltaTime);
if (progress < 1) window.requestAnimationFrame(time => this.frame(time));
else if (postAnimation) postAnimation(true);
this.lastFrame = time;
},
cancel: function () {
this.cancelled = true;
if (postAnimation) postAnimation(false);
}
};
window.requestAnimationFrame(time => animation.frame(time));
return animation;
}
/**
* Returns the offset position of an element
*
* Source: https://plainjs.com/javascript/styles/get-the-position-of-an-element-relative-to-the-document-24/
*
* @param element {Element}
* @returns {{top: number, left: number}}
*/
export const elementOffset = element => {
let rect = element.getBoundingClientRect(),
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return { top: rect.top + scrollTop, left: rect.left + scrollLeft }
}
/**
* Very simple deep equals, should not be used for complex objects. Is designed for comparing parsed json-objects.
* @param object1 {object}
* @param object2 {object}
* @returns {boolean}
*/
export const deepEquals = (object1, object2) => {
if (Object.is(object1, object2)) return true;
let type = typeof object1;
if (type !== typeof object2) return false;
if (type === 'number' || type === 'boolean' || type === 'string') return false;
if (Array.isArray(object1)){
let len = object1.length;
if (len !== object2.length) return false;
for (let i = 0; i < len; i++) {
if (!deepEquals(object1[i], object2[i])) return false;
}
return true;
}
for (let property in object1) {
if (!object1.hasOwnProperty(property)) continue;
if (!deepEquals(object1[property], object2[property])) return false;
}
return true;
}