Merge "main" into "restore/pr-6284 and resolve conflicts

This commit is contained in:
prakash 2026-07-07 18:19:41 +05:30
commit e51c1ad99b
No known key found for this signature in database
409 changed files with 1193 additions and 2873 deletions

View file

@ -3,13 +3,10 @@
"@uppy/google-photos-picker": patch
"@uppy/google-drive-picker": patch
"@uppy/thumbnail-generator": patch
"@uppy/companion-client": patch
"@uppy/golden-retriever": patch
"@uppy/image-generator": patch
"@uppy/provider-views": patch
"@uppy/remote-sources": patch
"@uppy/screen-capture": patch
"@uppy/store-default": patch
"@uppy/google-drive": patch
"@uppy/image-editor": patch
"@uppy/transloadit": patch
@ -31,7 +28,6 @@
"@uppy/webdav": patch
"@uppy/audio": patch
"@uppy/react": patch
"@uppy/utils": patch
"@uppy/core": patch
"@uppy/form": patch
"@uppy/zoom": patch

View file

@ -1,5 +1,5 @@
---
"@uppy/companion-client": patch
"@uppy/core": patch
---
uploadRemoteFile() now queues token request and websocket request as a single job in the request queue.

View file

@ -1,8 +1,8 @@
---
"@uppy/companion-client": major
"@uppy/core": major
"@uppy/companion": major
---
Send token using websocket instead of window.opener.
Breaking in `@uppy/companion-client` because it needs newest version of Companion in order to work.
Breaking in `@uppy/core` because it needs newest version of Companion in order to work.
Breaking in `@uppy/companion` because `companion.socket()` now requires `companionOptions` to be passed as the second argument.

View file

@ -1,8 +1,12 @@
/** @jsx h */
import { getAllowedHosts, Provider, tokenStorage } from '@uppy/companion-client'
import { UIPlugin } from '@uppy/core'
import { ProviderViews } from '@uppy/provider-views'
import {
getAllowedHosts,
Provider,
tokenStorage,
} from '@uppy/core/companion-client'
import { ProviderViews } from '@uppy/core/provider-views'
const defaultOptions = {}

View file

@ -4,11 +4,9 @@
"private": true,
"type": "module",
"dependencies": {
"@uppy/companion-client": "workspace:*",
"@uppy/core": "workspace:*",
"@uppy/dashboard": "workspace:*",
"@uppy/google-drive": "workspace:*",
"@uppy/provider-views": "workspace:*",
"@uppy/tus": "workspace:*",
"preact": "^10.29.2"
},

View file

@ -2,11 +2,7 @@
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": [
"@uppy/core#build",
"@uppy/dashboard#build",
"@uppy/utils#build"
],
"dependsOn": ["@uppy/core#build", "@uppy/dashboard#build"],
"inputs": [
"projects/uppy/angular/src/**/*.{js,ts,jsx,tsx}",
"package.json",

View file

@ -40,7 +40,6 @@
"./package.json": "./package.json"
},
"dependencies": {
"@uppy/utils": "workspace:^",
"preact": "^10.29.2"
},
"devDependencies": {

View file

@ -7,8 +7,8 @@ import type {
} from '@uppy/core'
import { UIPlugin } from '@uppy/core'
import type { LocaleStrings } from '@uppy/utils'
import { getFileTypeExtension } from '@uppy/utils'
import type { LocaleStrings } from '@uppy/core/utils'
import { getFileTypeExtension } from '@uppy/core/utils'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
import PermissionsScreen from './PermissionsScreen.js'

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils'
import type { I18n } from '@uppy/core/utils'
interface DiscardButtonProps {
onDiscard: () => void

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils'
import type { I18n } from '@uppy/core/utils'
import type { h } from 'preact'
interface PermissionsScreenProps {

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils'
import type { I18n } from '@uppy/core/utils'
interface RecordButtonProps {
recording: boolean

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils'
import type { I18n } from '@uppy/core/utils'
import { useEffect, useRef } from 'preact/hooks'
import AudioSourceSelect, {
type AudioSourceSelectProps,

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils'
import type { I18n } from '@uppy/core/utils'
interface SubmitButtonProps {
onSubmit: () => void

View file

@ -7,9 +7,6 @@
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -6,9 +6,6 @@
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -37,10 +37,6 @@
".": "./lib/index.js",
"./package.json": "./package.json"
},
"dependencies": {
"@uppy/companion-client": "workspace:^",
"@uppy/utils": "workspace:^"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.362.0",
"@aws-sdk/client-sts": "^3.362.0",

View file

@ -0,0 +1,444 @@
import type { Body, Meta, UppyFile } from '@uppy/core'
import type {
RateLimitedQueue,
WrapPromiseFunctionType,
} from '@uppy/core/utils'
import type AwsS3Multipart from './index.js'
import type {
AwsS3MultipartOptions,
AwsS3UploadParameters,
uploadPartBytes,
} from './index.js'
import { type Chunk, pausingUploadReason } from './MultipartUploader.js'
import type { UploadPartBytesResult, UploadResult } from './utils.js'
import { throwIfAborted } from './utils.js'
function removeMetadataFromURL(urlString: string) {
const urlObject = new URL(urlString)
urlObject.search = ''
urlObject.hash = ''
return urlObject.href
}
export class HTTPCommunicationQueue<M extends Meta, B extends Body> {
#abortMultipartUpload!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['abortMultipartUpload']
>
#cache = new WeakMap()
#createMultipartUpload!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['createMultipartUpload']
>
#fetchSignature!: WrapPromiseFunctionType<AwsS3Multipart<M, B>['signPart']>
#getUploadParameters!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['getUploadParameters']
>
#listParts!: WrapPromiseFunctionType<AwsS3Multipart<M, B>['listParts']>
#previousRetryDelay!: number
#requests
#retryDelays!: { values: () => Iterator<number> }
#sendCompletionRequest!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['completeMultipartUpload']
>
#setS3MultipartState
#uploadPartBytes!: WrapPromiseFunctionType<uploadPartBytes>
#getFile
constructor(
requests: RateLimitedQueue,
options: AwsS3MultipartOptions<M, B>,
setS3MultipartState: (file: UppyFile<M, B>, result: UploadResult) => void,
getFile: (file: UppyFile<M, B>) => UppyFile<M, B>,
) {
this.#requests = requests
this.#setS3MultipartState = setS3MultipartState
this.#getFile = getFile
this.setOptions(options)
}
setOptions(options: Partial<AwsS3MultipartOptions<M, B>>): void {
const requests = this.#requests
if ('abortMultipartUpload' in options) {
this.#abortMultipartUpload = requests.wrapPromiseFunction(
options.abortMultipartUpload as any,
{ priority: 1 },
)
}
if ('createMultipartUpload' in options) {
this.#createMultipartUpload = requests.wrapPromiseFunction(
options.createMultipartUpload as any,
{ priority: -1 },
)
}
if ('signPart' in options) {
this.#fetchSignature = requests.wrapPromiseFunction(
options.signPart as any,
)
}
if ('listParts' in options) {
this.#listParts = requests.wrapPromiseFunction(options.listParts as any)
}
if ('completeMultipartUpload' in options) {
this.#sendCompletionRequest = requests.wrapPromiseFunction(
options.completeMultipartUpload as any,
{ priority: 1 },
)
}
if ('retryDelays' in options) {
this.#retryDelays = options.retryDelays ?? []
}
if ('uploadPartBytes' in options) {
this.#uploadPartBytes = requests.wrapPromiseFunction(
options.uploadPartBytes as any,
{ priority: Infinity },
)
}
if ('getUploadParameters' in options) {
this.#getUploadParameters = requests.wrapPromiseFunction(
options.getUploadParameters as any,
)
}
}
async #shouldRetry(err: any, retryDelayIterator: Iterator<number>) {
const requests = this.#requests
const status = err?.source?.status
// TODO: this retry logic is taken out of Tus. We should have a centralized place for retrying,
// perhaps the rate limited queue, and dedupe all plugins with that.
if (status == null) {
return false
}
if (status === 403 && err.message === 'Request has expired') {
if (!requests.isPaused) {
// We don't want to exhaust the retryDelayIterator as long as there are
// more than one request in parallel, to give slower connection a chance
// to catch up with the expiry set in Companion.
if (requests.limit === 1 || this.#previousRetryDelay == null) {
const next = retryDelayIterator.next()
if (next == null || next.done) {
return false
}
// If there are more than 1 request done in parallel, the RLQ limit is
// decreased and the failed request is requeued after waiting for a bit.
// If there is only one request in parallel, the limit can't be
// decreased, so we iterate over `retryDelayIterator` as we do for
// other failures.
// `#previousRetryDelay` caches the value so we can re-use it next time.
this.#previousRetryDelay = next.value
}
// No need to stop the other requests, we just want to lower the limit.
requests.rateLimit(0)
await new Promise((resolve) =>
setTimeout(resolve, this.#previousRetryDelay),
)
}
} else if (status === 429) {
// HTTP 429 Too Many Requests => to avoid the whole download to fail, pause all requests.
if (!requests.isPaused) {
const next = retryDelayIterator.next()
if (next == null || next.done) {
return false
}
requests.rateLimit(next.value)
}
} else if (status > 400 && status < 500 && status !== 409) {
// HTTP 4xx, the server won't send anything, it's doesn't make sense to retry
return false
} else if (typeof navigator !== 'undefined' && navigator.onLine === false) {
// The navigator is offline, let's wait for it to come back online.
if (!requests.isPaused) {
requests.pause()
window.addEventListener(
'online',
() => {
requests.resume()
},
{ once: true },
)
}
} else {
// Other error code means the request can be retried later.
const next = retryDelayIterator.next()
if (next == null || next.done) {
return false
}
await new Promise((resolve) => setTimeout(resolve, next.value))
}
return true
}
async getUploadId(
file: UppyFile<M, B>,
signal: AbortSignal,
): Promise<UploadResult> {
let cachedResult: Promise<UploadResult> | UploadResult | undefined
// As the cache is updated asynchronously, there could be a race condition
// where we just miss a new result so we loop here until we get nothing back,
// at which point it's out turn to create a new cache entry.
for (;;) {
if (file.data == null) throw new Error('File data is empty')
cachedResult = this.#cache.get(file.data)
if (cachedResult == null) break
try {
return await cachedResult
} catch {
// In case of failure, we want to ignore the cached error.
// At this point, either there's a new cached value, or we'll exit the loop a create a new one.
}
}
const promise = this.#createMultipartUpload(this.#getFile(file), signal)
const abortPromise = () => {
if (file.data == null) throw new Error('File data is empty')
promise.abort(signal.reason)
this.#cache.delete(file.data)
}
signal.addEventListener('abort', abortPromise, { once: true })
this.#cache.set(file.data, promise)
promise.then(
async (result) => {
signal.removeEventListener('abort', abortPromise)
this.#setS3MultipartState(file, result)
if (file.data == null) throw new Error('File data is empty')
this.#cache.set(file.data, result)
},
() => {
signal.removeEventListener('abort', abortPromise)
if (file.data == null) throw new Error('File data is empty')
this.#cache.delete(file.data)
},
)
return promise
}
async abortFileUpload(file: UppyFile<M, B>): Promise<void> {
if (file.data == null) throw new Error('File data is empty')
const result = this.#cache.get(file.data)
if (result == null) {
// If the createMultipartUpload request never was made, we don't
// need to send the abortMultipartUpload request.
return
}
// Remove the cache entry right away for follow-up requests do not try to
// use the soon-to-be aborted cached values.
this.#cache.delete(file.data)
this.#setS3MultipartState(file, Object.create(null))
let awaitedResult: UploadResult
try {
awaitedResult = await result
} catch {
// If the cached result rejects, there's nothing to abort.
return
}
await this.#abortMultipartUpload(this.#getFile(file), awaitedResult)
}
async #nonMultipartUpload(
file: UppyFile<M, B>,
chunk: Chunk,
signal?: AbortSignal,
) {
const {
method = 'POST',
url,
fields,
headers,
} = await this.#getUploadParameters(this.#getFile(file), {
signal,
}).abortOn(signal)
let body: FormData | Blob
const data = chunk.getData()
if (method.toUpperCase() === 'POST') {
const formData = new FormData()
Object.entries(fields!).forEach(([key, value]) => {
formData.set(key, value)
})
formData.set('file', data)
body = formData
} else {
body = data
}
const { onProgress, onComplete } = chunk
const result = (await this.#uploadPartBytes({
signature: { url, headers, method } as any,
body,
size: data.size,
onProgress,
onComplete,
signal,
}).abortOn(signal)) as unknown as B // todo this doesn't make sense
// Note: `fields.key` is not returned by old Companion versions.
// See https://github.com/transloadit/uppy/pull/5602
const key = fields?.key
this.#setS3MultipartState(file, { key: key! })
return {
...result,
location:
(result.location as string | undefined) ?? removeMetadataFromURL(url),
bucket: fields?.bucket,
key,
}
}
async uploadFile(
file: UppyFile<M, B>,
chunks: Chunk[],
signal: AbortSignal,
): Promise<B & Partial<UploadPartBytesResult>> {
throwIfAborted(signal)
if (chunks.length === 1 && !chunks[0].shouldUseMultipart) {
return this.#nonMultipartUpload(file, chunks[0], signal)
}
const { uploadId, key } = await this.getUploadId(file, signal)
throwIfAborted(signal)
try {
const parts = await Promise.all(
chunks.map((chunk, i) => this.uploadChunk(file, i + 1, chunk, signal)),
)
throwIfAborted(signal)
return await this.#sendCompletionRequest(
this.#getFile(file),
{ key, uploadId, parts, signal },
signal,
).abortOn(signal)
} catch (err) {
if (err?.cause !== pausingUploadReason && err?.name !== 'AbortError') {
// We purposefully don't wait for the promise and ignore its status,
// because we want the error `err` to bubble up ASAP to report it to the
// user. A failure to abort is not that big of a deal anyway.
this.abortFileUpload(file)
}
throw err
}
}
restoreUploadFile(file: UppyFile<M, B>, uploadIdAndKey: UploadResult): void {
if (file.data == null) throw new Error('File data is empty')
this.#cache.set(file.data, uploadIdAndKey)
}
async resumeUploadFile(
file: UppyFile<M, B>,
chunks: Array<Chunk | null>,
signal: AbortSignal,
): Promise<B> {
throwIfAborted(signal)
if (
chunks.length === 1 &&
chunks[0] != null &&
!chunks[0].shouldUseMultipart
) {
return this.#nonMultipartUpload(file, chunks[0], signal)
}
const { uploadId, key } = await this.getUploadId(file, signal)
throwIfAborted(signal)
const alreadyUploadedParts = await this.#listParts(
this.#getFile(file),
{ uploadId, key, signal },
signal,
).abortOn(signal)
throwIfAborted(signal)
const parts = await Promise.all(
chunks.map((chunk, i) => {
const partNumber = i + 1
const alreadyUploadedInfo = alreadyUploadedParts.find(
({ PartNumber }) => PartNumber === partNumber,
)
if (alreadyUploadedInfo == null) {
return this.uploadChunk(file, partNumber, chunk!, signal)
}
// Already uploaded chunks are set to null. If we are restoring the upload, we need to mark it as already uploaded.
chunk?.setAsUploaded?.()
return { PartNumber: partNumber, ETag: alreadyUploadedInfo.ETag }
}),
)
throwIfAborted(signal)
return this.#sendCompletionRequest(
this.#getFile(file),
{ key, uploadId, parts, signal },
signal,
).abortOn(signal)
}
async uploadChunk(
file: UppyFile<M, B>,
partNumber: number,
chunk: Chunk,
signal: AbortSignal,
): Promise<UploadPartBytesResult & { PartNumber: number }> {
throwIfAborted(signal)
const { uploadId, key } = await this.getUploadId(file, signal)
const signatureRetryIterator = this.#retryDelays.values()
const chunkRetryIterator = this.#retryDelays.values()
const shouldRetrySignature = () => {
const next = signatureRetryIterator.next()
if (next == null || next.done) {
return null
}
return next.value
}
for (;;) {
throwIfAborted(signal)
const chunkData = chunk.getData()
const { onProgress, onComplete } = chunk
let signature: AwsS3UploadParameters
try {
signature = await this.#fetchSignature(this.#getFile(file), {
// Always defined for multipart uploads
uploadId: uploadId!,
key,
partNumber,
body: chunkData,
signal,
}).abortOn(signal)
} catch (err) {
const timeout = shouldRetrySignature()
if (timeout == null || signal.aborted) {
throw err
}
await new Promise((resolve) => setTimeout(resolve, timeout))
continue
}
throwIfAborted(signal)
try {
return {
PartNumber: partNumber,
...(await this.#uploadPartBytes({
signature,
body: chunkData,
size: chunkData.size,
onProgress,
onComplete,
signal,
}).abortOn(signal)),
}
} catch (err) {
if (!(await this.#shouldRetry(err, chunkRetryIterator))) throw err
}
}
}
}

View file

@ -0,0 +1,265 @@
import type { Uppy } from '@uppy/core'
import type { Body, Meta, UppyFile } from '@uppy/core/utils'
import { AbortController } from '@uppy/core/utils'
import type { HTTPCommunicationQueue } from './HTTPCommunicationQueue.js'
const MB = 1024 * 1024
interface MultipartUploaderOptions<M extends Meta, B extends Body> {
getChunkSize?: (file: { size: number }) => number
onProgress?: (bytesUploaded: number, bytesTotal: number) => void
onPartComplete?: (part: { PartNumber: number; ETag: string }) => void
shouldUseMultipart?: boolean | ((file: UppyFile<M, B>) => boolean)
onSuccess?: (result: B) => void
onError?: (err: unknown) => void
companionComm: HTTPCommunicationQueue<M, B>
file: UppyFile<M, B>
log: Uppy<M, B>['log']
uploadId?: string
key: string
}
const defaultOptions = {
getChunkSize(file: { size: number }) {
return Math.ceil(file.size / 10000)
},
onProgress() {},
onPartComplete() {},
onSuccess() {},
onError(err: unknown) {
throw err
},
} satisfies Partial<MultipartUploaderOptions<any, any>>
export interface Chunk {
getData: () => Blob
onProgress: (ev: ProgressEvent) => void
onComplete: (etag: string) => void
shouldUseMultipart: boolean
setAsUploaded?: () => void
}
function ensureInt<T>(value: T): T extends number | string ? number : never {
if (typeof value === 'string') {
// @ts-expect-error TS is not able to recognize it's fine.
return parseInt(value, 10)
}
if (typeof value === 'number') {
// @ts-expect-error TS is not able to recognize it's fine.
return value
}
throw new TypeError('Expected a number')
}
export const pausingUploadReason = Symbol('pausing upload, not an actual error')
/**
* A MultipartUploader instance is used per file upload to determine whether a
* upload should be done as multipart or as a regular S3 upload
* (based on the user-provided `shouldUseMultipart` option value) and to manage
* the chunk splitting.
*/
class MultipartUploader<M extends Meta, B extends Body> {
options: MultipartUploaderOptions<M, B> &
Required<Pick<MultipartUploaderOptions<M, B>, keyof typeof defaultOptions>>
#abortController = new AbortController()
#chunks: Array<Chunk | null> = []
#chunkState: { uploaded: number; etag?: string; done?: boolean }[] = []
/**
* The (un-chunked) data to upload.
*/
#data: Blob
#file: UppyFile<M, B>
#uploadHasStarted = false
#onError: (err: unknown) => void
#onSuccess: (result: B) => void
#shouldUseMultipart: MultipartUploaderOptions<M, B>['shouldUseMultipart']
#isRestoring: boolean
#onReject = (err: unknown) =>
(err as any)?.cause === pausingUploadReason ? null : this.#onError(err)
#maxMultipartParts = 10_000
#minPartSize = 5 * MB
constructor(data: Blob, options: MultipartUploaderOptions<M, B>) {
this.options = {
...defaultOptions,
...options,
}
// Use default `getChunkSize` if it was null or something
this.options.getChunkSize ??= defaultOptions.getChunkSize
this.#data = data
this.#file = options.file
this.#onSuccess = this.options.onSuccess
this.#onError = this.options.onError
this.#shouldUseMultipart = this.options.shouldUseMultipart
// When we are restoring an upload, we already have an UploadId and a Key. Otherwise
// we need to call `createMultipartUpload` to get an `uploadId` and a `key`.
// Non-multipart uploads are not restorable.
this.#isRestoring = (options.uploadId && options.key) as any as boolean
this.#initChunks()
}
// initChunks checks the user preference for using multipart uploads (opts.shouldUseMultipart)
// and calculates the optimal part size. When using multipart part uploads every part except for the last has
// to be at least 5 MB and there can be no more than 10K parts.
// This means we sometimes need to change the preferred part size from the user in order to meet these requirements.
#initChunks() {
const fileSize = this.#data.size
const shouldUseMultipart =
typeof this.#shouldUseMultipart === 'function'
? this.#shouldUseMultipart(this.#file)
: Boolean(this.#shouldUseMultipart)
if (shouldUseMultipart && fileSize > this.#minPartSize) {
// At least 5MB per request:
let chunkSize = Math.max(
this.options.getChunkSize(this.#data) as number, // Math.max can take undefined but TS does not think so
this.#minPartSize,
)
let arraySize = Math.floor(fileSize / chunkSize)
// At most 10k requests per file:
if (arraySize > this.#maxMultipartParts) {
arraySize = this.#maxMultipartParts
chunkSize = fileSize / this.#maxMultipartParts
}
this.#chunks = Array(arraySize)
for (let offset = 0, j = 0; offset < fileSize; offset += chunkSize, j++) {
const end = Math.min(fileSize, offset + chunkSize)
// Defer data fetching/slicing until we actually need the data, because it's slow if we have a lot of files
const getData = () => {
const i2 = offset
return this.#data.slice(i2, end)
}
this.#chunks[j] = {
getData,
onProgress: this.#onPartProgress(j),
onComplete: this.#onPartComplete(j),
shouldUseMultipart,
}
if (this.#isRestoring) {
const size =
offset + chunkSize > fileSize ? fileSize - offset : chunkSize
// setAsUploaded is called by listPart, to keep up-to-date the
// quantity of data that is left to actually upload.
this.#chunks[j]!.setAsUploaded = () => {
this.#chunks[j] = null
this.#chunkState[j].uploaded = size
}
}
}
} else {
this.#chunks = [
{
getData: () => this.#data,
onProgress: this.#onPartProgress(0),
onComplete: this.#onPartComplete(0),
shouldUseMultipart,
},
]
}
this.#chunkState = this.#chunks.map(() => ({ uploaded: 0 }))
}
#createUpload() {
this.options.companionComm
.uploadFile(
this.#file,
this.#chunks as Chunk[],
this.#abortController.signal,
)
.then(this.#onSuccess, this.#onReject)
this.#uploadHasStarted = true
}
#resumeUpload() {
this.options.companionComm
.resumeUploadFile(this.#file, this.#chunks, this.#abortController.signal)
.then(this.#onSuccess, this.#onReject)
}
#onPartProgress = (index: number) => (ev: ProgressEvent) => {
if (!ev.lengthComputable) return
this.#chunkState[index].uploaded = ensureInt(ev.loaded)
const totalUploaded = this.#chunkState.reduce((n, c) => n + c.uploaded, 0)
this.options.onProgress(totalUploaded, this.#data.size)
}
#onPartComplete = (index: number) => (etag: string) => {
// This avoids the net::ERR_OUT_OF_MEMORY in Chromium Browsers.
this.#chunks[index] = null
this.#chunkState[index].etag = etag
this.#chunkState[index].done = true
const part = {
PartNumber: index + 1,
ETag: etag,
}
this.options.onPartComplete(part)
}
#abortUpload() {
this.#abortController.abort()
this.options.companionComm
.abortFileUpload(this.#file)
.catch((err: unknown) => this.options.log(err as Error))
}
start(): void {
if (this.#uploadHasStarted) {
if (!this.#abortController.signal.aborted)
this.#abortController.abort(pausingUploadReason)
this.#abortController = new AbortController()
this.#resumeUpload()
} else if (this.#isRestoring) {
this.options.companionComm.restoreUploadFile(this.#file, {
uploadId: this.options.uploadId,
key: this.options.key,
})
this.#resumeUpload()
} else {
this.#createUpload()
}
}
pause(): void {
this.#abortController.abort(pausingUploadReason)
// Swap it out for a new controller, because this instance may be resumed later.
this.#abortController = new AbortController()
}
abort(opts?: { really?: boolean }): void {
if (opts?.really) this.#abortUpload()
else this.pause()
}
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via Symbol in tests
private [Symbol.for('uppy test: getChunkState')]() {
return this.#chunkState
}
}
export default MultipartUploader

View file

@ -7,12 +7,6 @@
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -7,12 +7,6 @@
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -34,9 +34,6 @@
"./package.json": "./package.json"
},
"dependencies": {
"@uppy/companion-client": "workspace:^",
"@uppy/provider-views": "workspace:^",
"@uppy/utils": "workspace:^",
"preact": "^10.29.2"
},
"peerDependencies": {

View file

@ -1,9 +1,3 @@
import {
type CompanionPluginOptions,
getAllowedHosts,
Provider,
tokenStorage,
} from '@uppy/companion-client'
import type {
AsyncStore,
Body,
@ -13,9 +7,14 @@ import type {
UppyFile,
} from '@uppy/core'
import { UIPlugin, type Uppy } from '@uppy/core'
import { ProviderViews } from '@uppy/provider-views'
import type { LocaleStrings } from '@uppy/utils'
import {
type CompanionPluginOptions,
getAllowedHosts,
Provider,
tokenStorage,
} from '@uppy/core/companion-client'
import { ProviderViews } from '@uppy/core/provider-views'
import type { LocaleStrings } from '@uppy/core/utils'
// biome-ignore lint/style/useImportType: h is not a type
import { type ComponentChild, h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }

View file

@ -7,15 +7,6 @@
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../provider-views/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -6,15 +6,6 @@
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../provider-views/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -1 +0,0 @@
tsconfig.*

View file

@ -1,301 +0,0 @@
# @uppy/companion-client
## 5.1.1
### Patch Changes
- 0c16fe4: - Split UppyFile into two intefaces distinguished by the `isRemote` boolean:
- LocalUppyFile
- RemoteUppyFile
- Updated dependencies [0c16fe4]
- @uppy/core@5.1.1
- @uppy/utils@7.1.1
## 5.1.0
### Minor Changes
- 5ba2c1c: Introduce the concept of server-side search and add support for it for the Dropbox provider. Previously, only client-side filtering in the currently viewed folder was possible, which was limiting. Now users using Companion with Dropbox can perform a search across their entire account.
### Patch Changes
- Updated dependencies [5ba2c1c]
- @uppy/utils@7.1.0
- @uppy/core@5.1.0
## 5.0.1
### Patch Changes
- 975317d: Removed "main" from package.json, since export maps serve as the contract for the public API.
- Updated dependencies [4b6a76c]
- Updated dependencies [975317d]
- Updated dependencies [9bac4c8]
- @uppy/core@5.0.2
- @uppy/utils@7.0.2
## 5.0.0
### Major Changes
- c5b51f6: ### Export maps for all packages
All packages now have export maps. This is a breaking change in two cases:
1. The css imports have changed from `@uppy[package]/dist/styles.min.css` to `@uppy[package]/css/styles.min.css`
2. You were importing something that wasn't exported from the root, for instance `@uppy/core/lib/foo.js`. You can now only import things we explicitly exported.
#### Changed imports for `@uppy/react`, `@uppy/vue`, and `@uppy/svelte`
Some components, like Dashboard, require a peer dependency to work but since all components were exported from a single file you were forced to install all peer dependencies. Even if you never imported, for instance, the status bar component.
Every component that requires a peer dependency has now been moved to a subpath, such as `@uppy/react/dashboard`, so you only need to install the peer dependencies you need.
**Example for `@uppy/react`:**
**Before:**
```javascript
import { Dashboard, StatusBar } from "@uppy/react";
```
**Now:**
```javascript
import Dashboard from "@uppy/react/dashboard";
import StatusBar from "@uppy/react/status-bar";
```
### Patch Changes
- Updated dependencies [d301c01]
- Updated dependencies [c5b51f6]
- @uppy/utils@7.0.0
- @uppy/core@5.0.0
## 4.5.2
### Patch Changes
- 1b1a9e3: Define "files" in package.json
- Updated dependencies [1b1a9e3]
- @uppy/utils@6.2.2
- @uppy/core@4.5.2
## 4.5.0
### Minor Changes
- 0c24c5a: Use TypeScript compiler instead of Babel
### Patch Changes
- Updated dependencies [0c24c5a]
- Updated dependencies [0c24c5a]
- @uppy/core@4.5.0
- @uppy/utils@6.2.0
## 4.4.2
Released: 2025-05-18
Included in: Uppy v4.16.0
- @uppy/companion-client: don't reject on incorrect origin (Mikael Finstad / #5736)
## 4.4.0
Released: 2025-01-06
Included in: Uppy v4.11.0
- @uppy/angular,@uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive-picker,@uppy/google-drive,@uppy/google-photos-picker,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/webdav,@uppy/xhr-upload,@uppy/zoom: Remove "paths" from all tsconfig's (Merlijn Vos / #5572)
## 4.2.0
Released: 2024-12-05
Included in: Uppy v4.8.0
- @uppy/companion-client: Fix allowed origins (Mikael Finstad / #5536)
- @uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: cleanup tsconfig (Mikael Finstad / #5520)
## 4.1.1
Released: 2024-10-31
Included in: Uppy v4.6.0
- @uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react-native,@uppy/react,@uppy/redux-dev-tools,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: Fix links (Anthony Veaudry / #5492)
## 4.0.0
Released: 2024-07-10
Included in: Uppy v4.0.0
- docs,@uppy/companion-client: don't close socket when pausing (Mikael Finstad / #4821)
## 4.0.0-beta.7
Released: 2024-06-04
Included in: Uppy v4.0.0-beta.10
- @uppy/companion-client: do not allow boolean `RequestOptions` (Mikael Finstad / #5198)
- @uppy/companion-client: remove deprecated options (Mikael Finstad / #5198)
- @uppy/companion-client: make `supportsRefreshToken` default (Mikael Finstad / #5198)
- @uppy/companion-client: remove optional chaining (Mikael Finstad / #5198)
- @uppy/companion-client: remove `Socket` (Mikael Finstad / #5198)
## 4.0.0-beta.6
Released: 2024-05-14
Included in: Uppy v4.0.0-beta.7
- @uppy/companion-client,@uppy/dropbox,@uppy/screen-capture,@uppy/unsplash,@uppy/url,@uppy/webcam: Use `title` consistently from locales (Merlijn Vos / #5134)
## 4.0.0-beta.1
Released: 2024-03-28
Included in: Uppy v4.0.0-beta.1
- @uppy/companion-client,@uppy/provider-views,@uppy/status-bar: fix type imports (Antoine du Hamel / #5038)
- @uppy/companion-client: Replace Provider.initPlugin with composition (Merlijn Vos / #4977)
## 3.8.0
Released: 2024-03-27
Included in: Uppy v3.24.0
- @uppy/box,@uppy/companion-client,@uppy/provider-views,@uppy/status-bar: fix type imports (Antoine du Hamel / #5038)
- @uppy/companion-client: Replace Provider.initPlugin with composition (Merlijn Vos / #4977)
## 3.7.4
Released: 2024-02-28
Included in: Uppy v3.23.0
- @uppy/companion-client,@uppy/utils,@uppy/xhr-upload: improvements for #4922 (Mikael Finstad / #4960)
## 3.7.3
Released: 2024-02-22
Included in: Uppy v3.22.2
- @uppy/companion-client: fix body/url on upload-success (Merlijn Vos / #4922)
- @uppy/companion-client: remove unnecessary `'use strict'` directives (Antoine du Hamel / #4943)
- @uppy/companion-client: type changes for provider-views (Antoine du Hamel / #4938)
- @uppy/companion-client: update types (Antoine du Hamel / #4927)
## 3.7.1
Released: 2024-02-19
Included in: Uppy v3.22.0
- @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/companion-client,@uppy/tus,@uppy/xhr-upload: update `uppyfile` objects before emitting events (antoine du hamel / #4928)
- @uppy/companion-client: fix tests and linter (antoine du hamel / #4890)
- @uppy/companion-client: migrate to ts (merlijn vos / #4864)
- @uppy/companion-client: fix `typeerror` (antoine du hamel)
## 3.7.0
Released: 2023-12-12
Included in: Uppy v3.21.0
- @uppy/companion-client: avoid unnecessary preflight requests (Antoine du Hamel / #4462)
## 3.6.1
Released: 2023-11-24
Included in: Uppy v3.20.0
- @uppy/companion-client: fix log type error (Mikael Finstad / #4766)
- @uppy/companion-client: revert breaking change (Antoine du Hamel / #4801)
## 3.5.0
Released: 2023-10-20
Included in: Uppy v3.18.0
- @uppy/companion-client: fixup! Added Companion OAuth Key type (Murderlon / #4668)
- @uppy/companion-client: Added Companion OAuth Key type (Chris Pratt / #4668)
## 3.4.1
Released: 2023-09-29
Included in: Uppy v3.17.0
- @uppy/companion-client: fix a refresh token race condition (Mikael Finstad / #4695)
## 3.4.0
Released: 2023-09-05
Included in: Uppy v3.15.0
- @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/companion-client,@uppy/core,@uppy/tus,@uppy/utils,@uppy/xhr-upload: Move remote file upload logic into companion-client (Merlijn Vos / #4573)
## 3.3.0
Released: 2023-08-15
Included in: Uppy v3.14.0
- @uppy/companion-client,@uppy/provider-views: make authentication optional (Dominik Schmidt / #4556)
## 3.1.2
Released: 2023-04-04
Included in: Uppy v3.7.0
- @uppy/companion-client: do not open socket more than once (Artur Paikin)
## 3.1.1
Released: 2022-11-16
Included in: Uppy v3.3.1
- @uppy/companion-client: treat `*` the same as missing header (Antoine du Hamel / #4221)
## 3.1.0
Released: 2022-11-10
Included in: Uppy v3.3.0
- @uppy/companion-client: add support for `AbortSignal` (Antoine du Hamel / #4201)
- @uppy/companion-client: prevent preflight race condition (Mikael Finstad / #4182)
## 3.0.2
Released: 2022-09-25
Included in: Uppy v3.1.0
- @uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/companion,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/redux-dev-tools,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: add missing entries to changelog for individual packages (Antoine du Hamel / #4092)
## 3.0.0
Released: 2022-08-22
Included in: Uppy v3.0.0
- Switch to ESM
## 2.2.0
Released: 2022-05-30
Included in: Uppy v2.11.0
- @uppy/companion-client: Revert "Revert "@uppy/companion-client: refactor to ESM"" (Antoine du Hamel / #3730)
## 2.1.0
Released: 2022-05-14
Included in: Uppy v2.10.0
- @uppy/companion-client: refactor to ESM (Antoine du Hamel / #3693)
## 2.0.6
Released: 2022-04-07
Included in: Uppy v2.9.2
- @uppy/aws-s3,@uppy/companion-client,@uppy/transloadit,@uppy/utils: Propagate `isNetworkError` through error wrappers (Renée Kooi / #3620)
## 2.0.5
Released: 2022-02-14
Included in: Uppy v2.5.0
- @uppy/companion-client,@uppy/companion,@uppy/provider-views,@uppy/robodog: Finishing touches on Companion dynamic Oauth (Renée Kooi / #2802)

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 Transloadit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,55 +0,0 @@
# @uppy/companion-client
<img src="https://uppy.io/img/logo.svg" width="120" alt="Uppy logo: a smiling puppy above a pink upwards arrow" align="right">
[![npm version](https://img.shields.io/npm/v/@uppy/companion-client.svg?style=flat-square)](https://www.npmjs.com/package/@uppy/companion-client)
![CI status for Uppy tests](https://github.com/transloadit/uppy/workflows/CI/badge.svg)
![CI status for Companion tests](https://github.com/transloadit/uppy/workflows/Companion/badge.svg)
![CI status for browser tests](https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg)
Client library for communication with Companion. Intended for use in Uppy
plugins.
Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
a versatile file encoding service.
## Example
```js
import Uppy from '@uppy/core'
import { Provider, RequestClient, Socket } from '@uppy/companion-client'
const uppy = new Uppy()
const client = new RequestClient(uppy, {
companionUrl: 'https://uppy.mywebsite.com/',
})
client.get('/drive/list').then(() => {})
const provider = new Provider(uppy, {
companionUrl: 'https://uppy.mywebsite.com/',
provider: providerPluginInstance,
})
provider.checkAuth().then(() => {})
const socket = new Socket({ target: 'wss://uppy.mywebsite.com/' })
socket.on('progress', () => {})
```
## Installation
> Unless you are writing a custom provider plugin, you do not need to install
> this.
```bash
$ npm install @uppy/companion-client
```
## Documentation
Documentation for this plugin can be found on the
[Uppy website](https://uppy.io/docs/companion).
## License
[The MIT License](./LICENSE).

View file

@ -1,51 +0,0 @@
{
"name": "@uppy/companion-client",
"description": "Client library for communication with Companion. Intended for use in Uppy plugins.",
"version": "5.1.1",
"license": "MIT",
"type": "module",
"sideEffects": false,
"scripts": {
"build": "tsc --build tsconfig.build.json",
"typecheck": "tsc --build",
"test": "vitest run --environment=jsdom --silent='passed-only'"
},
"keywords": [
"file uploader",
"uppy",
"uppy-plugin",
"companion",
"provider"
],
"homepage": "https://uppy.io",
"bugs": {
"url": "https://github.com/transloadit/uppy/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/transloadit/uppy.git"
},
"files": [
"src",
"lib",
"dist",
"CHANGELOG.md"
],
"exports": {
".": "./lib/index.js",
"./package.json": "./package.json"
},
"dependencies": {
"@uppy/utils": "workspace:^",
"namespace-emitter": "^2.0.1",
"p-retry": "^6.1.0"
},
"devDependencies": {
"jsdom": "^29.1.1",
"typescript": "^5.8.3",
"vitest": "^4.1.6"
},
"peerDependencies": {
"@uppy/core": "workspace:^"
}
}

View file

@ -1,17 +0,0 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src"
},
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -1,16 +0,0 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"emitDeclarationOnly": false,
"noEmit": true
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -1,8 +0,0 @@
{
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": ["^build", "@uppy/core#build"]
}
}
}

View file

@ -5,7 +5,7 @@ import type {
UnknownProviderPluginState,
UppyEventMap,
} from '@uppy/core'
import type { ProviderViews } from '@uppy/provider-views'
import type { ProviderViews } from '@uppy/core/provider-views'
import { dequal } from 'dequal/lite'
import { Subscribers } from './utils.js'

View file

@ -18,12 +18,6 @@
},
{
"path": "../image-editor/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -17,12 +17,6 @@
},
{
"path": "../image-editor/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -32,7 +32,6 @@
},
"dependencies": {
"@transloadit/prettier-bytes": "^1.1.0",
"@uppy/utils": "workspace:^",
"compressorjs": "^1.3.0",
"preact": "^10.29.2",
"promise-queue": "^2.2.5"

View file

@ -1,7 +1,7 @@
import fs from 'node:fs'
import path from 'node:path'
import Core from '@uppy/core'
import { getFileNameAndExtension } from '@uppy/utils'
import { getFileNameAndExtension } from '@uppy/core/utils'
import { describe, expect, it } from 'vitest'
import CompressorPlugin from './index.js'

View file

@ -1,8 +1,8 @@
import { prettierBytes } from '@transloadit/prettier-bytes'
import type { DefinePluginOpts, PluginOpts } from '@uppy/core'
import { BasePlugin, type Uppy } from '@uppy/core'
import type { Body, LocalUppyFile, Meta, UppyFile } from '@uppy/utils'
import { getFileNameAndExtension, RateLimitedQueue } from '@uppy/utils'
import type { Body, LocalUppyFile, Meta, UppyFile } from '@uppy/core/utils'
import { getFileNameAndExtension, RateLimitedQueue } from '@uppy/core/utils'
import CompressorJS from 'compressorjs'
import locale from './locale.js'

View file

@ -7,9 +7,6 @@
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -7,9 +7,6 @@
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}

View file

@ -10,7 +10,7 @@
],
"scripts": {
"build": "tsc --build tsconfig.build.json",
"build:css": "sass --load-path=../../ src/style.scss dist/style.css && postcss dist/style.css -u cssnano -o dist/style.min.css",
"build:css": "sass --load-path=../../ src/style.scss dist/style.css && sass --load-path=../../ src/provider-views/style.scss dist/provider-views.css && postcss dist/style.css -u cssnano -o dist/style.min.css && postcss dist/provider-views.css -u cssnano -o dist/provider-views.min.css",
"typecheck": "tsc --build",
"test": "vitest run --environment=jsdom --silent='passed-only'"
},
@ -38,20 +38,32 @@
"./css/style.min.css": "./dist/style.min.css",
"./css/style.css": "./dist/style.css",
"./css/style.scss": "./src/style.scss",
"./store-default": "./lib/store/index.js",
"./utils": "./lib/utils/index.js",
"./companion-client": "./lib/companion-client/index.js",
"./provider-views": "./lib/provider-views/index.js",
"./provider-views/css/style.min.css": "./dist/provider-views.min.css",
"./provider-views/css/style.css": "./dist/provider-views.css",
"./provider-views/css/style.scss": "./src/provider-views/style.scss",
"./package.json": "./package.json"
},
"dependencies": {
"@transloadit/prettier-bytes": "^1.1.0",
"@uppy/store-default": "workspace:^",
"@uppy/utils": "workspace:^",
"classnames": "^2.5.1",
"lodash": "^4.18.1",
"mime-match": "^1.0.2",
"namespace-emitter": "^2.0.1",
"nanoid": "^5.1.11",
"p-queue": "^9.3.0",
"p-retry": "^6.1.0",
"preact": "^10.29.2"
},
"devDependencies": {
"@types/deep-freeze": "^0",
"@types/gapi": "^0.0.47",
"@types/google.accounts": "^0.0.14",
"@types/google.picker": "^0.0.42",
"@types/lodash": "^4.14.199",
"@types/node": "^20.19.0",
"cssnano": "^8.0.1",
"deep-freeze": "^0.0.1",

View file

@ -7,9 +7,14 @@
* See `Plugin` for the extended version with Preact rendering for interfaces.
*/
import type { Body, I18n, Meta, OptionalPluralizeLocale } from '@uppy/utils'
import { Translator } from '@uppy/utils'
import type { State, UnknownPlugin, Uppy } from './Uppy.js'
import type {
Body,
I18n,
Meta,
OptionalPluralizeLocale,
} from './utils/index.js'
import { Translator } from './utils/index.js'
export type PluginOpts = {
locale?: OptionalPluralizeLocale

View file

@ -1,5 +1,5 @@
import type { Body, Meta, UppyFile } from '@uppy/utils'
import type { _UppyEventMap, Uppy, UppyEventMap } from './Uppy.js'
import type { Body, Meta, UppyFile } from './utils/index.js'
/**
* Create a wrapper around an event emitter with a `remove` method to remove

View file

@ -1,8 +1,8 @@
import { prettierBytes } from '@transloadit/prettier-bytes'
import type { Body, I18n, Meta, UppyFile } from '@uppy/utils'
// @ts-expect-error untyped
import match from 'mime-match'
import type { NonNullableUppyOptions, State } from './Uppy.js'
import type { Body, I18n, Meta, UppyFile } from './utils/index.js'
export type Restrictions = {
maxFileSize: number | null

View file

@ -1,9 +1,9 @@
import type { Body, Meta } from '@uppy/utils'
import { findDOMElement, getTextDirection } from '@uppy/utils'
import { render } from 'preact'
import type { PluginOpts } from './BasePlugin.js'
import BasePlugin from './BasePlugin.js'
import type { State } from './Uppy.js'
import type { Body, Meta } from './utils/index.js'
import { findDOMElement, getTextDirection } from './utils/index.js'
/**
* Defer a frequent call to the microtask queue.

View file

@ -3,7 +3,6 @@ import fs from 'node:fs'
import path from 'node:path'
import { prettierBytes } from '@transloadit/prettier-bytes'
import type { Body, Meta } from '@uppy/core'
import type { Locale } from '@uppy/utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import BasePlugin, {
type DefinePluginOpts,
@ -21,6 +20,7 @@ import InvalidPluginWithoutType from './mocks/invalidPluginWithoutType.js'
import { RestrictionError } from './Restricter.js'
import UIPlugin from './UIPlugin.js'
import type { State } from './Uppy.js'
import type { Locale } from './utils/index.js'
const sampleImage = fs.readFileSync(
path.join(__dirname, '../../compressor/fixtures/image.jpg'),

View file

@ -1,10 +1,28 @@
/* global AggregateError */
import DefaultStore, { type Store } from '@uppy/store-default'
import throttle from 'lodash/throttle.js'
// @ts-expect-error untyped
import ee from 'namespace-emitter'
import { nanoid } from 'nanoid/non-secure'
import type { h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import type BasePlugin from './BasePlugin.js'
import type Provider from './companion-client/Provider.js'
import type SearchProvider from './companion-client/SearchProvider.js'
import getFileName from './getFileName.js'
import locale from './locale.js'
import { debugLogger, justErrorsLogger } from './loggers.js'
import type ProviderView from './provider-views/ProviderView/ProviderView.js'
import type { Restrictions, ValidateableFile } from './Restricter.js'
import {
defaultOptions as defaultRestrictionOptions,
Restricter,
RestrictionError,
} from './Restricter.js'
import DefaultStore, { type Store } from './store/index.js'
import supportsUploadProgress from './supportsUploadProgress.js'
import type {
Body,
CompanionClientProvider,
CompanionClientSearchProvider,
CompanionFile,
FileProgressNotStarted,
FileProgressStarted,
@ -17,30 +35,13 @@ import type {
RemoteUppyFile,
UppyFile,
UppyFileId,
} from '@uppy/utils'
} from './utils/index.js'
import {
getFileNameAndExtension,
getFileType,
getSafeFileId,
Translator,
} from '@uppy/utils'
import throttle from 'lodash/throttle.js'
// @ts-expect-error untyped
import ee from 'namespace-emitter'
import { nanoid } from 'nanoid/non-secure'
import type { h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import type BasePlugin from './BasePlugin.js'
import getFileName from './getFileName.js'
import locale from './locale.js'
import { debugLogger, justErrorsLogger } from './loggers.js'
import type { Restrictions, ValidateableFile } from './Restricter.js'
import {
defaultOptions as defaultRestrictionOptions,
Restricter,
RestrictionError,
} from './Restricter.js'
import supportsUploadProgress from './supportsUploadProgress.js'
} from './utils/index.js'
type Processor = (
fileIDs: string[],
@ -165,8 +166,6 @@ export interface BaseProviderPlugin {
* UnknownProviderPlugin can be any Companion plugin (such as Google Drive)
* that uses the Companion-assisted OAuth flow.
* 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()`.
@ -178,16 +177,25 @@ export type UnknownProviderPlugin<
BaseProviderPlugin & {
rootFolderId: string | null
files: UppyFile<M, B>[]
provider: CompanionClientProvider
// Can't be typed unfortunately, we can't depend on `provider-views` in `core`.
view: any
// Structural subset of the real `Provider` class,structural rather than the nominal class type so custom
// providers matching the public surface aren't forced to inherit from `Provider`.
provider: Pick<
Provider<M, B>,
| 'name'
| 'provider'
| 'login'
| 'logout'
| 'fetchPreAuthToken'
| 'fileUrl'
| 'list'
| 'search'
>
view: ProviderView<M, B>
}
/*
* 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()`.
@ -203,7 +211,11 @@ export type UnknownSearchProviderPlugin<
B extends Body,
> = UnknownPlugin<M, B, UnknownSearchProviderPluginState> &
BaseProviderPlugin & {
provider: CompanionClientSearchProvider
// Structural subset of the real `SearchProvider` class (see note above).
provider: Pick<
SearchProvider<M, B>,
'name' | 'provider' | 'fileUrl' | 'search'
>
}
// for better readability

View file

@ -1,4 +1,4 @@
import type { AsyncStore, UIPluginOptions } from '@uppy/core'
import type { AsyncStore, UIPluginOptions } from '../index.js'
export interface CompanionPluginOptions extends UIPluginOptions {
storage?: AsyncStore

View file

@ -4,14 +4,13 @@ import type {
PluginOpts,
UnknownProviderPlugin,
Uppy,
} from '@uppy/core'
import {
type CompanionClientProvider,
getSocketHost,
type RequestOptions,
} from '@uppy/utils'
} from '../index.js'
import { getSocketHost } from '../utils/index.js'
import type { CompanionPluginOptions } from './index.js'
import RequestClient, { authErrorStatusCode } from './RequestClient.js'
import RequestClient, {
authErrorStatusCode,
type RequestOptions,
} from './RequestClient.js'
export interface Opts extends PluginOpts, CompanionPluginOptions {
pluginId: string
@ -31,10 +30,10 @@ function getOrigin() {
return location.origin
}
export default class Provider<M extends Meta, B extends Body>
extends RequestClient<M, B>
implements CompanionClientProvider
{
export default class Provider<
M extends Meta,
B extends Body,
> extends RequestClient<M, B> {
#refreshingTokenPromise: Promise<void> | undefined
provider: string
@ -273,11 +272,13 @@ export default class Provider<M extends Meta, B extends Body>
}
async login({
uppyVersions,
uppyVersions = '',
authFormData,
signal,
}: {
uppyVersions: string
// `uppyVersions` is optional because callers such as `ProviderView` do not
// have it on hand; the OAuth/simple-auth helpers always receive a string.
uppyVersions?: string
authFormData: unknown
signal: AbortSignal
}): Promise<void> {

View file

@ -1,21 +1,24 @@
import type Uppy from '@uppy/core'
import type {
Body,
Meta,
RemoteUppyFile,
RequestOptions,
UppyFile,
} from '@uppy/utils'
import pRetry, { AbortError } from 'p-retry'
import packageJson from '../../package.json' with { type: 'json' }
import type Uppy from '../index.js'
import type { Body, Meta, RemoteUppyFile, UppyFile } from '../utils/index.js'
import {
ErrorWithCause,
fetchWithNetworkError,
getSocketHost,
UserFacingApiError,
} from '@uppy/utils'
import pRetry, { AbortError } from 'p-retry'
import packageJson from '../package.json' with { type: 'json' }
} from '../utils/index.js'
import AuthError from './AuthError.js'
export type RequestOptions = {
method?: string
data?: Record<string, unknown>
skipPostResponse?: boolean
signal?: AbortSignal
authFormData?: unknown
qs?: Record<string, string>
}
type CompanionHeaders = Record<string, string> | undefined
export type Opts = {

View file

@ -1,5 +1,4 @@
import type { Body, Meta, Uppy } from '@uppy/core'
import type { CompanionClientSearchProvider } from '@uppy/utils'
import type { Body, Meta, Uppy } from '../index.js'
import RequestClient, { type Opts } from './RequestClient.js'
const getName = (id: string): string => {
@ -9,10 +8,10 @@ const getName = (id: string): string => {
.join(' ')
}
export default class SearchProvider<M extends Meta, B extends Body>
extends RequestClient<M, B>
implements CompanionClientSearchProvider
{
export default class SearchProvider<
M extends Meta,
B extends Body,
> extends RequestClient<M, B> {
provider: string
id: string

View file

@ -5,6 +5,9 @@
export type { CompanionPluginOptions } from './CompanionPluginOptions.js'
export { default as getAllowedHosts } from './getAllowedHosts.js'
export { default as Provider } from './Provider.js'
export { default as RequestClient } from './RequestClient.js'
export {
default as RequestClient,
type RequestOptions,
} from './RequestClient.js'
export { default as SearchProvider } from './SearchProvider.js'
export * as tokenStorage from './tokenStorage.js'

View file

@ -1,16 +1,10 @@
export type { Store } from '@uppy/store-default'
export type {
Body,
Meta,
MinimalRequiredUppyFile,
UppyFile,
} from '@uppy/utils'
export type { DefinePluginOpts, PluginOpts } from './BasePlugin.js'
export { default as BasePlugin } from './BasePlugin.js'
export { default as EventManager } from './EventManager.js'
export { debugLogger } from './loggers.js'
export type { Restrictions, ValidateableFile } from './Restricter.js'
export { RestrictionError } from './Restricter.js'
export type { Store } from './store/index.js'
export type { PluginTarget, UIPluginOptions } from './UIPlugin.js'
export { default as UIPlugin } from './UIPlugin.js'
export type {
@ -34,3 +28,9 @@ export type {
UppyOptions,
} from './Uppy.js'
export { default, default as Uppy } from './Uppy.js'
export type {
Body,
Meta,
MinimalRequiredUppyFile,
UppyFile,
} from './utils/index.js'

View file

@ -1,4 +1,4 @@
import { getTimeStamp } from '@uppy/utils'
import { getTimeStamp } from './utils/index.js'
// Swallow all logs, except errors.
// default if logger is not set or debug: false

View file

@ -1,5 +1,5 @@
import type { Body, Meta, PartialTreeFolder } from '@uppy/core'
import { Fragment, type h } from 'preact'
import type { Body, Meta, PartialTreeFolder } from '../index.js'
import type ProviderView from './ProviderView/index.js'
type BreadcrumbsProps<M extends Meta, B extends Body> = {

View file

@ -1,12 +1,12 @@
import { useEffect, useState } from 'preact/hooks'
import type {
Body,
Meta,
PartialTreeFile,
PartialTreeFolderNode,
} from '@uppy/core'
import type { I18n } from '@uppy/utils'
import { VirtualList } from '@uppy/utils'
import { useEffect, useState } from 'preact/hooks'
} from '../index.js'
import type { I18n } from '../utils/index.js'
import { VirtualList } from '../utils/index.js'
import Item from './Item/index.js'
import type ProviderView from './ProviderView/ProviderView.js'

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils'
import type { I18n } from '../utils/index.js'
import { useSearchForm } from './useSearchForm.js'
interface FilterInputProps {

View file

@ -1,7 +1,7 @@
import type { Body, Meta, PartialTree } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import classNames from 'classnames'
import { useMemo } from 'preact/hooks'
import type { Body, Meta, PartialTree } from '../index.js'
import type { I18n } from '../utils/index.js'
import type ProviderView from './ProviderView/ProviderView.js'
import getNumberOfSelectedFiles from './utils/PartialTreeUtils/getNumberOfSelectedFiles.js'

View file

@ -1,6 +1,6 @@
import type { AsyncStore, Uppy } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import type { AsyncStore, Uppy } from '../../index.js'
import type { I18n } from '../../utils/index.js'
import AuthView from '../ProviderView/AuthView.js'
import {
authorize,

View file

@ -1,5 +1,5 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core'
import type { h } from 'preact'
import type { PartialTreeFile, PartialTreeFolderNode } from '../../../index.js'
import ItemIcon from './ItemIcon.js'
type GridItemProps = {

View file

@ -1,9 +1,9 @@
import type { h } from 'preact'
import type {
PartialTreeFile,
PartialTreeFolderNode,
PartialTreeId,
} from '@uppy/core'
import type { h } from 'preact'
} from '../../../index.js'
import ItemIcon from './ItemIcon.js'
// if folder:

View file

@ -1,6 +1,6 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import classNames from 'classnames'
import type { PartialTreeFile, PartialTreeFolderNode } from '../../../index.js'
import type { I18n } from '../../../utils/index.js'
import type ProviderView from '../../ProviderView/ProviderView.js'
import ItemIcon from './ItemIcon.js'

View file

@ -1,11 +1,11 @@
import classNames from 'classnames'
import type { h } from 'preact'
import type {
PartialTreeFile,
PartialTreeFolderNode,
PartialTreeId,
} from '@uppy/core'
import type { I18n } from '@uppy/utils'
import classNames from 'classnames'
import type { h } from 'preact'
} from '../../index.js'
import type { I18n } from '../../utils/index.js'
import GridItem from './components/GridItem.js'
import ListItem from './components/ListItem.js'

View file

@ -1,7 +1,7 @@
import type { Body, Meta } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import type { h } from 'preact'
import { useCallback } from 'preact/hooks'
import type { Body, Meta } from '../../index.js'
import type { I18n } from '../../utils/index.js'
import type ProviderViews from './ProviderView.js'
import type { Opts } from './ProviderView.js'

View file

@ -1,5 +1,5 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import type { PartialTreeFile, PartialTreeFolderNode } from '../../index.js'
import type { I18n } from '../../utils/index.js'
import SearchResultItem from '../Item/components/SearchResultItem.js'
import type ProviderView from './ProviderView.js'

View file

@ -1,7 +1,7 @@
import type { Body, Meta, PartialTreeFolder } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import classNames from 'classnames'
import type { h } from 'preact'
import type { Body, Meta, PartialTreeFolder } from '../../index.js'
import type { I18n } from '../../utils/index.js'
import Breadcrumbs from '../Breadcrumbs.js'
import type ProviderView from './ProviderView.js'
import User from './User.js'

View file

@ -1,3 +1,7 @@
import classNames from 'classnames'
import debounce from 'lodash/debounce.js'
import type { h } from 'preact'
import packageJson from '../../../package.json' with { type: 'json' }
import type {
Body,
Meta,
@ -9,13 +13,9 @@ import type {
UnknownProviderPlugin,
UnknownProviderPluginState,
ValidateableFile,
} from '@uppy/core'
import type { CompanionFile, I18n } from '@uppy/utils'
import { remoteFileObjToLocal } from '@uppy/utils'
import classNames from 'classnames'
import debounce from 'lodash/debounce.js'
import type { h } from 'preact'
import packageJson from '../../package.json' with { type: 'json' }
} from '../../index.js'
import type { CompanionFile, I18n } from '../../utils/index.js'
import { remoteFileObjToLocal } from '../../utils/index.js'
import Browser from '../Browser.js'
import FilterInput from '../FilterInput.js'
import FooterActions from '../FooterActions.js'
@ -66,6 +66,17 @@ const getDefaultState = (
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
/**
* Shape of the responses Companion returns from its `list` and `search`
* endpoints. The `Provider` methods are generic, so we pass this as the
* expected response type at the call sites.
*/
type ProviderListResponse = {
username: string
nextPagePath: string | null
items: CompanionFile[]
}
export interface Opts<M extends Meta, B extends Body> {
provider: UnknownProviderPlugin<M, B>['provider']
viewType: 'list' | 'grid'
@ -248,10 +259,13 @@ export default class ProviderView<M extends Meta, B extends Body> {
await this.#withAbort(async (signal) => {
const scopePath =
currentFolder.type === 'root' ? undefined : currentFolderId
const { items } = await this.provider.search!(searchString, {
signal,
path: scopePath,
})
const { items } = await this.provider.search<ProviderListResponse>(
searchString,
{
signal,
path: scopePath,
},
)
// For each searched file, build the entire path (from the root all the way to the leaf node)
// This is because we need to make sure all ancestor folders are present in the partialTree before we open the folder or check the file.
@ -389,10 +403,10 @@ export default class ProviderView<M extends Meta, B extends Body> {
let currentPagePath = folderId
let currentItems: CompanionFile[] = []
do {
const { username, nextPagePath, items } = await this.provider.list(
currentPagePath,
{ signal },
)
const { username, nextPagePath, items } =
await this.provider.list<ProviderListResponse>(currentPagePath, {
signal,
})
// It's important to set the username during one of our first fetches
this.plugin.setPluginState({ username })
@ -478,10 +492,11 @@ export default class ProviderView<M extends Meta, B extends Body> {
) {
this.isHandlingScroll = true
await this.#withAbort(async (signal) => {
const { nextPagePath, items } = await this.provider.list(
currentFolder.nextPagePath,
{ signal },
)
const { nextPagePath, items } =
await this.provider.list<ProviderListResponse>(
currentFolder.nextPagePath,
{ signal },
)
const newPartialTree = PartialTreeUtils.afterScrollFolder(
partialTree,
currentFolderId,

View file

@ -1,3 +1,6 @@
import classNames from 'classnames'
import type { h } from 'preact'
import packageJson from '../../../package.json' with { type: 'json' }
import type {
Body,
DefinePluginOpts,
@ -9,12 +12,9 @@ import type {
UnknownSearchProviderPlugin,
UnknownSearchProviderPluginState,
ValidateableFile,
} from '@uppy/core'
import type { CompanionFile } from '@uppy/utils'
import { remoteFileObjToLocal } from '@uppy/utils'
import classNames from 'classnames'
import type { h } from 'preact'
import packageJson from '../../package.json' with { type: 'json' }
} from '../../index.js'
import type { CompanionFile } from '../../utils/index.js'
import { remoteFileObjToLocal } from '../../utils/index.js'
import Browser from '../Browser.js'
import FilterInput from '../FilterInput.js'
import FooterActions from '../FooterActions.js'

View file

@ -1,13 +1,13 @@
// p-queue does not have a `"main"` field in its `package.json`, and that makes `import/no-unresolved` freak out.
// We can safely ignore it because bundlers will happily use the `"exports"` field instead.
import PQueue from 'p-queue'
import type {
PartialTree,
PartialTreeFile,
PartialTreeFolderNode,
PartialTreeId,
} from '@uppy/core'
import type { CompanionFile } from '@uppy/utils'
// p-queue does not have a `"main"` field in its `package.json`, and that makes `import/no-unresolved` freak out.
// We can safely ignore it because bundlers will happily use the `"exports"` field instead.
import PQueue from 'p-queue'
} from '../../../index.js'
import type { CompanionFile } from '../../../utils/index.js'
import shallowClone from './shallowClone.js'
export type ApiList = (directory: PartialTreeId) => Promise<{

View file

@ -3,8 +3,8 @@ import type {
PartialTreeFile,
PartialTreeFolder,
PartialTreeFolderNode,
} from '@uppy/core'
import type { CompanionFile } from '@uppy/utils'
} from '../../../index.js'
import type { CompanionFile } from '../../../utils/index.js'
const afterOpenFolder = (
oldPartialTree: PartialTree,

View file

@ -4,8 +4,8 @@ import type {
PartialTreeFolder,
PartialTreeFolderNode,
PartialTreeId,
} from '@uppy/core'
import type { CompanionFile } from '@uppy/utils'
} from '../../../index.js'
import type { CompanionFile } from '../../../utils/index.js'
const afterScrollFolder = (
oldPartialTree: PartialTree,

View file

@ -4,7 +4,7 @@ import type {
PartialTreeFolder,
PartialTreeFolderNode,
PartialTreeId,
} from '@uppy/core'
} from '../../../index.js'
import shallowClone from './shallowClone.js'
/*

View file

@ -3,7 +3,7 @@ import type {
PartialTreeFolder,
PartialTreeFolderNode,
PartialTreeId,
} from '@uppy/core'
} from '../../../index.js'
const getBreadcrumbs = (
partialTree: PartialTree,

View file

@ -3,8 +3,8 @@ import type {
PartialTreeFile,
PartialTreeFolderNode,
PartialTreeId,
} from '@uppy/core'
import type { CompanionFile } from '@uppy/utils'
} from '../../../index.js'
import type { CompanionFile } from '../../../utils/index.js'
export interface Cache {
[key: string]: (PartialTreeFile | PartialTreeFolderNode)[]

View file

@ -1,4 +1,4 @@
import type { PartialTree } from '@uppy/core'
import type { PartialTree } from '../../../index.js'
/**
* We're interested in all 'checked' leaves of this tree,

View file

@ -1,12 +1,12 @@
import { describe, expect, it, vi } from 'vitest'
import type {
PartialTree,
PartialTreeFile,
PartialTreeFolderNode,
PartialTreeFolderRoot,
PartialTreeId,
} from '@uppy/core'
import type { CompanionFile } from '@uppy/utils'
import { describe, expect, it, vi } from 'vitest'
} from '../../../index.js'
import type { CompanionFile } from '../../../utils/index.js'
import afterFill from './afterFill.js'
import afterOpenFolder from './afterOpenFolder.js'
import afterScrollFolder from './afterScrollFolder.js'

View file

@ -1,4 +1,4 @@
import type { PartialTree } from '@uppy/core'
import type { PartialTree } from '../../../index.js'
/**
* One-level copying is sufficient as mutations within our `partialTree` are limited to properties

View file

@ -1,19 +1,23 @@
import type { UnknownPlugin } from '@uppy/core'
import type {
UnknownPlugin,
UnknownProviderPlugin,
UnknownSearchProviderPlugin,
} from '../../index.js'
import type {
Body,
CompanionClientProvider,
CompanionClientSearchProvider,
CompanionFile,
Meta,
UppyFileNonGhost,
} from '@uppy/utils'
import { getSafeFileId } from '@uppy/utils'
} from '../../utils/index.js'
import { getSafeFileId } from '../../utils/index.js'
import companionFileToUppyFile from './companionFileToUppyFile.js'
const addFiles = <M extends Meta, B extends Body>(
companionFiles: CompanionFile[],
plugin: UnknownPlugin<M, B>,
provider: CompanionClientProvider | CompanionClientSearchProvider,
provider:
| UnknownProviderPlugin<M, B>['provider']
| UnknownSearchProviderPlugin<M, B>['provider'],
): void => {
const uppyFiles = companionFiles.map((f) =>
companionFileToUppyFile<M, B>(f, plugin, provider),

View file

@ -1,17 +1,21 @@
import type { UnknownPlugin } from '@uppy/core'
import type {
UnknownPlugin,
UnknownProviderPlugin,
UnknownSearchProviderPlugin,
} from '../../index.js'
import type {
Body,
CompanionClientProvider,
CompanionClientSearchProvider,
CompanionFile,
Meta,
RemoteUppyFile,
} from '@uppy/utils'
} from '../../utils/index.js'
const companionFileToUppyFile = <M extends Meta, B extends Body>(
file: CompanionFile,
plugin: UnknownPlugin<M, B>,
provider: CompanionClientProvider | CompanionClientSearchProvider,
provider:
| UnknownProviderPlugin<M, B>['provider']
| UnknownSearchProviderPlugin<M, B>['provider'],
): RemoteUppyFile<M, B> => {
const name = file.name || file.id

View file

@ -1,4 +1,4 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core'
import type { PartialTreeFile, PartialTreeFolderNode } from '../../index.js'
// Shift-clicking selects a single consecutive list of items
// starting at the previous click.

Some files were not shown because too many files have changed in this diff Show more