From 0442d3419211649f53f360c233a2e97f219fb7bc Mon Sep 17 00:00:00 2001
From: cballevre
Date: Thu, 16 May 2024 17:44:40 +0200
Subject: [PATCH 1/3] feat: Add a collection to access files into a Nextcloud
---
.../cozy-stack-client/src/CozyStackClient.js | 6 +++
.../src/NextcloudFilesCollection.js | 46 +++++++++++++++++++
2 files changed, 52 insertions(+)
create mode 100644 packages/cozy-stack-client/src/NextcloudFilesCollection.js
diff --git a/packages/cozy-stack-client/src/CozyStackClient.js b/packages/cozy-stack-client/src/CozyStackClient.js
index 5ffe076548..605bf436f9 100644
--- a/packages/cozy-stack-client/src/CozyStackClient.js
+++ b/packages/cozy-stack-client/src/CozyStackClient.js
@@ -26,6 +26,10 @@ import MicroEE from 'microee'
import errors, { FetchError } from './errors'
import logger from './logger'
import PromiseCache from './promise-cache'
+import {
+ NEXTCLOUD_FILES_DOCTYPE,
+ NextcloudFilesCollection
+} from './NextcloudFilesCollection'
const normalizeUri = uriArg => {
let uri = uriArg
@@ -100,6 +104,8 @@ class CozyStackClient {
return new ShortcutsCollection(this)
case APPS_REGISTRY_DOCTYPE:
return new AppsRegistryCollection(this)
+ case NEXTCLOUD_FILES_DOCTYPE:
+ return new NextcloudFilesCollection(this)
default:
return new DocumentCollection(doctype, this)
}
diff --git a/packages/cozy-stack-client/src/NextcloudFilesCollection.js b/packages/cozy-stack-client/src/NextcloudFilesCollection.js
new file mode 100644
index 0000000000..34278823e4
--- /dev/null
+++ b/packages/cozy-stack-client/src/NextcloudFilesCollection.js
@@ -0,0 +1,46 @@
+import DocumentCollection from './DocumentCollection'
+
+const NEXTCLOUD_FILES_DOCTYPE = 'io.cozy.remote.nextcloud.files'
+
+const normalizeDoc = DocumentCollection.normalizeDoctypeJsonApi(
+ NEXTCLOUD_FILES_DOCTYPE
+)
+const normalizeNextcloudFile = (sourceAccount, path) => file => {
+ const extendedAttributes = {
+ ...file.attributes,
+ path: `${path}${path.endsWith('/') ? '' : '/'}${file.attributes.name}`,
+ cozyMetadata: {
+ ...file.attributes.cozyMetadata,
+ sourceAccount
+ }
+ }
+
+ return {
+ ...normalizeDoc(file, NEXTCLOUD_FILES_DOCTYPE),
+ ...extendedAttributes
+ }
+}
+
+class NextcloudFilesCollection extends DocumentCollection {
+ constructor(stackClient) {
+ super(NEXTCLOUD_FILES_DOCTYPE, stackClient)
+ }
+
+ async find(selector) {
+ if (selector.sourceAccount && selector.path) {
+ const resp = await this.stackClient.fetchJSON(
+ 'GET',
+ `/remote/nextcloud/${selector.sourceAccount}/${selector.path}`
+ )
+
+ return {
+ data: resp.data.map(
+ normalizeNextcloudFile(selector.sourceAccount, selector.path)
+ )
+ }
+ }
+ throw new Error('Not implemented')
+ }
+}
+
+export { NextcloudFilesCollection, NEXTCLOUD_FILES_DOCTYPE }
From 98a9e8e77c242e927acdb7ebfa120aa7439dcb3a Mon Sep 17 00:00:00 2001
From: cballevre
Date: Thu, 16 May 2024 17:44:07 +0200
Subject: [PATCH 2/3] feat(nextcloud): Download a file
---
docs/api/cozy-stack-client.md | 15 +++++++++++++++
packages/cozy-stack-client/src/FileCollection.js | 10 ++--------
.../src/NextcloudFilesCollection.js | 16 ++++++++++++++++
packages/cozy-stack-client/src/utils.js | 16 ++++++++++++++++
4 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/docs/api/cozy-stack-client.md b/docs/api/cozy-stack-client.md
index dd81f3acd1..6d74476151 100644
--- a/docs/api/cozy-stack-client.md
+++ b/docs/api/cozy-stack-client.md
@@ -108,6 +108,9 @@ query to work
Rules determine the behavior of the sharing when changes are made to the shared document
See https://docs.cozy.io/en/cozy-stack/sharing-design/#description-of-a-sharing
+forceDownload
+Force a download from the given href
+
## Functions
@@ -2200,6 +2203,18 @@ See https://docs.cozy.io/en/cozy-stack/sharing-design/#description-of-a-sharing
| document | [Sharing
](#Sharing) | The document to share. Should have and _id and a name |
| sharingType | [SharingType
](#SharingType) | The type of the sharing |
+
+
+## forceDownload
+Force a download from the given href
+
+**Kind**: global constant
+
+| Param | Type | Description |
+| --- | --- | --- |
+| href | string
| The link to download |
+| filename | string
| The file name to download |
+
## getAccessToken() ⇒ string
diff --git a/packages/cozy-stack-client/src/FileCollection.js b/packages/cozy-stack-client/src/FileCollection.js
index 91a507b618..e7f84beb3d 100644
--- a/packages/cozy-stack-client/src/FileCollection.js
+++ b/packages/cozy-stack-client/src/FileCollection.js
@@ -6,7 +6,7 @@ import pick from 'lodash/pick'
import { MangoQueryOptions } from './mangoIndex'
import DocumentCollection, { normalizeDoc } from './DocumentCollection'
-import { uri, slugify, formatBytes } from './utils'
+import { uri, slugify, formatBytes, forceDownload } from './utils'
import { FetchError } from './errors'
import { dontThrowNotFoundError } from './Collection'
import { getIllegalCharacters } from './getIllegalCharacter'
@@ -659,13 +659,7 @@ class FileCollection extends DocumentCollection {
* @param {string} filename - The file name to download
*/
forceFileDownload = (href, filename) => {
- const element = document.createElement('a')
- element.setAttribute('href', href)
- element.setAttribute('download', filename)
- element.style.display = 'none'
- document.body.appendChild(element)
- element.click()
- document.body.removeChild(element)
+ forceDownload(href, filename)
}
/**
diff --git a/packages/cozy-stack-client/src/NextcloudFilesCollection.js b/packages/cozy-stack-client/src/NextcloudFilesCollection.js
index 34278823e4..503c6a5ef9 100644
--- a/packages/cozy-stack-client/src/NextcloudFilesCollection.js
+++ b/packages/cozy-stack-client/src/NextcloudFilesCollection.js
@@ -1,4 +1,5 @@
import DocumentCollection from './DocumentCollection'
+import { forceDownload } from './utils'
const NEXTCLOUD_FILES_DOCTYPE = 'io.cozy.remote.nextcloud.files'
@@ -41,6 +42,21 @@ class NextcloudFilesCollection extends DocumentCollection {
}
throw new Error('Not implemented')
}
+
+ /**
+ * Download a file from a Nextcloud server
+ *
+ */
+ async download(file) {
+ const res = await this.stackClient.fetch(
+ 'GET',
+ `/remote/nextcloud/${file.cozyMetadata.sourceAccount}/${file.path}?Dl=1`
+ )
+ const blob = await res.blob()
+ const href = URL.createObjectURL(blob)
+ const filename = file.path.split('/').pop()
+ forceDownload(href, filename)
+ }
}
export { NextcloudFilesCollection, NEXTCLOUD_FILES_DOCTYPE }
diff --git a/packages/cozy-stack-client/src/utils.js b/packages/cozy-stack-client/src/utils.js
index 954dc58b00..336c4aa5db 100644
--- a/packages/cozy-stack-client/src/utils.js
+++ b/packages/cozy-stack-client/src/utils.js
@@ -69,3 +69,19 @@ export const formatBytes = (bytes, decimals = 2) => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
+
+/**
+ * Force a download from the given href
+ *
+ * @param {string} href - The link to download
+ * @param {string} filename - The file name to download
+ */
+export const forceDownload = (href, filename) => {
+ const element = document.createElement('a')
+ element.setAttribute('href', href)
+ element.setAttribute('download', filename)
+ element.style.display = 'none'
+ document.body.appendChild(element)
+ element.click()
+ document.body.removeChild(element)
+}
From d0cda89ccba153efc35741fff5e0a8f1833d96c1 Mon Sep 17 00:00:00 2001
From: cballevre
Date: Wed, 22 May 2024 16:47:49 +0200
Subject: [PATCH 3/3] fix(useQuery): Add function type for definition prop
To use the enabled option, the definition is passed in the form of a general function. I've adapted the typing to this practice. This allows me to display the warning also for definition passed with a function
---
docs/api/cozy-client/README.md | 2 +-
packages/cozy-client/src/hooks/useQuery.js | 4 ++--
packages/cozy-client/types/hooks/useQuery.d.ts | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md
index 7e2d1f8c00..39ae3f65a9 100644
--- a/docs/api/cozy-client/README.md
+++ b/docs/api/cozy-client/README.md
@@ -894,7 +894,7 @@ Fetches a queryDefinition and returns the queryState
| Name | Type | Description |
| :------ | :------ | :------ |
-| `queryDefinition` | [`QueryDefinition`](classes/QueryDefinition.md) | Definition created with Q() |
+| `queryDefinition` | [`QueryDefinition`](classes/QueryDefinition.md) | () => [`QueryDefinition`](classes/QueryDefinition.md) | Definition created with Q() |
| `options` | `QueryOptions` | Options created with Q() |
*Returns*
diff --git a/packages/cozy-client/src/hooks/useQuery.js b/packages/cozy-client/src/hooks/useQuery.js
index db6f5c3b9e..7bff34c242 100644
--- a/packages/cozy-client/src/hooks/useQuery.js
+++ b/packages/cozy-client/src/hooks/useQuery.js
@@ -21,7 +21,7 @@ const generateFetchMoreQueryDefinition = queryResult => {
/**
* Fetches a queryDefinition and returns the queryState
*
- * @param {QueryDefinition} queryDefinition - Definition created with Q()
+ * @param {QueryDefinition|(() => QueryDefinition)} queryDefinition - Definition created with Q()
* @param {import("../types").QueryOptions} options - Options created with Q()
* @returns {import("../types").UseQueryReturnValue}
*/
@@ -51,7 +51,7 @@ const useQuery = (queryDefinition, options) => {
const client = useClient()
const queryState = useSelector(() => {
- if (options.singleDocData === undefined && queryDefinition.id) {
+ if (options.singleDocData === undefined && definition?.id) {
logger.warn(
'useQuery options.singleDocData will pass to true in a next version of cozy-client, please add it now to prevent any problem in the future.'
)
diff --git a/packages/cozy-client/types/hooks/useQuery.d.ts b/packages/cozy-client/types/hooks/useQuery.d.ts
index 0465270e1c..d05eae2c1b 100644
--- a/packages/cozy-client/types/hooks/useQuery.d.ts
+++ b/packages/cozy-client/types/hooks/useQuery.d.ts
@@ -3,9 +3,9 @@ export default useQuery;
/**
* Fetches a queryDefinition and returns the queryState
*
- * @param {QueryDefinition} queryDefinition - Definition created with Q()
+ * @param {QueryDefinition|(() => QueryDefinition)} queryDefinition - Definition created with Q()
* @param {import("../types").QueryOptions} options - Options created with Q()
* @returns {import("../types").UseQueryReturnValue}
*/
-declare function useQuery(queryDefinition: QueryDefinition, options: import("../types").QueryOptions): import("../types").UseQueryReturnValue;
+declare function useQuery(queryDefinition: QueryDefinition | (() => QueryDefinition), options: import("../types").QueryOptions): import("../types").UseQueryReturnValue;
import { QueryDefinition } from "../queries/dsl";