diff options
Diffstat (limited to 'apps/files/src/views/folderTree.ts')
-rw-r--r-- | apps/files/src/views/folderTree.ts | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/apps/files/src/views/folderTree.ts b/apps/files/src/views/folderTree.ts new file mode 100644 index 00000000000..2ce4e501e6f --- /dev/null +++ b/apps/files/src/views/folderTree.ts @@ -0,0 +1,176 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { TreeNode } from '../services/FolderTree.ts' + +import PQueue from 'p-queue' +import { FileType, Folder, Node, View, getNavigation } from '@nextcloud/files' +import { translate as t } from '@nextcloud/l10n' +import { emit, subscribe } from '@nextcloud/event-bus' +import { isSamePath } from '@nextcloud/paths' +import { loadState } from '@nextcloud/initial-state' + +import FolderSvg from '@mdi/svg/svg/folder.svg?raw' +import FolderMultipleSvg from '@mdi/svg/svg/folder-multiple-outline.svg?raw' + +import { + folderTreeId, + getContents, + getFolderTreeNodes, + getSourceParent, + sourceRoot, +} from '../services/FolderTree.ts' + +const isFolderTreeEnabled = loadState('files', 'config', { folder_tree: true }).folder_tree + +let showHiddenFiles = loadState('files', 'config', { show_hidden: false }).show_hidden + +const Navigation = getNavigation() + +const queue = new PQueue({ concurrency: 5, intervalCap: 5, interval: 200 }) + +const registerQueue = new PQueue({ concurrency: 5, intervalCap: 5, interval: 200 }) + +const registerTreeChildren = async (path: string = '/') => { + await queue.add(async () => { + const nodes = await getFolderTreeNodes(path) + const promises = nodes.map(node => registerQueue.add(() => registerNodeView(node))) + await Promise.allSettled(promises) + }) +} + +const getLoadChildViews = (node: TreeNode | Folder) => { + return async (view: View): Promise<void> => { + // @ts-expect-error Custom property on View instance + if (view.loading || view.loaded) { + return + } + // @ts-expect-error Custom property + view.loading = true + await registerTreeChildren(node.path) + // @ts-expect-error Custom property + view.loading = false + // @ts-expect-error Custom property + view.loaded = true + // @ts-expect-error No payload + emit('files:navigation:updated') + // @ts-expect-error No payload + emit('files:folder-tree:expanded') + } +} + +const registerNodeView = (node: TreeNode | Folder) => { + const registeredView = Navigation.views.find(view => view.id === node.encodedSource) + if (registeredView) { + Navigation.remove(registeredView.id) + } + if (!showHiddenFiles && node.basename.startsWith('.')) { + return + } + Navigation.register(new View({ + id: node.encodedSource, + parent: getSourceParent(node.source), + + // @ts-expect-error Casing differences + name: node.displayName ?? node.displayname ?? node.basename, + + icon: FolderSvg, + + getContents, + loadChildViews: getLoadChildViews(node), + + params: { + view: folderTreeId, + fileid: String(node.fileid), // Needed for matching exact routes + dir: node.path, + }, + })) +} + +const removeFolderView = (folder: Folder) => { + const viewId = folder.encodedSource + Navigation.remove(viewId) +} + +const removeFolderViewSource = (source: string) => { + Navigation.remove(source) +} + +const onCreateNode = (node: Node) => { + if (node.type !== FileType.Folder) { + return + } + registerNodeView(node) +} + +const onDeleteNode = (node: Node) => { + if (node.type !== FileType.Folder) { + return + } + removeFolderView(node) +} + +const onMoveNode = ({ node, oldSource }) => { + if (node.type !== FileType.Folder) { + return + } + removeFolderViewSource(oldSource) + registerNodeView(node) + + const newPath = node.source.replace(sourceRoot, '') + const oldPath = oldSource.replace(sourceRoot, '') + const childViews = Navigation.views.filter(view => { + if (!view.params?.dir) { + return false + } + if (isSamePath(view.params.dir, oldPath)) { + return false + } + return view.params.dir.startsWith(oldPath) + }) + for (const view of childViews) { + // @ts-expect-error FIXME Allow setting parent + view.parent = getSourceParent(node.source) + // @ts-expect-error dir param is defined + view.params.dir = view.params.dir.replace(oldPath, newPath) + } +} + +const onUserConfigUpdated = async ({ key, value }) => { + if (key === 'show_hidden') { + showHiddenFiles = value + await registerTreeChildren() + // @ts-expect-error No payload + emit('files:folder-tree:initialized') + } +} + +const registerTreeRoot = () => { + Navigation.register(new View({ + id: folderTreeId, + + name: t('files', 'Folder tree'), + caption: t('files', 'List of your files and folders.'), + + icon: FolderMultipleSvg, + order: 50, // Below all other views + + getContents, + })) +} + +export const registerFolderTreeView = async () => { + if (!isFolderTreeEnabled) { + return + } + registerTreeRoot() + await registerTreeChildren() + subscribe('files:node:created', onCreateNode) + subscribe('files:node:deleted', onDeleteNode) + subscribe('files:node:moved', onMoveNode) + subscribe('files:config:updated', onUserConfigUpdated) + // @ts-expect-error No payload + emit('files:folder-tree:initialized') +} |