8000 Typescript Typed events by Hawxy · Pull Request #3085 · transloadit/uppy · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Typescript Typed events 8000 #3085

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 46 additions & 42 deletions packages/@uppy/core/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -173,22 +153,22 @@ 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>[]
}

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
Expand All @@ -200,32 +180,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this not be a breaking change if we flip the order? So TMeta and then UppyEventMap?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did consider that, however Typescript does not have partial generic interference (yet), so as soon as you specify one generic the rest must be explicitly specified.


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

Expand Down
15 changes: 11 additions & 4 deletions packages/@uppy/core/types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}

{
Expand Down
15 changes: 14 additions & 1 deletion packages/@uppy/dashboard/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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' {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to declare this module?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's so the various definitions of the UppyEventMap interface are additive with the initial @uppy/core version, and not treated as if they're from separate modules. Typescript refers to this pattern as module augmentation.

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 changes: 4 additions & 0 deletions packages/@uppy/dashboard/types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ import Dashboard from '..'
},
],
})

uppy.on('dashboard:file-edit-state', (file) => {
const fileName = file.name
})
}

{
Expand Down
6 changes: 3 additions & 3 deletions packages/@uppy/drag-drop/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {}
Expand Down
14 changes: 13 additions & 1 deletion packages/@uppy/image-editor/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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>
}
}
17 changes: 16 additions & 1 deletion packages/@uppy/image-editor/types/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -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
})
}
14 changes: 12 additions & 2 deletions packages/@uppy/thumbnail-generator/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PluginOptions, UIPlugin } from '@uppy/core'
import type { PluginOptions, UIPlugin, UppyFile } from '@uppy/core'

import ThumbnailGeneratorLocale from './generatedLocale'

Expand All @@ -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 changes: 7 additions & 0 deletions packages/@uppy/thumbnail-generator/types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
23 changes: 21 additions & 2 deletions packages/@uppy/transloadit/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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 changes: 8 additions & 0 deletions packages/@uppy/transloadit/types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}

{
Expand Down
15 changes: 8 additions & 7 deletions packages/@uppy/utils/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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
Expand Down
0