-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathfilepicker-builder.ts
More file actions
307 lines (276 loc) · 7.63 KB
/
filepicker-builder.ts
File metadata and controls
307 lines (276 loc) · 7.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFilePickerButton, IFilePickerButtonFactory, IFilePickerFilter } from './components/types'
import type { Node } from '@nextcloud/files'
import { basename } from 'path'
import { spawnDialog } from '@nextcloud/vue/functions/dialogs'
import { n, t } from './utils/l10n'
import IconMove from '@mdi/svg/svg/folder-move.svg?raw'
import IconCopy from '@mdi/svg/svg/folder-multiple.svg?raw'
/**
* @deprecated
*/
export enum FilePickerType {
Choose = 1,
Move = 2,
Copy = 3,
CopyMove = 4,
Custom = 5,
}
/**
* Error that is thrown in the rejected promise when the FilePicker was closed
*/
export class FilePickerClosed extends Error {}
export class FilePicker<IsMultiSelect extends boolean> {
private title: string
private multiSelect: IsMultiSelect
private mimeTypeFilter: string[]
private directoriesAllowed: boolean
private buttons: IFilePickerButton[] | IFilePickerButtonFactory
private path?: string
private filter?: IFilePickerFilter
private container?: string
private disabledNavigation: boolean
public constructor(title: string,
multiSelect: IsMultiSelect,
mimeTypeFilter: string[],
directoriesAllowed: boolean,
buttons: IFilePickerButton[] | IFilePickerButtonFactory,
path?: string,
filter?: IFilePickerFilter,
container?: string,
disabledNavigation = false,
) {
this.title = title
this.multiSelect = multiSelect
this.mimeTypeFilter = mimeTypeFilter
this.directoriesAllowed = directoriesAllowed
this.path = path
this.filter = filter
this.buttons = buttons
this.container = container
this.disabledNavigation = disabledNavigation
}
/**
* Pick files using the FilePicker.
*
* @return Promise with array of picked files or rejected promise on close without picking
*/
public async pickNodes(): Promise<Node[]> {
const { FilePickerVue } = await import('./components/FilePicker/index')
return new Promise((resolve, reject) => {
spawnDialog(FilePickerVue, {
allowPickDirectory: this.directoriesAllowed,
buttons: this.buttons,
container: this.container,
name: this.title,
path: this.path,
mimetypeFilter: this.mimeTypeFilter,
multiselect: this.multiSelect,
filterFn: this.filter,
disabledNavigation: this.disabledNavigation,
}, (...rest: unknown[]) => {
const [nodes] = rest as [nodes: Node[]]
if (!Array.isArray(nodes) || nodes.length === 0) {
reject(new FilePickerClosed('FilePicker: No nodes selected'))
} else {
resolve(nodes)
}
})
})
}
/**
* Pick files using the FilePicker
*
* @return Promise with array of paths of picked files or rejected promise on close without picking
*/
public async pick(): Promise<IsMultiSelect extends true ? string[] : string> {
const nodes = await this.pickNodes()
if (this.multiSelect) {
return (nodes[0]?.path ?? '/') as (IsMultiSelect extends true ? string[] : string)
}
return nodes.map((node) => node.path) as (IsMultiSelect extends true ? string[] : string)
}
}
export class FilePickerBuilder<IsMultiSelect extends boolean> {
private title: string
private multiSelect = false
private mimeTypeFilter: string[] = []
private directoriesAllowed = false
private path?: string
private filter?: IFilePickerFilter
private buttons: IFilePickerButton[] | IFilePickerButtonFactory = []
private container?: string
private disabledNavigation = false
/**
* Construct a new FilePicker
*
* @param title Title of the FilePicker
*/
public constructor(title: string) {
this.title = title
}
/**
* Set the container where the FilePicker will be mounted
* By default 'body' is used
*
* @param container The dialog container
*/
public setContainer(container: string) {
this.container = container
return this
}
/**
* Enable or disable picking multiple files
*
* @param ms True to enable picking multiple files, false otherwise
*/
public setMultiSelect<T extends boolean>(ms: T) {
this.multiSelect = ms
return this as unknown as FilePickerBuilder<T extends true ? true : false>
}
/**
* Add allowed MIME type
*
* @param filter MIME type to allow
*/
public addMimeTypeFilter(filter: string) {
this.mimeTypeFilter.push(filter)
return this
}
/**
* Set allowed MIME types
*
* @param filter Array of allowed MIME types
*/
public setMimeTypeFilter(filter: string[]) {
this.mimeTypeFilter = filter
return this
}
/**
* Add a button to the FilePicker
* Note: This overrides any previous `setButtonFactory` call
*
* @param button The button
*/
public addButton(button: IFilePickerButton) {
if (typeof this.buttons === 'function') {
console.warn('FilePicker buttons were set to factory, now overwritten with button object.')
this.buttons = []
}
this.buttons.push(button)
return this
}
/**
* Set the button factory which is used to generate buttons from current view, path and selected nodes
* Note: This overrides any previous `addButton` call
*
* @param factory The button factory
*/
public setButtonFactory(factory: IFilePickerButtonFactory) {
this.buttons = factory
return this
}
/**
* Set FilePicker type based on legacy file picker types
* @param type The legacy filepicker type to emulate
* @deprecated Use `addButton` or `setButtonFactory` instead as with setType you do not know which button was pressed
*/
public setType(type: FilePickerType) {
this.buttons = (nodes, path) => {
const buttons: IFilePickerButton[] = []
const node = nodes?.[0]?.attributes?.displayName || nodes?.[0]?.basename
const target = node || basename(path)
if (type === FilePickerType.Choose) {
let label = t('Choose')
if (nodes.length === 1) {
label = t('Choose {file}', { file: node })
} else if (this.multiSelect) {
label = n('Choose %n file', 'Choose %n files', nodes.length)
}
buttons.push({
callback: () => {},
type: 'primary',
label,
})
}
if (type === FilePickerType.CopyMove || type === FilePickerType.Copy) {
buttons.push({
callback: () => {},
label: target ? t('Copy to {target}', { target }) : t('Copy'),
type: 'primary',
icon: IconCopy,
})
}
if (type === FilePickerType.Move || type === FilePickerType.CopyMove) {
buttons.push({
callback: () => {},
label: target ? t('Move to {target}', { target }) : t('Move'),
type: type === FilePickerType.Move ? 'primary' : 'secondary',
icon: IconMove,
})
}
return buttons
}
return this
}
/**
* Allow to pick directories besides files
*
* @param allow True to allow picking directories
*/
public allowDirectories(allow = true) {
this.directoriesAllowed = allow
return this
}
/**
* Set starting path of the FilePicker
*
* @param path Path to start from picking
*/
public startAt(path: string) {
this.path = path
return this
}
/**
* Add filter function to filter file list of FilePicker
*
* @param filter Filter function to apply
*/
public setFilter(filter: IFilePickerFilter) {
this.filter = filter
return this
}
/**
* Disable navigation (view selection)
*/
public disableNavigation() {
this.disabledNavigation = true
return this
}
/**
* Construct the configured FilePicker
*/
public build() {
return new FilePicker<IsMultiSelect>(
this.title,
this.multiSelect as IsMultiSelect,
this.mimeTypeFilter,
this.directoriesAllowed,
this.buttons,
this.path,
this.filter,
this.container,
this.disabledNavigation,
)
}
}
/**
*
* @param title Title of the file picker
*/
export function getFilePickerBuilder(title: string): FilePickerBuilder<boolean> {
return new FilePickerBuilder(title)
}