UNPKG

iportal

Version:

web-portal

212 lines (197 loc) 6.66 kB
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 }