diff options
Diffstat (limited to 'apps/files/src/store/files.ts')
-rw-r--r-- | apps/files/src/store/files.ts | 152 |
1 files changed, 121 insertions, 31 deletions
diff --git a/apps/files/src/store/files.ts b/apps/files/src/store/files.ts index 56ae01192ef..0bcf4ce9350 100644 --- a/apps/files/src/store/files.ts +++ b/apps/files/src/store/files.ts @@ -1,32 +1,19 @@ /** - * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ + +import type { FilesStore, RootsStore, RootOptions, Service, FilesState, FileSource } from '../types' import type { Folder, Node } from '@nextcloud/files' -import type { FilesStore, RootsStore, RootOptions, Service, FilesState, FileId } from '../types' import { defineStore } from 'pinia' import { subscribe } from '@nextcloud/event-bus' import logger from '../logger' import Vue from 'vue' +import { fetchNode } from '../services/WebdavClient.ts' +import { usePathsStore } from './paths.ts' + export const useFilesStore = function(...args) { const store = defineStore('files', { state: (): FilesState => ({ @@ -36,32 +23,85 @@ export const useFilesStore = function(...args) { getters: { /** - * Get a file or folder by id + * Get a file or folder by its source + * @param state */ - getNode: (state) => (id: FileId): Node|undefined => state.files[id], + getNode: (state) => (source: FileSource): Node|undefined => state.files[source], /** * Get a list of files or folders by their IDs - * Does not return undefined values + * Note: does not return undefined values + * @param state */ - getNodes: (state) => (ids: FileId[]): Node[] => ids - .map(id => state.files[id]) + getNodes: (state) => (sources: FileSource[]): Node[] => sources + .map(source => state.files[source]) .filter(Boolean), + + /** + * Get files or folders by their file ID + * Multiple nodes can have the same file ID but different sources + * (e.g. in a shared context) + * @param state + */ + getNodesById: (state) => (fileId: number): Node[] => Object.values(state.files).filter(node => node.fileid === fileId), + /** - * Get a file or folder by id + * Get the root folder of a service + * @param state */ getRoot: (state) => (service: Service): Folder|undefined => state.roots[service], }, actions: { + /** + * Get cached directory matching a given path + * + * @param service - The service (files view) + * @param path - The path relative within the service + * @return The folder if found + */ + getDirectoryByPath(service: string, path?: string): Folder | undefined { + const pathsStore = usePathsStore() + let folder: Folder | undefined + + // Get the containing folder from path store + if (!path || path === '/') { + folder = this.getRoot(service) + } else { + const source = pathsStore.getPath(service, path) + if (source) { + folder = this.getNode(source) as Folder | undefined + } + } + + return folder + }, + + /** + * Get cached child nodes within a given path + * + * @param service - The service (files view) + * @param path - The path relative within the service + * @return Array of cached nodes within the path + */ + getNodesByPath(service: string, path?: string): Node[] { + const folder = this.getDirectoryByPath(service, path) + + // If we found a cache entry and the cache entry was already loaded (has children) then use it + return (folder?._children ?? []) + .map((source: string) => this.getNode(source)) + .filter(Boolean) + }, + updateNodes(nodes: Node[]) { // Update the store all at once const files = nodes.reduce((acc, node) => { if (!node.fileid) { - logger.error('Trying to update/set a node without fileid', node) + logger.error('Trying to update/set a node without fileid', { node }) return acc } - acc[node.fileid] = node + + acc[node.source] = node return acc }, {} as FilesStore) @@ -70,8 +110,8 @@ export const useFilesStore = function(...args) { deleteNodes(nodes: Node[]) { nodes.forEach(node => { - if (node.fileid) { - Vue.delete(this.files, node.fileid) + if (node.source) { + Vue.delete(this.files, node.source) } }) }, @@ -88,9 +128,55 @@ export const useFilesStore = function(...args) { this.updateNodes([node]) }, - onUpdatedNode(node: Node) { + onMovedNode({ node, oldSource }: { node: Node, oldSource: string }) { + if (!node.fileid) { + logger.error('Trying to update/set a node without fileid', { node }) + return + } + + // Update the path of the node + Vue.delete(this.files, oldSource) this.updateNodes([node]) }, + + async onUpdatedNode(node: Node) { + if (!node.fileid) { + logger.error('Trying to update/set a node without fileid', { node }) + return + } + + // If we have multiple nodes with the same file ID, we need to update all of them + const nodes = this.getNodesById(node.fileid) + if (nodes.length > 1) { + await Promise.all(nodes.map(node => fetchNode(node.path))).then(this.updateNodes) + logger.debug(nodes.length + ' nodes updated in store', { fileid: node.fileid }) + return + } + + // If we have only one node with the file ID, we can update it directly + if (nodes.length === 1 && node.source === nodes[0].source) { + this.updateNodes([node]) + return + } + + // Otherwise, it means we receive an event for a node that is not in the store + fetchNode(node.path).then(n => this.updateNodes([n])) + }, + + // Handlers for legacy sidebar (no real nodes support) + onAddFavorite(node: Node) { + const ourNode = this.getNode(node.source) + if (ourNode) { + Vue.set(ourNode.attributes, 'favorite', 1) + } + }, + + onRemoveFavorite(node: Node) { + const ourNode = this.getNode(node.source) + if (ourNode) { + Vue.set(ourNode.attributes, 'favorite', 0) + } + }, }, }) @@ -100,6 +186,10 @@ export const useFilesStore = function(...args) { subscribe('files:node:created', fileStore.onCreatedNode) subscribe('files:node:deleted', fileStore.onDeletedNode) subscribe('files:node:updated', fileStore.onUpdatedNode) + subscribe('files:node:moved', fileStore.onMovedNode) + // legacy sidebar + subscribe('files:favorites:added', fileStore.onAddFavorite) + subscribe('files:favorites:removed', fileStore.onRemoveFavorite) fileStore._initialized = true } |