aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/components/FileEntry/FileEntryActions.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/components/FileEntry/FileEntryActions.vue')
-rw-r--r--apps/files/src/components/FileEntry/FileEntryActions.vue168
1 files changed, 96 insertions, 72 deletions
diff --git a/apps/files/src/components/FileEntry/FileEntryActions.vue b/apps/files/src/components/FileEntry/FileEntryActions.vue
index 4e7bec88452..5c537d878fe 100644
--- a/apps/files/src/components/FileEntry/FileEntryActions.vue
+++ b/apps/files/src/components/FileEntry/FileEntryActions.vue
@@ -22,33 +22,68 @@
type="tertiary"
:force-menu="enabledInlineActions.length === 0 /* forceMenu only if no inline actions */"
:inline="enabledInlineActions.length"
- :open.sync="openedMenu"
- @close="openedSubmenu = null">
- <!-- Default actions list-->
- <NcActionButton v-for="action in enabledMenuActions"
+ :open="openedMenu"
+ @close="onMenuClose"
+ @closed="onMenuClosed">
+ <!-- Non-destructive actions list -->
+ <!-- Please keep this block in sync with the destructive actions block below -->
+ <NcActionButton v-for="action, index in renderedNonDestructiveActions"
:key="action.id"
:ref="`action-${action.id}`"
+ class="files-list__row-action"
:class="{
[`files-list__row-action-${action.id}`]: true,
- [`files-list__row-action--menu`]: isMenu(action.id)
+ 'files-list__row-action--inline': index < enabledInlineActions.length,
+ 'files-list__row-action--menu': isValidMenu(action),
}"
- :close-after-click="!isMenu(action.id)"
+ :close-after-click="!isValidMenu(action)"
:data-cy-files-list-row-action="action.id"
- :is-menu="isMenu(action.id)"
+ :is-menu="isValidMenu(action)"
:aria-label="action.title?.([source], currentView)"
:title="action.title?.([source], currentView)"
@click="onActionClick(action)">
<template #icon>
- <NcLoadingIcon v-if="isLoadingAction(action)" :size="18" />
- <NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" />
+ <NcLoadingIcon v-if="isLoadingAction(action)" />
+ <NcIconSvgWrapper v-else
+ class="files-list__row-action-icon"
+ :svg="action.iconSvgInline([source], currentView)" />
</template>
- {{ mountType === 'shared' && action.id === 'sharing-status' ? '' : actionDisplayName(action) }}
+ {{ actionDisplayName(action) }}
</NcActionButton>
+ <!-- Destructive actions list -->
+ <template v-if="renderedDestructiveActions.length > 0">
+ <NcActionSeparator />
+ <NcActionButton v-for="action, index in renderedDestructiveActions"
+ :key="action.id"
+ :ref="`action-${action.id}`"
+ class="files-list__row-action"
+ :class="{
+ [`files-list__row-action-${action.id}`]: true,
+ 'files-list__row-action--inline': index < enabledInlineActions.length,
+ 'files-list__row-action--menu': isValidMenu(action),
+ 'files-list__row-action--destructive': true,
+ }"
+ :close-after-click="!isValidMenu(action)"
+ :data-cy-files-list-row-action="action.id"
+ :is-menu="isValidMenu(action)"
+ :aria-label="action.title?.([source], currentView)"
+ :title="action.title?.([source], currentView)"
+ @click="onActionClick(action)">
+ <template #icon>
+ <NcLoadingIcon v-if="isLoadingAction(action)" />
+ <NcIconSvgWrapper v-else
+ class="files-list__row-action-icon"
+ :svg="action.iconSvgInline([source], currentView)" />
+ </template>
+ {{ actionDisplayName(action) }}
+ </NcActionButton>
+ </template>
+
<!-- Submenu actions list-->
<template v-if="openedSubmenu && enabledSubmenuActions[openedSubmenu?.id]">
<!-- Back to top-level button -->
- <NcActionButton class="files-list__row-action-back" @click="onBackToMenuClick(openedSubmenu)">
+ <NcActionButton class="files-list__row-action-back" data-cy-files-list-row-action="menu-back" @click="onBackToMenuClick(openedSubmenu)">
<template #icon>
<ArrowLeftIcon />
</template>
@@ -63,10 +98,11 @@
class="files-list__row-action--submenu"
close-after-click
:data-cy-files-list-row-action="action.id"
+ :aria-label="action.title?.([source], currentView)"
:title="action.title?.([source], currentView)"
@click="onActionClick(action)">
<template #icon>
- <NcLoadingIcon v-if="isLoadingAction(action)" :size="18" />
+ <NcLoadingIcon v-if="isLoadingAction(action)" />
<NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" />
</template>
{{ actionDisplayName(action) }}
@@ -82,22 +118,23 @@ import type { FileAction, Node } from '@nextcloud/files'
import { DefaultType, NodeStatus } from '@nextcloud/files'
import { defineComponent, inject } from 'vue'
-import { translate as t } from '@nextcloud/l10n'
+import { t } from '@nextcloud/l10n'
+import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
-import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import CustomElementRender from '../CustomElementRender.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'
+import NcActionButton from '@nextcloud/vue/components/NcActionButton'
+import NcActions from '@nextcloud/vue/components/NcActions'
+import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
+import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
+import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import { executeAction } from '../../utils/actionUtils.ts'
import { useActiveStore } from '../../store/active.ts'
import { useFileListWidth } from '../../composables/useFileListWidth.ts'
import { useNavigation } from '../../composables/useNavigation'
import { useRouteParameters } from '../../composables/useRouteParameters.ts'
+import actionsMixins from '../../mixins/actionsMixin.ts'
import logger from '../../logger.ts'
export default defineComponent({
@@ -113,6 +150,8 @@ export default defineComponent({
NcLoadingIcon,
},
+ mixins: [actionsMixins],
+
props: {
opened: {
type: Boolean,
@@ -146,12 +185,6 @@ export default defineComponent({
}
},
- data() {
- return {
- openedSubmenu: null as FileAction | null,
- }
- },
-
computed: {
isActive() {
return this.activeStore?.activeNode?.source === this.source.source
@@ -209,16 +242,12 @@ export default defineComponent({
return actions.filter(action => !(action.parent && topActionsIds.includes(action.parent)))
},
- enabledSubmenuActions() {
- return this.enabledFileActions
- .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[]>)
+ renderedNonDestructiveActions() {
+ return this.enabledMenuActions.filter(action => !action.destructive)
+ },
+
+ renderedDestructiveActions() {
+ return this.enabledMenuActions.filter(action => action.destructive)
},
openedMenu: {
@@ -238,14 +267,10 @@ export default defineComponent({
getBoundariesElement() {
return document.querySelector('.app-content > .files-list')
},
-
- mountType() {
- return this.source.attributes['mount-type']
- },
},
watch: {
- // Close any submenu when the menu is closed
+ // Close any submenu when the menu state changes
openedMenu() {
this.openedSubmenu = null
},
@@ -287,7 +312,7 @@ export default defineComponent({
return this.activeStore?.activeAction?.id === action.id
},
- async onActionClick(action, isSubmenu = false) {
+ async onActionClick(action) {
// If the action is a submenu, we open it
if (this.enabledSubmenuActions[action.id]) {
this.openedSubmenu = action
@@ -295,34 +320,10 @@ export default defineComponent({
}
// Make sure we set the node as active
- this.activeStore.setActiveNode(this.source)
+ this.activeStore.activeNode = this.source
// Execute the action
await executeAction(action)
-
- // If that was a submenu, we just go back after the action
- if (isSubmenu) {
- this.openedSubmenu = null
- }
- },
-
- isMenu(id: string) {
- return this.enabledSubmenuActions[id]?.length > 0
- },
-
- async onBackToMenuClick(action: FileAction) {
- this.openedSubmenu = null
- // Wait for first render
- await this.$nextTick()
-
- // Focus the previous menu action button
- this.$nextTick(() => {
- // Focus the action button
- const menuAction = this.$refs[`action-${action.id}`]?.[0]
- if (menuAction) {
- menuAction.$el.querySelector('button')?.focus()
- }
- })
},
onKeyDown(event: KeyboardEvent) {
@@ -341,6 +342,16 @@ export default defineComponent({
this.openedMenu = true
}
},
+
+ onMenuClose() {
+ // We reset the submenu state when the menu is closing
+ this.openedSubmenu = null
+ },
+
+ onMenuClosed() {
+ // We reset the actions menu state when the menu is finally closed
+ this.openedMenu = false
+ },
},
})
</script>
@@ -363,13 +374,26 @@ main.app-content[style*="mouse-pos-x"] .v-popper__popper {
}
</style>
-<style lang="scss" scoped>
-:deep(.button-vue--icon-and-text, .files-list__row-action-sharing-status) {
- .button-vue__text {
- color: var(--color-primary-element);
+<style scoped lang="scss">
+.files-list__row-action {
+ --max-icon-size: calc(var(--default-clickable-area) - 2 * var(--default-grid-baseline));
+
+ // inline icons can have clickable area size so they still fit into the row
+ &.files-list__row-action--inline {
+ --max-icon-size: var(--default-clickable-area);
}
- .button-vue__icon {
- color: var(--color-primary-element);
+
+ // Some icons exceed the default size so we need to enforce a max width and height
+ .files-list__row-action-icon :deep(svg) {
+ max-height: var(--max-icon-size) !important;
+ max-width: var(--max-icon-size) !important;
+ }
+
+ &.files-list__row-action--destructive {
+ ::deep(button) {
+ color: var(--color-error) !important;
+ }
}
}
+
</style>