diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2023-10-20 12:37:10 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2023-11-08 08:39:01 +0100 |
commit | 72ffd4999a82c242a7f82afd750372a8d49e517e (patch) | |
tree | b09106a4f865f34e1681255c5dd65f7a1044d727 /apps/files | |
parent | 1d568a89a45456d9405f347d57b2caf9a80ad179 (diff) | |
download | nextcloud-server-72ffd4999a82c242a7f82afd750372a8d49e517e.tar.gz nextcloud-server-72ffd4999a82c242a7f82afd750372a8d49e517e.zip |
feat(files): support nested actions
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files')
-rw-r--r-- | apps/files/src/components/FileEntry/FileEntryActions.vue | 111 | ||||
-rw-r--r-- | apps/files/src/init.ts | 39 |
2 files changed, 132 insertions, 18 deletions
diff --git a/apps/files/src/components/FileEntry/FileEntryActions.vue b/apps/files/src/components/FileEntry/FileEntryActions.vue index 688524962f5..284806f7c94 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"> - <NcActionButton v-for="action in enabledMenuActions" - :key="action.id" - :class="'files-list__row-action-' + action.id" - :close-after-click="true" - :data-cy-files-list-row-action="action.id" - :title="action.title?.([source], currentView)" - @click="onActionClick(action)"> - <template #icon> - <NcLoadingIcon v-if="loading === action.id" :size="18" /> - <NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" /> - </template> - {{ actionDisplayName(action) }} - </NcActionButton> + :open.sync="openedMenu" + @close="openedSubmenu = null"> + <!-- Default actions list--> + <template> + <NcActionButton v-for="action in enabledMenuActions" + :key="action.id" + :class="{ + [`files-list__row-action-${action.id}`]: true, + [`files-list__row-action--menu`]: isMenu(action.id) + }" + :close-after-click="!isMenu(action.id)" + :data-cy-files-list-row-action="action.id" + :title="action.title?.([source], currentView)" + @click="onActionClick(action)"> + <template #icon> + <ChevronRightIcon v-if="isMenu(action.id)" /> + <NcLoadingIcon v-else-if="loading === action.id" :size="18" /> + <NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" /> + </template> + {{ actionDisplayName(action) }} + </NcActionButton> + </template> + + <!-- Submenu actions list--> + <template v-if="openedSubmenu && enabledSubmenuActions[openedSubmenu]"> + <!-- Back to top-level button --> + <NcActionButton class="files-list__row-action-back" @click="openedSubmenu = ''"> + <template #icon> + <ArrowLeftIcon /> + </template> + {{ t('files', 'Back') }} + </NcActionButton> + <NcActionSeparator /> + + <!-- Submenu actions --> + <NcActionButton v-for="action in enabledSubmenuActions[openedSubmenu]" + :key="action.id" + :class="`files-list__row-action-${action.id}`" + class="files-list__row-action--submenu" + :close-after-click="true" + :data-cy-files-list-row-action="action.id" + :title="action.title?.([source], currentView)" + @click="onActionClick(action)"> + <template #icon> + <NcLoadingIcon v-if="loading === action.id" :size="18" /> + <NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" /> + </template> + {{ actionDisplayName(action) }} + </NcActionButton> + </template> </NcActions> </td> </template> @@ -63,8 +99,11 @@ import { showError, showSuccess } from '@nextcloud/dialogs' import { translate as t } from '@nextcloud/l10n'; import Vue, { PropType } from 'vue' +import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.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: { + ArrowLeftIcon, + 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<string, FileAction>) }, 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 9cbf3dc2e69..db6803101ba 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -19,7 +19,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -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', +})) |