@nextcloud/upload
Version:
Nextcloud file upload client
580 lines (559 loc) • 18.2 kB
TypeScript
import { AxiosResponse } from 'axios';
import { ComponentOptionsMixin } from 'vue';
import { default as default_2 } from 'p-cancelable';
import { DefineComponent } from 'vue';
import { Entry } from '@nextcloud/files';
import { ExtractPropTypes } from 'vue';
import { Folder } from '@nextcloud/files';
import { Node as Node_2 } from '@nextcloud/files';
import { PropType } from 'vue';
import { TypedEventTarget } from 'typescript-event-target';
import { Uploader as Uploader_2 } from '../index.ts';
export declare interface ConflictPickerOptions {
/**
* When this is set to true a hint is shown that conflicts in directories are handles recursively
* You still need to call this function for each directory separately.
*/
recursive?: boolean;
}
export declare type ConflictResolutionResult<T extends File | FileSystemEntry | Node_2> = {
selected: T[];
renamed: T[];
};
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/**
* Helpers to generate a file tree when the File and Directory API is used (e.g. Drag and Drop or <input type="file" webkitdirectory>)
*/
/**
* This is a helper class to allow building a file tree for uploading
* It allows to create virtual directories
*/
export declare class Directory extends File {
private _originalName;
private _path;
private _children;
constructor(path: string);
get size(): number;
get lastModified(): number;
get originalName(): string;
get children(): Array<File | Directory>;
get webkitRelativePath(): string;
getChild(name: string): File | Directory | null;
/**
* Add multiple children at once
* @param files The files to add
*/
addChildren(files: Array<File | FileSystemEntry>): Promise<void>;
/**
* Add a child to the directory.
* If it is a nested child the parents will be created if not already exist.
* @param file The child to add
*/
addChild(file: File | FileSystemEntry): Promise<void>;
}
export declare class Eta extends TypedEventTarget<EtaEventsMap> {
/** Bytes done */
private _done;
/** Total bytes to do */
private _total;
/** Current progress (cached) as interval [0,1] */
private _progress;
/** Status of the ETA */
private _status;
/** Time of the last update */
private _startTime;
/** Total elapsed time for current ETA */
private _elapsedTime;
/** Current speed in bytes per second */
private _speed;
/** Expected duration to finish in seconds */
private _eta;
/**
* Cutoff time for the low pass filter of the ETA.
* A higher value will consider more history information for calculation,
* and thus suppress spikes of the speed,
* but will make the overall resposiveness slower.
*/
private _cutoffTime;
constructor(options?: EtaOptions);
/**
* Add more transferred bytes.
* @param done Additional bytes done.
*/
add(done: number): void;
/**
* Update the transmission state.
*
* @param done The new value of transferred bytes.
* @param total Optionally also update the total bytes we expect.
*/
update(done: number, total?: number): void;
reset(): void;
/**
* Pause the ETA calculation.
*/
pause(): void;
/**
* Resume the ETA calculation.
*/
resume(): void;
/**
* Status of the Eta (paused, active, idle).
*/
get status(): EtaStatus;
/**
* Progress (percent done)
*/
get progress(): number;
/**
* Estimated time in seconds.
*/
get time(): number;
/**
* Human readable version of the estimated time.
*/
get timeReadable(): string;
/**
* Transfer speed in bytes per second.
* Returns `-1` if not yet estimated.
*/
get speed(): number;
/**
* Get the speed in human readable format using file sizes like 10KB/s.
* Returns the empty string if not yet estimated.
*/
get speedReadable(): string;
}
export declare interface EtaEventsMap {
pause: CustomEvent;
reset: CustomEvent;
resume: CustomEvent;
update: CustomEvent;
}
declare interface EtaOptions {
/** Low pass filter cutoff time for smoothing the speed */
cutoffTime?: number;
/** Total number of bytes to be expected */
total?: number;
/** Start the estimation directly */
start?: boolean;
}
export declare enum EtaStatus {
Idle = 0,
Paused = 1,
Running = 2
}
/**
* Get the conflicts between two sets of files
* @param {Array<File|FileSystemEntry|Node>} files the incoming files
* @param {Node[]} content all the existing files in the directory
* @return {boolean} true if there is a conflict
*/
export declare function getConflicts<T extends File | FileSystemEntry | Node_2>(files: T[], content: Node_2[]): T[];
/**
* Get the global Uploader instance.
*
* Note: If you need a local uploader you can just create a new instance,
* this global instance will be shared with other apps.
*
* @param isPublic Set to true to use public upload endpoint (by default it is auto detected)
* @param forceRecreate Force a new uploader instance - main purpose is for testing
*/
export declare function getUploader(isPublic?: boolean, forceRecreate?: boolean): Uploader;
/**
* Check if there is a conflict between two sets of files
* @param {Array<File|FileSystemEntry|Node>} files the incoming files
* @param {Node[]} content all the existing files in the directory
* @return {boolean} true if there is a conflict
*/
export declare function hasConflict(files: (File | FileSystemEntry | Node_2)[], content: Node_2[]): boolean;
/**
* Interface of the internal Directory class
*/
export declare type IDirectory = Pick<Directory, keyof Directory>;
/**
* Open the conflict resolver
* @param {string} dirname the directory name
* @param {(File|Node)[]} conflicts the incoming files
* @param {Node[]} content all the existing files in the directory
* @param {ConflictPickerOptions} options Optional settings for the conflict picker
* @return {Promise<ConflictResolutionResult>} the selected and renamed files
*/
export declare function openConflictPicker<T extends File | FileSystemEntry | Node_2>(dirname: string | undefined, conflicts: T[], content: Node_2[], options?: ConflictPickerOptions): Promise<ConflictResolutionResult<T>>;
export declare class Upload {
private _source;
private _file;
private _isChunked;
private _chunks;
private _size;
private _uploaded;
private _startTime;
private _status;
private _controller;
private _response;
constructor(source: string, chunked: boolean | undefined, size: number, file: File);
get source(): string;
get file(): File;
get isChunked(): boolean;
get chunks(): number;
get size(): number;
get startTime(): number;
set response(response: AxiosResponse | null);
get response(): AxiosResponse | null;
get uploaded(): number;
/**
* Update the uploaded bytes of this upload
*/
set uploaded(length: number);
get status(): number;
/**
* Update this upload status
*/
set status(status: UploadStatus);
/**
* Returns the axios cancel token source
*/
get signal(): AbortSignal;
/**
* Cancel any ongoing requests linked to this upload
*/
cancel(): void;
}
/**
* Upload a file
* This will init an Uploader instance if none exists.
* You will be able to retrieve it with `getUploader`
*
* @param {string} destinationPath the destination path
* @param {File} file the file to upload
* @return {Uploader} the uploader instance
*/
export declare function upload(destinationPath: string, file: File): Uploader;
/**
* Helper function to create a conflict resolution callback for the `Uploader.batchUpload` method.
*
* This creates a callback that will open the conflict picker to resolve the conflicts.
* In case of a rename the new name is validated and the invalid filename dialog is shown an error happens there.
*
* @param contentsCallback Callback to retrieve contents of a given path
*/
export declare function uploadConflictHandler(contentsCallback: (path: string) => Promise<Node_2[]>): (nodes: Array<File | IDirectory>, path: string) => Promise<Array<File | IDirectory> | false>;
export declare class Uploader {
private _destinationFolder;
private _isPublic;
private _customHeaders;
private _uploadQueue;
private _jobQueue;
private _queueSize;
private _queueProgress;
private _queueStatus;
private _eta;
private _notifiers;
/**
* Initialize uploader
*
* @param {boolean} isPublic are we in public mode ?
* @param {Folder} destinationFolder the context folder to operate, relative to the root folder
*/
constructor(isPublic?: boolean, destinationFolder?: Folder);
/**
* Get the upload destination path relative to the root folder
*/
get destination(): Folder;
/**
* Set the upload destination path relative to the root folder
*/
set destination(folder: Folder);
/**
* Get the root folder
*/
get root(): string;
/**
* Get registered custom headers for uploads
*/
get customHeaders(): Record<string, string>;
/**
* Set a custom header
* @param name The header to set
* @param value The string value
*/
setCustomHeader(name: string, value?: string): void;
/**
* Unset a custom header
* @param name The header to unset
*/
deleteCustomerHeader(name: string): void;
/**
* Get the upload queue
*/
get queue(): Upload[];
private reset;
/**
* Pause any ongoing upload(s)
*/
pause(): void;
/**
* Resume any pending upload(s)
*/
start(): void;
/**
* Get the estimation for the uploading time.
*/
get eta(): Eta;
/**
* Get the upload queue stats
*/
get info(): {
size: number;
progress: number;
status: UploaderStatus;
};
private updateStats;
addNotifier(notifier: (upload: Upload) => void): void;
/**
* Notify listeners of the upload completion
* @param upload The upload that finished
*/
private _notifyAll;
/**
* Uploads multiple files or folders while preserving the relative path (if available)
* @param {string} destination The destination path relative to the root folder. e.g. /foo/bar (a file "a.txt" will be uploaded then to "/foo/bar/a.txt")
* @param {Array<File|FileSystemEntry>} files The files and/or folders to upload
* @param {Function} callback Callback that receives the nodes in the current folder and the current path to allow resolving conflicts, all nodes that are returned will be uploaded (if a folder does not exist it will be created)
* @return Cancelable promise that resolves to an array of uploads
*
* @example
* ```ts
* // For example this is from handling the onchange event of an input[type=file]
* async handleFiles(files: File[]) {
* this.uploads = await this.uploader.batchUpload('uploads', files, this.handleConflicts)
* }
*
* async handleConflicts(nodes: File[], currentPath: string) {
* const conflicts = getConflicts(nodes, this.fetchContent(currentPath))
* if (conflicts.length === 0) {
* // No conflicts so upload all
* return nodes
* } else {
* // Open the conflict picker to resolve conflicts
* try {
* const { selected, renamed } = await openConflictPicker(currentPath, conflicts, this.fetchContent(currentPath), { recursive: true })
* return [...selected, ...renamed]
* } catch (e) {
* return false
* }
* }
* }
* ```
*/
batchUpload(destination: string, files: (File | FileSystemEntry)[], callback?: (nodes: Array<File | IDirectory>, currentPath: string) => Promise<Array<File | IDirectory> | false>): default_2<Upload[]>;
/**
* Helper to create a directory wrapped inside an Upload class
* @param destination Destination where to create the directory
* @param directory The directory to create
* @param client The cached WebDAV client
*/
private createDirectory;
private uploadDirectory;
/**
* Upload a file to the given path
* @param {string} destination the destination path relative to the root folder. e.g. /foo/bar.txt
* @param {File|FileSystemFileEntry} fileHandle the file to upload
* @param {string} root the root folder to upload to
* @param retries number of retries
*/
upload(destination: string, fileHandle: File | FileSystemFileEntry, root?: string, retries?: number): default_2<Upload>;
/**
* Create modification time headers if valid value is available.
* It can be invalid on Android devices if SD cards with NTFS / FAT are used,
* as those files might use the NT epoch for time so the value will be negative.
*
* @param file The file to upload
*/
private _mtimeHeader;
}
export declare enum UploaderStatus {
IDLE = 0,
UPLOADING = 1,
PAUSED = 2
}
export declare const UploadPicker: DefineComponent< {
accept: {
type: PropType<string[]>;
default: null;
};
disabled: {
type: BooleanConstructor;
default: boolean;
};
multiple: {
type: BooleanConstructor;
default: boolean;
};
/**
* Allow to disable the "new"-menu for this UploadPicker instance
*/
noMenu: {
type: BooleanConstructor;
default: boolean;
};
/**
* Make the "New"-button primary color.
*/
primary: {
type: BooleanConstructor;
default: boolean;
};
destination: {
type: typeof Folder;
default: undefined;
};
allowFolders: {
type: BooleanConstructor;
default: boolean;
};
/**
* List of file present in the destination folder
* It is also possible to provide a function that takes a relative path to the current directory and returns the content of it
* Note: If a function is passed it should return the current base directory when no path or an empty is passed
*/
content: {
type: PropType<Node_2[] | ((relativePath?: string) => Node_2[] | PromiseLike<Node_2[]>)>;
default: () => never[];
};
/**
* Overwrite forbidden characters (by default the capabilities of the server are used)
* @deprecated Deprecated and will be removed in the next major version
*/
forbiddenCharacters: {
type: PropType<string[]>;
default: () => never[];
};
}, {
t: (original: string, placeholders?: Record<string, string | number>) => string;
progressTimeId: string;
}, {
newFileMenuEntries: Entry[];
openedMenu: boolean;
uploadManager: Uploader_2;
}, {
menuEntriesUpload(): Entry[];
menuEntriesNew(): Entry[];
menuEntriesOther(): Entry[];
/**
* Check whether the current browser supports uploading directories
* Hint: This does not check if the current connection supports this, as some browsers require a secure context!
*/
canUploadFolders(): boolean;
queue(): Upload[];
hasFailure(): boolean;
isAssembling(): boolean;
isUploading(): boolean;
isOnlyAssembling(): boolean;
isPaused(): boolean;
buttonLabel(): string;
haveMenu(): boolean;
}, {
etaTimeAndSpeed(): string;
/**
* Handle clicking a new-menu entry
* @param entry The entry that was clicked
*/
onClick(entry: Entry): Promise<void>;
/**
* Trigger file picker
* @param uploadFolders Upload folders
*/
onTriggerPick(uploadFolders?: boolean): void;
/**
* Helper for backwards compatibility that queries the content of the current directory
* @param path The current path
*/
getContent(path?: string): Promise<Node_2[]>;
/**
* Start uploading
*/
onPick(): Promise<void>;
resetForm(): void;
/**
* Cancel ongoing queue
*/
onCancel(): void;
setDestination(destination: Folder): void;
onUploadCompletion(upload: Upload): void;
onKeyDown(event: KeyboardEvent): void;
}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, Readonly<ExtractPropTypes< {
accept: {
type: PropType<string[]>;
default: null;
};
disabled: {
type: BooleanConstructor;
default: boolean;
};
multiple: {
type: BooleanConstructor;
default: boolean;
};
/**
* Allow to disable the "new"-menu for this UploadPicker instance
*/
noMenu: {
type: BooleanConstructor;
default: boolean;
};
/**
* Make the "New"-button primary color.
*/
primary: {
type: BooleanConstructor;
default: boolean;
};
destination: {
type: typeof Folder;
default: undefined;
};
allowFolders: {
type: BooleanConstructor;
default: boolean;
};
/**
* List of file present in the destination folder
* It is also possible to provide a function that takes a relative path to the current directory and returns the content of it
* Note: If a function is passed it should return the current base directory when no path or an empty is passed
*/
content: {
type: PropType<Node_2[] | ((relativePath?: string) => Node_2[] | PromiseLike<Node_2[]>)>;
default: () => never[];
};
/**
* Overwrite forbidden characters (by default the capabilities of the server are used)
* @deprecated Deprecated and will be removed in the next major version
*/
forbiddenCharacters: {
type: PropType<string[]>;
default: () => never[];
};
}>>, {
destination: Folder;
primary: boolean;
allowFolders: boolean;
accept: string[];
disabled: boolean;
multiple: boolean;
noMenu: boolean;
content: Node_2[] | ((relativePath?: string) => Node_2[] | PromiseLike<Node_2[]>);
forbiddenCharacters: string[];
}>;
export declare enum UploadStatus {
INITIALIZED = 0,
UPLOADING = 1,
ASSEMBLING = 2,
FINISHED = 3,
CANCELLED = 4,
FAILED = 5
}
export { }