From 27bb9302b8c5a9fb75a565dda744ec43159cfda5 Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Wed, 23 Aug 2023 09:28:11 +0200 Subject: [PATCH 01/17] feat: Update manifest to add acceptFromFlagship - Also add placeholder route that will handle mobile uploads later --- src/drive/targets/manifest.webapp | 7 +++++++ src/drive/web/modules/navigation/AppRoute.jsx | 2 ++ .../views/Upload/UploadFromFlagship.jsx | 13 ++++++++++++ .../views/Upload/useUploadFromFlagship.js | 20 +++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 src/drive/web/modules/views/Upload/UploadFromFlagship.jsx create mode 100644 src/drive/web/modules/views/Upload/useUploadFromFlagship.js diff --git a/src/drive/targets/manifest.webapp b/src/drive/targets/manifest.webapp index 97eaf3e5ba..f8d1cda0fa 100644 --- a/src/drive/targets/manifest.webapp +++ b/src/drive/targets/manifest.webapp @@ -175,5 +175,12 @@ "verbs": ["POST"], "description": "Remote-doctype required to send anonymized measures to the DACC shared among mycozy.eu's Cozy." } + }, + "accept_from_flagship": true, + "accept_documents_from_flagship": { + "accepted_mime_types": ["*/*"], + "max_number_of_files": 1, + "max_size_per_file_in_MB": 10, + "route_to_upload": "/#/create?fromFlagshipUpload=true" } } diff --git a/src/drive/web/modules/navigation/AppRoute.jsx b/src/drive/web/modules/navigation/AppRoute.jsx index ea1641982b..1baaf6bdaa 100644 --- a/src/drive/web/modules/navigation/AppRoute.jsx +++ b/src/drive/web/modules/navigation/AppRoute.jsx @@ -29,6 +29,7 @@ import { ShareDisplayedFolderView } from 'drive/web/modules/views/Modal/ShareDis import { ShareFileView } from 'drive/web/modules/views/Modal/ShareFileView' import { QualifyFileView } from 'drive/web/modules/views/Modal/QualifyFileView' import { MoveFilesView } from 'drive/web/modules/views/Modal/MoveFilesView' +import { UploadFromFlagship } from 'drive/web/modules/views/Upload/UploadFromFlagship' const FilesRedirect = () => { const { folderId } = useParams() @@ -42,6 +43,7 @@ const AppRoute = () => ( {__TARGET__ === 'mobile' && ( } /> )} + } /> } /> } /> diff --git a/src/drive/web/modules/views/Upload/UploadFromFlagship.jsx b/src/drive/web/modules/views/Upload/UploadFromFlagship.jsx new file mode 100644 index 0000000000..4ca80b6585 --- /dev/null +++ b/src/drive/web/modules/views/Upload/UploadFromFlagship.jsx @@ -0,0 +1,13 @@ +import React from 'react' +import { useUploadFromFlagship } from './useUploadFromFlagship' + +export const UploadFromFlagship = () => { + const { loading } = useUploadFromFlagship() + + return ( +
+

Upload From Flagship Page

+

{loading ? 'loading' : 'Ready to call hasFilesToHandle'}

+
+ ) +} diff --git a/src/drive/web/modules/views/Upload/useUploadFromFlagship.js b/src/drive/web/modules/views/Upload/useUploadFromFlagship.js new file mode 100644 index 0000000000..fb91cd959b --- /dev/null +++ b/src/drive/web/modules/views/Upload/useUploadFromFlagship.js @@ -0,0 +1,20 @@ +import { useSearchParams } from 'react-router-dom' +import { useWebviewIntent } from 'cozy-intent' +import { useEffect, useState } from 'react' + +export const useUploadFromFlagship = () => { + const [loading, setLoading] = useState(true) + const [searchParams] = useSearchParams() + const webviewIntent = useWebviewIntent() + const fromFlagshipUpload = searchParams.get('fromFlagshipUpload') + + useEffect(() => { + if (fromFlagshipUpload && webviewIntent) { + setLoading(false) + } + }, [fromFlagshipUpload, webviewIntent]) + + return { + loading + } +} From d01f57c6c3c2a390981886720bb24d23637f8ef1 Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Thu, 24 Aug 2023 11:40:01 +0200 Subject: [PATCH 02/17] refactor: updated upload component to typescript It is believed this will help integrating with the existing store logic. No runtime changes are intended, only typings so everything is as clear as possible when reusing the already existing mobile upload flow --- .eslintrc.json | 5 +- babel.config.js | 2 +- jest.config.js | 2 +- package.json | 4 +- src/declarations.d.ts | 1 + src/drive/targets/manifest.webapp | 2 +- src/drive/web/modules/navigation/AppRoute.jsx | 4 +- src/drive/web/modules/upload/index.js | 23 +- .../views/Upload/UploadFromFlagship.jsx | 13 - .../web/modules/views/Upload/UploadTypes.ts | 40 ++ .../web/modules/views/Upload/UploadUtils.ts | 5 + .../views/Upload/UploaderComponent.spec.tsx | 61 ++ .../views/Upload/UploaderComponent.tsx | 208 ++++++ .../views/Upload/useUploadFromFlagship.js | 20 - .../views/Upload/useUploadFromFlagship.ts | 85 +++ tsconfig.json | 13 + yarn.lock | 659 +++++++++++++++++- 17 files changed, 1070 insertions(+), 77 deletions(-) create mode 100644 src/declarations.d.ts delete mode 100644 src/drive/web/modules/views/Upload/UploadFromFlagship.jsx create mode 100644 src/drive/web/modules/views/Upload/UploadTypes.ts create mode 100644 src/drive/web/modules/views/Upload/UploadUtils.ts create mode 100644 src/drive/web/modules/views/Upload/UploaderComponent.spec.tsx create mode 100644 src/drive/web/modules/views/Upload/UploaderComponent.tsx delete mode 100644 src/drive/web/modules/views/Upload/useUploadFromFlagship.js create mode 100644 src/drive/web/modules/views/Upload/useUploadFromFlagship.ts create mode 100644 tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json index 9a1264d8b8..5e2e97fff8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -20,5 +20,8 @@ "react/display-name": "off" } } - ] + ], + "parserOptions": { + "project": "tsconfig.json" + } } diff --git a/babel.config.js b/babel.config.js index b4d7fb3463..c5df3bc678 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - presets: ['cozy-app', '@babel/env'] + presets: ['cozy-app'] } diff --git a/jest.config.js b/jest.config.js index 8e63556af6..162602ee90 100644 --- a/jest.config.js +++ b/jest.config.js @@ -31,7 +31,7 @@ module.exports = { clearMocks: true, snapshotSerializers: ['enzyme-to-json/serializer'], transform: { - '^.+\\.jsx?$': 'babel-jest', + '^.+\\.(ts|tsx|js|jsx)?$': 'babel-jest', '^.+\\.webapp$': '/test/jestLib/json-transformer.js' }, transformIgnorePatterns: ['node_modules/(?!cozy-ui)/'], diff --git a/package.json b/package.json index 697f87af51..62f5bdddbd 100644 --- a/package.json +++ b/package.json @@ -82,14 +82,16 @@ "@testing-library/jest-dom": "5.16.4", "@testing-library/react": "11.2.7", "@testing-library/react-hooks": "8.0.1", + "@types/react-redux": "7.1.26", "@welldone-software/why-did-you-render": "^6.1.4", - "babel-core": "7.0.0-bridge.0", + "babel-preset-cozy-app": "2.1.0", "babel-runtime": "^6.26.0", "bundlemon": "1.3.1", "chrome-remote-interface": "0.31.2", "cordova": "8.1.2", "cordova-android": "9.1.0", "cozy-jobs-cli": "^2.1.0", + "cozy-tsconfig": "1.2.0", "css-mediaquery": "^0.1.2", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.6", diff --git a/src/declarations.d.ts b/src/declarations.d.ts new file mode 100644 index 0000000000..ae1aa97a25 --- /dev/null +++ b/src/declarations.d.ts @@ -0,0 +1 @@ +declare module 'cozy-ui/*' diff --git a/src/drive/targets/manifest.webapp b/src/drive/targets/manifest.webapp index f8d1cda0fa..26e50ef85a 100644 --- a/src/drive/targets/manifest.webapp +++ b/src/drive/targets/manifest.webapp @@ -181,6 +181,6 @@ "accepted_mime_types": ["*/*"], "max_number_of_files": 1, "max_size_per_file_in_MB": 10, - "route_to_upload": "/#/create?fromFlagshipUpload=true" + "route_to_upload": "/#/upload?fromFlagshipUpload=true" } } diff --git a/src/drive/web/modules/navigation/AppRoute.jsx b/src/drive/web/modules/navigation/AppRoute.jsx index 1baaf6bdaa..ff2c72fd51 100644 --- a/src/drive/web/modules/navigation/AppRoute.jsx +++ b/src/drive/web/modules/navigation/AppRoute.jsx @@ -29,7 +29,7 @@ import { ShareDisplayedFolderView } from 'drive/web/modules/views/Modal/ShareDis import { ShareFileView } from 'drive/web/modules/views/Modal/ShareFileView' import { QualifyFileView } from 'drive/web/modules/views/Modal/QualifyFileView' import { MoveFilesView } from 'drive/web/modules/views/Modal/MoveFilesView' -import { UploadFromFlagship } from 'drive/web/modules/views/Upload/UploadFromFlagship' +import UploaderComponent from 'drive/web/modules//views/Upload/UploaderComponent' const FilesRedirect = () => { const { folderId } = useParams() @@ -43,7 +43,7 @@ const AppRoute = () => ( {__TARGET__ === 'mobile' && ( } /> )} - } /> + } /> } /> } /> diff --git a/src/drive/web/modules/upload/index.js b/src/drive/web/modules/upload/index.js index 55027b0d30..ed95361422 100644 --- a/src/drive/web/modules/upload/index.js +++ b/src/drive/web/modules/upload/index.js @@ -375,7 +375,13 @@ export const overwriteFile = async (client, file, path, options = {}) => { } export const uploadFilesFromNative = - (files, folderId, uploadFilesSuccessCallback, { client, vaultClient }) => + ( + files, + folderId, + uploadFilesSuccessCallback, + { client, vaultClient }, + alternateUploader + ) => async dispatch => { dispatch({ type: ADD_TO_UPLOAD_QUEUE, @@ -401,10 +407,17 @@ export const uploadFilesFromNative = contentType: file.file.type }) } else { - await doMobileUpload(client, file.file.fileUrl, { - ...fileOpts, - contentType: file.file.type - }) + if (alternateUploader) { + await alternateUploader(client, file.file.fileUrl, { + ...fileOpts, + contentType: file.file.type + }) + } else { + await doMobileUpload(client, file.file.fileUrl, { + ...fileOpts, + contentType: file.file.type + }) + } } dispatch(removeFileToUploadQueue(file.file)) } catch (error) { diff --git a/src/drive/web/modules/views/Upload/UploadFromFlagship.jsx b/src/drive/web/modules/views/Upload/UploadFromFlagship.jsx deleted file mode 100644 index 4ca80b6585..0000000000 --- a/src/drive/web/modules/views/Upload/UploadFromFlagship.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import { useUploadFromFlagship } from './useUploadFromFlagship' - -export const UploadFromFlagship = () => { - const { loading } = useUploadFromFlagship() - - return ( -
-

Upload From Flagship Page

-

{loading ? 'loading' : 'Ready to call hasFilesToHandle'}

-
- ) -} diff --git a/src/drive/web/modules/views/Upload/UploadTypes.ts b/src/drive/web/modules/views/Upload/UploadTypes.ts new file mode 100644 index 0000000000..4bc7878896 --- /dev/null +++ b/src/drive/web/modules/views/Upload/UploadTypes.ts @@ -0,0 +1,40 @@ +import CozyClient from 'cozy-client' + +export interface Folder { + _id: string +} + +export interface FileForQueue { + file: FileFromNative + isDirectory: false +} + +export interface DumbUploadProps { + t: (key: string, options?: { smart_count: number }) => string + stopMediaBackup: () => void + navigate: (path: string) => void + uploadFilesFromNative: DispatchProps['uploadFilesFromNative'] + client: CozyClient + vaultClient: CozyClient + startMediaBackup: () => void + items: FileFromNative[] +} + +export interface DispatchProps { + uploadFilesFromNative: ( + files: FileForQueue[], + folderId: string, + successCallback: () => void, + options: { client: CozyClient; vaultClient?: CozyClient }, + alternateUploader?: ( + files: FileForQueue[], + folderId: string + ) => Promise + ) => Promise + stopMediaBackup: () => void + startMediaBackup: () => void +} + +export interface FileFromNative { + fileName: string +} diff --git a/src/drive/web/modules/views/Upload/UploadUtils.ts b/src/drive/web/modules/views/Upload/UploadUtils.ts new file mode 100644 index 0000000000..996968c233 --- /dev/null +++ b/src/drive/web/modules/views/Upload/UploadUtils.ts @@ -0,0 +1,5 @@ +import type { FileFromNative, FileForQueue } from './UploadTypes' + +export const generateForQueue = (files: FileFromNative[]): FileForQueue[] => { + return files.map(file => ({ file: file, isDirectory: false })) +} diff --git a/src/drive/web/modules/views/Upload/UploaderComponent.spec.tsx b/src/drive/web/modules/views/Upload/UploaderComponent.spec.tsx new file mode 100644 index 0000000000..841e05ace7 --- /dev/null +++ b/src/drive/web/modules/views/Upload/UploaderComponent.spec.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import { render, RenderResult, waitFor } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' +import { DumbUpload } from 'drive/mobile/modules/upload' +import { generateForQueue } from './UploadUtils' + +jest.mock('cozy-keys-lib', () => ({ + withVaultClient: jest.fn().mockReturnValue({}) +})) + +const tSpy = jest.fn() +const uploadFilesFromNativeSpy = jest.fn() + +describe('DumbUpload component', () => { + const defaultItems = [{ fileName: 'File1.pdf' }] + + const setupComponent = (): RenderResult< + typeof import('/home/anc/cozy/cozy-drive/node_modules/@testing-library/dom/types/queries'), + HTMLElement + > => { + const props = { + client: {}, + vaultClient: {}, + t: tSpy, + uploadFilesFromNative: uploadFilesFromNativeSpy, + stopMediaBackup: jest.fn(), + router: jest.fn(), + navigate: jest.fn() + } + + return render() + } + + describe('generateForQueue', () => { + it('should generate the right object for the Drive queue', () => { + const genetaredForQueue = generateForQueue(defaultItems) + expect(genetaredForQueue).toEqual([ + { file: defaultItems[0], isDirectory: false } + ]) + }) + }) + + describe('Upload files', () => { + it('should call uploadFileFromNative with the right arguments', async () => { + const { rerender } = setupComponent() + const folderId = 'io.cozy.root' + + rerender() + + await waitFor(() => { + const genetaredForQueue = generateForQueue(defaultItems) + expect(uploadFilesFromNativeSpy).toHaveBeenCalledWith( + genetaredForQueue, + folderId, + expect.any(Function), + { client: {}, vaultClient: {} } + ) + }) + }) + }) +}) diff --git a/src/drive/web/modules/views/Upload/UploaderComponent.tsx b/src/drive/web/modules/views/Upload/UploaderComponent.tsx new file mode 100644 index 0000000000..4122661c8c --- /dev/null +++ b/src/drive/web/modules/views/Upload/UploaderComponent.tsx @@ -0,0 +1,208 @@ +/* eslint-disable no-console */ +import React, { useCallback, useState } from 'react' +import localforage from 'localforage' +import { connect } from 'react-redux' +import { useNavigate } from 'react-router-dom' +import { Query, useClient } from 'cozy-client' +import Alerter from 'cozy-ui/transpiled/react/deprecated/Alerter' +import { FixedDialog } from 'cozy-ui/transpiled/react/CozyDialogs' +import { useI18n } from 'cozy-ui/transpiled/react' +import { uploadFilesFromNative } from 'drive/web/modules/upload' +import { ROOT_DIR_ID } from 'drive/constants/config' +import Header from 'drive/web/modules/move/Header' +import Explorer from 'drive/web/modules/move/Explorer' +import FileList from 'drive/web/modules/move/FileList' +import Loader from 'drive/web/modules/move/Loader' +import LoadMore from 'drive/web/modules/move/LoadMore' +import Footer from 'drive/web/modules/move/Footer' +import Topbar from 'drive/web/modules/move/Topbar' +import { startMediaBackup } from 'drive/mobile/modules/mediaBackup/duck' +import { buildMoveOrImportQuery, buildOnlyFolderQuery } from '../../queries' +import { generateForQueue } from './UploadUtils' +import { DumbUploadProps, Folder } from './UploadTypes' +import { ThunkDispatch } from 'redux-thunk' +import { useUploadFromFlagship } from './useUploadFromFlagship' + +const TypedUseI18n = useI18n as () => { + t: (str: string, arg?: Record) => string +} +const TypedAlerter = Alerter as { success: (str: string) => void } +const TypedLoadMore = LoadMore as React.FC<{ + hasMore: boolean + fetchMore: () => void +}> + +const DumbUpload = ({ + uploadFilesFromNative +}: DumbUploadProps): JSX.Element | null => { + const [folder, setFolder] = useState({ _id: ROOT_DIR_ID }) + const [uploadInProgress] = useState(false) + const { t } = TypedUseI18n() + const navigate = useNavigate() + const client = useClient() + const { items, resetFilesToHandle, uploadFilesFromFlagship } = + useUploadFromFlagship() + + const callbackSuccess = useCallback(() => { + TypedAlerter.success( + t('ImportToDrive.success', { smart_count: items?.length }) + ) + + resetFilesToHandle() + .then(() => { + return console.log('resetFilesToHandle done') + }) + .catch(e => { + console.log('resetFilesToHandle error', e) + }) + }, [items?.length, resetFilesToHandle, t]) + + const onClose = useCallback(() => { + void localforage.removeItem('importedFiles') + navigate('/') + }, [navigate]) + + const uploadFiles = useCallback(() => { + if (!items || items.length === 0 || !client) return + + const filesForQueue = generateForQueue(items) + uploadFilesFromNative( + filesForQueue, + folder._id, + callbackSuccess, + { client }, + uploadFilesFromFlagship + ) + .then(() => { + return console.log('uploadFilesFromNative done') + }) + .catch(e => { + console.log('uploadFilesFromNative error', e) + }) + + setTimeout(() => navigate(`/folder/${folder._id}`), 50) + }, [ + items, + client, + uploadFilesFromNative, + folder._id, + callbackSuccess, + uploadFilesFromFlagship, + navigate + ]) + + const contentQuery = buildMoveOrImportQuery(folder._id) + const folderQuery = buildOnlyFolderQuery(folder._id) + + if (!items || items.length === 0) { + return null + } + + return ( + +
+ + {({ + data, + fetchStatus + }: { + data: Record[] + fetchStatus: string + }): JSX.Element => ( + + )} + + + } + content={ + + {({ + data, + fetchStatus, + hasMore, + fetchMore + }: { + data: Record[] + fetchStatus: string + hasMore: boolean + fetchMore: () => void + }): JSX.Element => ( + + +
+ + +
+
+
+ )} +
+ } + actions={ +