uppy/packages/@uppy/components/src/hooks/remote-source.ts

130 lines
3.5 KiB
TypeScript

import type Uppy from '@uppy/core'
import type {
PartialTreeFolder,
UnknownProviderPlugin,
UnknownProviderPluginState,
UppyEventMap,
} from '@uppy/core'
import type { ProviderViews } from '@uppy/core/provider-views'
import { dequal } from 'dequal/lite'
import { Subscribers } from './utils.js'
export type RemoteSourceKeys =
| 'Box'
| 'Dropbox'
| 'Facebook'
| 'GoogleDrive'
| 'OneDrive'
| 'Unsplash'
| 'Url'
| 'Zoom'
export type RemoteSourceSnapshot = {
state: UnknownProviderPluginState & {
breadcrumbs: PartialTreeFolder[]
selectedAmount: number
error: string | null
}
login: ProviderViews<any, any>['handleAuth']
logout: ProviderViews<any, any>['logout']
open: ProviderViews<any, any>['openFolder']
checkbox: ProviderViews<any, any>['toggleCheckbox']
done: ProviderViews<any, any>['donePicking']
cancel: ProviderViews<any, any>['cancelSelection']
}
export type RemoteSourceStore = {
subscribe: (listener: () => void) => () => void
getSnapshot: () => RemoteSourceSnapshot
mount: () => void
unmount: () => void
}
export function createRemoteSourceController(
uppy: Uppy,
sourceId: RemoteSourceKeys,
): RemoteSourceStore {
const plugin = uppy.getPlugin<UnknownProviderPlugin<any, any>>(sourceId)
if (!plugin) {
throw new Error(
`(${sourceId}) is not installed. Install the plugin or the preset @uppy/remote-sources and add it to the Uppy instance`,
)
}
const subscribers = new Subscribers()
const view = plugin.view as ProviderViews<any, any>
let didFirstRender = false
const onStateUpdate: UppyEventMap<any, any>['state-update'] = (
prev,
next,
patch,
) => {
if (patch?.plugins?.[sourceId]) {
subscribers.emit()
}
}
// Keep a cached snapshot so that the reference stays stable when nothing
// has changed, as expected by `useSyncExternalStore` from React
let cachedSnapshot: RemoteSourceSnapshot = {
state: {
...plugin.getPluginState(),
// By default the partialTree is an array of all folders you have opened at some point,
// not the contents of the current folder. We filter it here to make it more intuitive to work with.
partialTree: view.getDisplayedPartialTree(),
selectedAmount: view.getSelectedAmount(),
error: view.validateAggregateRestrictions(
plugin.getPluginState().partialTree,
),
breadcrumbs: view.getBreadcrumbs(),
},
login: view?.handleAuth,
logout: view?.logout,
open: view?.openFolder,
checkbox: view?.toggleCheckbox,
done: view?.donePicking,
cancel: view?.cancelSelection,
}
const getSnapshot = () => {
const nextSnapshot = {
...cachedSnapshot,
state: {
...plugin.getPluginState(),
partialTree: view.getDisplayedPartialTree(),
selectedAmount: view.getSelectedAmount(),
error: view.validateAggregateRestrictions(
plugin.getPluginState().partialTree,
),
breadcrumbs: view.getBreadcrumbs(),
},
}
if (!dequal(cachedSnapshot.state, nextSnapshot.state)) {
cachedSnapshot = nextSnapshot
}
return cachedSnapshot
}
const mount = () => {
uppy.on('state-update', onStateUpdate)
if (!didFirstRender) {
view.openFolder?.(plugin.rootFolderId)
view.provider.fetchPreAuthToken?.()
didFirstRender = true
}
}
const unmount = () => {
didFirstRender = false
uppy.off('state-update', onStateUpdate)
}
return {
mount,
unmount,
subscribe: subscribers.add,
getSnapshot,
}
}