Skip to content

Commit

Permalink
feat(FilePicker): Support button factories which can be used to creat…
Browse files Browse the repository at this point in the history
…e reactive buttons (e.g. labels like `move to...`)

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed Aug 24, 2023
1 parent 535e20c commit a4657be
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 41 deletions.
24 changes: 15 additions & 9 deletions lib/components/FilePicker/FilePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</template>

<script setup lang="ts">
import type { IFilePickerButton, IFilePickerFilter } from '../types'
import type { IFilePickerButton, IFilePickerButtonFactory, IFilePickerFilter } from '../types'
import type { Node } from '@nextcloud/files'
import IconFile from 'vue-material-design-icons/File.vue'
Expand All @@ -63,7 +63,7 @@ import { t } from '../../utils/l10n'
const props = withDefaults(defineProps<{
/** Buttons to be displayed */
buttons: IFilePickerButton[]
buttons: IFilePickerButton[] | IFilePickerButtonFactory
/** The name of file picker dialog (heading) */
name: string
Expand Down Expand Up @@ -132,13 +132,19 @@ const dialogProps = computed(() => ({
/**
* Map buttons to Dialog buttons by wrapping the callback function to pass the selected files
*/
const dialogButtons = computed(() => [...props.buttons].map(button => ({
...button,
callback: async () => {
const nodes = selectedFiles.value.length === 0 && props.allowPickDirectory ? [await getFile(currentPath.value)] : selectedFiles.value as Node[]
return button.callback(nodes)
},
})))
const dialogButtons = computed(() => {
const buttons = typeof props.buttons === 'function'
? props.buttons(selectedFiles.value as Node[], currentPath.value, currentView.value)
: props.buttons
return buttons.map((button) => ({
...button,
callback: async () => {
const nodes = selectedFiles.value.length === 0 && props.allowPickDirectory ? [await getFile(currentPath.value)] : selectedFiles.value as Node[]
button.callback(nodes)
},
} as IFilePickerButton))
})
/**
* Name of the currently active view
Expand Down
2 changes: 2 additions & 0 deletions lib/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface IFilePickerButton extends Omit<IDialogButton, 'callback'> {
callback: (nodes: Node[]) => void
}

export type IFilePickerButtonFactory = (selectedNodes: Node[], currentPath: string, currentView: string) => IFilePickerButton[]

/**
* Type of filter functions to filter the FilePicker's file list
*/
Expand Down
85 changes: 53 additions & 32 deletions lib/filepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
*
*/

import type { IFilePickerButton, IFilePickerFilter } from './components/types'
import type { IFilePickerButton, IFilePickerButtonFactory, IFilePickerFilter } from './components/types'
import type { Node } from '@nextcloud/files'

import { basename } from 'path'
import { spawnDialog } from './utils/dialogs'
import { FilePickerVue } from './components/FilePicker/index'
import { 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
*/
Expand All @@ -44,15 +48,15 @@ export class FilePicker<IsMultiSelect extends boolean> {
private multiSelect: IsMultiSelect
private mimeTypeFilter: string[]
private directoriesAllowed: boolean
private buttons: IFilePickerButton[]
private buttons: IFilePickerButton[] | IFilePickerButtonFactory
private path?: string
private filter?: IFilePickerFilter

public constructor(title: string,
multiSelect: IsMultiSelect,
mimeTypeFilter: string[],
directoriesAllowed: boolean,
buttons: IFilePickerButton[],
buttons: IFilePickerButton[] | IFilePickerButtonFactory,
path?: string,
filter?: IFilePickerFilter) {
this.title = title
Expand Down Expand Up @@ -105,7 +109,7 @@ export class FilePickerBuilder<IsMultiSelect extends boolean> {
private directoriesAllowed = false
private path?: string
private filter?: IFilePickerFilter
private buttons: IFilePickerButton[] = []
private buttons: IFilePickerButton[] | IFilePickerButtonFactory = []

/**
* Construct a new FilePicker
Expand Down Expand Up @@ -148,48 +152,65 @@ export class FilePickerBuilder<IsMultiSelect extends boolean> {

/**
* 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` instead as with setType you do not know which button was pressed
* @deprecated Use `addButton` or `setButtonFactory` instead as with setType you do not know which button was pressed
*/
public setType(type: FilePickerType) {
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',
})
}
this.buttons = (nodes, path) => {
const buttons: IFilePickerButton[] = []
const node = nodes?.[0]?.attributes?.displayName || nodes?.[0]?.basename
const target = node || basename(path)

if (type === FilePickerType.CopyMove) {
this.buttons.push({
callback: () => {},
label: t('Move'),
type: 'secondary',
})
if (type === FilePickerType.Choose) {
buttons.push({
callback: () => {},
label: node && !this.multiSelect ? t('Choose {file}', { file: node }) : t('Choose'),
type: 'primary',
})
}
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
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"vue": "^2.7.14"
},
"dependencies": {
"@mdi/svg": "^7.2.96",
"@nextcloud/files": "^3.0.0-beta.16",
"@nextcloud/l10n": "^2.2.0",
"@nextcloud/typings": "^1.7.0",
Expand Down

0 comments on commit a4657be

Please sign in to comment.