Skip to content

Commit

Permalink
fix(files): Provide file actions from list entry to make it reactive
Browse files Browse the repository at this point in the history
This fixes non reactive default action text of the name component.
Also use download action as default action so that only one place
is needed to define how to download a file.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed Jul 25, 2024
1 parent 6e558a6 commit 36abcc7
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 52 deletions.
4 changes: 3 additions & 1 deletion apps/files/src/actions/downloadAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { generateUrl } from '@nextcloud/router'
import { FileAction, Permission, Node, FileType, View } from '@nextcloud/files'
import { FileAction, Permission, Node, FileType, View, DefaultType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'

Expand Down Expand Up @@ -43,6 +43,8 @@ const isDownloadable = function(node: Node) {

export const action = new FileAction({
id: 'download',
default: DefaultType.DEFAULT,

displayName: () => t('files', 'Download'),
iconSvgInline: () => ArrowDownSvg,

Expand Down
39 changes: 12 additions & 27 deletions apps/files/src/components/FileEntry/FileEntryActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@
import type { PropType, ShallowRef } from 'vue'
import type { FileAction, Node, View } from '@nextcloud/files'

import { DefaultType, NodeStatus, getFileActions } from '@nextcloud/files'
import { DefaultType, NodeStatus } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import { defineComponent } from 'vue'
import { defineComponent, inject } from 'vue'

import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
Expand All @@ -95,9 +95,6 @@ import CustomElementRender from '../CustomElementRender.vue'
import { useNavigation } from '../../composables/useNavigation'
import logger from '../../logger.js'

// The registered actions list
const actions = getFileActions()

export default defineComponent({
name: 'FileEntryActions',

Expand Down Expand Up @@ -136,10 +133,14 @@ export default defineComponent({

setup() {
const { currentView } = useNavigation()
const defaultFileAction = inject<FileAction | undefined>('defaultFileAction')
const enabledFileActions = inject<FileAction[]>('enabledFileActions', [])

return {
// The file list is guaranteed to be only shown with active view
currentView: currentView as ShallowRef<View>,
defaultFileAction,
enabledFileActions,
}
},

Expand All @@ -158,36 +159,20 @@ export default defineComponent({
return this.source.status === NodeStatus.LOADING
},

// Sorted actions that are enabled for this node
enabledActions() {
if (this.source.status === NodeStatus.FAILED) {
return []
}

return actions
.filter(action => !action.enabled || action.enabled([this.source], this.currentView))
.sort((a, b) => (a.order || 0) - (b.order || 0))
},

// Enabled action that are displayed inline
enabledInlineActions() {
if (this.filesListWidth < 768 || this.gridMode) {
return []
}
return this.enabledActions.filter(action => action?.inline?.(this.source, this.currentView))
return this.enabledFileActions.filter(action => action?.inline?.(this.source, this.currentView))
},

// Enabled action that are displayed inline with a custom render function
enabledRenderActions() {
if (this.gridMode) {
return []
}
return this.enabledActions.filter(action => typeof action.renderInline === 'function')
},

// Default actions
enabledDefaultActions() {
return this.enabledActions.filter(action => !!action?.default)
return this.enabledFileActions.filter(action => typeof action.renderInline === 'function')
},

// Actions shown in the menu
Expand All @@ -202,7 +187,7 @@ export default defineComponent({
// Showing inline first for the NcActions inline prop
...this.enabledInlineActions,
// Then the rest
...this.enabledActions.filter(action => action.default !== DefaultType.HIDDEN && typeof action.renderInline !== 'function'),
...this.enabledFileActions.filter(action => action.default !== DefaultType.HIDDEN && typeof action.renderInline !== 'function'),
].filter((value, index, self) => {
// Then we filter duplicates to prevent inline actions to be shown twice
return index === self.findIndex(action => action.id === value.id)
Expand All @@ -216,7 +201,7 @@ export default defineComponent({
},

enabledSubmenuActions() {
return this.enabledActions
return this.enabledFileActions
.filter(action => action.parent)
.reduce((arr, action) => {
if (!arr[action.parent!]) {
Expand Down Expand Up @@ -306,11 +291,11 @@ export default defineComponent({
}
},
execDefaultAction(event) {
if (this.enabledDefaultActions.length > 0) {
if (this.defaultFileAction) {
event.preventDefault()
event.stopPropagation()
// Execute the first default action if any
this.enabledDefaultActions[0].exec(this.source, this.currentView, this.currentDir)
this.defaultFileAction.exec(this.source, this.currentView, this.currentDir)
}
},

Expand Down
35 changes: 13 additions & 22 deletions apps/files/src/components/FileEntry/FileEntryName.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@
</template>

<script lang="ts">
import type { Node } from '@nextcloud/files'
import type { FileAction, Node } from '@nextcloud/files'
import type { PropType } from 'vue'

import axios, { isAxiosError } from '@nextcloud/axios'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileType, NodeStatus, Permission } from '@nextcloud/files'
import { FileType, NodeStatus } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { defineComponent } from 'vue'
import { defineComponent, inject } from 'vue'

import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'

Expand Down Expand Up @@ -98,8 +98,11 @@ export default defineComponent({
const { currentView } = useNavigation()
const renamingStore = useRenamingStore()

const defaultFileAction = inject<FileAction | undefined>('defaultFileAction')

return {
currentView,
defaultFileAction,

renamingStore,
}
Expand Down Expand Up @@ -139,10 +142,8 @@ export default defineComponent({
}
}

const enabledDefaultActions = this.$parent?.$refs?.actions?.enabledDefaultActions
if (enabledDefaultActions?.length > 0) {
const action = enabledDefaultActions[0]
const displayName = action.displayName([this.source], this.currentView)
if (this.defaultFileAction && this.currentView) {
const displayName = this.defaultFileAction.displayName([this.source], this.currentView)
return {
is: 'a',
params: {
Expand All @@ -153,18 +154,8 @@ export default defineComponent({
}
}

if (this.source?.permissions & Permission.READ) {
return {
is: 'a',
params: {
download: this.source.basename,
href: this.source.source,
title: t('files', 'Download file {name}', { name: `${this.basename}${this.extension}` }),
tabindex: '0',
},
}
}

// nothing interactive here, there is no default action
// so if not even the download action works we only can show the list entry
return {
is: 'span',
}
Expand Down Expand Up @@ -280,12 +271,12 @@ export default defineComponent({
// Reset the renaming store
this.stopRenaming()
this.$nextTick(() => {
this.$refs.basename?.focus()
const nameContainter = this.$refs.basename as HTMLElement | undefined
nameContainter?.focus()
})
} catch (error) {
logger.error('Error while renaming file', { error })
this.source.rename(oldName)
this.$refs.renameInput?.focus()
this.startRenaming()

if (isAxiosError(error)) {
// TODO: 409 means current folder does not exist, redirect ?
Expand Down
28 changes: 27 additions & 1 deletion apps/files/src/components/FileEntryMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ComponentPublicInstance, PropType } from 'vue'
import type { FileSource } from '../types.ts'

import { showError } from '@nextcloud/dialogs'
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node } from '@nextcloud/files'
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node, getFileActions } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { vOnClickOutside } from '@vueuse/components'
Expand All @@ -23,6 +23,8 @@ import FileEntryActions from '../components/FileEntry/FileEntryActions.vue'

Vue.directive('onClickOutside', vOnClickOutside)

const actions = getFileActions()

export default defineComponent({
props: {
source: {
Expand All @@ -47,6 +49,13 @@ export default defineComponent({
},
},

provide() {
return {
defaultFileAction: this.defaultFileAction,
enabledFileActions: this.enabledFileActions,
}
},

data() {
return {
loading: '',
Expand Down Expand Up @@ -178,6 +187,23 @@ export default defineComponent({
color: `color-mix(in srgb, var(--color-main-text) ${ratio}%, var(--color-text-maxcontrast))`,
}
},

/**
* Sorted actions that are enabled for this node
*/
enabledFileActions() {
if (this.source.status === NodeStatus.FAILED) {
return []
}

return actions
.filter(action => !action.enabled || action.enabled([this.source], this.currentView))
.sort((a, b) => (a.order || 0) - (b.order || 0))
},

defaultFileAction() {
return this.enabledFileActions.find((action) => !!action.default)
},
},

watch: {
Expand Down
5 changes: 4 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Configuration } from 'webpack'
import {
applyChangesToNextcloud,
configureNextcloud,
Expand All @@ -11,8 +12,8 @@ import {
} from './cypress/dockerNode'
import { defineConfig } from 'cypress'
import cypressSplit from 'cypress-split'
import { removeDirectory } from 'cypress-delete-downloads-folder'
import webpackPreprocessor from '@cypress/webpack-preprocessor'
import type { Configuration } from 'webpack'

import webpackConfig from './webpack.config.js'

Expand Down Expand Up @@ -63,6 +64,8 @@ export default defineConfig({

on('file:preprocessor', webpackPreprocessor({ webpackOptions: webpackConfig as Configuration }))

on('task', { removeDirectory })

// Disable spell checking to prevent rendering differences
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium' && browser.name !== 'electron') {
Expand Down
Loading

0 comments on commit 36abcc7

Please sign in to comment.