aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/components/FileEntry
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/components/FileEntry')
-rw-r--r--apps/files/src/components/FileEntry/FavoriteIcon.vue6
-rw-r--r--apps/files/src/components/FileEntry/FileEntryActions.vue168
-rw-r--r--apps/files/src/components/FileEntry/FileEntryCheckbox.vue6
-rw-r--r--apps/files/src/components/FileEntry/FileEntryName.vue26
-rw-r--r--apps/files/src/components/FileEntry/FileEntryPreview.vue14
5 files changed, 130 insertions, 90 deletions
diff --git a/apps/files/src/components/FileEntry/FavoriteIcon.vue b/apps/files/src/components/FileEntry/FavoriteIcon.vue
index 84f9fd828fc..c66cb8fbd7f 100644
--- a/apps/files/src/components/FileEntry/FavoriteIcon.vue
+++ b/apps/files/src/components/FileEntry/FavoriteIcon.vue
@@ -11,7 +11,7 @@ import { translate as t } from '@nextcloud/l10n'
import { defineComponent } from 'vue'
import StarSvg from '@mdi/svg/svg/star.svg?raw'
-import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
+import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
/**
* A favorite icon to be used for overlaying favorite entries like the file preview / icon
@@ -56,8 +56,8 @@ export default defineComponent({
:deep() {
svg {
// We added a stroke for a11y so we must increase the size to include the stroke
- width: 26px !important;
- height: 26px !important;
+ width: 20px !important;
+ height: 20px !important;
// Override NcIconSvgWrapper defaults of 20px
max-width: unset !important;
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>
diff --git a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
index 1b6112373f4..5b80a971118 100644
--- a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
+++ b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
@@ -21,11 +21,11 @@ import type { FileSource } from '../../types.ts'
import { FileType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
+import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { defineComponent } from 'vue'
-import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js'
-import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
-import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
+import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import { useActiveStore } from '../../store/active.ts'
import { useKeyboardStore } from '../../store/keyboard.ts'
diff --git a/apps/files/src/components/FileEntry/FileEntryName.vue b/apps/files/src/components/FileEntry/FileEntryName.vue
index 2a727b55c29..418f9581eb6 100644
--- a/apps/files/src/components/FileEntry/FileEntryName.vue
+++ b/apps/files/src/components/FileEntry/FileEntryName.vue
@@ -23,7 +23,6 @@
<component :is="linkTo.is"
v-else
ref="basename"
- :aria-hidden="isRenaming"
class="files-list__row-name-link"
data-cy-files-list-row-name-link
v-bind="linkTo.params">
@@ -31,7 +30,7 @@
<span class="files-list__row-name-text" dir="auto">
<!-- Keep the filename stuck to the extension to avoid whitespace rendering issues-->
<span class="files-list__row-name-" v-text="basename" />
- <span class="files-list__row-name-ext" v-text="extension" />
+ <span v-if="userConfigStore.userConfig.show_files_extensions" class="files-list__row-name-ext" v-text="extension" />
</span>
</component>
</template>
@@ -45,13 +44,14 @@ import { FileType, NodeStatus } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { defineComponent, inject } from 'vue'
-import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
+import NcTextField from '@nextcloud/vue/components/NcTextField'
-import { useNavigation } from '../../composables/useNavigation'
+import { getFilenameValidity } from '../../utils/filenameValidity.ts'
import { useFileListWidth } from '../../composables/useFileListWidth.ts'
-import { useRouteParameters } from '../../composables/useRouteParameters.ts'
+import { useNavigation } from '../../composables/useNavigation.ts'
import { useRenamingStore } from '../../store/renaming.ts'
-import { getFilenameValidity } from '../../utils/filenameValidity.ts'
+import { useRouteParameters } from '../../composables/useRouteParameters.ts'
+import { useUserConfigStore } from '../../store/userconfig.ts'
import logger from '../../logger.ts'
export default defineComponent({
@@ -96,6 +96,7 @@ export default defineComponent({
const { directory } = useRouteParameters()
const filesListWidth = useFileListWidth()
const renamingStore = useRenamingStore()
+ const userConfigStore = useUserConfigStore()
const defaultFileAction = inject<FileAction | undefined>('defaultFileAction')
@@ -106,6 +107,7 @@ export default defineComponent({
filesListWidth,
renamingStore,
+ userConfigStore,
}
},
@@ -117,11 +119,11 @@ export default defineComponent({
return this.isRenaming && this.filesListWidth < 512
},
newName: {
- get() {
- return this.renamingStore.newName
+ get(): string {
+ return this.renamingStore.newNodeName
},
- set(newName) {
- this.renamingStore.newName = newName
+ set(newName: string) {
+ this.renamingStore.newNodeName = newName
},
},
@@ -249,7 +251,9 @@ export default defineComponent({
try {
const status = await this.renamingStore.rename()
if (status) {
- showSuccess(t('files', 'Renamed "{oldName}" to "{newName}"', { oldName, newName }))
+ showSuccess(
+ t('files', 'Renamed "{oldName}" to "{newName}"', { oldName, newName: this.source.basename }),
+ )
this.$nextTick(() => {
const nameContainer = this.$refs.basename as HTMLElement | undefined
nameContainer?.focus()
diff --git a/apps/files/src/components/FileEntry/FileEntryPreview.vue b/apps/files/src/components/FileEntry/FileEntryPreview.vue
index 2d5844f851f..3d0fffe7584 100644
--- a/apps/files/src/components/FileEntry/FileEntryPreview.vue
+++ b/apps/files/src/components/FileEntry/FileEntryPreview.vue
@@ -21,6 +21,7 @@
class="files-list__row-icon-blurhash"
aria-hidden="true" />
<img v-if="backgroundFailed !== true"
+ :key="source.fileid"
ref="previewImg"
alt=""
class="files-list__row-icon-preview"
@@ -63,7 +64,7 @@ import FolderIcon from 'vue-material-design-icons/Folder.vue'
import FolderOpenIcon from 'vue-material-design-icons/FolderOpen.vue'
import KeyIcon from 'vue-material-design-icons/Key.vue'
import LinkIcon from 'vue-material-design-icons/Link.vue'
-import NetworkIcon from 'vue-material-design-icons/Network.vue'
+import NetworkIcon from 'vue-material-design-icons/NetworkOutline.vue'
import TagIcon from 'vue-material-design-icons/Tag.vue'
import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
@@ -147,6 +148,17 @@ export default defineComponent({
return null
}
+ if (this.source.attributes['has-preview'] !== true
+ && this.source.mime !== undefined
+ && this.source.mime !== 'application/octet-stream'
+ ) {
+ const previewUrl = generateUrl('/core/mimeicon?mime={mime}', {
+ mime: this.source.mime,
+ })
+ const url = new URL(window.location.origin + previewUrl)
+ return url.href
+ }
+
try {
const previewUrl = this.source.attributes.previewUrl
|| (this.isPublic