diff --git a/src/components/App/client-api.ts b/src/components/App/client-api.ts index f1ccfc2b..b54912ad 100644 --- a/src/components/App/client-api.ts +++ b/src/components/App/client-api.ts @@ -2,14 +2,16 @@ import App from './index'; import { expose } from 'comlink'; +const API_VERSION = 1; + export function exposeAPI(app: App) { - self.parent.postMessage('READY', '*'); + self.parent.postMessage({ type: 'READY', version: API_VERSION }, '*'); self.addEventListener('message', (ev: MessageEvent) => { if (ev.data !== 'READY?') { return; } ev.stopPropagation(); - self.parent.postMessage('READY', '*'); + self.parent.postMessage({ type: 'READY', version: API_VERSION }, '*'); }); expose(new API(app), self.parent); } @@ -18,11 +20,13 @@ class API { constructor(private app: App) { } async setFile(blob: Blob, name: string) { - await new Promise((resolve) => { - this.app.setState({ file: new File([blob], name) }, resolve); - }); - await new Promise((resolve) => { - document.addEventListener('squooshingdone', resolve, { once: true }); + return new Promise(async (resolve) => { + document.addEventListener( + 'squoosh:processingstart', + () => resolve(), + { once: true }, + ); + this.app.openFile(new File([blob], name)); }); } @@ -30,8 +34,41 @@ class API { if (!this.app.state.file || !this.app.compressInstance) { throw new Error('No file has been loaded'); } + if ( + !this.app.compressInstance!.state.loading && + !this.app.compressInstance!.state.sides[side].loading + ) { + return this.app.compressInstance!.state.sides[side].file; + } - await this.app.compressInstance.compressionJobs[side]; - return this.app.compressInstance.state.sides[side].file; + return new Promise((resolve, reject) => { + document.addEventListener( + 'squoosh:processingdone', + (ev) => { + if ((ev as CustomEvent).detail.side !== side) { + return; + } + resolve(this.app.compressInstance!.state.sides[side].file); + }, + ); + document.addEventListener( + 'squoosh:processingabort', + (ev) => { + if ((ev as CustomEvent).detail.side !== side) { + return; + } + reject('aborted'); + }, + ); + document.addEventListener( + 'squoosh:processingerroor', + (ev) => { + if ((ev as CustomEvent).detail.side !== side) { + return; + } + reject((ev as CustomEvent).detail.msg); + }, + ); + }); } } diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 7d4e3ded..a0703673 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -73,6 +73,11 @@ export default class App extends Component { import('./client-api').then(m => m.exposeAPI(this)); } + @bind openFile(file: File | Fileish) { + this.openEditor(); + this.setState({ file }); + } + @bind private onFileDrop({ files }: FileDropEvent) { if (!files || files.length === 0) return; @@ -83,8 +88,7 @@ export default class App extends Component { @bind private onIntroPickFile(file: File | Fileish) { - this.openEditor(); - this.setState({ file }); + return this.openFile(file); } @bind diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx index c84fe7d7..bc470854 100644 --- a/src/components/compress/index.tsx +++ b/src/components/compress/index.tsx @@ -205,7 +205,6 @@ const originalDocumentTitle = document.title; export default class Compress extends Component { widthQuery = window.matchMedia('(max-width: 599px)'); - compressionJobs: [Promise, Promise] = [Promise.resolve(), Promise.resolve()]; state: State = { source: undefined, @@ -306,7 +305,7 @@ export default class Compress extends Component { // The image only needs updated if the encoder/preprocessor settings have changed, or the // source has changed. if (sourceDataChanged || encoderChanged || preprocessorChanged) { - this.compressionJobs[i] = this.updateImage(i, { + this.queueUpdateImage(i, { skipPreprocessing: !sourceDataChanged && !preprocessorChanged, }); } @@ -396,8 +395,8 @@ export default class Compress extends Component { // Either processor is good enough here. const processor = this.leftProcessor; - this.setState({ loadingCounter, loading: true }); - + this.setState({ loadingCounter, loading: true }, this.signalProcessingStart); + // this.signalProcessingStart(); // Abort any current encode jobs, as they're redundant now. this.leftProcessor.abortCurrent(); this.rightProcessor.abortCurrent(); @@ -445,7 +444,9 @@ export default class Compress extends Component { this.updateDocumentTitle(file.name); this.setState(newState); } catch (err) { - if (err.name === 'AbortError') return; + if (err.name === 'AbortError') { + return; + } console.error(err); // Another file has been opened/processed before this one processed. if (this.state.loadingCounter !== loadingCounter) return; @@ -467,7 +468,8 @@ export default class Compress extends Component { this.updateImageTimeoutIds[index] = self.setTimeout( () => { - this.updateImage(index, options).catch((err) => { + this.updateImage(index, options) + .catch((err) => { console.error(err); }); }, @@ -475,6 +477,31 @@ export default class Compress extends Component { ); } + @bind + private dispatchCustomEvent(name: string, detail?: {}) { + (this.base || document).dispatchEvent(new CustomEvent(name, { detail, bubbles: true })); + } + + @bind + private signalProcessingStart() { + this.dispatchCustomEvent('squoosh:processingstart'); + } + + @bind + private signalProcessingDone(side: 0|1) { + this.dispatchCustomEvent('squoosh:processingdone', { side }); + } + + @bind + private signalProcessingAbort(side: 0|1) { + this.dispatchCustomEvent('squoosh:processingabort', { side }); + } + + @bind + private signalProcessingError(side: 0|1, msg: string) { + this.dispatchCustomEvent('squoosh:processingerror', { side, msg }); + } + private async updateImage(index: number, options: UpdateImageOptions = {}): Promise { const { skipPreprocessing = false, @@ -490,9 +517,7 @@ export default class Compress extends Component { loading: true, }); - this.setState({ sides }, () => this.base!.dispatchEvent( - new CustomEvent('squooshingdone', { bubbles: true }), - )); + this.setState({ sides }); const side = sides[index]; const settings = side.latestSettings; @@ -538,8 +563,13 @@ export default class Compress extends Component { }); } } catch (err) { - if (err.name === 'AbortError') return; - this.props.showSnack(`Processing error (type=${settings.encoderState.type}): ${err}`); + if (err.name === 'AbortError') { + this.signalProcessingAbort(index as 0 | 1); + return; + } + const errorMsg = `Processing error (type=${settings.encoderState.type}): ${err}`; + this.signalProcessingError(index as 0|1, errorMsg); + this.props.showSnack(errorMsg); throw err; } } @@ -562,7 +592,7 @@ export default class Compress extends Component { encodedSettings: settings, }); - this.setState({ sides }); + this.setState({ sides }, () => this.signalProcessingDone(index as 0|1)); } render({ onBack }: Props, { loading, sides, source, mobileView }: State) {