diff --git a/l10n/messages.pot b/l10n/messages.pot index a6fdf926..308079f3 100644 --- a/l10n/messages.pot +++ b/l10n/messages.pot @@ -18,10 +18,12 @@ msgstr "" msgid "All files" msgstr "" +#: lib/filepicker.ts:183 #: lib/legacy.ts:135 msgid "Choose" msgstr "" +#: lib/filepicker.ts:171 #: lib/legacy.ts:141 msgid "Copy" msgstr "" @@ -43,6 +45,8 @@ msgstr "" msgid "Modified" msgstr "" +#: lib/filepicker.ts:177 +#: lib/filepicker.ts:191 #: lib/legacy.ts:147 msgid "Move" msgstr "" diff --git a/lib/filepicker.ts b/lib/filepicker.ts index e4f9ae33..b34dc56a 100644 --- a/lib/filepicker.ts +++ b/lib/filepicker.ts @@ -1,5 +1,40 @@ -/// +/** + * @copyright Copyright (c) 2023 Ferdinand Thiessen + * + * @author Ferdinand Thiessen + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +import type { IFilePickerButton } from './components/types' +import type { Node } from '@nextcloud/files' + +import { spawnDialog } from './utils/dialogs' +import { FilePickerVue } from './components/FilePicker/index' +import { t } from './utils/l10n' + +/** + * Type of filter functions to filter the FilePicker's file list + */ +export type FilePickerFilter = (node: Node) => boolean +/** + * @deprecated + */ export enum FilePickerType { Choose = 1, Move = 2, @@ -8,70 +43,56 @@ export enum FilePickerType { Custom = 5, } -export interface FilePickerButton { - text: string - type?: 'primary' | 'secondary' - /** Passed on the callback as second argument */ - id: number -} - export class FilePicker { private title: string private multiSelect: boolean - private mimeTypeFiler: string[] - private modal: boolean - private type: FilePickerType + private mimeTypeFilter: string[] private directoriesAllowed: boolean - private buttons?: FilePickerButton[] + private buttons: IFilePickerButton[] private path?: string - private filter?: Nextcloud.v24.FilePickerFilter + private filter?: FilePickerFilter public constructor(title: string, multiSelect: boolean, mimeTypeFilter: string[], - modal: boolean, - type: FilePickerType, directoriesAllowed: boolean, + buttons: IFilePickerButton[], path?: string, - filter?: Nextcloud.v24.FilePickerFilter, - buttons?: FilePickerButton[]) { + filter?: FilePickerFilter) { this.title = title this.multiSelect = multiSelect - this.mimeTypeFiler = mimeTypeFilter - this.modal = modal - this.type = type + this.mimeTypeFilter = mimeTypeFilter this.directoriesAllowed = directoriesAllowed this.path = path this.filter = filter this.buttons = buttons } - public async pick(): Promise<[string|string[], FilePickerType]> { - // Async import for module splitting (treeshaking) - const filepicker = (await import('./legacy')).filepicker - - return new Promise((resolve) => { - const buttons = this.buttons?.map(button => ({ - defaultButton: button.type === 'primary', - label: button.text, - type: button.id, + /** + * Pick files using the FilePicker + * + * @return Promise with array of picked files or rejected promise on close without picking + */ + public async pick(): Promise { + return new Promise((resolve, reject) => { + const buttons = this.buttons.map((button) => ({ + ...button, + callback: (nodes: Node[]) => { + button.callback(nodes) + resolve(nodes.map((node) => node.path)) + }, })) - filepicker( - this.title, - (path: string | string[], type: FilePickerType) => resolve([path, type]), - this.multiSelect, - this.mimeTypeFiler, - this.modal, - this.type, - this.path, - { - allowDirectoryChooser: this.directoriesAllowed, - filter: this.filter, - buttons, - }, - ) + spawnDialog(FilePickerVue, { + allowPickDirectory: this.directoriesAllowed, + buttons, + name: this.title, + path: this.path, + mimetypeFilter: this.mimeTypeFilter, + multiselect: this.multiSelect, + filterFn: this.filter, + }, reject) }) } @@ -81,82 +102,142 @@ export class FilePickerBuilder { private title: string private multiSelect = false - private mimeTypeFiler: string[] = [] - private modal = true - private type: FilePickerType = FilePickerType.Choose + private mimeTypeFilter: string[] = [] private directoriesAllowed = false private path?: string - private filter?: Nextcloud.v24.FilePickerFilter - private buttons: FilePickerButton[] = [] + private filter?: FilePickerFilter + private buttons: IFilePickerButton[] = [] + /** + * Construct a new FilePicker + * + * @param title Title of the FilePicker + */ public constructor(title: string) { this.title = title } + /** + * Enable or disable picking multiple files + * + * @param ms True to enable picking multiple files, false otherwise + */ public setMultiSelect(ms: boolean): FilePickerBuilder { this.multiSelect = ms return this } + /** + * Add allowed MIME type + * + * @param filter MIME type to allow + */ public addMimeTypeFilter(filter: string): FilePickerBuilder { - this.mimeTypeFiler.push(filter) + this.mimeTypeFilter.push(filter) return this } + /** + * Set allowed MIME types + * + * @param filter Array of allowed MIME types + */ public setMimeTypeFilter(filter: string[]): FilePickerBuilder { - this.mimeTypeFiler = filter + this.mimeTypeFilter = filter return this } - public addButton(button: FilePickerButton): FilePickerBuilder { + /** + * Add a button to the FilePicker + * + * @param button The button + */ + public addButton(button: IFilePickerButton): FilePickerBuilder { this.buttons.push(button) return this } /** - * @param modal no function - * @deprecated Does not have any effect as the dialog is always modal + * Set FilePicker type based on legacy file picker types + * @param type The legacy filepicker type to emulate + * @deprecated Use `addButton` instead as with setType you do not know which button was pressed */ - public setModal(modal: boolean): FilePickerBuilder { - this.modal = modal - return this - } - public setType(type: FilePickerType): FilePickerBuilder { - this.type = type + this.buttons = [] + + if (type === FilePickerType.CopyMove || type === FilePickerType.Copy) { + this.buttons.push({ + callback: () => {}, + label: t('Copy'), + type: 'primary', + }) + } else if (type === FilePickerType.Move) { + this.buttons.push({ + callback: () => {}, + label: t('Move'), + type: 'primary', + }) + } else if (type === FilePickerType.Choose) { + this.buttons.push({ + callback: () => {}, + label: t('Choose'), + type: 'primary', + }) + } + + if (type === FilePickerType.CopyMove) { + this.buttons.push({ + callback: () => {}, + label: t('Move'), + type: 'secondary', + }) + } + return this } + /** + * Allow to pick directories besides files + * + * @param allow True to allow picking directories + */ public allowDirectories(allow = true): FilePickerBuilder { this.directoriesAllowed = allow return this } + /** + * Set starting path of the FilePicker + * + * @param path Path to start from picking + */ public startAt(path: string): FilePickerBuilder { this.path = path return this } - public setFilter(filter: Nextcloud.v24.FilePickerFilter): FilePickerBuilder { + /** + * Add filter function to filter file list of FilePicker + * + * @param filter Filter function to apply + */ + public setFilter(filter: FilePickerFilter): FilePickerBuilder { this.filter = filter return this } + /** + * Construct the configured FilePicker + */ public build(): FilePicker { - if (this.buttons && this.type !== FilePickerType.Custom) { - console.error('FilePickerBuilder: When adding custom buttons the `type` must be set to `FilePickerType.Custom`.') - } - return new FilePicker( this.title, this.multiSelect, - this.mimeTypeFiler, - this.modal, - this.type, + this.mimeTypeFilter, this.directoriesAllowed, + this.buttons, this.path, this.filter, - this.buttons, ) }