diff --git a/apps/files/src/components/FileEntry/FileEntryActions.vue b/apps/files/src/components/FileEntry/FileEntryActions.vue index bd4649cdee568..3997cfc58b2ae 100644 --- a/apps/files/src/components/FileEntry/FileEntryActions.vue +++ b/apps/files/src/components/FileEntry/FileEntryActions.vue @@ -39,20 +39,56 @@ :force-name="true" :force-menu="enabledInlineActions.length === 0 /* forceMenu only if no inline actions */" :inline="enabledInlineActions.length" - :open.sync="openedMenu"> - - - {{ actionDisplayName(action) }} - + :open.sync="openedMenu" + @close="openedSubmenu = null"> + + + + + @@ -63,8 +99,11 @@ import { showError, showSuccess } from '@nextcloud/dialogs' import { translate as t } from '@nextcloud/l10n'; import Vue, { PropType } from 'vue' +import ChevronLeftIcon from 'vue-material-design-icons/ChevronLeft.vue' +import ChevronRightIcon from 'vue-material-design-icons/ChevronRight.vue' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' +import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js' import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' @@ -78,11 +117,14 @@ export default Vue.extend({ name: 'FileEntryActions', components: { + ChevronLeftIcon, + ChevronRightIcon, + CustomElementRender, NcActionButton, NcActions, + NcActionSeparator, NcIconSvgWrapper, NcLoadingIcon, - CustomElementRender, }, props: { @@ -108,8 +150,9 @@ export default Vue.extend({ }, }, - setup() { + data() { return { + openedSubmenu: '', } }, @@ -159,7 +202,13 @@ export default Vue.extend({ // Actions shown in the menu enabledMenuActions() { - return [ + // If we're in a submenu, only render the inline + // actions before the filtered submenu + if (this.openedSubmenu) { + return this.enabledInlineActions + } + + const actions = [ // Showing inline first for the NcActions inline prop ...this.enabledInlineActions, // Then the rest @@ -168,6 +217,24 @@ export default Vue.extend({ // Then we filter duplicates to prevent inline actions to be shown twice return index === self.findIndex(action => action.id === value.id) }) + + // Generate list of all top-level actions ids + const topActionsIds = actions.filter(action => !action.parent).map(action => action.id) as string[] + + // Filter actions that are not top-level AND have a valid parent + return actions.filter(action => !(action.parent && topActionsIds.includes(action.parent))) + }, + + enabledSubmenuActions() { + return this.enabledActions + .filter(action => action.parent) + .reduce((arr, action) => { + if (!arr[action.parent]) { + arr[action.parent] = [] + } + arr[action.parent].push(action) + return arr + }, {} as Record) }, openedMenu: { @@ -201,6 +268,12 @@ export default Vue.extend({ }, async onActionClick(action) { + // If the action is a submenu, we open it + if (this.enabledSubmenuActions[action.id]) { + this.openedSubmenu = action.id + return + } + const displayName = action.displayName([this.source], this.currentView) try { // Set the loading marker @@ -237,6 +310,10 @@ export default Vue.extend({ } }, + isMenu(id: string) { + return this.enabledSubmenuActions[id]?.length > 0 + }, + t, }, }) diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts index 9cbf3dc2e69de..db6803101ba5b 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -19,7 +19,8 @@ * along with this program. If not, see . * */ -import { addNewFileMenuEntry, registerFileAction } from '@nextcloud/files' +import MenuIcon from '@mdi/svg/svg/sun-compass.svg?raw' +import { FileAction, addNewFileMenuEntry, registerFileAction } from '@nextcloud/files' import { action as deleteAction } from './actions/deleteAction' import { action as downloadAction } from './actions/downloadAction' @@ -62,3 +63,39 @@ registerRecentView() // Register preview service worker registerPreviewServiceWorker() + +registerFileAction(new FileAction({ + id: 'menu', + displayName: () => 'Menu', + iconSvgInline: () => MenuIcon, + exec: async () => true, +})) + +registerFileAction(new FileAction({ + id: 'submenu1', + displayName: () => 'Submenu 1', + iconSvgInline: () => MenuIcon, + exec: async () => alert('Hello 1'), + parent: 'menu', +})) +registerFileAction(new FileAction({ + id: 'submenu2', + displayName: () => 'Submenu 2', + iconSvgInline: () => MenuIcon, + exec: async () => alert('Hello 2'), + parent: 'menu', +})) +registerFileAction(new FileAction({ + id: 'submenu3', + displayName: () => 'Submenu 3', + iconSvgInline: () => MenuIcon, + exec: async () => alert('Hello 3'), + parent: 'menu', +})) +registerFileAction(new FileAction({ + id: 'submenu4', + displayName: () => 'Submenu 4', + iconSvgInline: () => MenuIcon, + exec: async () => alert('Hello 4'), + parent: 'menu', +}))