-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Typescript Typed events #3085
Changes from all commits
2bfcd4c
f718bfb
b9f09bb
af5c0db
3865623
273c365
61ae8fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a version of off<K extends keyof UppyEventMap, TMeta extends IndexedObject<any>>(event: K, callback: UppyEventMap<TMeta>[K]): this Without that, I can‘t pass the same event handler function to interface MyMetadata {
...
}
const handleUpload = (file: UploadedUppyFile<MyMetadata, unknown>) => { ... };
// this is fine
uppy.on<'upload-success', MyMetadata>('upload-success', handleUpload);
// however, this leads to the TypeScript error below
uppy.off<'upload-success'>('upload-success', handleUpload); leads to
If you want, I can also create a ticket for this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for raising this, I'll put up a PR for this shortly. |
||
|
||
/** | ||
* For use by plugins only. | ||
*/ | ||
emit(event: Event, ...args: any[]): void | ||
emit(event: string, ...args: any[]): void | ||
|
||
updateAll(state: Record<string, unknown>): void | ||
|
||
|
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' | ||
|
||
|
@@ -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' { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to declare this module? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's so the various definitions of the |
||
export interface UppyEventMap<TMeta> { | ||
'dashboard:modal-open': GenericEventCallback | ||
'dashboard:modal-closed': GenericEventCallback | ||
'dashboard:file-edit-state': DashboardFileEditStartCallback<TMeta> | ||
'dashboard:file-edit-complete': DashboardFileEditCompleteCallback<TMeta> | ||
} | ||
} |
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 | ||
}) | ||
} |
There was a problem hiding this comment.
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 thenUppyEventMap
?There was a problem hiding this comment.
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.