import { LitElement, html, css } from "lit"; import { wrapCss, IS_APP } from "./misc"; import prettyBytes from "pretty-bytes"; import { parseURLSchemeHostPath } from "./pageutils"; // =========================================================================== class Loader extends LitElement { constructor() { super(); this.progress = 0; this.total = 0; this.percent = 0; this.coll = ""; this.state = "waiting"; this.loadInfo = null; this.currentSize = 0; this.totalSize = 0; this.tryFileHandle = !!window.showOpenFilePicker; this.fileHandle = null; this.errorAllowRetry = false; this.pingInterval = 0; this.noWebWorker = false; } static get properties() { return { sourceUrl: { type: String }, loadInfo: { type: Object }, state: { type: String }, progress: { type: Number }, percent: { type: Number }, currentSize: { type: Number }, totalSize: { type: Number }, error: { type: String}, total: { type: Number }, status: { type: String }, coll: { type: String }, embed: { type: String }, tryFileHandle: { type: Boolean }, errorAllowRetry: { type: Boolean }, extraMsg: { type: String } }; } firstUpdated() { this.initMessages(); //this.doLoad(); } initMessages() { this.noWebWorker = this.loadInfo && this.loadInfo.noWebWorker; if (!this.noWebWorker) { // eslint-disable-next-line no-undef this.worker = new Worker(__SW_NAME__); } else { if (!navigator.serviceWorker) { return; } this.worker = navigator.serviceWorker; } this.worker.addEventListener("message", (event) => { switch (event.data.msg_type) { case "collProgress": if (event.data.name === this.coll) { this.percent = event.data.percent; if (event.data.error) { this.error = event.data.error; this.state = "errored"; this.errorAllowRetry = true; this.fileHandle = event.data.fileHandle; if (this.error === "missing_local_file") { this.tryFileHandle = false; } else if (this.error === "permission_needed" && event.data.fileHandle) { this.state = "permission_needed"; break; } } if (event.data.currentSize && event.data.totalSize) { this.currentSize = event.data.currentSize; this.totalSize = event.data.totalSize; } this.extraMsg = event.data.extraMsg; } break; case "collAdded": if (event.data.name === this.coll) { if (!this.total) { this.total = 100; } this.progress = this.total; this.percent = 100; this.dispatchEvent(new CustomEvent("coll-loaded", {detail: event.data})); if (!this.noWebWorker) { this.worker.terminate(); } else { if (this.pingInterval) { clearInterval(this.pingInterval); } } this.worker = null; } break; } }); } async doLoad() { let sourceUrl = this.sourceUrl; let source = null; this.percent = this.currentSize = this.totalSize = 0; // const noSWError = getSWErrorMsg(); if (this.loadInfo && this.loadInfo.swError) { this.state = "errored"; this.error = this.loadInfo.swError; this.errorAllowRetry = false; return; } // custom protocol handlers here... try { const {scheme, host, path} = parseURLSchemeHostPath(sourceUrl); switch (scheme) { case "googledrive": this.state = "googledrive"; source = await this.googledriveInit(); break; case "s3": source = {sourceUrl, loadUrl: `https://${host}.s3.amazonaws.com${path}`, name: this.sourceUrl}; break; case "file": if (!this.loadInfo && !this.tryFileHandle) { this.state = "errored"; this.error = `\ File URLs can not be entered directly or shared. You can select a file to upload from the main page by clicking the 'Choose File...' button.`; this.errorAllowRetry = false; return; } source = this.loadInfo; break; case "ipfs": if (IS_APP) { // eslint-disable-next-line no-undef const url = new URL(__APP_FILE_SERVE_PREFIX__); const hash = sourceUrl.split("#", 1)[0]; url.searchParams.set("ipfs", hash.slice("ipfs://".length)); source = {sourceUrl, loadUrl: url.href}; } break; case "proxy": sourceUrl = "proxy:" + sourceUrl.slice("proxy://".length); break; } } catch (e) { console.log(e); } if (!source) { source = {sourceUrl}; } this.state = "started"; let type = undefined; let extraConfig = undefined; if (this.loadInfo) { source.newFullImport = this.loadInfo.newFullImport; source.noCache = this.loadInfo.noCache; if (this.loadInfo.extraConfig) { extraConfig = this.loadInfo.extraConfig; } // todo: too special case? if (sourceUrl.startsWith("proxy:") && extraConfig && extraConfig.recording) { type = "recordingproxy"; } } const msg = { msg_type: "addColl", name: this.coll, extraConfig, type, skipExisting: true, file: source }; if (!navigator.serviceWorker.controller) { await new Promise((resolve) => { navigator.serviceWorker.addEventListener("controllerchange", () => resolve()); }); } if (this.worker) { if (!this.noWebWorker) { this.worker.postMessage(msg); } else { navigator.serviceWorker.controller.postMessage(msg); // ping service worker with messages to avoid shutdown while loading // (mostly for Firefox) this.pingInterval = setInterval(() => { navigator.serviceWorker.controller.postMessage({"msg_type": "ping"}); }, 15000); } } } googledriveInit() { this._gdWait = new Promise((resolve) => this._gdResolve = resolve); return this._gdWait; } async onLoadReady(event) { if (this._gdResolve) { //const digest = await digestMessage(url, 'SHA-256'); //this.coll = "id-" + digest.slice(0, 12); this._gdResolve(event.detail); } } onCancel() { if (!this.worker) { return; } const msg = {"msg_type": "cancelLoad", "name": this.coll}; if (!this.noWebWorker) { this.worker.postMessage(msg); return; } if (navigator.serviceWorker && navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage(msg); if (this.pingInterval) { clearInterval(this.pingInterval); } } } updated(changedProperties) { if (this.sourceUrl && changedProperties.has("sourceUrl") || changedProperties.has("tryFileHandle")) { this.doLoad(); } } static get styles() { return wrapCss(css` :host { height: 100%; display: flex; } .logo { width: 96px; height: 96px; margin: 1em; flex-grow: 1; } .progress-div { position: relative; width: 400px !important; } .progress-label { position: absolute; top: 0; left: 50%; transform: translateX(-50%); font-size: calc(1.5rem / 1.5); line-height: 1.5rem; } .loaded-prog { margin-bottom: 1em; } .error { white-space: pre-wrap; margin-bottom: 2em; } section.container { margin: auto; } .extra-msg { font-size: 0.8rem; } `); } render() { return html`
${!this.embed ? html`

Loading ${this.sourceUrl}...

` : ""}
${this.renderContent()}
`; } renderContent() { switch (this.state) { case "googledrive": return html``; case "started": return html`
${this.currentSize && this.totalSize ? html`
Loaded ${prettyBytes(this.currentSize)} of ${prettyBytes(this.totalSize)} ${this.extraMsg && html`

(${this.extraMsg})

`}
` : html``} ${!this.embed ? html` ` : ""}
`; case "errored": return html`
${this.error}
${this.errorAllowRetry ? html` window.parent.location.reload()}>Try Again` : ""} ${this.embed ? html`` : html` Back`}
`; case "permission_needed": return html`
Permission is needed to reload the archive file. (Click Cancel to cancel loading this archive.)
Cancel
`; case "waiting": default: return html``; } } async onAskPermission() { const result = await this.fileHandle.requestPermission({mode: "read"}); if (result === "granted") { this.doLoad(); } } } customElements.define("wr-loader", Loader); export { Loader };