web-portals
Version:
web-portals
358 lines (323 loc) • 11.2 kB
text/typescript
'use strict'
import { Animate } from '../animate/index'
import { Application } from '../Application'
import { TransformAnimation } from './animation'
import { TransformAnimateEvent, Module } from '../types'
type TransformToParam = [
id: string,
param?: string,
history?: number,
touches?: TouchEvent
]
class TransformSwitch extends TransformAnimation {
private readonly windowSet: string[] = []
private readonly promiseQueue: (Promise<void> | undefined)[] = []
private readonly promiseParamQueue: [...TransformToParam][] = []
constructor (app: Application) {
super(app)
}
public checkSingleLock (): boolean {
return this.options.singleLock && this.module.config.level === 0 ? true : false
}
public createContainer (module: Module) {
if (module.status.init) return
const container = document.createElement('module-container')
container.setAttribute('name', this.id)
container.setAttribute('type', ['frameworks', 'system'].indexOf(this.id) !== -1 ? this.id : 'module')
module.rigesterElement('container', container)
this.resetContainer(module, this.switchover || !this.animation)
this.getViewport(module).appendChild(container)
}
resetContainer (module: Module, situ: boolean = false) {
const config = module.config
const container = module.elements.container
const systemLevel = ['frameworks', 'system'].includes(module.rel)
const isDarkModel = this.app.properties.darkTheme
container.style.cssText = ''
if (!systemLevel) {
container.style.cssText = `
position: absolute;
z-index: ${(Number(module.level) || 0) + 1};
width: 100%;
height: 100%;
background: ${config.color || (isDarkModel ? '#000' : '#fff')};
transform: ${situ ? 'translate3d(0, 0, 0)' : 'translate3d(200%, 200%, 0)'};
contain: strict;
`
}
}
public getViewport (module: Module = this.module): HTMLElement | ShadowRoot {
return module.rel === 'system' ? this.fixedViewport : module.config.free === false ? (this.relativeViewport.shadowRoot || this.relativeViewport) : (this.absoluteViewport.shadowRoot || this.absoluteViewport)
}
public checkSwitchover (modulu: Module = this.modulu, module: Module = this.module): boolean {
return modulu?.rel === 'module' && module.config.free !== modulu.config.free ? true : false
}
public async to (...args: TransformToParam): Promise<void> {
return this.pushPromise(this.next() || this.promise(...args), args)
}
private prev (): Promise<void> | undefined {
return this.promiseQueue[0]
}
private next (): Promise<void> | undefined {
const prev = this.prev()
if (prev) {
return new Promise((resolve, reject) => {
prev.then(() => {
if (this.promiseParamQueue[0]) {
this.promise(...this.promiseParamQueue[0]).then(resolve).catch(reject)
}
})
})
}
return
}
private pushPromise <P extends Promise<void>> (promise: P, param: TransformToParam): P {
this.promiseParamQueue.push(param)
this.promiseQueue.push(promise)
return promise
}
private shiftPromise () {
this.promiseParamQueue.shift()
this.promiseQueue.shift()
}
public limit (id: string) {
const limit = Math.max(this?.options?.limit || 3, 2)
const index = this.windowSet.indexOf(id)
if (this.module.rel !== 'module' || this.module.config.background === true) return
if (index !== -1) this.windowSet.splice(index, 1)
this.windowSet.push(id)
if (this.windowSet.length > limit) {
this.app.modules[this.windowSet.splice(0, 1)[0]]?.destroy()
}
}
public destroy (module: Module) {
if (module.transient && this.history === -1) {
this.app.del(module).then(() => {
const index = this.windowSet.indexOf(module.id)
this.windowSet.splice(index, 1)
})
} else {
module.fate().then(() => {
module.destroy().then(() => {
const index = this.windowSet.indexOf(module.id)
this.windowSet.splice(index, 1)
})
})
}
}
public beforehandDependencies (dependencies: string[] = []) {
const allPromise: Promise<any>[] = []
for (const dep of dependencies) {
allPromise.push(new Promise((resolve, reject) => {
this.app.get(dep).then((module) => {
module.prerender().then(resolve).catch(resolve)
}).catch(reject)
}))
}
return new Promise((resolve, reject) => {
Promise.all(allPromise).then(resolve).catch(reject)
})
}
public checkPushState (): boolean {
return this.history !== -1 && this.module.viewType !== 'portal'
}
private promise (id: string, param: string = location.search, history: number = 1, touches?: TouchEvent): Promise<void> {
return new Promise((resolve, reject) => {
const od = this.od
const ids = od ? [id, od] : [id]
const module = this.app.modules[id]
const modulu = this.app.modules[od]
const moduli = od ? [module, modulu] : [module]
if (!module) {
return this.app.get(id).then(() => {
this.promise(id, param, history).then(() => {
this.shiftPromise()
resolve()
}).catch(() => {
reject()
})
})
}
this.id = id
this.ids = ids
this.param = param
this.module = module
this.modulu = modulu
this.moduli = moduli
this.history = history
this.touches = touches
this.animation = this.getAnimationGroup()
this.switchover = this.checkSwitchover()
this.target = this.getViewport()
this.createContainer(module)
this.start().then(() => {
this.hintWillTrans(this.viewport)
if (this.checkPushState() === true) {
this.pushState(id, module.config.title, param as string)
}
module.create(!!this.animation).then(() => {
document.title = module.config.title
this.transform().then((stillness) => {
this.od = id
this.end(stillness)
this.shiftPromise()
this.limit(this.id)
resolve()
})
this.beforehandDependencies(module.config?.prerender).then(() => {
this.app.trigger('prerenderComplete')
})
})
})
})
}
public pos () {
let x = 0
let y = 0
let attach: string | Array<number> = 'center'
let origin: string | Array<number> = 'center'
const width = this.relativeViewport.offsetWidth
const height = this.relativeViewport.offsetHeight
const event = this.touches ? this.touches['srcEvent'] : {}
if (event.changedTouches) {
x = event.changedTouches[0].pageX
y = event.changedTouches[0].pageY
} else {
x = event.x
y = event.y
}
if (x && y) {
origin = [x, y]
if (x < width / 4) {
x = 0
} else if (x > width * 3 / 4) {
x = width
}
if (y < height / 4) {
y = 0
} else if (y > height * 3 / 4) {
y = height
}
attach = [x, y]
}
return {
x, y, width, height, attach, origin
}
}
get backsetState () {
return (this.moduli.length === 1 || this.module.level === this.modulu.level) ? -1 : (this.module.level > this.modulu.level ? 0 : 1)
}
get viewport (): [HTMLElement, HTMLElement] {
return this.switchover ? [
this.module.config.free === false ? this.relativeViewport : this.absoluteViewport,
this.modulu?.config.free === false ? this.relativeViewport : this.absoluteViewport
] : [
this.module.elements.container,
this.modulu?.elements.container
]
}
public modulation (callback: (stillness: boolean) => void): TransformAnimateEvent {
const viewport = this.viewport
const backset = this.backsetState
const reverse = backset === 0 ? false : true
const pos = this.pos()
const animateEvent: TransformAnimateEvent = {
x: pos.x,
y: pos.y,
in: new Animate(viewport[0]),
out: new Animate(viewport[1]),
view: viewport,
width: pos.width,
height: pos.height,
viewport: [this.relativeViewport, this.absoluteViewport],
modules: this.moduli,
reverse: reverse,
direction: reverse ? -1 : 1,
backset: backset,
origin: origin,
attach: pos.attach,
touches: this.touches,
callback: callback
}
return animateEvent
}
public transform (): Promise<boolean> {
return new Promise((resolve) => {
if (!this.animation) {
this.switchViewport()
resolve(true)
return
}
const backset = this.backsetState
const animation = this.getAnimationOneSide(backset)
const end = (stillness: boolean) => resolve(stillness)
if (!animation) return end(true)
const prosise = animation(this.modulation(end))
if (prosise instanceof Promise) prosise.then(end)
})
}
private hintWillTrans (viewport: [HTMLElement, HTMLElement]) {
viewport[0].style.willChange = 'transform, opacity'
if (viewport[1]) {
viewport[1].style.willChange = 'transform, opacity'
}
}
private removeTransHint (viewport: [HTMLElement, HTMLElement]) {
viewport[0].style.willChange = 'auto'
if (viewport[1]) {
viewport[1].style.willChange = 'auto'
}
}
public start (): Promise<void> {
return new Promise((resolve, reject) => {
this.app.trigger('transformStart', this.moduli)
const transformStart = this.module.events.transformStart
if (typeof transformStart === 'function') {
if (transformStart.call(this.module) === false) return reject()
}
if (!this.animation || this.switchover) {
this.module.elements.container.style.transform = 'translate3d(0, 0, 0)'
}
resolve()
})
}
public end (stillness: boolean = false) {
const transformEnd = this.module.events.transformEnd
this.removeTransHint(this.viewport)
if (this.switchover) {
this.switchViewport()
}
if (this.modulu) {
if (!this.animation || !stillness) {
if (this.modulu.rel === 'module') {
this.modulu.elements.container.style.transform = 'translate3d(200%, 200%, 0)'
}
this.resetContainer(this.module, true)
}
this.modulu.elements.container.style.transitionDuration = '0ms'
if (this.modulu.config.background === true) {
this.modulu.hide()
} else {
this.destroy(this.modulu)
}
}
this.module.show()
if (typeof transformEnd === 'function') {
transformEnd.call(this.module)
}
this.app.trigger('transformEnd', this.moduli)
}
public switchViewport () {
this.resetViewport()
if (this.module?.config?.free === false) {
this.absoluteViewport.style.transform = 'translate3d(200%, 200%, 0)'
this.relativeViewport.style.transform = 'translate3d(0, 0, 0)'
} else {
this.relativeViewport.style.transform = 'translate3d(200%, 200%, 0)'
this.absoluteViewport.style.transform = 'translate3d(0, 0, 0)'
}
}
}
export {
TransformSwitch
}