Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable31] fix(files): also show file list headers on empty views #51275

Draft
wants to merge 2 commits into
base: stable31
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions apps/files/src/components/FilesListHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
</template>

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

/**
* This component is used to render custom
* elements provided by an API. Vue doesn't allow
Expand All @@ -19,21 +22,21 @@ export default {
name: 'FilesListHeader',
props: {
header: {
type: Object,
type: Object as PropType<Header>,
required: true,
},
currentFolder: {
type: Object,
type: Object as PropType<Folder>,
required: true,
},
currentView: {
type: Object,
type: Object as PropType<View>,
required: true,
},
},
computed: {
enabled() {
return this.header.enabled(this.currentFolder, this.currentView)
return this.header.enabled?.(this.currentFolder, this.currentView) ?? true
},
},
watch: {
Expand All @@ -49,7 +52,7 @@ export default {
},
mounted() {
console.debug('Mounted', this.header.id)
this.header.render(this.$refs.mount, this.currentFolder, this.currentView)
this.header.render(this.$refs.mount as HTMLElement, this.currentFolder, this.currentView)
},
}
</script>
19 changes: 6 additions & 13 deletions apps/files/src/components/FilesListVirtual.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

<template #before>
<!-- Headers -->
<FilesListHeader v-for="header in sortedHeaders"
<FilesListHeader v-for="header in headers"
:key="header.id"
:current-folder="currentFolder"
:current-view="currentView"
Expand Down Expand Up @@ -63,19 +63,20 @@
import type { Location } from 'vue-router'

import { defineComponent } from 'vue'
import { getFileListHeaders, Folder, Permission, View, getFileActions, FileType } from '@nextcloud/files'

Check failure on line 66 in apps/files/src/components/FilesListVirtual.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'getFileListHeaders' is defined but never used
import { showError } from '@nextcloud/dialogs'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { translate as t } from '@nextcloud/l10n'
import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js'

import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { getSummaryFor } from '../utils/fileUtils'
import { useActiveStore } from '../store/active.ts'
import { useFileListHeaders } from '../composables/useFileListHeaders.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import { useActiveStore } from '../store/active.ts'
import { useSelectionStore } from '../store/selection.js'
import { useUserConfigStore } from '../store/userconfig.ts'
import { getSummaryFor } from '../utils/fileUtils.ts'

import FileEntry from './FileEntry.vue'
import FileEntryGrid from './FileEntryGrid.vue'
Expand All @@ -84,8 +85,8 @@
import FilesListTableFooter from './FilesListTableFooter.vue'
import FilesListTableHeader from './FilesListTableHeader.vue'
import FilesListTableHeaderActions from './FilesListTableHeaderActions.vue'
import logger from '../logger.ts'
import VirtualList from './VirtualList.vue'
import logger from '../logger.ts'

export default defineComponent({
name: 'FilesListVirtual',
Expand Down Expand Up @@ -125,6 +126,7 @@
return {
fileId,
fileListWidth,
headers: useFileListHeaders(),
openDetails,
openFile,

Expand All @@ -140,7 +142,6 @@
return {
FileEntry,
FileEntryGrid,
headers: getFileListHeaders(),
scrollToIndex: 0,
openFileId: null as number|null,
}
Expand Down Expand Up @@ -170,14 +171,6 @@
return this.nodes.some(node => node.size !== undefined)
},

sortedHeaders() {
if (!this.currentFolder || !this.currentView) {
return []
}

return [...this.headers].sort((a, b) => a.order - b.order)
},

cantUpload() {
return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) === 0
},
Expand Down
41 changes: 41 additions & 0 deletions apps/files/src/composables/useFileListHeaders.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { Header } from '@nextcloud/files'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useFileListHeaders } from './useFileListHeaders.ts'

const getFileListHeaders = vi.hoisted(() => vi.fn())

vi.mock('@nextcloud/files', async (originalModule) => {
return {
...(await originalModule()),
getFileListHeaders,
}
})

describe('useFileListHeaders', () => {
beforeEach(() => vi.resetAllMocks())

it('gets the headers', () => {
const header = new Header({ id: '1', order: 5, render: vi.fn(), updated: vi.fn() })
getFileListHeaders.mockImplementationOnce(() => [header])

const headers = useFileListHeaders()
expect(headers.value).toEqual([header])
expect(getFileListHeaders).toHaveBeenCalledOnce()
})

it('headers are sorted', () => {
const header = new Header({ id: '1', order: 10, render: vi.fn(), updated: vi.fn() })
const header2 = new Header({ id: '2', order: 5, render: vi.fn(), updated: vi.fn() })
getFileListHeaders.mockImplementationOnce(() => [header, header2])

const headers = useFileListHeaders()
// lower order first
expect(headers.value.map(({ id }) => id)).toStrictEqual(['2', '1'])
expect(getFileListHeaders).toHaveBeenCalledOnce()
})
})
19 changes: 19 additions & 0 deletions apps/files/src/composables/useFileListHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Header } from '@nextcloud/files'
import type { ComputedRef } from 'vue'

import { getFileListHeaders } from '@nextcloud/files'
import { computed, ref } from 'vue'

/**
* Get the registered and sorted file list headers.
*/
export function useFileListHeaders(): ComputedRef<Header[]> {
const headers = ref(getFileListHeaders())
const sorted = computed(() => [...headers.value].sort((a, b) => a.order - b.order) as Header[])

return sorted
}
25 changes: 22 additions & 3 deletions apps/files/src/views/FilesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
</div>

<!-- Drag and drop notice -->
<DragAndDropNotice v-if="!loading && canUpload" :current-folder="currentFolder" />
<DragAndDropNotice v-if="!loading && canUpload && currentFolder" :current-folder="currentFolder" />

<!-- Initial loading -->
<NcLoadingIcon v-if="loading && !isRefreshing"
Expand All @@ -80,7 +80,15 @@
:name="t('files', 'Loading current folder')" />

<!-- Empty content placeholder -->
<template v-else-if="!loading && isEmptyDir">
<template v-else-if="!loading && isEmptyDir && currentFolder && currentView">
<div class="files-list__before">
<!-- Headers -->
<FilesListHeader v-for="header in headers"
:key="header.id"
:current-folder="currentFolder"
:current-view="currentView"
:header="header" />
</div>
<!-- Empty due to error -->
<NcEmptyContent v-if="error" :name="error" data-cy-files-content-error>
<template #action>
Expand Down Expand Up @@ -169,6 +177,7 @@ import ViewGridIcon from 'vue-material-design-icons/ViewGrid.vue'

import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { useNavigation } from '../composables/useNavigation.ts'
import { useFileListHeaders } from '../composables/useFileListHeaders.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import { useFilesStore } from '../store/files.ts'
Expand All @@ -178,12 +187,13 @@ import { useSelectionStore } from '../store/selection.ts'
import { useUploaderStore } from '../store/uploader.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
import { useViewConfigStore } from '../store/viewConfig.ts'
import { humanizeWebDAVError } from '../utils/davUtils.ts'
import BreadCrumbs from '../components/BreadCrumbs.vue'
import FilesListHeader from '../components/FilesListHeader.vue'
import FilesListVirtual from '../components/FilesListVirtual.vue'
import filesSortingMixin from '../mixins/filesSorting.ts'
import logger from '../logger.ts'
import DragAndDropNotice from '../components/DragAndDropNotice.vue'
import { humanizeWebDAVError } from '../utils/davUtils.ts'

const isSharingEnabled = (getCapabilities() as { files_sharing?: boolean })?.files_sharing !== undefined

Expand All @@ -193,6 +203,7 @@ export default defineComponent({
components: {
BreadCrumbs,
DragAndDropNotice,
FilesListHeader,
FilesListVirtual,
LinkIcon,
ListViewIcon,
Expand Down Expand Up @@ -241,6 +252,7 @@ export default defineComponent({
directory,
fileId,
fileListWidth,
headers: useFileListHeaders(),
t,

filesStore,
Expand Down Expand Up @@ -814,6 +826,13 @@ export default defineComponent({
}
}

&__before {
display: flex;
flex-direction: column;
gap: calc(var(--default-grid-baseline) * 2);
margin-inline: calc(var(--default-clickable-area) + 2 * var(--app-navigation-padding));
}

&__empty-view-wrapper {
display: flex;
height: 100%;
Expand Down
6 changes: 6 additions & 0 deletions cypress/e2e/files/FilesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export const searchForActionInRow = (row: JQuery<HTMLElement>, actionId: string)

// Else look in the action menu
const menuButtonId = row.find('button[aria-controls]').attr('aria-controls')
if (menuButtonId === undefined) {
return cy.wrap(Cypress.$())
}

// eslint-disable-next-line no-unused-expressions
expect(menuButtonId).not.to.be.undefined
return cy.get(`#${menuButtonId} [data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
}

Expand Down
20 changes: 20 additions & 0 deletions cypress/e2e/files_sharing/note-to-recipient.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ describe('files_sharing: Note to recipient', { testIsolation: true }, () => {
.and('contain.text', 'Hello, this is the note.')
})

it('displays the note to the sharee even if the file list is empty', () => {
cy.mkdir(user, '/folder')
cy.login(user)
cy.visit('/apps/files')

// share the folder
createShare('folder', sharee.userId, { read: true, download: true, note: 'Hello, this is the note.' })

cy.logout()
// Now for the sharee
cy.login(sharee)

// visit shared files view
cy.visit('/apps/files')
navigateToFolder('folder')
cy.get('.note-to-recipient')
.should('be.visible')
.and('contain.text', 'Hello, this is the note.')
})

/**
* Regression test for https://github.com/nextcloud/server/issues/46188
*/
Expand Down
Loading