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

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

Draft
wants to merge 2 commits into
base: stable30
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>
4 changes: 2 additions & 2 deletions apps/files/src/components/FilesListVirtual.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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,7 +63,7 @@
import type { ComponentPublicInstance, PropType } from 'vue'
import type { Location } from 'vue-router'

import { getFileListHeaders, Folder, 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 { translate as t } from '@nextcloud/l10n'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
Expand All @@ -71,9 +71,10 @@

import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import { getSummaryFor } from '../utils/fileUtils'

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

View workflow job for this annotation

GitHub Actions / NPM lint

'/home/runner/actions-runner/_work/server/server/apps/files/src/utils/fileUtils.ts' imported multiple times
import { useSelectionStore } from '../store/selection.js'
import { useUserConfigStore } from '../store/userconfig.ts'
import { getSummaryFor } from '../utils/fileUtils.ts'

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

View workflow job for this annotation

GitHub Actions / NPM lint

'getSummaryFor' is already defined

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

View workflow job for this annotation

GitHub Actions / NPM lint

'/home/runner/actions-runner/_work/server/server/apps/files/src/utils/fileUtils.ts' imported multiple times

import FileEntry from './FileEntry.vue'
import FileEntryGrid from './FileEntryGrid.vue'
Expand Down Expand Up @@ -135,7 +136,6 @@
return {
FileEntry,
FileEntryGrid,
headers: getFileListHeaders(),
scrollToIndex: 0,
openFileId: null as number|null,
}
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'

Check failure on line 7 in apps/files/src/composables/useFileListHeaders.spec.ts

View workflow job for this annotation

GitHub Actions / NPM lint

Unable to resolve path to module '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
}
23 changes: 20 additions & 3 deletions apps/files/src/views/FilesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,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 @@ -74,7 +74,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 @@ -170,13 +178,14 @@ 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 filesListWidthMixin from '../mixins/filesListWidth.ts'
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 @@ -186,6 +195,7 @@ export default defineComponent({
components: {
BreadCrumbs,
DragAndDropNotice,
FilesListHeader,
FilesListVirtual,
LinkIcon,
ListViewIcon,
Expand Down Expand Up @@ -746,6 +756,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 @@ -23,6 +23,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