Source: pages/pages.js

/**
 * @namespace qui.pages
 */

import $ from '$qui/lib/jquery.module.js'

import {AssertionError} from '$qui/base/errors.js'
import * as GlobalGlass from '$qui/global-glass.js'
import * as OptionsBar  from '$qui/main-ui/options-bar.js'
import {asap}           from '$qui/utils/misc.js'
import * as Window      from '$qui/window.js'

import PagesContext     from './pages-context.js'
import * as Breadcrumbs from './breadcrumbs.js'


let pagesContainer = null
let currentContext = null


/* Page context */

function newContext() {
    let context = new PagesContext()

    context.pushSignal.connect(function (page) {
        /* Whenever a page is added to a context, attach our UI handlers to it */
        attachPageHTMLHandlers(page)
    })

    context.popSignal.connect(function (page) {
        /* Whenever a page is removed from a context, detach our UI handlers from it */
        detachPageHTMLHandlers(page)
    })

    return context
}

/**
 * Return the current pages context.
 * @alias qui.pages.getCurrentContext
 * @returns {qui.pages.PagesContext}
 */
export function getCurrentContext() {
    return currentContext
}

/**
 * Replace the current pages context.
 * @alias qui.pages.setCurrentContext
 * @param {?qui.pages.PagesContext} context new context, or `null` to replace with a brand-new context
 */
export function setCurrentContext(context) {
    let pagesToDetach = currentContext.getPages()
    let currentPage = currentContext.getCurrentPage()
    if (currentPage) {
        currentPage.handleLeaveCurrent()
    }

    /* Hide & detach pages */
    pagesToDetach.forEach(function (page) {
        page.detach()
    })

    if (!context) {
        context = newContext()
    }

    /* Restore the context */
    currentContext = context

    /* Attach new pages */
    currentContext.getPages().forEach(function (page) {
        page.attach()
    })

    currentPage = currentContext.getCurrentPage()
    if (currentPage) {
        currentPage.handleBecomeCurrent()
    }

    updateUI()
}

/**
 * Return the current page.
 * @alias qui.pages.getCurrentPage
 * @returns {?qui.pages.PageMixin}
 */
export function getCurrentPage() {
    return currentContext.getCurrentPage()
}


/* Pages UI */

function updatePagesVisibility() {
    /* Reset page classes/styles */
    currentContext.getPages().forEach(function (page) {
        page.getPageHTML().removeClass('visible current').css({width: '', left: ''})
    })

    /* Hide pages that are still part of the DOM but no longer part of current context */
    $('div.qui-page').not('.sticky').removeClass('visible current').css({width: '', left: ''})

    if (!currentContext.getSize()) {
        GlobalGlass.updateVisibility()
        return
    }

    let currentPage = currentContext.getCurrentPage()
    currentPage.getPageHTML().addClass('current')

    let visiblePages = currentContext.getVisiblePages()
    let columnPages = visiblePages.filter(page => page.isColumnLayout())

    /* The minimum accepted width is 25% (on a large screen) */
    let expandWidth = 100 - columnPages.length * 25 /* As percent */
    let left = 0
    visiblePages.reverse().forEach(function (page) {
        let html = page.getPageHTML()
        html.addClass('visible')

        let l
        let w = null
        /* The current popup page has special treatment and does not contribute to expanded width */
        if (page.isPopup() && page === currentPage) {
            l = 0
        }
        else {
            l = left
            if (!page.isColumnLayout()) { /* Expand */
                w = expandWidth
                left += expandWidth
            }
            else {
                left += 25
            }
        }

        if (l != null) {
            html.css('left', `${l}%`)
        }
        if (w != null) {
            html.css('width', `${w}%`)
        }
    })

    pagesContainer.toggleClass('full', (left >= 100) || Window.isSmallScreen())

    GlobalGlass.updateVisibility()
}

function updateContentScroll() {
    /* Scrolled content */
    let scrolled = currentContext.getPages().some(function (p) {
        if (!p.isVisible()) {
            return false
        }

        return p.getPageHTML()[0].scrollTop !== 0

    })
    Window.$body.toggleClass('content-scrolled', scrolled)
}

function handlePageScroll() {
    updateContentScroll()

    let $this = $(this)
    let page = $this.data('page')
    if (!page) {
        throw AssertionError('page scroll event from a non-page HTML element')
    }

    page.handleVertScroll()
}

function triggerPageResize() {
    asap(function () {
        currentContext.getPages().forEach(function (page) {
            page.handleResize()
        })
    })
}

function triggerPageResizeOnTransitionEnd(e) {
    if (e.target !== this) {
        return
    }

    if (e.originalEvent.propertyName !== 'width') {
        return
    }

    triggerPageResize()
}

function attachPageHTMLHandlers(page) {
    page.getPageHTML().on('scroll', handlePageScroll)
    page.getPageHTML().on('transitionend', triggerPageResizeOnTransitionEnd)
}

function detachPageHTMLHandlers(page) {
    page.getPageHTML().off('scroll', handlePageScroll)
    page.getPageHTML().off('transitionend', triggerPageResizeOnTransitionEnd)
}

/**
 * Return the page container HTML element.
 * @alias qui.pages.getPagesContainer
 * @returns {jQuery}
 */
export function getPagesContainer() {
    return pagesContainer
}

/**
 * Update all UI elements depending on current page stack.
 * @alias qui.pages.updateUI
 */
export function updateUI() {
    /* Use asap() because some pages might have just been attached to DOM and animations won't probably work */
    asap(function () {
        Breadcrumbs.update()
        updatePagesVisibility()
        updateContentScroll()
    })
}


export function init() {
    pagesContainer = $('div.qui-pages-container')
    currentContext = newContext()

    Breadcrumbs.init()

    Window.resizeSignal.connect(function (width, height) {
        triggerPageResize()
    })

    OptionsBar.openCloseSignal.connect(function (opened) {
        /* Update _optionsBarOpen flag of current page */

        let currentPage = currentContext.getCurrentPage()
        if (!currentPage) {
            return
        }

        currentPage._optionsBarOpen = opened
    })
}