|
@@ -3,54 +3,98 @@ import PQueue from 'p-queue'
|
|
|
|
|
|
import { getSafeFileId } from '@uppy/utils/lib/generateFileID'
|
|
|
|
|
|
-import AuthView from './AuthView.jsx'
|
|
|
-import Header from './Header.jsx'
|
|
|
-import Browser from '../Browser.jsx'
|
|
|
-import CloseWrapper from '../CloseWrapper.js'
|
|
|
-import View from '../View.js'
|
|
|
-
|
|
|
+import type {
|
|
|
+ UnknownProviderPlugin,
|
|
|
+ UnknownProviderPluginState,
|
|
|
+ Uppy,
|
|
|
+} from '@uppy/core/lib/Uppy.ts'
|
|
|
+import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
|
|
|
+import type { CompanionFile } from '@uppy/utils/lib/CompanionFile.ts'
|
|
|
+import type Translator from '@uppy/utils/lib/Translator'
|
|
|
+import type { DefinePluginOpts } from '@uppy/core/lib/BasePlugin.ts'
|
|
|
+import AuthView from './AuthView.tsx'
|
|
|
+import Header from './Header.tsx'
|
|
|
+import Browser from '../Browser.tsx'
|
|
|
+import CloseWrapper from '../CloseWrapper.ts'
|
|
|
+import View, { type ViewOptions } from '../View.ts'
|
|
|
+
|
|
|
+// 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'
|
|
|
|
|
|
-function formatBreadcrumbs (breadcrumbs) {
|
|
|
- return breadcrumbs.slice(1).map((directory) => directory.name).join('/')
|
|
|
+function formatBreadcrumbs(
|
|
|
+ breadcrumbs: UnknownProviderPluginState['breadcrumbs'],
|
|
|
+): string {
|
|
|
+ return breadcrumbs
|
|
|
+ .slice(1)
|
|
|
+ .map((directory) => directory.name)
|
|
|
+ .join('/')
|
|
|
}
|
|
|
|
|
|
-function prependPath (path, component) {
|
|
|
+function prependPath(path: string | undefined, component: string): string {
|
|
|
if (!path) return component
|
|
|
return `${path}/${component}`
|
|
|
}
|
|
|
|
|
|
-export function defaultPickerIcon () {
|
|
|
+export function defaultPickerIcon(): JSX.Element {
|
|
|
return (
|
|
|
- <svg aria-hidden="true" focusable="false" width="30" height="30" viewBox="0 0 30 30">
|
|
|
+ <svg
|
|
|
+ aria-hidden="true"
|
|
|
+ focusable="false"
|
|
|
+ width="30"
|
|
|
+ height="30"
|
|
|
+ viewBox="0 0 30 30"
|
|
|
+ >
|
|
|
<path d="M15 30c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15C6.716 0 0 6.716 0 15c0 8.284 6.716 15 15 15zm4.258-12.676v6.846h-8.426v-6.846H5.204l9.82-12.364 9.82 12.364H19.26z" />
|
|
|
</svg>
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+type PluginType = 'Provider'
|
|
|
+
|
|
|
+const defaultOptions = {
|
|
|
+ viewType: 'list',
|
|
|
+ showTitles: true,
|
|
|
+ showFilter: true,
|
|
|
+ showBreadcrumbs: true,
|
|
|
+ loadAllFiles: false,
|
|
|
+}
|
|
|
+
|
|
|
+export interface ProviderViewOptions<M extends Meta, B extends Body>
|
|
|
+ extends ViewOptions<M, B, PluginType> {
|
|
|
+ renderAuthForm?: (args: {
|
|
|
+ pluginName: string
|
|
|
+ i18n: Translator['translateArray']
|
|
|
+ loading: boolean | string
|
|
|
+ onAuth: (authFormData: unknown) => Promise<void>
|
|
|
+ }) => JSX.Element
|
|
|
+}
|
|
|
+
|
|
|
+type Opts<M extends Meta, B extends Body> = DefinePluginOpts<
|
|
|
+ ProviderViewOptions<M, B>,
|
|
|
+ keyof typeof defaultOptions
|
|
|
+>
|
|
|
+
|
|
|
/**
|
|
|
* Class to easily generate generic views for Provider plugins
|
|
|
*/
|
|
|
-export default class ProviderView extends View {
|
|
|
+export default class ProviderView<M extends Meta, B extends Body> extends View<
|
|
|
+ M,
|
|
|
+ B,
|
|
|
+ PluginType,
|
|
|
+ Opts<M, B>
|
|
|
+> {
|
|
|
static VERSION = packageJson.version
|
|
|
|
|
|
- /**
|
|
|
- * @param {object} plugin instance of the plugin
|
|
|
- * @param {object} opts
|
|
|
- */
|
|
|
- constructor (plugin, opts) {
|
|
|
- super(plugin, opts)
|
|
|
- // set default options
|
|
|
- const defaultOptions = {
|
|
|
- viewType: 'list',
|
|
|
- showTitles: true,
|
|
|
- showFilter: true,
|
|
|
- showBreadcrumbs: true,
|
|
|
- loadAllFiles: false,
|
|
|
- }
|
|
|
+ username: string | undefined
|
|
|
+
|
|
|
+ nextPagePath: string
|
|
|
|
|
|
- // merge default options with the ones set by user
|
|
|
- this.opts = { ...defaultOptions, ...opts }
|
|
|
+ constructor(
|
|
|
+ plugin: UnknownProviderPlugin<M, B>,
|
|
|
+ opts: ProviderViewOptions<M, B>,
|
|
|
+ ) {
|
|
|
+ super(plugin, { ...defaultOptions, ...opts })
|
|
|
|
|
|
// Logic
|
|
|
this.filterQuery = this.filterQuery.bind(this)
|
|
@@ -80,13 +124,13 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
|
- tearDown () {
|
|
|
+ tearDown(): void {
|
|
|
// Nothing.
|
|
|
}
|
|
|
|
|
|
- #abortController
|
|
|
+ #abortController: AbortController | undefined
|
|
|
|
|
|
- async #withAbort (op) {
|
|
|
+ async #withAbort(op: (signal: AbortSignal) => Promise<void>) {
|
|
|
// prevent multiple requests in parallel from causing race conditions
|
|
|
this.#abortController?.abort()
|
|
|
const abortController = new AbortController()
|
|
@@ -96,19 +140,37 @@ export default class ProviderView extends View {
|
|
|
this.clearSelection()
|
|
|
}
|
|
|
try {
|
|
|
+ // @ts-expect-error this should be typed in @uppy/dashboard.
|
|
|
+ // Even then I don't think we can make this work without adding dashboard
|
|
|
+ // as a dependency to provider-views.
|
|
|
this.plugin.uppy.on('dashboard:close-panel', cancelRequest)
|
|
|
this.plugin.uppy.on('cancel-all', cancelRequest)
|
|
|
|
|
|
await op(abortController.signal)
|
|
|
} finally {
|
|
|
+ // @ts-expect-error this should be typed in @uppy/dashboard.
|
|
|
+ // Even then I don't think we can make this work without adding dashboard
|
|
|
+ // as a dependency to provider-views.
|
|
|
this.plugin.uppy.off('dashboard:close-panel', cancelRequest)
|
|
|
this.plugin.uppy.off('cancel-all', cancelRequest)
|
|
|
this.#abortController = undefined
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async #list ({ requestPath, absDirPath, signal }) {
|
|
|
- const { username, nextPagePath, items } = await this.provider.list(requestPath, { signal })
|
|
|
+ async #list({
|
|
|
+ requestPath,
|
|
|
+ absDirPath,
|
|
|
+ signal,
|
|
|
+ }: {
|
|
|
+ requestPath: string
|
|
|
+ absDirPath: string
|
|
|
+ signal: AbortSignal
|
|
|
+ }) {
|
|
|
+ const { username, nextPagePath, items } = await this.provider.list<{
|
|
|
+ username: string
|
|
|
+ nextPagePath: string
|
|
|
+ items: CompanionFile[]
|
|
|
+ }>(requestPath, { signal })
|
|
|
this.username = username || this.username
|
|
|
|
|
|
return {
|
|
@@ -120,15 +182,25 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async #listFilesAndFolders ({ breadcrumbs, signal }) {
|
|
|
+ async #listFilesAndFolders({
|
|
|
+ breadcrumbs,
|
|
|
+ signal,
|
|
|
+ }: {
|
|
|
+ breadcrumbs: UnknownProviderPluginState['breadcrumbs']
|
|
|
+ signal: AbortSignal
|
|
|
+ }) {
|
|
|
const absDirPath = formatBreadcrumbs(breadcrumbs)
|
|
|
|
|
|
- const { items, nextPagePath } = await this.#list({ requestPath: this.nextPagePath, absDirPath, signal })
|
|
|
+ const { items, nextPagePath } = await this.#list({
|
|
|
+ requestPath: this.nextPagePath,
|
|
|
+ absDirPath,
|
|
|
+ signal,
|
|
|
+ })
|
|
|
|
|
|
this.nextPagePath = nextPagePath
|
|
|
|
|
|
- const files = []
|
|
|
- const folders = []
|
|
|
+ const files: CompanionFile[] = []
|
|
|
+ const folders: CompanionFile[] = []
|
|
|
|
|
|
items.forEach((item) => {
|
|
|
if (item.isFolder) {
|
|
@@ -145,12 +217,8 @@ export default class ProviderView extends View {
|
|
|
* Select a folder based on its id: fetches the folder and then updates state with its contents
|
|
|
* TODO rename to something better like selectFolder or navigateToFolder (breaking change?)
|
|
|
*
|
|
|
- * @param {string} requestPath
|
|
|
- * the path we need to use when sending list request to companion (for some providers it's different from ID)
|
|
|
- * @param {string} name used in the UI and to build the absDirPath
|
|
|
- * @returns {Promise} Folders/files in folder
|
|
|
*/
|
|
|
- async getFolder (requestPath, name) {
|
|
|
+ async getFolder(requestPath: string, name: string): Promise<void> {
|
|
|
this.setLoading(true)
|
|
|
try {
|
|
|
await this.#withAbort(async (signal) => {
|
|
@@ -158,7 +226,9 @@ export default class ProviderView extends View {
|
|
|
|
|
|
let { breadcrumbs } = this.plugin.getPluginState()
|
|
|
|
|
|
- const index = breadcrumbs.findIndex((dir) => requestPath === dir.requestPath)
|
|
|
+ const index = breadcrumbs.findIndex(
|
|
|
+ (dir) => requestPath === dir.requestPath,
|
|
|
+ )
|
|
|
|
|
|
if (index !== -1) {
|
|
|
// means we navigated back to a known directory (already in the stack), so cut the stack off there
|
|
@@ -169,28 +239,41 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
|
|
|
this.nextPagePath = requestPath
|
|
|
- let files = []
|
|
|
- let folders = []
|
|
|
+ let files: CompanionFile[] = []
|
|
|
+ let folders: CompanionFile[] = []
|
|
|
do {
|
|
|
- const { files: newFiles, folders: newFolders } = await this.#listFilesAndFolders({
|
|
|
- breadcrumbs, signal,
|
|
|
- })
|
|
|
+ const { files: newFiles, folders: newFolders } =
|
|
|
+ await this.#listFilesAndFolders({
|
|
|
+ breadcrumbs,
|
|
|
+ signal,
|
|
|
+ })
|
|
|
|
|
|
files = files.concat(newFiles)
|
|
|
folders = folders.concat(newFolders)
|
|
|
|
|
|
- this.setLoading(this.plugin.uppy.i18n('loadedXFiles', { numFiles: files.length + folders.length }))
|
|
|
- } while (
|
|
|
- this.opts.loadAllFiles && this.nextPagePath
|
|
|
- )
|
|
|
-
|
|
|
- this.plugin.setPluginState({ folders, files, breadcrumbs, filterInput: '' })
|
|
|
+ this.setLoading(
|
|
|
+ this.plugin.uppy.i18n('loadedXFiles', {
|
|
|
+ numFiles: files.length + folders.length,
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ } while (this.opts.loadAllFiles && this.nextPagePath)
|
|
|
+
|
|
|
+ this.plugin.setPluginState({
|
|
|
+ folders,
|
|
|
+ files,
|
|
|
+ breadcrumbs,
|
|
|
+ filterInput: '',
|
|
|
+ })
|
|
|
})
|
|
|
} catch (err) {
|
|
|
// This is the first call that happens when the provider view loads, after auth, so it's probably nice to show any
|
|
|
// error occurring here to the user.
|
|
|
if (err?.name === 'UserFacingApiError') {
|
|
|
- this.plugin.uppy.info({ message: this.plugin.uppy.i18n(err.message) }, 'warning', 5000)
|
|
|
+ this.plugin.uppy.info(
|
|
|
+ { message: this.plugin.uppy.i18n(err.message) },
|
|
|
+ 'warning',
|
|
|
+ 5000,
|
|
|
+ )
|
|
|
return
|
|
|
}
|
|
|
|
|
@@ -202,10 +285,8 @@ export default class ProviderView extends View {
|
|
|
|
|
|
/**
|
|
|
* Fetches new folder
|
|
|
- *
|
|
|
- * @param {object} folder
|
|
|
*/
|
|
|
- getNextFolder (folder) {
|
|
|
+ getNextFolder(folder: CompanionFile): void {
|
|
|
this.getFolder(folder.requestPath, folder.name)
|
|
|
this.lastCheckbox = undefined
|
|
|
}
|
|
@@ -213,10 +294,17 @@ export default class ProviderView extends View {
|
|
|
/**
|
|
|
* Removes session token on client side.
|
|
|
*/
|
|
|
- async logout () {
|
|
|
+ async logout(): Promise<void> {
|
|
|
try {
|
|
|
await this.#withAbort(async (signal) => {
|
|
|
- const res = await this.provider.logout({ signal })
|
|
|
+ const res = await this.provider.logout<{
|
|
|
+ ok: boolean
|
|
|
+ revoked: boolean
|
|
|
+ manual_revoke_url: string
|
|
|
+ }>({
|
|
|
+ signal,
|
|
|
+ })
|
|
|
+ // res.ok is from the JSON body, not to be confused with Response.ok
|
|
|
if (res.ok) {
|
|
|
if (!res.revoked) {
|
|
|
const message = this.plugin.uppy.i18n('companionUnauthorizeHint', {
|
|
@@ -241,15 +329,15 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- filterQuery (input) {
|
|
|
+ filterQuery(input: string): void {
|
|
|
this.plugin.setPluginState({ filterInput: input })
|
|
|
}
|
|
|
|
|
|
- clearFilter () {
|
|
|
+ clearFilter(): void {
|
|
|
this.plugin.setPluginState({ filterInput: '' })
|
|
|
}
|
|
|
|
|
|
- async handleAuth (authFormData) {
|
|
|
+ async handleAuth(authFormData?: unknown): Promise<void> {
|
|
|
try {
|
|
|
await this.#withAbort(async (signal) => {
|
|
|
this.setLoading(true)
|
|
@@ -259,7 +347,11 @@ export default class ProviderView extends View {
|
|
|
})
|
|
|
} catch (err) {
|
|
|
if (err.name === 'UserFacingApiError') {
|
|
|
- this.plugin.uppy.info({ message: this.plugin.uppy.i18n(err.message) }, 'warning', 5000)
|
|
|
+ this.plugin.uppy.info(
|
|
|
+ { message: this.plugin.uppy.i18n(err.message) },
|
|
|
+ 'warning',
|
|
|
+ 5000,
|
|
|
+ )
|
|
|
return
|
|
|
}
|
|
|
|
|
@@ -269,7 +361,7 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async handleScroll (event) {
|
|
|
+ async handleScroll(event: Event): Promise<void> {
|
|
|
if (this.shouldHandleScroll(event) && this.nextPagePath) {
|
|
|
this.isHandlingScroll = true
|
|
|
|
|
@@ -277,14 +369,19 @@ export default class ProviderView extends View {
|
|
|
await this.#withAbort(async (signal) => {
|
|
|
const { files, folders, breadcrumbs } = this.plugin.getPluginState()
|
|
|
|
|
|
- const { files: newFiles, folders: newFolders } = await this.#listFilesAndFolders({
|
|
|
- breadcrumbs, signal,
|
|
|
- })
|
|
|
+ const { files: newFiles, folders: newFolders } =
|
|
|
+ await this.#listFilesAndFolders({
|
|
|
+ breadcrumbs,
|
|
|
+ signal,
|
|
|
+ })
|
|
|
|
|
|
const combinedFiles = files.concat(newFiles)
|
|
|
const combinedFolders = folders.concat(newFolders)
|
|
|
|
|
|
- this.plugin.setPluginState({ folders: combinedFolders, files: combinedFiles })
|
|
|
+ this.plugin.setPluginState({
|
|
|
+ folders: combinedFolders,
|
|
|
+ files: combinedFiles,
|
|
|
+ })
|
|
|
})
|
|
|
} catch (error) {
|
|
|
this.handleError(error)
|
|
@@ -294,7 +391,21 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async #recursivelyListAllFiles ({ requestPath, absDirPath, relDirPath, queue, onFiles, signal }) {
|
|
|
+ async #recursivelyListAllFiles({
|
|
|
+ requestPath,
|
|
|
+ absDirPath,
|
|
|
+ relDirPath,
|
|
|
+ queue,
|
|
|
+ onFiles,
|
|
|
+ signal,
|
|
|
+ }: {
|
|
|
+ requestPath: string
|
|
|
+ absDirPath: string
|
|
|
+ relDirPath: string
|
|
|
+ queue: PQueue
|
|
|
+ onFiles: (files: CompanionFile[]) => void
|
|
|
+ signal: AbortSignal
|
|
|
+ }) {
|
|
|
let curPath = requestPath
|
|
|
|
|
|
while (curPath) {
|
|
@@ -307,37 +418,41 @@ export default class ProviderView extends View {
|
|
|
onFiles(files)
|
|
|
|
|
|
// recursively queue call to self for each folder
|
|
|
- const promises = folders.map(async (folder) => queue.add(async () => (
|
|
|
- this.#recursivelyListAllFiles({
|
|
|
- requestPath: folder.requestPath,
|
|
|
- absDirPath: prependPath(absDirPath, folder.name),
|
|
|
- relDirPath: prependPath(relDirPath, folder.name),
|
|
|
- queue,
|
|
|
- onFiles,
|
|
|
- signal,
|
|
|
- })
|
|
|
- )))
|
|
|
+ const promises = folders.map(async (folder) =>
|
|
|
+ queue.add(async () =>
|
|
|
+ this.#recursivelyListAllFiles({
|
|
|
+ requestPath: folder.requestPath,
|
|
|
+ absDirPath: prependPath(absDirPath, folder.name),
|
|
|
+ relDirPath: prependPath(relDirPath, folder.name),
|
|
|
+ queue,
|
|
|
+ onFiles,
|
|
|
+ signal,
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
await Promise.all(promises) // in case we get an error
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async donePicking () {
|
|
|
+ async donePicking(): Promise<void> {
|
|
|
this.setLoading(true)
|
|
|
try {
|
|
|
await this.#withAbort(async (signal) => {
|
|
|
const { currentSelection } = this.plugin.getPluginState()
|
|
|
|
|
|
- const messages = []
|
|
|
- const newFiles = []
|
|
|
+ const messages: string[] = []
|
|
|
+ const newFiles: CompanionFile[] = []
|
|
|
|
|
|
for (const selectedItem of currentSelection) {
|
|
|
const { requestPath } = selectedItem
|
|
|
|
|
|
- const withRelDirPath = (newItem) => ({
|
|
|
+ const withRelDirPath = (newItem: CompanionFile) => ({
|
|
|
...newItem,
|
|
|
// calculate the file's path relative to the user's selected item's path
|
|
|
// see https://github.com/transloadit/uppy/pull/4537#issuecomment-1614236655
|
|
|
- relDirPath: newItem.absDirPath.replace(selectedItem.absDirPath, '').replace(/^\//, ''),
|
|
|
+ relDirPath: (newItem.absDirPath as string)
|
|
|
+ .replace(selectedItem.absDirPath as string, '')
|
|
|
+ .replace(/^\//, ''),
|
|
|
})
|
|
|
|
|
|
if (selectedItem.isFolder) {
|
|
@@ -346,9 +461,10 @@ export default class ProviderView extends View {
|
|
|
|
|
|
const queue = new PQueue({ concurrency: 6 })
|
|
|
|
|
|
- const onFiles = (files) => {
|
|
|
+ const onFiles = (files: CompanionFile[]) => {
|
|
|
for (const newFile of files) {
|
|
|
const tagFile = this.getTagFile(newFile)
|
|
|
+
|
|
|
const id = getSafeFileId(tagFile)
|
|
|
// If the same folder is added again, we don't want to send
|
|
|
// X amount of duplicate file notifications, we want to say
|
|
@@ -357,7 +473,11 @@ export default class ProviderView extends View {
|
|
|
if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) {
|
|
|
newFiles.push(withRelDirPath(newFile))
|
|
|
numNewFiles++
|
|
|
- this.setLoading(this.plugin.uppy.i18n('addedNumFiles', { numFiles: numNewFiles }))
|
|
|
+ this.setLoading(
|
|
|
+ this.plugin.uppy.i18n('addedNumFiles', {
|
|
|
+ numFiles: numNewFiles,
|
|
|
+ }),
|
|
|
+ )
|
|
|
}
|
|
|
isEmpty = false
|
|
|
}
|
|
@@ -365,7 +485,10 @@ export default class ProviderView extends View {
|
|
|
|
|
|
await this.#recursivelyListAllFiles({
|
|
|
requestPath,
|
|
|
- absDirPath: prependPath(selectedItem.absDirPath, selectedItem.name),
|
|
|
+ absDirPath: prependPath(
|
|
|
+ selectedItem.absDirPath,
|
|
|
+ selectedItem.name,
|
|
|
+ ),
|
|
|
relDirPath: selectedItem.name,
|
|
|
queue,
|
|
|
onFiles,
|
|
@@ -385,7 +508,8 @@ export default class ProviderView extends View {
|
|
|
// (only later after addFiles has been called) so we should probably rewrite this.
|
|
|
// Example: If all files fail to add due to restriction error, it will still say "Added 100 files from folder"
|
|
|
message = this.plugin.uppy.i18n('folderAdded', {
|
|
|
- smart_count: numNewFiles, folder: selectedItem.name,
|
|
|
+ smart_count: numNewFiles,
|
|
|
+ folder: selectedItem.name,
|
|
|
})
|
|
|
}
|
|
|
|
|
@@ -401,10 +525,15 @@ export default class ProviderView extends View {
|
|
|
// finished all async operations before we add any file
|
|
|
// see https://github.com/transloadit/uppy/pull/4384
|
|
|
this.plugin.uppy.log('Adding files from a remote provider')
|
|
|
- this.plugin.uppy.addFiles(newFiles.map((file) => this.getTagFile(file, this.requestClientId)))
|
|
|
+ this.plugin.uppy.addFiles(
|
|
|
+ // @ts-expect-error `addFiles` expects `body` to be `File` or `Blob`,
|
|
|
+ // but as the todo comment in `View.ts` indicates, we strangly pass `CompanionFile` as `body`.
|
|
|
+ // For now it's better to ignore than to have a potential breaking change.
|
|
|
+ newFiles.map((file) => this.getTagFile(file, this.requestClientId)),
|
|
|
+ )
|
|
|
|
|
|
this.plugin.setPluginState({ filterInput: '' })
|
|
|
- messages.forEach(message => this.plugin.uppy.info(message))
|
|
|
+ messages.forEach((message) => this.plugin.uppy.info(message))
|
|
|
|
|
|
this.clearSelection()
|
|
|
})
|
|
@@ -415,7 +544,10 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- render (state, viewOptions = {}) {
|
|
|
+ render(
|
|
|
+ state: unknown,
|
|
|
+ viewOptions: Omit<ViewOptions<M, B, PluginType>, 'provider'> = {},
|
|
|
+ ): JSX.Element {
|
|
|
const { authenticated, didFirstRender } = this.plugin.getPluginState()
|
|
|
const { i18n } = this.plugin.uppy
|
|
|
|
|
@@ -424,7 +556,8 @@ export default class ProviderView extends View {
|
|
|
}
|
|
|
|
|
|
const targetViewOptions = { ...this.opts, ...viewOptions }
|
|
|
- const { files, folders, filterInput, loading, currentSelection } = this.plugin.getPluginState()
|
|
|
+ const { files, folders, filterInput, loading, currentSelection } =
|
|
|
+ this.plugin.getPluginState()
|
|
|
const { isChecked, toggleCheckbox, recordShiftKeyPress, filterItems } = this
|
|
|
const hasInput = filterInput !== ''
|
|
|
const pluginIcon = this.plugin.icon || defaultPickerIcon
|
|
@@ -465,7 +598,8 @@ export default class ProviderView extends View {
|
|
|
handleScroll: this.handleScroll,
|
|
|
done: this.donePicking,
|
|
|
cancel: this.cancelPicking,
|
|
|
- headerComponent: Header(headerProps),
|
|
|
+ // eslint-disable-next-line react/jsx-props-no-spreading
|
|
|
+ headerComponent: <Header<M, B> {...headerProps} />,
|
|
|
title: this.plugin.title,
|
|
|
viewType: targetViewOptions.viewType,
|
|
|
showTitles: targetViewOptions.showTitles,
|
|
@@ -473,7 +607,9 @@ export default class ProviderView extends View {
|
|
|
pluginIcon,
|
|
|
i18n: this.plugin.uppy.i18n,
|
|
|
uppyFiles: this.plugin.uppy.getFiles(),
|
|
|
- validateRestrictions: (...args) => this.plugin.uppy.validateRestrictions(...args),
|
|
|
+ validateRestrictions: (
|
|
|
+ ...args: Parameters<Uppy<M, B>['validateRestrictions']>
|
|
|
+ ) => this.plugin.uppy.validateRestrictions(...args),
|
|
|
isLoading: loading,
|
|
|
}
|
|
|
|
|
@@ -495,7 +631,7 @@ export default class ProviderView extends View {
|
|
|
return (
|
|
|
<CloseWrapper onUnmount={this.clearSelection}>
|
|
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
|
|
- <Browser {...browserProps} />
|
|
|
+ <Browser<M, B> {...browserProps} />
|
|
|
</CloseWrapper>
|
|
|
)
|
|
|
}
|