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)
 | 
						|
    }
 | 
						|
}
 |