Explorar el Código

@uppy/companion-client: type changes for provider-views (#4938)

Antoine du Hamel hace 1 año
padre
commit
d7e577a449

+ 19 - 23
packages/@uppy/companion-client/src/Provider.ts

@@ -1,10 +1,12 @@
-import type { Uppy, BasePlugin } from '@uppy/core'
-import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile'
+import type { Uppy } from '@uppy/core'
+import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
 import type { PluginOpts } from '@uppy/core/lib/BasePlugin.ts'
-import RequestClient, {
-  authErrorStatusCode,
-  type RequestOptions,
-} from './RequestClient.ts'
+import type {
+  RequestOptions,
+  CompanionClientProvider,
+} from '@uppy/utils/lib/CompanionClientProvider'
+import type { UnknownProviderPlugin } from '@uppy/core/lib/Uppy.ts'
+import RequestClient, { authErrorStatusCode } from './RequestClient.ts'
 import * as tokenStorage from './tokenStorage.ts'
 
 // TODO: remove deprecated options in next major release
@@ -22,13 +24,6 @@ export interface Opts extends PluginOpts {
   provider: string
 }
 
-interface ProviderPlugin<M extends Meta, B extends Body>
-  extends BasePlugin<Opts, M, B> {
-  files: UppyFile<M, B>[]
-
-  storage: typeof tokenStorage
-}
-
 const getName = (id: string) => {
   return id
     .split('-')
@@ -64,10 +59,10 @@ function isOriginAllowed(
   ) // allowing for trailing '/'
 }
 
-export default class Provider<
-  M extends Meta,
-  B extends Body,
-> extends RequestClient<M, B> {
+export default class Provider<M extends Meta, B extends Body>
+  extends RequestClient<M, B>
+  implements CompanionClientProvider
+{
   #refreshingTokenPromise: Promise<void> | undefined
 
   provider: string
@@ -141,7 +136,10 @@ export default class Provider<
   }
 
   #getPlugin() {
-    const plugin = this.uppy.getPlugin(this.pluginId) as ProviderPlugin<M, B>
+    const plugin = this.uppy.getPlugin(this.pluginId) as UnknownProviderPlugin<
+      M,
+      B
+    >
     if (plugin == null) throw new Error('Plugin was nullish')
     return plugin
   }
@@ -375,23 +373,21 @@ export default class Provider<
     }
   }
 
-  list<ResBody extends Record<string, unknown>>(
+  list<ResBody>(
     directory: string | undefined,
     options: RequestOptions,
   ): Promise<ResBody> {
     return this.get<ResBody>(`${this.id}/list/${directory || ''}`, options)
   }
 
-  async logout<ResBody extends Record<string, unknown>>(
-    options: RequestOptions,
-  ): Promise<ResBody> {
+  async logout<ResBody>(options?: RequestOptions): Promise<ResBody> {
     const response = await this.get<ResBody>(`${this.id}/logout`, options)
     await this.removeAuthToken()
     return response
   }
 
   static initPlugin(
-    plugin: ProviderPlugin<any, any>, // any because static methods cannot use class generics
+    plugin: UnknownProviderPlugin<any, any>, // any because static methods cannot use class generics
     opts: Opts,
     defaultOpts: Record<string, unknown>,
   ): void {

+ 1 - 7
packages/@uppy/companion-client/src/RequestClient.ts

@@ -11,6 +11,7 @@ import getSocketHost from '@uppy/utils/lib/getSocketHost'
 
 import type Uppy from '@uppy/core'
 import type { UppyFile, Meta, Body } from '@uppy/utils/lib/UppyFile'
+import type { RequestOptions } from '@uppy/utils/lib/CompanionClientProvider.ts'
 import AuthError from './AuthError.ts'
 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 // @ts-ignore We don't want TS to generate types for the package.json
@@ -28,13 +29,6 @@ export type Opts = {
   companionKeysParams?: Record<string, string>
 }
 
-export type RequestOptions = {
-  method?: string
-  data?: Record<string, unknown>
-  skipPostResponse?: boolean
-  signal?: AbortSignal
-  qs?: Record<string, string>
-}
 type _RequestOptions =
   | boolean // TODO: remove this on the next major
   | RequestOptions

+ 6 - 8
packages/@uppy/companion-client/src/SearchProvider.ts

@@ -2,6 +2,7 @@
 
 import type { Body, Meta } from '@uppy/utils/lib/UppyFile.ts'
 import type { Uppy } from '@uppy/core'
+import type { CompanionClientSearchProvider } from '@uppy/utils/lib/CompanionClientProvider'
 import RequestClient, { type Opts } from './RequestClient.ts'
 
 const getName = (id: string): string => {
@@ -11,10 +12,10 @@ const getName = (id: string): string => {
     .join(' ')
 }
 
-export default class SearchProvider<
-  M extends Meta,
-  B extends Body,
-> extends RequestClient<M, B> {
+export default class SearchProvider<M extends Meta, B extends Body>
+  extends RequestClient<M, B>
+  implements CompanionClientSearchProvider
+{
   provider: string
 
   id: string
@@ -35,10 +36,7 @@ export default class SearchProvider<
     return `${this.hostname}/search/${this.id}/get/${id}`
   }
 
-  search<ResBody extends Record<string, unknown>>(
-    text: string,
-    queries?: string,
-  ): Promise<ResBody> {
+  search<ResBody>(text: string, queries?: string): Promise<ResBody> {
     return this.get<ResBody>(
       `search/${this.id}/list?q=${encodeURIComponent(text)}${
         queries ? `&${queries}` : ''

+ 79 - 10
packages/@uppy/core/src/Uppy.ts

@@ -14,6 +14,11 @@ import getFileType from '@uppy/utils/lib/getFileType'
 import getFileNameAndExtension from '@uppy/utils/lib/getFileNameAndExtension'
 import { getSafeFileId } from '@uppy/utils/lib/generateFileID'
 import type { UppyFile, Meta, Body } from '@uppy/utils/lib/UppyFile'
+import type { CompanionFile } from '@uppy/utils/lib/CompanionFile'
+import type {
+  CompanionClientProvider,
+  CompanionClientSearchProvider,
+} from '@uppy/utils/lib/CompanionClientProvider'
 import type {
   FileProgressNotStarted,
   FileProgressStarted,
@@ -46,19 +51,83 @@ type FileRemoveReason = 'user' | 'cancel-all'
 
 type LogLevel = 'info' | 'warning' | 'error' | 'success'
 
-export type UnknownPlugin<M extends Meta, B extends Body> = InstanceType<
-  typeof BasePlugin<any, M, B> | typeof UIPlugin<any, M, B>
->
-
-type UnknownProviderPlugin<M extends Meta, B extends Body> = UnknownPlugin<
-  M,
-  B
-> & {
-  provider: {
-    logout: () => void
+export type UnknownPlugin<
+  M extends Meta,
+  B extends Body,
+  PluginState extends Record<string, unknown> = Record<string, unknown>,
+> = BasePlugin<any, M, B, PluginState>
+
+export type UnknownProviderPluginState = {
+  authenticated: boolean | undefined
+  breadcrumbs: {
+    requestPath: string
+    name: string
+    id?: string
+  }[]
+  didFirstRender: boolean
+  currentSelection: CompanionFile[]
+  filterInput: string
+  loading: boolean | string
+  folders: CompanionFile[]
+  files: CompanionFile[]
+  isSearchVisible: boolean
+}
+/*
+ * UnknownProviderPlugin can be any Companion plugin (such as Google Drive).
+ * As the plugins are passed around throughout Uppy we need a generic type for this.
+ * It may seems like duplication, but this type safe. Changing the type of `storage`
+ * will error in the `Provider` class of @uppy/companion-client and vice versa.
+ *
+ * Note that this is the *plugin* class, not a version of the `Provider` class.
+ * `Provider` does operate on Companion plugins with `uppy.getPlugin()`.
+ */
+export type UnknownProviderPlugin<
+  M extends Meta,
+  B extends Body,
+> = UnknownPlugin<M, B, UnknownProviderPluginState> & {
+  onFirstRender: () => void
+  title: string
+  files: UppyFile<M, B>[]
+  icon: () => JSX.Element
+  provider: CompanionClientProvider
+  storage: {
+    getItem: (key: string) => Promise<string | null>
+    setItem: (key: string, value: string) => Promise<void>
+    removeItem: (key: string) => Promise<void>
   }
 }
 
+/*
+ * UnknownSearchProviderPlugin can be any search Companion plugin (such as Unsplash).
+ * As the plugins are passed around throughout Uppy we need a generic type for this.
+ * It may seems like duplication, but this type safe. Changing the type of `title`
+ * will error in the `SearchProvider` class of @uppy/companion-client and vice versa.
+ *
+ * Note that this is the *plugin* class, not a version of the `SearchProvider` class.
+ * `SearchProvider` does operate on Companion plugins with `uppy.getPlugin()`.
+ */
+export type UnknownSearchProviderPluginState = {
+  isInputMode?: boolean
+  searchTerm?: string | null
+} & Pick<
+  UnknownProviderPluginState,
+  | 'loading'
+  | 'files'
+  | 'folders'
+  | 'currentSelection'
+  | 'filterInput'
+  | 'didFirstRender'
+>
+export type UnknownSearchProviderPlugin<
+  M extends Meta,
+  B extends Body,
+> = UnknownPlugin<M, B, UnknownSearchProviderPluginState> & {
+  onFirstRender: () => void
+  title: string
+  icon: () => JSX.Element
+  provider: CompanionClientSearchProvider
+}
+
 // The user facing type for UppyFile used in uppy.addFile() and uppy.setOptions()
 export type MinimalRequiredUppyFile<M extends Meta, B extends Body> = Required<
   Pick<UppyFile<M, B>, 'name' | 'data' | 'type' | 'source'>

+ 8 - 1
packages/@uppy/core/src/index.ts

@@ -1,5 +1,12 @@
 export { default } from './Uppy.ts'
-export { default as Uppy, type UppyEventMap, type State } from './Uppy.ts'
+export {
+  default as Uppy,
+  type UppyEventMap,
+  type State,
+  type UnknownPlugin,
+  type UnknownProviderPlugin,
+  type UnknownSearchProviderPlugin,
+} from './Uppy.ts'
 export { default as UIPlugin } from './UIPlugin.ts'
 export { default as BasePlugin } from './BasePlugin.ts'
 export { debugLogger } from './loggers.ts'

+ 2 - 0
packages/@uppy/utils/package.json

@@ -66,6 +66,8 @@
     "./lib/fileFilters": "./lib/fileFilters.js",
     "./lib/VirtualList": "./lib/VirtualList.js",
     "./lib/UppyFile": "./lib/UppyFile.js",
+    "./lib/CompanionFile": "./lib/CompanionFile.js",
+    "./lib/CompanionClientProvider": "./lib/CompanionClientProvider.js",
     "./lib/FileProgress": "./lib/FileProgress.js",
     "./src/microtip.scss": "./src/microtip.scss",
     "./lib/UserFacingApiError": "./lib/UserFacingApiError.js"

+ 35 - 0
packages/@uppy/utils/src/CompanionClientProvider.ts

@@ -0,0 +1,35 @@
+export type RequestOptions = {
+  method?: string
+  data?: Record<string, unknown>
+  skipPostResponse?: boolean
+  signal?: AbortSignal
+  authFormData?: unknown
+  qs?: Record<string, string>
+}
+
+/**
+ * CompanionClientProvider is subset of the types of the `Provider`
+ * class from @uppy/companion-client.
+ *
+ * This is needed as the `Provider` class is passed around in Uppy and we
+ * need to have shared types for it. Although we are duplicating some types,
+ * this is still safe as `Provider implements CompanionClientProvider`
+ * so any changes here will error there and vice versa.
+ *
+ * TODO: remove this once companion-client and provider-views are merged into a single plugin.
+ */
+export interface CompanionClientProvider {
+  name: string
+  provider: string
+  login(options?: RequestOptions): Promise<void>
+  logout<ResBody>(options?: RequestOptions): Promise<ResBody>
+  list<ResBody>(
+    directory: string | undefined,
+    options: RequestOptions,
+  ): Promise<ResBody>
+}
+export interface CompanionClientSearchProvider {
+  name: string
+  provider: string
+  search<ResBody>(text: string, queries?: string): Promise<ResBody>
+}

+ 25 - 0
packages/@uppy/utils/src/CompanionFile.ts

@@ -0,0 +1,25 @@
+/**
+ * CompanionFile represents a file object returned by the Companion API.
+ */
+export type CompanionFile = {
+  id: string
+  name: string
+  /*
+   * Url to the thumbnail icon
+   */
+  icon: string
+  type: string
+  mimeType: string
+  extension: string
+  size: number
+  isFolder: boolean
+  modifiedDate: string
+  thumbnail?: string
+  requestPath: string
+  relDirPath?: string
+  absDirPath?: string
+  author?: {
+    name?: string
+    url?: string
+  }
+}