iportal
Version:
web-portal
212 lines (197 loc) • 6.66 kB
text/typescript
import { ModulePrefetch } from './prefetch'
import { Sandbox } from '../Sandbox'
import { ModuleManifest, ModuleConfig, Application } from '../types'
import inject from './inject'
class ModuleView extends ModulePrefetch {
constructor (id: string, model: ModuleManifest, application: Application) {
super(id, model, application)
}
public attach (element: HTMLElement) {
if (!element) return
this.status.init = true
this.elements.container = element
this.addPanMoveHolder(element)
}
private addPanMoveHolder (container: HTMLElement) {
const touchBorder = this.config.touchBorder
if (!touchBorder) return
const threshold = 12
const topHolder = document.createElement('div')
const rightHolder = document.createElement('div')
const bottomHolder = document.createElement('div')
const leftHolder = document.createElement('div')
const mainHolder = document.createElement('div')
topHolder.style.cssText = `position: absolute; top: 0; right: 0; left: 0; z-index: 3; height: ${threshold}px;`
rightHolder.style.cssText = `position: absolute; top: 0; right: 0; bottom: 0; z-index: 3; width: ${threshold}px;`
bottomHolder.style.cssText = `position: absolute; right: 0; bottom: 0; left: 0; z-index: 3; height: ${threshold}px;`
leftHolder.style.cssText = `position: absolute; top: 0; bottom: 0; left: 0; z-index: 3; width: ${threshold}px;`
mainHolder.style.cssText = `display: none; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 4;`
container.appendChild(topHolder)
container.appendChild(rightHolder)
container.appendChild(bottomHolder)
container.appendChild(leftHolder)
container.appendChild(mainHolder)
touchBorder({
topHolder,
rightHolder,
bottomHolder,
leftHolder,
mainHolder
}, this, this.application)
}
private createSandbox (uri?: string, config?: ModuleConfig['sandbox']): HTMLIFrameElement {
this.sandbox = new Sandbox(uri, config)
return this.sandbox.sandbox
}
private createShadowbox (render: (target: HTMLElement) => void): HTMLElement {
const shadowbox = document.createElement('shadow-view')
const shadowboxInner = document.createElement('shadow-inner')
shadowbox?.attachShadow({ mode: 'open' })
const shadowRoot = shadowbox.shadowRoot ? shadowbox.shadowRoot : shadowbox
shadowRoot.appendChild(shadowboxInner)
render(shadowboxInner)
this.elements.container.appendChild(shadowbox)
return shadowbox
}
private createPortals (uri: string): HTMLPortalElement {
const portal = document.createElement('portal') as HTMLPortalElement
portal.src = uri
return portal
}
private createView (): HTMLElement | HTMLPortalElement | HTMLIFrameElement | undefined {
if (this.view) return this.view
if (this.config.render) {
return this.view = this.createShadowbox(this.config.render)
}
if (this.rel !== 'module') return
if (this.viewType === 'portal') {
this.view = this.createPortals(this.uri)
} else {
this.view = this.createSandbox(this.uri, this.config.sandbox)
}
return this.view
}
private loadContent () {
const container = this.elements.container
const shadowView = this.view
if (!shadowView) return
switch (this.viewType) {
case 'portal':
container.appendChild(shadowView as HTMLPortalElement)
break
case 'iframe':
default:
if (!this.sandbox) {
if (this.rel === 'frameworks') {
inject(window, this)
}
return
}
if (this.uri) {
this.sandbox.enter(container)
const contentWindow = (shadowView as HTMLIFrameElement).contentWindow
if (contentWindow && this.sameOrigin) {
inject(contentWindow?.window, this)
contentWindow?.window.postMessage({
type: 'container-ready'
}, '*')
}
} else {
this.sandbox.append(this.config?.source?.html, container)
const update = this.loadContent.bind(this)
this.sandbox.unload = update
inject(this.sandbox.contentWindow?.window, this)
}
break
}
}
public mountPresetView () {
const view = window?.__application_preset?.modules?.[this.id]
if (view) {
this.view = view
this.elements.container.appendChild(view)
delete window?.__application_preset?.[this.id]
}
}
public create (prepare = true): Promise<Event | string> {
this.unfate()
this.timeTick()
this.mountPresetView()
if (this.status.prerender) {
return Promise.resolve('prerender')
}
return new Promise((resolve, reject) => {
this.createTime = Date.now()
const shadowView = this.createView()
if (!shadowView) {
return resolve('null')
}
if (this.viewType === 'shadow') {
return resolve('shadow')
}
shadowView.style.cssText = `
position: absolute;
z-index: 0;
width: 100%;
height: 100%;
border: 0;
transform: translate3d(0, 0, 0);
`
shadowView.onload = (e) => {
this.status.prerender = true
this.events?.load.bind(this)()
this.trigger('load')
setTimeout(() => {
resolve(e)
}, 50)
}
shadowView.onerror = (e) => {
this.events?.loadError.bind(this)()
this.trigger('loadError')
reject(e)
}
this.loadContent()
if (prepare === false || this.config.animation === false) {
return resolve('instant')
}
setTimeout(() => {
resolve('timeout')
}, this.status.preload ? 800 : 200)
})
}
public preload (): Promise<Event> {
return new Promise((resolve, reject) => {
const uri = this.uri
const head = document.head
const sandbox = new Sandbox(uri, '')
sandbox.onload = (e) => {
this.status.preload = true
sandbox.exit()
this.events?.preload.bind(this)()
this.trigger('preload')
resolve(e)
}
sandbox.onerror = (e) => {
sandbox.exit()
reject(e)
}
if (uri) {
sandbox.enter(head)
} else {
sandbox.append(this.config?.source?.html, head)
}
})
}
public prerender (): Promise<Event | void> {
if (this.status.preload || this.status.prerender) {
return Promise.resolve()
}
return new Promise((resolve, reject) => {
this.preload().then(resolve).catch(reject)
this.beforehandLink(this.uri, 'prerender', 'document')
})
}
}
export {
ModuleView
}