diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2025-06-27 16:12:10 +0200 |
---|---|---|
committer | skjnldsv <skjnldsv@protonmail.com> | 2025-06-27 16:12:10 +0200 |
commit | 331e61d5b9b28f4d0da568695962b8db6bfa884c (patch) | |
tree | ce8bafbf6298f3398f11f0b6f85c17904f8babd2 | |
parent | acd9b89861eabf53ce10154edefc480423810bd2 (diff) | |
download | nextcloud-server-feat/files-home-view.tar.gz nextcloud-server-feat/files-home-view.zip |
fixup! feat(files): add Home viewfeat/files-home-view
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
-rw-r--r-- | apps/files/src/actions/openInFilesAction.ts | 5 | ||||
-rw-r--r-- | apps/files/src/services/RecommendedFiles.ts | 92 | ||||
-rw-r--r-- | apps/files/src/views/FilesHeaderHomeSearch.vue | 4 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 7 | ||||
-rw-r--r-- | apps/files/src/views/home.ts | 19 |
5 files changed, 68 insertions, 59 deletions
diff --git a/apps/files/src/actions/openInFilesAction.ts b/apps/files/src/actions/openInFilesAction.ts index 10e19e7eace..b478b99b60d 100644 --- a/apps/files/src/actions/openInFilesAction.ts +++ b/apps/files/src/actions/openInFilesAction.ts @@ -2,8 +2,9 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ +import type { Node, View } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' -import { type Node, FileType, FileAction, DefaultType } from '@nextcloud/files' +import { DefaultType, FileAction, FileType } from '@nextcloud/files' /** * TODO: Move away from a redirect and handle @@ -14,7 +15,7 @@ export const action = new FileAction({ displayName: () => t('files', 'Open in Files'), iconSvgInline: () => '', - enabled: (nodes, view) => view.id === 'recent', + enabled: (nodes, view: View) => ['home', 'recent'].includes(view.id), async exec(node: Node) { let dir = node.dirname diff --git a/apps/files/src/services/RecommendedFiles.ts b/apps/files/src/services/RecommendedFiles.ts index 6c81d83bc58..f76bdda8200 100644 --- a/apps/files/src/services/RecommendedFiles.ts +++ b/apps/files/src/services/RecommendedFiles.ts @@ -3,75 +3,63 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import type { ContentsWithRoot } from '@nextcloud/files' +import type { FileStat, ResponseDataDetailed } from 'webdav' import { CancelablePromise } from 'cancelable-promise' -import { File, Folder, Permission, } from '@nextcloud/files' -import { generateOcsUrl } from '@nextcloud/router' +import { File, Folder, Permission } from '@nextcloud/files' import { getCurrentUser } from '@nextcloud/auth' -import { getRemoteURL, getRootPath } from '@nextcloud/files/dav' -import axios from '@nextcloud/axios' +import { getDefaultPropfind, getRemoteURL, registerDavProperty, resultToNode } from '@nextcloud/files/dav' +import { client } from './WebdavClient' +import logger from '../logger' +import { getCapabilities } from '@nextcloud/capabilities' +import { getContents as getRecentContents } from './Recent' -import { getContents as getDefaultContents } from './Files' - -type RecommendedFiles = { - 'id': string - 'timestamp': number - 'name': string - 'directory': string - 'extension': string - 'mimeType': string - 'hasPreview': boolean - 'reason': string -} - -type RecommendedFilesResponse = { - 'recommendations': RecommendedFiles[] +// Check if the recommendations capability is enabled +// If not, we'll just use recent files +const isRecommendationEnabled = getCapabilities()?.recommendations?.enabled === true +if (isRecommendationEnabled) { + registerDavProperty('nc:recommendation-reason', { nc: 'http://nextcloud.org/ns' }) + registerDavProperty('nc:recommendation-reason-label', { nc: 'http://nextcloud.org/ns' }) } -const fetchRecommendedFiles = (controller: AbortController): Promise<RecommendedFilesResponse> => { - const url = generateOcsUrl('apps/recommendations/api/v1/recommendations/always') - - return axios.get(url, { - signal: controller.signal, - headers: { - 'OCS-APIRequest': 'true', - 'Content-Type': 'application/json', - }, - }).then(resp => resp.data.ocs.data as RecommendedFilesResponse) -} - -export const getContents = (path = '/'): CancelablePromise<ContentsWithRoot> => { - if (path !== '/') { - return getDefaultContents(path) +export const getContents = (): CancelablePromise<ContentsWithRoot> => { + if (!isRecommendationEnabled) { + logger.debug('Recommendations capability is not enabled, falling back to recent files') + return getRecentContents() } const controller = new AbortController() - return new CancelablePromise(async (resolve, reject, cancel) => { - cancel(() => controller.abort()) + const propfindPayload = getDefaultPropfind() + + return new CancelablePromise(async (resolve, reject, onCancel) => { + onCancel(() => controller.abort()) + + const root = `/recommendations/${getCurrentUser()?.uid}` try { - const { recommendations } = await fetchRecommendedFiles(controller) + const contentsResponse = await client.getDirectoryContents(root, { + details: true, + data: propfindPayload, + includeSelf: false, + signal: controller.signal, + }) as ResponseDataDetailed<FileStat[]> + const contents = contentsResponse.data resolve({ folder: new Folder({ id: 0, - source: `${getRemoteURL()}${getRootPath()}`, - root: getRootPath(), + source: `${getRemoteURL()}${root}`, + root, owner: getCurrentUser()?.uid || null, permissions: Permission.READ, }), - contents: recommendations.map((rec) => { - const Node = rec.mimeType === 'httpd/unix-directory' ? Folder : File - return new Node({ - id: parseInt(rec.id), - source: `${getRemoteURL()}/${getRootPath()}/${rec.directory}/${rec.name}`.replace(/\/\//g, '/'), - root: getRootPath(), - mime: rec.mimeType, - mtime: new Date(rec.timestamp * 1000), - owner: getCurrentUser()?.uid || null, - permissions: Permission.READ, - attributes: rec, - }) - }), + contents: contents.map((result) => { + try { + return resultToNode(result, root) + } catch (error) { + logger.error(`Invalid node detected '${result.basename}'`, { error }) + return null + } + }).filter(Boolean) as File[], }) } catch (error) { reject(error) diff --git a/apps/files/src/views/FilesHeaderHomeSearch.vue b/apps/files/src/views/FilesHeaderHomeSearch.vue index 39bd9ce1dbb..431a6f7b4e9 100644 --- a/apps/files/src/views/FilesHeaderHomeSearch.vue +++ b/apps/files/src/views/FilesHeaderHomeSearch.vue @@ -56,7 +56,7 @@ export default defineComponent({ <style lang="scss"> // Align everything in the middle .files-list__header-home-search-wrapper, -.files-list__filters { +.files-content__home .files-list__filters { display: flex !important; max-width: var(--breakpoint-mobile) !important; height: auto !important; @@ -71,7 +71,7 @@ export default defineComponent({ } // Align the filters with the search input for the Home view -.files-list__filters { +.files-content__home .files-list__filters { padding-block: calc(var(--default-grid-baseline, 4px) * 2) !important; } </style> diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 60791a2b527..a7311f3c546 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -3,7 +3,9 @@ - SPDX-License-Identifier: AGPL-3.0-or-later --> <template> - <NcAppContent :page-heading="pageHeading" data-cy-files-content> + <NcAppContent :class="['files-content', `files-content__${currentView?.id}`]" + :page-heading="pageHeading" + data-cy-files-content> <div class="files-list__header" :class="{ 'files-list__header--public': isPublic }"> <!-- Current folder breadcrumbs --> <BreadCrumbs :path="directory" @reload="fetchContent"> @@ -89,6 +91,7 @@ :current-view="currentView" :header="header" /> </div> + <!-- Empty due to error --> <NcEmptyContent v-if="error" :name="error" data-cy-files-content-error> <template #action> @@ -103,10 +106,12 @@ <IconAlertCircleOutline /> </template> </NcEmptyContent> + <!-- Custom empty view --> <div v-else-if="currentView?.emptyView" class="files-list__empty-view-wrapper"> <div ref="customEmptyView" /> </div> + <!-- Default empty directory view --> <NcEmptyContent v-else :name="currentView?.emptyTitle || t('files', 'No files in here')" diff --git a/apps/files/src/views/home.ts b/apps/files/src/views/home.ts index ddc70561f72..82de46609c7 100644 --- a/apps/files/src/views/home.ts +++ b/apps/files/src/views/home.ts @@ -2,12 +2,12 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { ComponentPublicInstance, VueConstructor } from 'vue' +import type { VueConstructor } from 'vue' import { translate as t } from '@nextcloud/l10n' import HomeSvg from '@mdi/svg/svg/home.svg?raw' import { getContents } from '../services/RecommendedFiles' -import { Folder, getNavigation, Header, registerFileListHeaders, View } from '@nextcloud/files' +import { Column, Folder, getNavigation, Header, registerFileListHeaders, View } from '@nextcloud/files' import Vue from 'vue' export const registerHomeView = () => { @@ -19,7 +19,22 @@ export const registerHomeView = () => { icon: HomeSvg, order: -50, + defaultSortKey: 'mtime', + getContents, + + columns: [ + new Column({ + id: 'recommendation-reason', + title: t('files', 'Reason'), + render(node) { + let reason = node.attributes?.['recommendation-reason-label'] || t('files', 'Suggestion') + const span = document.createElement('span') + span.textContent = reason + return span + }, + }), + ], })) let FilesHeaderHomeSearch: VueConstructor |