Skip to content

Commit

Permalink
Merge pull request #942 from nextcloud-libraries/backport/icons-and-path
Browse files Browse the repository at this point in the history
[stable4] Add icons for filepicker and allow reactive button based on current path and selection
  • Loading branch information
susnux committed Aug 24, 2023
2 parents d451137 + a2cd69e commit d1dfd5f
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 50 deletions.
30 changes: 21 additions & 9 deletions l10n/messages.pot
Expand Up @@ -22,19 +22,27 @@ msgstr ""
msgid "All files"
msgstr ""

#: lib/legacy.ts:135
#: lib/legacy.ts:155
msgid "Choose"
msgstr ""

#: lib/legacy.ts:141
#: lib/legacy.ts:155
msgid "Choose {file}"
msgstr ""

#: lib/legacy.ts:163
msgid "Copy"
msgstr ""

#: lib/components/FilePicker/FilePicker.vue:242
#: lib/legacy.ts:163
msgid "Copy to {target}"
msgstr ""

#: lib/components/FilePicker/FilePicker.vue:249
msgid "Could not create the new folder"
msgstr ""

#: lib/components/FilePicker/FilePicker.vue:152
#: lib/components/FilePicker/FilePicker.vue:159
#: lib/components/FilePicker/FilePickerNavigation.vue:65
msgid "Favorites"
msgstr ""
Expand All @@ -43,27 +51,31 @@ msgstr ""
msgid "File name cannot be empty."
msgstr ""

#: lib/components/FilePicker/FilePicker.vue:228
#: lib/components/FilePicker/FilePicker.vue:235
msgid "Files and folders you mark as favorite will show up here."
msgstr ""

#: lib/components/FilePicker/FilePicker.vue:226
#: lib/components/FilePicker/FilePicker.vue:233
msgid "Files and folders you recently modified will show up here."
msgstr ""

#: lib/components/FilePicker/FileList.vue:39
msgid "Modified"
msgstr ""

#: lib/legacy.ts:147
#: lib/legacy.ts:171
msgid "Move"
msgstr ""

#: lib/legacy.ts:171
msgid "Move to {target}"
msgstr ""

#: lib/components/FilePicker/FileList.vue:19
msgid "Name"
msgstr ""

#: lib/components/FilePicker/FilePicker.vue:152
#: lib/components/FilePicker/FilePicker.vue:159
#: lib/components/FilePicker/FilePickerNavigation.vue:61
msgid "Recent"
msgstr ""
Expand Down Expand Up @@ -94,6 +106,6 @@ msgstr ""
msgid "Unset"
msgstr ""

#: lib/components/FilePicker/FilePicker.vue:224
#: lib/components/FilePicker/FilePicker.vue:231
msgid "Upload some content or sync with your devices!"
msgstr ""
5 changes: 3 additions & 2 deletions lib/components/DialogButton.vue
Expand Up @@ -2,14 +2,15 @@
<NcButton :aria-label="props.label" :type="props.type" @click="handleClick">
{{ props.label }}
<template v-if="props.icon !== undefined" #icon>
<component :is="props.icon" :size="20" />
<NcIconSvgWrapper v-if="typeof props.icon === 'string'" :svg="props.icon" />
<component :is="props.icon" v-else :size="20" />
</template>
</NcButton>
</template>

<script setup lang="ts">
import type { IDialogButton } from './types'
import { NcButton } from '@nextcloud/vue'
import { NcButton, NcIconSvgWrapper } from '@nextcloud/vue'
// with vue 3.3:
// const props = defineProps<IDialogButton>()
Expand Down
29 changes: 18 additions & 11 deletions lib/components/FilePicker/FilePicker.vue
Expand Up @@ -44,7 +44,7 @@
</template>

<script setup lang="ts">
import type { IFilePickerButton } 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 @@ -64,7 +64,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 All @@ -84,7 +84,7 @@ const props = withDefaults(defineProps<{
/**
* Custom filter function used to filter pickable files
*/
filterFn?: (node: Node) => boolean
filterFn?: IFilePickerFilter
/**
* List of allowed mime types
Expand Down Expand Up @@ -115,7 +115,7 @@ const props = withDefaults(defineProps<{
})
const emit = defineEmits<{
(e: 'close'): void
(e: 'close', v?: Node[]): void
}>()
/**
Expand All @@ -133,13 +133,20 @@ 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)
emit('close', selectedFiles.value as Node[])
},
} as IFilePickerButton))
})
/**
* Name of the currently active view
Expand Down
20 changes: 19 additions & 1 deletion lib/components/types.ts
Expand Up @@ -25,11 +25,29 @@ import type { AsyncComponent, Component } from 'vue'

export interface IDialogButton {
label: string,
icon?: Component | AsyncComponent,

/** Callback on button click */
callback: () => void,
/**
* Optional Icon for the button
* Can be a Vue component, async Vue component, or SVG
*/
icon?: Component | AsyncComponent | string,

/**
* Button type
* @see https://nextcloud-vue-components.netlify.app/#/Components/NcButton
*/
type?: 'primary' | 'secondary' | 'error' | 'warning' | 'success'
}

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
*/
export type IFilePickerFilter = (node: Node) => boolean
66 changes: 42 additions & 24 deletions lib/legacy.ts
Expand Up @@ -25,13 +25,17 @@
*/

/// <reference types="@nextcloud/typings" />
import type { IFilePickerButton } from './components/FilePicker/FilePicker.vue'
import type { IFilePickerButton, IFilePickerButtonFactory } from './components/types'
import type { Node } from '@nextcloud/files'
import type { AsyncComponent, Component } from 'vue'

import { basename } from 'path'
import { t } from './utils/l10n'
import { FilePickerVue, FilePickerType } from '.'

import DialogBase from './components/DialogBase.vue'
import IconCopy from '@mdi/svg/svg/folder-multiple.svg'
import IconMove from '@mdi/svg/svg/folder-move.svg'
import Vue from 'vue'

/**
Expand Down Expand Up @@ -129,34 +133,48 @@ export async function filepicker(title: string, callback: (s: string | string[],
sharePermissions: null,
})

const buttons: IFilePickerButton[] = []
if (type === FilePickerType.Choose) {
buttons.push({
label: t('Choose'),
type: 'primary',
callback: legacyCallback(callback, FilePickerType.Choose),
})
} else if (type === FilePickerType.Copy || type === FilePickerType.CopyMove) {
buttons.push({
label: t('Copy'),
callback: legacyCallback(callback, FilePickerType.Copy),
})
}
if (type === FilePickerType.CopyMove || type === FilePickerType.Move) {
buttons.push({
label: t('Move'),
type: 'primary',
callback: legacyCallback(callback, FilePickerType.Move),
})
}
let buttonFn: IFilePickerButtonFactory|IFilePickerButton[]
if (type === FilePickerType.Custom) {
(options.buttons || []).forEach((button) => {
buttons.push({
buttonFn = [] as IFilePickerButton[]
(options.buttons || []).forEach((button: { text: string, defaultButton: boolean, type: FilePickerType }) => {
(buttonFn as IFilePickerButton[]).push({
callback: legacyCallback(callback, button.type),
label: button.text,
type: button.defaultButton ? 'primary' : 'secondary',
})
})
} else {
buttonFn = (nodes, path) => {
const buttons: IFilePickerButton[] = []
const node = nodes?.[0]?.attributes?.displayName || nodes?.[0]?.basename
const target = node || basename(path)

if (type === FilePickerType.Choose) {
buttons.push({
callback: legacyCallback(callback, FilePickerType.Choose),
label: node && !multiselect ? t('Choose {file}', { file: node }) : t('Choose'),
type: 'primary',
})
}

if (type === FilePickerType.CopyMove || type === FilePickerType.Copy) {
buttons.push({
callback: legacyCallback(callback, FilePickerType.Copy),
label: target ? t('Copy to {target}', { target }) : t('Copy'),
type: 'primary',
icon: IconCopy,
})
}
if (type === FilePickerType.Move || type === FilePickerType.CopyMove) {
buttons.push({
callback: legacyCallback(callback, FilePickerType.Move),
label: target ? t('Move to {target}', { target }) : t('Move'),
type: type === FilePickerType.Move ? 'primary' : 'secondary',
icon: IconMove,
})
}
return buttons
}
}

const filter = {} as any
Expand All @@ -170,7 +188,7 @@ export async function filepicker(title: string, callback: (s: string | string[],
spawnDialog(FilePickerVue, {
...filter,
name: title,
buttons,
buttons: buttonFn,
multiselect,
path,
mimetypeFilter,
Expand Down
9 changes: 9 additions & 0 deletions lib/svg.d.ts
@@ -0,0 +1,9 @@
declare module '*.svg' {
const content: string
export default content
}

declare module '*.svg?raw' {
const content: string
export default content
}
6 changes: 3 additions & 3 deletions lib/utils/dialogs.ts
Expand Up @@ -30,7 +30,7 @@ import Vue from 'vue'
* @param props Properties to pass to the dialog
* @param onClose Callback when the dialog is closed
*/
export const spawnDialog = (dialog: Component | AsyncComponent, props: any, onClose: () => void = () => {}) => {
export const spawnDialog = (dialog: Component | AsyncComponent, props: any, onClose: (...rest: unknown[]) => void = () => {}) => {
const el = document.createElement('div')

const container: HTMLElement = document.querySelector(props?.container) || document.body
Expand All @@ -43,8 +43,8 @@ export const spawnDialog = (dialog: Component | AsyncComponent, props: any, onCl
h(dialog, {
props,
on: {
close: () => {
onClose()
close: (...rest: unknown[]) => {
onClose(rest)
vue.$destroy()
},
},
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
Expand Up @@ -54,6 +54,7 @@
"vue": "^2.7.14"
},
"dependencies": {
"@mdi/svg": "^7.2.96",
"@nextcloud/files": "^3.0.0-beta.14",
"@nextcloud/l10n": "^2.2.0",
"@nextcloud/router": "^2.1.2",
Expand Down

0 comments on commit d1dfd5f

Please sign in to comment.