From 3315188bb387895bf695f16d6fa062a3eb86719a Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 11 Feb 2019 15:48:31 +0000 Subject: [PATCH] Jake code review --- src/components/App/client-api.ts | 60 +++++++++++++++++-------------- src/components/compress/index.tsx | 53 ++++++++++++++++++++------- 2 files changed, 73 insertions(+), 40 deletions(-) diff --git a/src/components/App/client-api.ts b/src/components/App/client-api.ts index 3f3dc9bc..e093d3f5 100644 --- a/src/components/App/client-api.ts +++ b/src/components/App/client-api.ts @@ -1,5 +1,5 @@ import App from './index'; -import { SideEvent, SideEventType } from '../compress/index'; +import { SquooshStartEventType, SquooshSideEventType } from '../compress/index'; import { expose } from 'comlink'; @@ -15,21 +15,26 @@ export function exposeAPI(app: App) { expose(new API(app), self.parent); } -class API { - constructor(private _app: App) { } +function addRemovableGlobalListener< + K extends keyof GlobalEventHandlersEventMap +>(name: K, listener: (ev: GlobalEventHandlersEventMap[K]) => void): () => void { + document.addEventListener(name, listener); + return () => document.removeEventListener(name, listener); +} - setFile(blob: Blob, name: string) { +class API { + constructor(private _app: App) {} + + async setFile(blob: Blob, name: string) { return new Promise((resolve) => { - document.addEventListener( - SideEventType.START, - () => resolve(), - { once: true }, - ); + document.addEventListener(SquooshStartEventType.START, () => resolve(), { + once: true, + }); this._app.openFile(new File([blob], name)); }); } - getBlob(side: 0 | 1) { + async getBlob(side: 0 | 1) { if (!this._app.state.file || !this._app.compressInstance) { throw new Error('No file has been loaded'); } @@ -40,34 +45,35 @@ class API { return this._app.compressInstance!.state.sides[side].file; } - return new Promise((resolve, reject) => { - document.addEventListener( - SideEventType.DONE, - (event: Event) => { - if ((event as SideEvent).side !== side) { + const listeners: ReturnType[] = []; + + const r = new Promise((resolve, reject) => { + listeners.push( + addRemovableGlobalListener(SquooshSideEventType.DONE, (event) => { + if (event.side !== side) { return; } resolve(this._app.compressInstance!.state.sides[side].file); - }, + }), ); - document.addEventListener( - SideEventType.ABORT, - (event) => { - if ((event as SideEvent).side !== side) { + listeners.push( + addRemovableGlobalListener(SquooshSideEventType.ABORT, (event) => { + if (event.side !== side) { return; } reject(new DOMException('Aborted', 'AbortError')); - }, + }), ); - document.addEventListener( - SideEventType.ERROR, - (event) => { - if ((event as SideEvent).side !== side) { + listeners.push( + addRemovableGlobalListener(SquooshSideEventType.ERROR, (event) => { + if (event.side !== side) { return; } - reject((event as SideEvent).error); - }, + reject(event.error); + }), ); }); + r.then(() => listeners.forEach(remove => remove())); + return r; } } diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx index 061bc53a..5c9d8c5a 100644 --- a/src/components/compress/index.tsx +++ b/src/components/compress/index.tsx @@ -32,26 +32,51 @@ import { ExpandIcon, CopyAcrossIconProps } from '../../lib/icons'; import SnackBarElement from '../../lib/SnackBar'; import { InputProcessorState, defaultInputProcessorState } from '../../codecs/input-processors'; -export enum SideEventType { - START = 'squoosh:START', +// Safari and Edge don't quite support extending Event, this works around it. +function fixExtendedEvent(instance: Event, type: Function) { + if (!(instance instanceof type)) { + Object.setPrototypeOf(instance, type.prototype); + } +} +export enum SquooshStartEventType { + START = 'squoosh:start', +} +export class SquooshStartEvent extends Event { + constructor(init?: EventInit) { + super(SquooshStartEventType.START, init); + fixExtendedEvent(this, SquooshStartEvent); + } +} + +export enum SquooshSideEventType { DONE = 'squoosh:done', ABORT = 'squoosh:abort', ERROR = 'squoosh:error', } -export interface SideEventInit extends EventInit { - side?: 0|1; +export interface SquooshSideEventInit extends EventInit { + side: 0|1; error?: Error; } -export class SideEvent extends Event { - public side?: 0|1; +export class SquooshSideEvent extends Event { + public side: 0|1; public error?: Error; - constructor(name: SideEventType, init: SideEventInit) { + constructor(name: SquooshSideEventType, init: SquooshSideEventInit) { super(name, init); + fixExtendedEvent(this, SquooshSideEvent); this.side = init.side; this.error = init.error; } } +declare global { + interface GlobalEventHandlersEventMap { + [SquooshStartEventType.START]: SquooshStartEvent; + [SquooshSideEventType.DONE]: SquooshSideEvent; + [SquooshSideEventType.ABORT]: SquooshSideEvent; + [SquooshSideEventType.ERROR]: SquooshSideEvent; + } +} + export interface SourceImage { file: File | Fileish; decoded: ImageData; @@ -494,30 +519,32 @@ export default class Compress extends Component { } @bind - private dispatchSideEvent(type: SideEventType, init: SideEventInit = {}) { + private dispatchSideEvent(type: SquooshSideEventType, init: SquooshSideEventInit) { document.dispatchEvent( - new SideEvent(type, init), + new SquooshSideEvent(type, init), ); } @bind private signalProcessingStart() { - this.dispatchSideEvent(SideEventType.START); + document.dispatchEvent( + new SquooshStartEvent(), + ); } @bind private signalProcessingDone(side: 0|1) { - this.dispatchSideEvent(SideEventType.DONE, { side }); + this.dispatchSideEvent(SquooshSideEventType.DONE, { side }); } @bind private signalProcessingAbort(side: 0|1) { - this.dispatchSideEvent(SideEventType.ABORT, { side }); + this.dispatchSideEvent(SquooshSideEventType.ABORT, { side }); } @bind private signalProcessingError(side: 0|1, msg: string) { - this.dispatchSideEvent(SideEventType.ERROR, { side, error: new Error(msg) }); + this.dispatchSideEvent(SquooshSideEventType.ERROR, { side, error: new Error(msg) }); } private async updateImage(index: number, options: UpdateImageOptions = {}): Promise {