import { LitElement, html, css } from 'lit-element'; 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 = ""; } 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 } } } firstUpdated(props) { this.initMessages(); //this.doLoad(); } initMessages() { if (!navigator.serviceWorker) { return; } navigator.serviceWorker.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; } } break; case "collAdded": if (event.data.name === this.coll) { if (!this.total) { this.total = 100; } this.progress = this.total; this.percent = 100; if (this.pingInterval) { clearInterval(this.pingInterval); } this.dispatchEvent(new CustomEvent("coll-loaded", {detail: event.data})); } break; } }); } async doLoad() { let sourceUrl = this.sourceUrl; let source = null; this.percent = this.currentSize = this.totalSize = 0; if (!navigator.serviceWorker) { this.state = "errored"; if (window.location.protocol === "http:") { this.error = `\ Sorry, the ReplayWeb.page system must be loaded from an HTTPS URL, but was loaded from: ${window.location.host}. Please try loading this page from an HTTPS URL`; } else { this.error = "Sorry, this browser is not supported. Please try a different browser\n(If you're using Firefox, try without Private Mode)"; } 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) { 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; } } catch (e) { console.log(e); } if (!source) { source = {sourceUrl}; } this.state = "started"; const msg = {"msg_type": "addColl", "name": this.coll, skipExisting: true, file: source}; if (this.loadInfo && this.loadInfo.extraConfig) { msg.extraConfig = this.loadInfo.extraConfig; } if (!navigator.serviceWorker.controller) { await new Promise((resolve) => { navigator.serviceWorker.addEventListener("controllerchange", () => resolve); }); } 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 (navigator.serviceWorker && navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({"msg_type": "cancelLoad", "name": this.coll}); 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; } `); } 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)}

` : 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 };