514 lines
15 KiB
JavaScript
514 lines
15 KiB
JavaScript
import { Points } from '../../utils.js'
|
|
|
|
export class ObjectUtils {
|
|
/**
|
|
* Finds the previous key in an object.
|
|
*
|
|
* @param {string} targetKey - The target key, of whom the predecessor is wanted.
|
|
*/
|
|
static findPredecessor(targetKey, obj) {
|
|
// Initialize pointers ...
|
|
let prev = null
|
|
let first = null
|
|
// ... and a hit flag.
|
|
let hit = false
|
|
|
|
//Iterate over all available items.
|
|
for (let key in obj)
|
|
if (obj.hasOwnProperty(key)) {
|
|
if (!hit) {
|
|
//Assign both values,
|
|
//while not hit
|
|
prev = first
|
|
first = key
|
|
|
|
//When hit, set flag.
|
|
if (key == targetKey) {
|
|
hit = true
|
|
if (prev) return prev //If it was not hit on the first item, we can return prev.
|
|
}
|
|
} else {
|
|
//Otherwise, when first item was hit, we iterate over the entire object.
|
|
first = key
|
|
}
|
|
}
|
|
|
|
return first
|
|
}
|
|
|
|
/**
|
|
* Finds the successing key in an object.
|
|
*
|
|
* @param {string} targetKey - The key of whom the successor key should be found.
|
|
* @param {object} obj - The object, that is scanned for the successor.
|
|
*/
|
|
static findSuccessor(targetKey, obj) {
|
|
let first = null
|
|
let next = false
|
|
|
|
for (let key in obj)
|
|
if (obj.hasOwnProperty(key)) {
|
|
if (!first) first = key
|
|
|
|
if (next) {
|
|
return key
|
|
}
|
|
|
|
if (targetKey == key) {
|
|
next = true
|
|
}
|
|
}
|
|
|
|
return first
|
|
}
|
|
|
|
static fromPath(obj, path, separator = '.') {
|
|
let arr = path.split(separator)
|
|
let result = obj
|
|
|
|
for (let i = 0; i < arr.length; i++) {
|
|
if (result[arr[i]] !== null) {
|
|
result = result[arr[i]]
|
|
} else return null
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
export class PathUtils {
|
|
static fixTrailingSlash(url) {
|
|
return url.replace(/\/?$/, '/')
|
|
}
|
|
}
|
|
|
|
export class TimeUtils {
|
|
static minutesToMs(minutes) {
|
|
return TimeUtils.minutesToSeconds(minutes) * 1000
|
|
}
|
|
|
|
static minutesToSeconds(minutes) {
|
|
return minutes * 60
|
|
}
|
|
}
|
|
|
|
export class StringUtils {
|
|
/* Used from: https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript */
|
|
static capitalize(string) {
|
|
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
}
|
|
}
|
|
|
|
export class GeometryUtils {
|
|
static RandomRange(min, max) {
|
|
return GeometryUtils.toRadians(Math.random() * (max - min) + min)
|
|
}
|
|
|
|
static RandomSign() {
|
|
return Math.sign(Math.random() - 0.5)
|
|
}
|
|
|
|
static toDegrees(radians) {
|
|
return (radians * 180) / Math.PI
|
|
}
|
|
|
|
static toRadians(degrees) {
|
|
return (degrees / 180) * Math.PI
|
|
}
|
|
|
|
/*
|
|
Recursion is buggy.
|
|
*/
|
|
static subdivide(polygon, factor = 0.5, n = 0) {
|
|
console.log(polygon.length, factor)
|
|
let points = []
|
|
for (let i = 0; i < polygon.length; i++) {
|
|
let prev = i - 1 < 0 ? polygon[polygon.length - 1] : polygon[i - 1]
|
|
let next = i + 1 < polygon.length ? polygon[i + 1] : polygon[0]
|
|
let current = polygon[i]
|
|
points.push(GeometryUtils.linearInterpolation(prev, current, factor))
|
|
points.push(GeometryUtils.linearInterpolation(current, next, 1 - factor))
|
|
}
|
|
if (n > 0) {
|
|
n--
|
|
points = GeometryUtils.subdivide(points, n)
|
|
}
|
|
return points
|
|
}
|
|
|
|
static linearInterpolation(first, second, n) {
|
|
let point = new PIXI.Point()
|
|
|
|
point.x = first.x * n + second.x * (1 - n)
|
|
point.y = first.y * n + second.y * (1 - n)
|
|
return point
|
|
}
|
|
}
|
|
|
|
export class Matrix {
|
|
constructor(a, b, d, e, c = 0, f = 0) {
|
|
Object.assign(this, {
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f,
|
|
g: 0,
|
|
h: 0,
|
|
i: 1
|
|
})
|
|
}
|
|
|
|
static Transpose(m) {
|
|
let first = ['b', 'c', 'f']
|
|
let second = ['d', 'g', 'h']
|
|
|
|
for (let i = 0; i < first.length; i++) {
|
|
let tmp = m[first[i]]
|
|
first[i] = second[i]
|
|
second[i] = tmp
|
|
}
|
|
}
|
|
|
|
static Rotation(deg) {
|
|
let rad = (2 * Math.PI * deg) / 180
|
|
return new Matrix(Math.cos(rad), -Math.sin(rad), Math.sin(rad), Math.cos(rad))
|
|
}
|
|
|
|
static Transform(x, y) {
|
|
return new Matrix(1, 0, 0, 1, x, y)
|
|
}
|
|
|
|
static MultiplyPoint(m, p) {
|
|
x = p.x * m.a + m.b * p.y + m.c * 1
|
|
y = p.x * m.d + p.y * m.e + m.f * 1
|
|
|
|
return {
|
|
x,
|
|
y
|
|
}
|
|
}
|
|
}
|
|
|
|
export class DomUtils {
|
|
static getCenter(element) {
|
|
let x = (element.clientLeft + element.clientWidth) / 2
|
|
let y = (element.clientTop + element.clientHeight) / 2
|
|
return { x, y }
|
|
}
|
|
|
|
static getTransformedPosition(element) {
|
|
let mat = this.getCSSMatrix(element)
|
|
return { x: mat[4], y: mat[5] }
|
|
}
|
|
|
|
static getCSSMatrix(element) {
|
|
let matrix = window.getComputedStyle(element).transform
|
|
|
|
if (matrix == 'none') {
|
|
return [1, 0, 0, 1, 0, 0]
|
|
} else {
|
|
const pre = 'matrix('
|
|
let values = matrix.substring(pre.length, matrix.length - 1).split(',')
|
|
for (let i in values) {
|
|
values[i] = parseFloat(values[i])
|
|
}
|
|
return values
|
|
}
|
|
}
|
|
|
|
static positionOnElement(element, position) {
|
|
const matrix = this.getCSSMatrix(element)
|
|
|
|
const inX = element.offsetWidth * (position.x - 0.5)
|
|
const inY = element.offsetHeight * (position.y - 0.5)
|
|
|
|
let outX = matrix[0] * inX + matrix[2] * inY + matrix[4]
|
|
let outY = matrix[1] * inX + matrix[3] * inY + matrix[5]
|
|
|
|
let out = {
|
|
x: outX,
|
|
y: outY
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
static applyTransform(target, transformedPosition, size = 0) {
|
|
return {
|
|
x: target.offsetLeft + target.offsetWidth / 2 + transformedPosition.x - size / 2,
|
|
y: target.offsetTop + target.offsetHeight / 2 + transformedPosition.y - size / 2
|
|
}
|
|
}
|
|
}
|
|
|
|
export class Vector {
|
|
static length(vector) {
|
|
return Math.sqrt(vector.x * vector.x + vector.y * vector.y)
|
|
}
|
|
|
|
static normalize(vector) {
|
|
return Points.multiplyScalar(vector, 1 / this.length(vector))
|
|
}
|
|
|
|
static scaleTo(vector, length) {
|
|
let normalized = this.normalize(vector)
|
|
return Points.multiplyScalar(normalized, length)
|
|
}
|
|
}
|
|
|
|
export class PIXIUtils {
|
|
/*
|
|
* Transform a pixi text to it's actual screensize,
|
|
* ignoring it's local transforms
|
|
*/
|
|
static toScreenFontSize(pixiText, fontSize = null) {
|
|
pixiText._recursivePostUpdateTransform();
|
|
|
|
let normalizedScale = {
|
|
x: pixiText.scale.x / pixiText.transform.worldTransform.a,
|
|
y: pixiText.scale.x / pixiText.transform.worldTransform.d
|
|
};
|
|
|
|
pixiText.scale = { x: normalizedScale.x, y: normalizedScale.y };
|
|
if (fontSize) pixiText.style.fontSize = fontSize;
|
|
}
|
|
|
|
static saveFill(graphics) {
|
|
return {
|
|
fill: graphics.fill.color,
|
|
alpha: graphics.fill.alpha
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The EventHandler class is used to take care of a event based design
|
|
* pattern. Callbacks can subscribe to an event and these unknown sources
|
|
* get notified whenever the event changes.
|
|
*
|
|
* @export
|
|
* @class EventHandler
|
|
*/
|
|
export class EventHandler {
|
|
/**
|
|
* Creates an instance of EventHandler.
|
|
* @param {any} name
|
|
* @param {any} [{
|
|
* listeners = [] - With the listnerers parameter the user can specify a function, array of functions or null (no function - useful when used in constructor with optional parameter).
|
|
* }={}]
|
|
* @memberof EventHandler
|
|
*/
|
|
constructor(name, { listeners = [] } = {}) {
|
|
this.name = name
|
|
this.listeners = []
|
|
this.onces = []
|
|
|
|
/**
|
|
* One may initialize the eventListener using a parameter
|
|
* that is either passed or null.
|
|
*/
|
|
|
|
if (listeners == null) {
|
|
// Null is a valid value as the EventHandler assumes no listener is passed on purpose.
|
|
// This is useful, when a default parameter is passed as null.
|
|
} else if (Array.isArray(listeners)) this.listeners = listeners
|
|
else if (typeof listeners == 'function') {
|
|
this.listeners = []
|
|
this.add(listeners)
|
|
} else {
|
|
console.warn(
|
|
"The provided 'listeners' is neither an Array of functions, nor a function. No eventcallback was added!",
|
|
listeners,
|
|
this
|
|
)
|
|
}
|
|
}
|
|
|
|
addMultiple(...callbacks) {
|
|
for (let callback of callbacks) {
|
|
this.listeners.push(callback)
|
|
}
|
|
}
|
|
|
|
add(callback) {
|
|
this.listeners.push(callback)
|
|
return callback
|
|
}
|
|
|
|
once(callback) {
|
|
this.onces.push(callback)
|
|
}
|
|
|
|
remove(func) {
|
|
for (const [idx, listener] of this.listeners.entries()) {
|
|
if (listener === func) {
|
|
this.listeners.splice(idx, 1)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
empty() {
|
|
this.listeners = []
|
|
}
|
|
|
|
call(context, ...args) {
|
|
if (context == null) {
|
|
this.listeners.forEach(listener => listener(...args))
|
|
this.onces.forEach(listener => listener(...args))
|
|
} else {
|
|
this.listeners.forEach(listener => listener.call(context, ...args))
|
|
this.onces.forEach(listener => listener.call(context, ...args))
|
|
}
|
|
|
|
this.onces = []
|
|
}
|
|
|
|
get length() {
|
|
return this.listeners.length + this.onces.length
|
|
}
|
|
}
|
|
|
|
export class Dom {
|
|
/**
|
|
* Popups should be displayed right over the text.
|
|
* Normally we would expect the popup to appear right over
|
|
* the center of the text. A problem in HTML is, that it's hard
|
|
* to determine the position of a text link, when it has a line-break
|
|
* in it.
|
|
*
|
|
* This function solves this problem in the (so far) only possible way.
|
|
*
|
|
* 1. It removes the link from the dom tree.
|
|
* 2. It adds an empty copy A of the link to the dom tree. (Copy is important, as the same styles have to be applied.)
|
|
* 3. The contents of the link are added one by one to A.
|
|
* 4. If the resulting boundingRect is bigger than the previous one, a line break is detected.
|
|
* 5. The old line is tested, if the point was inside that boundingBox. If so save that bounding box (Goto: 7), else:
|
|
* 6. Saves the content to a preceding clone B. And repeats from 3.
|
|
* 7. Replace A with the initial content
|
|
* 8. Return the found BoundingBox. If none found. Return the last bounding box.
|
|
*/
|
|
static getTextHitRect(link, point) {
|
|
// We cannot use it as it produces axis aligned bounding boxes
|
|
/* if (true) {
|
|
let rects = link.getClientRects()
|
|
let target = null
|
|
for (let [idx, rect] of Object.entries(rects)) {
|
|
target = rect
|
|
if (Rect.contains(rect, point))
|
|
break
|
|
}
|
|
return target
|
|
|
|
} else {*/
|
|
let processedText = link.cloneNode(true)
|
|
let content = processedText.innerHTML
|
|
let words = content.split(/ /g)
|
|
processedText.innerHTML = ''
|
|
event.target.innerHTML = ''
|
|
// let lineRect = event.target.getBoundingClientRect()
|
|
let local = Points.fromPageToNode(event.target.parentNode, point)
|
|
console.log(local)
|
|
|
|
let target = event.target
|
|
let height = 0
|
|
|
|
while (words.length > 0) {
|
|
let word = words.pop()
|
|
target.innerHTML += word + ' '
|
|
if (target.height != height) {
|
|
// New line was reached.
|
|
console.log('NEW LINE WAS REACHED!')
|
|
}
|
|
}
|
|
|
|
return {
|
|
top: 0,
|
|
left: 0,
|
|
right: 100,
|
|
bottom: 100,
|
|
width: 100,
|
|
height: 100
|
|
}
|
|
|
|
// let total = words.length
|
|
// while (words.length > 0) {
|
|
// let lastRect = lineRect
|
|
// let lastContent = event.target.innerHTML
|
|
// let added = words.length == total ? "" : " "
|
|
// added += words.shift()
|
|
// event.target.innerHTML += added
|
|
// lineRect = event.target.getBoundingClientRect()
|
|
|
|
// // When new line or last line:
|
|
// if (lineRect.height != lastRect.height) {
|
|
// //Reconstructure last line.
|
|
// event.target.innerHTML = lastContent
|
|
|
|
// //Create Rect from last line.
|
|
// lineRect = event.target.getBoundingClientRect()
|
|
// rects.push(lineRect)
|
|
|
|
// //Copy last line content to processed text.
|
|
// processedText.innerHTML += lastContent
|
|
|
|
// // Create content of new line.
|
|
// event.target.innerHTML = added
|
|
|
|
// if (!processedText.parentNode) event.target.parentNode.insertBefore(processedText, event.target)
|
|
|
|
// if (Rect.contains(lineRect, point)) {
|
|
// break
|
|
// }
|
|
// }
|
|
|
|
// if (words.length == 0) {
|
|
// lineRect = event.target.getBoundingClientRect()
|
|
// processedText.innerHTML += event.target.innerHTML
|
|
// rects.push(lineRect)
|
|
// }
|
|
// }
|
|
|
|
// event.target.innerHTML = content
|
|
// if (processedText.parentNode) processedText.parentNode.removeChild(processedText)
|
|
|
|
// return lineRect
|
|
// }
|
|
}
|
|
|
|
static printDomRect(rect, { color = 'red', pad = 0, parent = document.body }) {
|
|
let element = document.createElement('div')
|
|
Object.assign(element.style, {
|
|
padding: pad + 'px',
|
|
borderColor: color,
|
|
borderWidth: '2px',
|
|
borderStyle: 'solid',
|
|
top: rect.top + 'px',
|
|
left: rect.left + 'px',
|
|
width: rect.width + 'px',
|
|
height: rect.height + 'px',
|
|
position: 'absolute',
|
|
zIndex: 10000,
|
|
opacity: 0.3
|
|
})
|
|
parent.appendChild(element)
|
|
}
|
|
|
|
static printDomPoint(point, { color = 'red', parent = document.body }) {
|
|
let element = document.createElement('div')
|
|
Object.assign(element.style, {
|
|
position: 'absolute',
|
|
top: point.y + 'px',
|
|
left: point.x + 'px',
|
|
width: '10px',
|
|
height: '10px',
|
|
backgroundColor: color,
|
|
zIndex: 10000
|
|
})
|
|
parent.appendChild(element)
|
|
}
|
|
}
|