Source: frontend/src/module/routing.js

/** @module routing */

/**
 * A bare-bones history-api based SPA router.
 */
class Router {
    /**
     * Arguments for the current route.
     * @type {undefined|*}
     */
    route_args = undefined

    /** The currently navigated route. */
    current_route = undefined

    /**
     * List of callback functions listening for route changes.
     * @type {function[]}
     */
    subscribers = []

    /**
     * Array of router history records.
     * @type {object[]}
     */
    history = []

    /** Initialize the router and determine the current page. */
    constructor() {
        try {
            const route = location.href.split(APP_BASE_PATH).filter(Boolean)[0].split(/[#?]/)[0]
            if ( route && !route.toLowerCase().includes('http://') && !route.toLowerCase().includes('https://') ) {
                console.log('resume route', route)
                this.navigate(route, {})
            }
        } catch (e) {}
    }


    /**
     * Returns the APP_BASE_PATH of the application.
     * @return {string}
     */
    get base_path() {
        return APP_BASE_PATH
    }

    /**
     * Navigate the app to the given path with the given args.
     * @param {string} path
     * @param {*} args
     */
    navigate(path, args) {
        this.route_args = args
        this.history.push({path, args})
        window.history.pushState({}, path, this.build_url(path))
        this.current_route = path
        this.subscribers.forEach(sub => sub(path, args))
    }

    /**
     * Navigate back one route.
     */
    back() {
        window.history.back()
        if ( this.history.length < 2 ) return;
        this.history.pop()
        const { path, args } = this.history[this.history.length - 1]
        this.subscribers.forEach(sub => sub(path, args))
    }

    /**
     * Subscribe to listen for route changes. Returns an object with an unsubscribe() property.
     * @param {function} handler - callback called when the route changes
     * @return {object} - subscription manager
     */
    subscribe(handler) {
        if ( !this.subscribers.includes(handler) ) {
            this.subscribers.push(handler)
        }

        return {
            unsubscribe: () => {
                this.subscribers = this.subscribers.filter(handler)
            }
        }
    }

    /**
     * Given an array of route parts, build a joined URL route.
     * @param {...string} parts
     * @return {string}
     */
    build_url(...parts) {
        parts = [this.base_path, ...parts].map(part => {
            if ( part.endsWith('/') ) part = part.slice(0, -1)
            if ( part.startsWith('/') ) part = part.slice(1)
            return part
        })

        return parts.join('/')
    }
}

/**
 * Global router instance.
 * @type {Router}
 */
const router = new Router()
export { router }