From 3fabc4f7336bbf93d65acc918e1300616091dd54 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Tue, 30 Jul 2024 18:19:55 -0700 Subject: [PATCH] feat: Navigate via folder tree Signed-off-by: Christopher Ng --- apps/files/src/components/BreadCrumbs.vue | 17 +- .../components/FileEntry/FileEntryName.vue | 5 + .../src/components/FilesNavigationItem.vue | 170 ++++++++++++++++++ apps/files/src/composables/useNavigation.ts | 4 +- apps/files/src/eventbus.d.ts | 5 + apps/files/src/init.ts | 2 + apps/files/src/services/FolderTree.ts | 90 ++++++++++ apps/files/src/views/Navigation.vue | 127 ++++--------- apps/files/src/views/folderTree.ts | 147 +++++++++++++++ 9 files changed, 469 insertions(+), 98 deletions(-) create mode 100644 apps/files/src/components/FilesNavigationItem.vue create mode 100644 apps/files/src/services/FolderTree.ts create mode 100644 apps/files/src/views/folderTree.ts diff --git a/apps/files/src/components/BreadCrumbs.vue b/apps/files/src/components/BreadCrumbs.vue index d93330e1d29..21777562d18 100644 --- a/apps/files/src/components/BreadCrumbs.vue +++ b/apps/files/src/components/BreadCrumbs.vue @@ -109,12 +109,11 @@ export default defineComponent({ return this.dirs.map((dir: string, index: number) => { const source = this.getFileSourceFromPath(dir) const node: Node | undefined = source ? this.getNodeFromSource(source) : undefined - const to = { ...this.$route, params: { node: node?.fileid }, query: { dir } } return { dir, exact: true, name: this.getDirDisplayName(dir), - to, + to: this.getTo(dir, node), // disable drop on current directory disableDrop: index === this.dirs.length - 1, } @@ -163,6 +162,20 @@ export default defineComponent({ return node?.displayname || basename(path) }, + getTo(dir: string, node?: Node): Record { + if (node === undefined) { + return { + ...this.$route, + query: { dir }, + } + } + return { + ...this.$route, + params: { fileid: String(node.fileid) }, + query: { dir: node.path }, + } + }, + onClick(to) { if (to?.query?.dir === this.$route.query.dir) { this.$emit('reload') diff --git a/apps/files/src/components/FileEntry/FileEntryName.vue b/apps/files/src/components/FileEntry/FileEntryName.vue index 1d45f7de17e..439037b984e 100644 --- a/apps/files/src/components/FileEntry/FileEntryName.vue +++ b/apps/files/src/components/FileEntry/FileEntryName.vue @@ -45,6 +45,7 @@ import { showError, showSuccess } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' import { FileType, NodeStatus } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' +import { dirname } from '@nextcloud/paths' import { defineComponent, inject } from 'vue' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' @@ -269,6 +270,10 @@ export default defineComponent({ // Success 🎉 emit('files:node:updated', this.source) emit('files:node:renamed', this.source) + emit('files:node:moved', { + node: this.source, + oldSource: `${dirname(this.source.source)}/${oldName}`, + }) showSuccess(t('files', 'Renamed "{oldName}" to "{newName}"', { oldName, newName })) // Reset the renaming store diff --git a/apps/files/src/components/FilesNavigationItem.vue b/apps/files/src/components/FilesNavigationItem.vue new file mode 100644 index 00000000000..75507803957 --- /dev/null +++ b/apps/files/src/components/FilesNavigationItem.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/apps/files/src/composables/useNavigation.ts b/apps/files/src/composables/useNavigation.ts index f410aec895f..2fff5633e23 100644 --- a/apps/files/src/composables/useNavigation.ts +++ b/apps/files/src/composables/useNavigation.ts @@ -3,9 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import type { View } from '@nextcloud/files' +import type { ShallowRef } from 'vue' import { getNavigation } from '@nextcloud/files' -import { onMounted, onUnmounted, shallowRef, type ShallowRef } from 'vue' +import { onMounted, onUnmounted, shallowRef, triggerRef } from 'vue' /** * Composable to get the currently active files view from the files navigation @@ -28,6 +29,7 @@ export function useNavigation() { */ function onUpdateViews() { views.value = navigation.views + triggerRef(views) } onMounted(() => { diff --git a/apps/files/src/eventbus.d.ts b/apps/files/src/eventbus.d.ts index db90c40eeae..e1fd8c73b4b 100644 --- a/apps/files/src/eventbus.d.ts +++ b/apps/files/src/eventbus.d.ts @@ -11,7 +11,12 @@ declare module '@nextcloud/event-bus' { 'files:favorites:removed': Node 'files:favorites:added': Node + + 'files:node:created': Node + 'files:node:deleted': Node + 'files:node:updated': Node 'files:node:renamed': Node + 'files:node:moved': { node: Node, oldSource: string } 'files:filter:added': IFileListFilter 'files:filter:removed': string diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts index 4266453a4a3..846f1049d5a 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -27,6 +27,7 @@ import registerFavoritesView from './views/favorites' import registerRecentView from './views/recent' import registerPersonalFilesView from './views/personal-files' import registerFilesView from './views/files' +import { registerFolderTreeView } from './views/folderTree.ts' import registerPreviewServiceWorker from './services/ServiceWorker.js' import { initLivePhotos } from './services/LivePhotos' @@ -53,6 +54,7 @@ registerFavoritesView() registerFilesView() registerRecentView() registerPersonalFilesView() +registerFolderTreeView() // Register file list filters registerHiddenFilesFilter() diff --git a/apps/files/src/services/FolderTree.ts b/apps/files/src/services/FolderTree.ts new file mode 100644 index 00000000000..87c3aaa7db7 --- /dev/null +++ b/apps/files/src/services/FolderTree.ts @@ -0,0 +1,90 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { ContentsWithRoot } from '@nextcloud/files' + +import { CancelablePromise } from 'cancelable-promise' +import { + davRemoteURL, + Folder, +} from '@nextcloud/files' +import axios from '@nextcloud/axios' +import { generateOcsUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' +import { dirname, encodePath } from '@nextcloud/paths' + +import { getContents as getFiles } from './Files.ts' + +export const folderTreeId = 'folders' +export const sourceRoot = `${davRemoteURL}/files/${getCurrentUser()?.uid}` + +interface TreeNodeData { + id: number, + displayName?: string, + // eslint-disable-next-line no-use-before-define + children?: Tree, +} + +interface Tree { + [basename: string]: TreeNodeData, +} + +export interface TreeNode { + source: string, + path: string, + fileid: number, + basename: string, + displayName?: string, +} + +const getTreeNodes = (tree: Tree, nodes: TreeNode[] = [], currentPath: string = ''): TreeNode[] => { + for (const basename in tree) { + const path = `${currentPath}/${basename}` + const node: TreeNode = { + source: `${sourceRoot}${path}`, + path, + fileid: tree[basename].id, + basename, + displayName: tree[basename].displayName, + } + nodes.push(node) + if (tree[basename].children) { + getTreeNodes(tree[basename].children, nodes, path) + } + } + return nodes +} + +export const getFolderTreeNodes = async (): Promise => { + const { data: tree } = await axios.get(generateOcsUrl('/apps/files/api/v1/folder-tree')) + const nodes = getTreeNodes(tree) + return nodes +} + +export const getContents = (path: string): CancelablePromise => getFiles(path) + +export const encodeSource = (source: string): string => { + const { origin } = new URL(source) + return origin + encodePath(source.slice(origin.length)) +} + +export const getSourceParent = (source: string): string => { + const parent = dirname(source) + if (parent === sourceRoot) { + return folderTreeId + } + return encodeSource(parent) +} + +export const getFolderTreeViewId = (folder: Folder): string => { + return folder.encodedSource +} + +export const getFolderTreeParentId = (folder: Folder): string => { + if (folder.dirname === '/') { + return folderTreeId + } + return dirname(folder.encodedSource) +} diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue index b0588863f5d..cca38824d2c 100644 --- a/apps/files/src/views/Navigation.vue +++ b/apps/files/src/views/Navigation.vue @@ -4,38 +4,14 @@ -->