Forráskód Böngészése

Strictly type uppy events (#3085)

JT 3 éve
szülő
commit
89f09c810a

+ 46 - 42
packages/@uppy/core/types/index.d.ts

@@ -14,7 +14,9 @@ export interface IndexedObject<T> {
 export type UppyFile<
   TMeta extends IndexedObject<any> = Record<string, unknown>,
   TBody extends IndexedObject<any> = Record<string, unknown>
-> = UppyUtils.UppyFile<TMeta, TBody>
+  > = UppyUtils.UppyFile<TMeta, TBody>
+
+export type FileProgress = UppyUtils.FileProgress;
 
 // Replace the `meta` property type with one that allows omitting internal metadata addFile() will add that
 type UppyFileWithoutMeta<TMeta, TBody> = OmitKey<
@@ -28,28 +30,6 @@ type LocaleStrings<TNames extends string> = {
 
 type LogLevel = 'info' | 'warning' | 'error'
 
-// This hack accepts _any_ string for `Event`, but also tricks VSCode and friends into providing autocompletions
-// for the names listed. https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972
-// eslint-disable-next-line no-use-before-define
-type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>)
-
-type Event = LiteralUnion<
-  | 'file-added'
-  | 'file-removed'
-  | 'upload'
-  | 'upload-progress'
-  | 'upload-success'
-  | 'complete'
-  | 'error'
-  | 'upload-error'
-  | 'upload-retry'
-  | 'info-visible'
-  | 'info-hidden'
-  | 'cancel-all'
-  | 'restriction-failed'
-  | 'reset-progress'
->
-
 export type Store = UppyUtils.Store
 
 export type InternalMetadata = UppyUtils.InternalMetadata
@@ -65,7 +45,7 @@ export interface FailedUppyFile<TMeta, TBody> extends UppyFile<TMeta, TBody> {
 export interface AddFileOptions<
   TMeta = IndexedObject<any>,
   TBody = IndexedObject<any>
-> extends Partial<UppyFileWithoutMeta<TMeta, TBody>> {
+  > extends Partial<UppyFileWithoutMeta<TMeta, TBody>> {
   // `.data` is the only required property here.
   data: Blob | File
   meta?: Partial<InternalMetadata> & TMeta
@@ -177,7 +157,7 @@ export interface UppyOptions<TMeta extends IndexedObject<any> = Record<string, u
 export interface UploadResult<
   TMeta extends IndexedObject<any> = Record<string, unknown>,
   TBody extends IndexedObject<any> = Record<string, unknown>
-> {
+  > {
   successful: UploadedUppyFile<TMeta, TBody>[]
   failed: FailedUppyFile<TMeta, TBody>[]
 }
@@ -185,14 +165,14 @@ export interface UploadResult<
 export interface State<
   TMeta extends IndexedObject<any> = Record<string, unknown>,
   TBody extends IndexedObject<any> = Record<string, unknown>
-> extends IndexedObject<any> {
+  > extends IndexedObject<any> {
   capabilities?: { resumableUploads?: boolean }
   currentUploads: Record<string, unknown>
   error?: string
   files: {
     [key: string]:
-      | UploadedUppyFile<TMeta, TBody>
-      | FailedUppyFile<TMeta, TBody>
+    | UploadedUppyFile<TMeta, TBody>
+    | FailedUppyFile<TMeta, TBody>
   }
   info?: {
     isHidden: boolean
@@ -204,32 +184,56 @@ export interface State<
   totalProgress: number
 }
 
-type UploadSuccessCallback<T> = (file: UppyFile<T>, body: any, uploadURL: string) => void
-type UploadCompleteCallback<T> = (result: UploadResult<T>) => void
+export type GenericEventCallback = () => void;
+export type FileAddedCallback<TMeta> = (file: UppyFile<TMeta>) => void;
+export type FilesAddedCallback<TMeta> = (files: UppyFile<TMeta>[]) => void;
+export type FileRemovedCallback<TMeta> = (file: UppyFile<TMeta>, reason: 'removed-by-user' | 'cancel-all') => void;
+export type UploadCallback = (data: {id: string, fileIDs: string[]}) => void;
+export type ProgressCallback = (progress: number) => void;
+export type UploadProgressCallback<TMeta> = (file: UppyFile<TMeta>, progress: FileProgress) => void;
+export type UploadSuccessCallback<TMeta> = (file: UploadedUppyFile<TMeta, unknown>, body: unknown, uploadURL: string) => void
+export type UploadCompleteCallback<TMeta> = (result: UploadResult<TMeta>) => void
+export type ErrorCallback = (error: Error) => void;
+export type UploadErrorCallback<TMeta> = (file: FailedUppyFile<TMeta, unknown>, error: Error, response: unknown) => void;
+export type UploadRetryCallback = (fileID: string) => void;
+export type RestrictionFailedCallback<TMeta> = (file: UppyFile<TMeta>, error: Error) => void;
+
+export interface UppyEventMap<TMeta = Record<string, unknown>> {
+  'file-added': FileAddedCallback<TMeta>
+  'files-added': FilesAddedCallback<TMeta>
+  'file-removed': FileRemovedCallback<TMeta>
+  'upload': UploadCallback
+  'progress': ProgressCallback
+  'upload-progress': UploadProgressCallback<TMeta>
+  'upload-success': UploadSuccessCallback<TMeta>
+  'complete': UploadCompleteCallback<TMeta>
+  'error': ErrorCallback
+  'upload-error': UploadErrorCallback<TMeta>
+  'upload-retry': UploadRetryCallback
+  'info-visible': GenericEventCallback
+  'info-hidden': GenericEventCallback
+  'cancel-all': GenericEventCallback
+  'restriction-failed': RestrictionFailedCallback<TMeta>
+  'reset-progress': GenericEventCallback
+}
 
 export class Uppy {
   constructor(opts?: UppyOptions)
 
-  on<TMeta extends IndexedObject<any> = Record<string, unknown>>(event: 'upload-success', callback: UploadSuccessCallback<TMeta>): this
-
-  on<TMeta extends IndexedObject<any> = Record<string, unknown>>(event: 'complete', callback: UploadCompleteCallback<TMeta>): this
-
-  on(event: Event, callback: (...args: any[]) => void): this
-
-  once<TMeta extends IndexedObject<any> = Record<string, unknown>>(event: 'upload-success', callback: UploadSuccessCallback<TMeta>): this
+  on<K extends keyof UppyEventMap>(event: K, callback: UppyEventMap[K]): this
 
-  once<TMeta extends IndexedObject<any> = Record<string, unknown>>(event: 'complete', callback: UploadCompleteCallback<TMeta>): this
+  on<K extends keyof UppyEventMap, TMeta extends IndexedObject<any>>(event: K, callback: UppyEventMap<TMeta>[K]): this
 
-  once(event: Event, callback: (...args: any[]) => void): this
+  once<K extends keyof UppyEventMap>(event: K, callback: UppyEventMap[K]): this
 
-  off(event: Event, callback: (...args: any[]) => void): this
+  once<K extends keyof UppyEventMap, TMeta extends IndexedObject<any>>(event: K, callback: UppyEventMap<TMeta>[K]): this
 
-  off(event: Event, callback: (...args: any[]) => void): this
+  off<K extends keyof UppyEventMap>(event: K, callback: UppyEventMap[K]): this
 
   /**
    * For use by plugins only.
    */
-  emit(event: Event, ...args: any[]): void
+  emit(event: string, ...args: any[]): void
 
   updateAll(state: Record<string, unknown>): void
 

+ 11 - 4
packages/@uppy/core/types/index.test-d.ts

@@ -87,11 +87,18 @@ type anyObject = Record<string, unknown>
   uppy.once('upload', () => {})
   uppy.once('complete', () => {})
   uppy.once('error', () => {})
-
-  // can register listeners on custom events
-  uppy.on('dashboard:modal-closed', () => {})
-  uppy.once('dashboard:modal-closed', () => {})
   /* eslint-enable @typescript-eslint/no-empty-function */
+
+  // Normal event signature
+  uppy.on('complete', (result) => {
+    const successResults = result.successful
+  })
+
+  // Meta signature
+  type Meta = {myCustomMetadata: string}
+  uppy.on<'complete', Meta>('complete', (result) => {
+    const meta = result.successful[0].meta.myCustomMetadata
+  })
 }
 
 {

+ 14 - 1
packages/@uppy/dashboard/types/index.d.ts

@@ -1,4 +1,4 @@
-import type { PluginOptions, UIPlugin, PluginTarget, UppyFile } from '@uppy/core'
+import type { PluginOptions, UIPlugin, PluginTarget, UppyFile, GenericEventCallback } from '@uppy/core'
 import type { StatusBarLocale } from '@uppy/status-bar'
 import DashboardLocale from './generatedLocale'
 
@@ -75,3 +75,16 @@ declare class Dashboard extends UIPlugin<DashboardOptions> {
 }
 
 export default Dashboard
+
+// Events
+
+export type DashboardFileEditStartCallback<TMeta> = (file: UppyFile<TMeta>) => void;
+export type DashboardFileEditCompleteCallback<TMeta> = (file: UppyFile<TMeta>) => void;
+declare module '@uppy/core' {
+  export interface UppyEventMap<TMeta> {
+    'dashboard:modal-open': GenericEventCallback
+    'dashboard:modal-closed': GenericEventCallback
+    'dashboard:file-edit-state': DashboardFileEditStartCallback<TMeta>
+    'dashboard:file-edit-complete': DashboardFileEditCompleteCallback<TMeta>
+  }
+}

+ 4 - 0
packages/@uppy/dashboard/types/index.test-d.ts

@@ -48,6 +48,10 @@ import Dashboard from '..'
       },
     ],
   })
+
+  uppy.on('dashboard:file-edit-state', (file) => {
+    const fileName = file.name
+  })
 }
 
 {

+ 3 - 3
packages/@uppy/drag-drop/types/index.d.ts

@@ -9,9 +9,9 @@ export interface DragDropOptions extends PluginOptions {
   height?: string | number
   note?: string
   locale?: DragDropLocale
-  onDragOver?: (event: MouseEvent) => void
-  onDragLeave?: (event: MouseEvent) => void
-  onDrop?: (event: MouseEvent) => void
+  onDragOver?: (event: DragEvent) => void
+  onDragLeave?: (event: DragEvent) => void
+  onDrop?: (event: DragEvent) => void
 }
 
 declare class DragDrop extends UIPlugin<DragDropOptions> {}

+ 13 - 1
packages/@uppy/image-editor/types/index.d.ts

@@ -1,4 +1,4 @@
-import type { PluginOptions, UIPlugin, PluginTarget } from '@uppy/core'
+import type { PluginOptions, UIPlugin, PluginTarget, UppyFile } from '@uppy/core'
 import type Cropper from 'cropperjs'
 import ImageEditorLocale from './generatedLocale'
 
@@ -29,3 +29,15 @@ export interface ImageEditorOptions extends PluginOptions {
 declare class ImageEditor extends UIPlugin<ImageEditorOptions> {}
 
 export default ImageEditor
+
+// Events
+
+export type FileEditorStartCallback<TMeta> = (file: UppyFile<TMeta>) => void;
+export type FileEditorCompleteCallback<TMeta> = (updatedFile: UppyFile<TMeta>) => void;
+
+declare module '@uppy/core' {
+  export interface UppyEventMap<TMeta> {
+    'file-editor:start' : FileEditorStartCallback<TMeta>
+    'file-editor:complete': FileEditorCompleteCallback<TMeta>
+  }
+}

+ 16 - 1
packages/@uppy/image-editor/types/index.test-d.ts

@@ -1,2 +1,17 @@
-// import ImageEditor from '..'
 // TODO implement
+
+import Uppy from '@uppy/core'
+import ImageEditor from '..'
+
+{
+  const uppy = new Uppy()
+
+  uppy.use(ImageEditor)
+
+  uppy.on('file-editor:start', (file) => {
+    const fileName = file.name
+  })
+  uppy.on('file-editor:complete', (file) => {
+    const fileName = file.name
+  })
+}

+ 12 - 2
packages/@uppy/thumbnail-generator/types/index.d.ts

@@ -1,4 +1,4 @@
-import type { PluginOptions, UIPlugin } from '@uppy/core'
+import type { PluginOptions, UIPlugin, UppyFile } from '@uppy/core'
 
 import ThumbnailGeneratorLocale from './generatedLocale'
 
@@ -11,6 +11,16 @@ interface ThumbnailGeneratorOptions extends PluginOptions {
     locale?: ThumbnailGeneratorLocale,
 }
 
-declare class ThumbnailGenerator extends UIPlugin<ThumbnailGeneratorOptions> {}
+declare class ThumbnailGenerator extends UIPlugin<ThumbnailGeneratorOptions> { }
 
 export default ThumbnailGenerator
+
+// Events
+
+export type ThumbnailGeneratedCallback<TMeta> = (file: UppyFile<TMeta>, preview: string) => void;
+
+declare module '@uppy/core' {
+    export interface UppyEventMap<TMeta> {
+        'thumbnail:generated' : ThumbnailGeneratedCallback<TMeta>
+    }
+}

+ 7 - 0
packages/@uppy/thumbnail-generator/types/index.test-d.ts

@@ -15,4 +15,11 @@ import ThumbnailGenerator from '..'
       },
     },
   })
+
+  uppy.on('thumbnail:generated', (file, preview) => {
+    const img = document.createElement('img')
+    img.src = preview
+    img.width = 100
+    document.body.appendChild(img)
+  })
 }

+ 21 - 2
packages/@uppy/transloadit/types/index.d.ts

@@ -1,7 +1,7 @@
 import type { PluginOptions, UppyFile, BasePlugin } from '@uppy/core'
 import TransloaditLocale from './generatedLocale'
 
-  interface FileInfo {
+export interface FileInfo {
     id: string,
     name: string,
     basename: string,
@@ -27,7 +27,8 @@ export interface Result extends FileInfo {
     cost: number,
     execTime: number,
     queue: string,
-    queueTime: number
+    queueTime: number,
+    localId: string | null
   }
 
 export interface Assembly {
@@ -127,3 +128,21 @@ declare class Transloadit extends BasePlugin<TransloaditOptions> {
 }
 
 export default Transloadit
+
+// Events
+
+export type TransloaditAssemblyCreatedCallback = (assembly: Assembly, fileIDs: string[]) => void;
+export type TransloaditUploadedCallback = (file: FileInfo, assembly: Assembly) => void;
+export type TransloaditAssemblyExecutingCallback = (assembly: Assembly) => void;
+export type TransloaditResultCallback = (stepName: string, result: Result, assembly: Assembly) => void;
+export type TransloaditCompleteCallback = (assembly: Assembly) => void;
+
+declare module '@uppy/core' {
+  export interface UppyEventMap {
+    'transloadit:assembly-created': TransloaditAssemblyCreatedCallback
+    'transloadit:upload': TransloaditUploadedCallback
+    'transloadit:assembly-executing': TransloaditAssemblyExecutingCallback
+    'transloadit:result': TransloaditResultCallback
+    'transloadit:complete': TransloaditCompleteCallback
+  }
+}

+ 8 - 0
packages/@uppy/transloadit/types/index.test-d.ts

@@ -25,6 +25,14 @@ const validParams = {
       steps: {},
     },
   })
+  // Access to both transloadit events and core events
+  uppy.on('transloadit:assembly-created', (assembly, fileIDs) => {
+    const status = assembly.ok
+  })
+
+  uppy.on('complete', (result) => {
+    const success = result.successful
+  })
 }
 
 {

+ 8 - 7
packages/@uppy/utils/types/index.d.ts

@@ -250,6 +250,13 @@ declare module '@uppy/utils' {
     [key: number]: T
   }
   export type InternalMetadata = { name: string; type?: string }
+  export interface FileProgress  {
+    uploadStarted: number | null
+    uploadComplete: boolean
+    percentage: number
+    bytesUploaded: number
+    bytesTotal: number
+  }
   export interface UppyFile<
     TMeta = IndexedObject<any>,
     TBody = IndexedObject<any>
@@ -262,13 +269,7 @@ declare module '@uppy/utils' {
     meta: InternalMetadata & TMeta
     name: string
     preview?: string
-    progress?: {
-      uploadStarted: number | null
-      uploadComplete: boolean
-      percentage: number
-      bytesUploaded: number
-      bytesTotal: number
-    }
+    progress?: FileProgress
     remote?: {
       host: string
       url: string