Skip to content

Commit

Permalink
Merge branch 'main' into 4.x
Browse files Browse the repository at this point in the history
* main:
  @uppy/remote-sources: migrate to TS (#5020)
  @uppy/dashboard: refine option types (#5022)
  • Loading branch information
Murderlon committed Mar 26, 2024
2 parents f566443 + 6673b77 commit 7329094
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 109 deletions.
78 changes: 49 additions & 29 deletions packages/@uppy/dashboard/src/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,61 +112,75 @@ interface DashboardState<M extends Meta, B extends Body> {
[key: string]: unknown
}

export interface DashboardOptions<M extends Meta, B extends Body>
extends UIPluginOptions {
export interface DashboardModalOptions {
inline?: false
animateOpenClose?: boolean
browserBackButtonClose?: boolean
closeAfterFinish?: boolean
singleFileFullScreen?: boolean
closeModalOnClickOutside?: boolean
disableInformer?: boolean
disablePageScrollWhenModalOpen?: boolean
disableStatusBar?: boolean
disableThumbnailGenerator?: boolean
}

export interface DashboardInlineOptions {
inline: true

height?: string | number
thumbnailWidth?: number
thumbnailHeight?: number
thumbnailType?: string
nativeCameraFacingMode?: ConstrainDOMString
waitForThumbnailsBeforeUpload?: boolean
width?: string | number
}

interface DashboardMiscOptions<M extends Meta, B extends Body>
extends UIPluginOptions {
autoOpen?: 'metaEditor' | 'imageEditor' | null
/** @deprecated use option autoOpen instead */
autoOpenFileEditor?: boolean
defaultPickerIcon?: typeof defaultPickerIcon
disabled?: boolean
disableInformer?: boolean
disableLocalFiles?: boolean
disableStatusBar?: boolean
disableThumbnailGenerator?: boolean
doneButtonHandler?: () => void
fileManagerSelectionType?: 'files' | 'folders' | 'both'
hideCancelButton?: boolean
hidePauseResumeButton?: boolean
hideProgressAfterFinish?: boolean
hideRetryButton?: boolean
hideUploadButton?: boolean
inline?: boolean
metaFields?: MetaField[] | ((file: UppyFile<M, B>) => MetaField[])
nativeCameraFacingMode?: ConstrainDOMString
note?: string | null
onDragLeave?: (event: DragEvent) => void
onDragOver?: (event: DragEvent) => void
onDrop?: (event: DragEvent) => void
onRequestCloseModal?: () => void
plugins?: string[]
fileManagerSelectionType?: 'files' | 'folders' | 'both'
proudlyDisplayPoweredByUppy?: boolean
showLinkToFileUploadResult?: boolean
showProgressDetails?: boolean
showSelectedFiles?: boolean
showRemoveButtonAfterComplete?: boolean
showNativePhotoCameraButton?: boolean
showNativeVideoCameraButton?: boolean
showProgressDetails?: boolean
showRemoveButtonAfterComplete?: boolean
showSelectedFiles?: boolean
singleFileFullScreen?: boolean
theme?: 'auto' | 'dark' | 'light'
thumbnailHeight?: number
thumbnailType?: string
thumbnailWidth?: number
trigger?: string
width?: string | number
autoOpen?: 'metaEditor' | 'imageEditor' | null
/** @deprecated use option autoOpen instead */
autoOpenFileEditor?: boolean
disabled?: boolean
disableLocalFiles?: boolean
onRequestCloseModal?: () => void
doneButtonHandler?: () => void
onDragOver?: (event: DragEvent) => void
onDragLeave?: (event: DragEvent) => void
onDrop?: (event: DragEvent) => void
waitForThumbnailsBeforeUpload?: boolean
}

export type DashboardOptions<
M extends Meta,
B extends Body,
> = DashboardMiscOptions<M, B> &
(DashboardModalOptions | DashboardInlineOptions)

// set default options, must be kept in sync with packages/@uppy/react/src/DashboardModal.js
const defaultOptions = {
target: 'body',
metaFields: [],
inline: false,
inline: false as boolean,
width: 750,
height: 550,
thumbnailWidth: 280,
Expand Down Expand Up @@ -213,7 +227,13 @@ const defaultOptions = {
* Dashboard UI with previews, metadata editing, tabs for various services and more
*/
export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
DefinePluginOpts<DashboardOptions<M, B>, keyof typeof defaultOptions>,
DefinePluginOpts<
// The options object inside the class is not the discriminated union but and intersection of the different subtypes.
DashboardMiscOptions<M, B> &
Omit<DashboardInlineOptions, 'inline'> &
Omit<DashboardModalOptions, 'inline'> & { inline?: boolean },
keyof typeof defaultOptions
>,
M,
B,
DashboardState<M, B>
Expand Down
1 change: 1 addition & 0 deletions packages/@uppy/remote-sources/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tsconfig.*
76 changes: 0 additions & 76 deletions packages/@uppy/remote-sources/src/index.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import resizeObserverPolyfill from 'resize-observer-polyfill'
import Core from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import RemoteSources from './index.js'
import RemoteSources from './index.ts'

describe('RemoteSources', () => {
beforeAll(() => {
globalThis.ResizeObserver = resizeObserverPolyfill.default || resizeObserverPolyfill
globalThis.ResizeObserver =
// @ts-expect-error .default is fine
resizeObserverPolyfill.default || resizeObserverPolyfill
})

afterAll(() => {
// @ts-expect-error delete does not have to be conditional
delete globalThis.ResizeObserver
})

Expand All @@ -25,8 +28,13 @@ describe('RemoteSources', () => {
expect(() => {
const core = new Core()
core.use(Dashboard)
// @ts-expect-error companionUrl is missing
core.use(RemoteSources, { sources: ['Webcam'] })
}).toThrow(new Error('Please specify companionUrl for RemoteSources to work, see https://uppy.io/docs/remote-sources#companionUrl'))
}).toThrow(
new Error(
'Please specify companionUrl for RemoteSources to work, see https://uppy.io/docs/remote-sources#companionUrl',
),
)
})

it('should throw when trying to use a plugin which is not included in RemoteSources', () => {
Expand All @@ -35,8 +43,11 @@ describe('RemoteSources', () => {
core.use(Dashboard)
core.use(RemoteSources, {
companionUrl: 'https://example.com',
// @ts-expect-error test invalid
sources: ['Webcam'],
})
}).toThrow('Invalid plugin: "Webcam" is not one of: Box, Dropbox, Facebook, GoogleDrive, Instagram, OneDrive, Unsplash, Url, or Zoom.')
}).toThrow(
'Invalid plugin: "Webcam" is not one of: Box, Dropbox, Facebook, GoogleDrive, Instagram, OneDrive, Unsplash, Url, or Zoom.',
)
})
})
105 changes: 105 additions & 0 deletions packages/@uppy/remote-sources/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
BasePlugin,
Uppy,
type UIPluginOptions,
type UnknownProviderPlugin,
} from '@uppy/core'
import Dropbox from '@uppy/dropbox'
import GoogleDrive from '@uppy/google-drive'
import Instagram from '@uppy/instagram'
import Facebook from '@uppy/facebook'
import OneDrive from '@uppy/onedrive'
import Box from '@uppy/box'
import Unsplash from '@uppy/unsplash'
import Url from '@uppy/url'
import Zoom from '@uppy/zoom'

import type { DefinePluginOpts } from '@uppy/core/lib/BasePlugin'
import type { Body, Meta } from '../../utils/src/UppyFile'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We don't want TS to generate types for the package.json
import packageJson from '../package.json'

const availablePlugins = {
// Using a null-prototype object to avoid prototype pollution.
__proto__: null,
Box,
Dropbox,
Facebook,
GoogleDrive,
Instagram,
OneDrive,
Unsplash,
Url,
Zoom,
}

export interface RemoteSourcesOptions extends UIPluginOptions {
sources?: Array<keyof Omit<typeof availablePlugins, '__proto__'>>
companionUrl: string
}

const defaultOptions = {
sources: Object.keys(availablePlugins) as Array<
keyof Omit<typeof availablePlugins, '__proto__'>
>,
} satisfies Partial<RemoteSourcesOptions>

type Opts = DefinePluginOpts<RemoteSourcesOptions, keyof typeof defaultOptions>

export default class RemoteSources<
M extends Meta,
B extends Body,
> extends BasePlugin<Opts, M, B> {
static VERSION = packageJson.version

#installedPlugins: Set<UnknownProviderPlugin<M, B>> = new Set()

constructor(uppy: Uppy<M, B>, opts: RemoteSourcesOptions) {
super(uppy, { ...defaultOptions, ...opts })
this.id = this.opts.id || 'RemoteSources'
this.type = 'preset'

if (this.opts.companionUrl == null) {
throw new Error(
'Please specify companionUrl for RemoteSources to work, see https://uppy.io/docs/remote-sources#companionUrl',
)
}
}

setOptions(newOpts: Partial<Opts>): void {
this.uninstall()
super.setOptions(newOpts)
this.install()
}

install(): void {
this.opts.sources.forEach((pluginId) => {
const optsForRemoteSourcePlugin = { ...this.opts, sources: undefined }
const plugin = availablePlugins[pluginId]
if (plugin == null) {
const pluginNames = Object.keys(availablePlugins)
const formatter = new Intl.ListFormat('en', {
style: 'long',
type: 'disjunction',
})
throw new Error(
`Invalid plugin: "${pluginId}" is not one of: ${formatter.format(pluginNames)}.`,
)
}
this.uppy.use(plugin, optsForRemoteSourcePlugin)
// `plugin` is a class, but we want to track the instance object
// so we have to do `getPlugin` here.
this.#installedPlugins.add(
this.uppy.getPlugin(pluginId) as UnknownProviderPlugin<M, B>,
)
})
}

uninstall(): void {
for (const plugin of this.#installedPlugins) {
this.uppy.removePlugin(plugin)
}
this.#installedPlugins.clear()
}
}
Loading

0 comments on commit 7329094

Please sign in to comment.