aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/store/files.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/store/files.ts')
-rw-r--r--apps/files/src/store/files.ts152
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
}