diff --git a/src/components/FolderPicker/FolderPickerAddFolderItem.tsx b/src/components/FolderPicker/FolderPickerAddFolderItem.tsx new file mode 100644 index 0000000000..0d915fd4e9 --- /dev/null +++ b/src/components/FolderPicker/FolderPickerAddFolderItem.tsx @@ -0,0 +1,87 @@ +import React, { FC } from 'react' +import { useDispatch } from 'react-redux' + +import { useClient } from 'cozy-client' +import { useVaultClient } from 'cozy-keys-lib' +import Divider from 'cozy-ui/transpiled/react/Divider' +import Icon from 'cozy-ui/transpiled/react/Icon' +import IconFolder from 'cozy-ui/transpiled/react/Icons/FileTypeFolder' +import ListItem from 'cozy-ui/transpiled/react/ListItem' +import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon' +import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert' +import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints' +import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' + +import FilenameInput from 'modules/filelist/FilenameInput' +import { createFolder } from 'modules/navigation/duck' +import IconEncryptedFolder from 'modules/views/Folder/EncryptedFolderIcon' + +interface FolderPickerAddFolderItemProps { + isEncrypted: boolean + currentFolderId: string + visible: boolean + afterSubmit: () => void + afterAbort: () => void +} + +const FolderPickerAddFolderItem: FC = ({ + isEncrypted, + currentFolderId, + visible, + afterSubmit, + afterAbort +}) => { + const { isMobile } = useBreakpoints() + const gutters = isMobile ? 'default' : 'double' + const dispatch = useDispatch() + const { showAlert } = useAlert() + const { t } = useI18n() + const vaultClient = useVaultClient() + const client = useClient() + + const handleSubmit = (name: string): void => { + dispatch( + createFolder(client, vaultClient, name, currentFolderId, { + isEncryptedFolder: isEncrypted, + showAlert, + t + }) + ) + if (typeof afterSubmit === 'function') { + afterSubmit() + } + } + + const handleAbort = (accidental: boolean): void => { + if (accidental) { + showAlert({ + message: t('alert.folder_abort'), // + severity: 'secondary' + }) + } + if (typeof afterAbort === 'function') { + afterAbort() + } + } + + if (visible) { + return ( + <> + + + + + + + + + ) + } + + return null +} + +export { FolderPickerAddFolderItem } diff --git a/src/components/FolderPicker/FolderPickerContentCozy.tsx b/src/components/FolderPicker/FolderPickerContentCozy.tsx index 4461ac5748..030b621a97 100644 --- a/src/components/FolderPicker/FolderPickerContentCozy.tsx +++ b/src/components/FolderPicker/FolderPickerContentCozy.tsx @@ -1,18 +1,19 @@ import React, { useMemo } from 'react' import { useQuery, useClient } from 'cozy-client' +import { isDirectory } from 'cozy-client/dist/models/file' import { IOCozyFile } from 'cozy-client/types/types' +import List from 'cozy-ui/transpiled/react/List' -import { FolderPickerContentExplorer } from 'components/FolderPicker/FolderPickerContentExplorer' +import { FolderPickerListItem } from './FolderPickerListItem' +import { FolderPickerAddFolderItem } from 'components/FolderPicker/FolderPickerAddFolderItem' import { FolderPickerContentLoadMore } from 'components/FolderPicker/FolderPickerContentLoadMore' import { FolderPickerContentLoader } from 'components/FolderPicker/FolderPickerContentLoader' import { isInvalidMoveTarget } from 'components/FolderPicker/helpers' import { computeNextcloudRootFolder } from 'components/FolderPicker/helpers' -import { FolderPickerEntry } from 'components/FolderPicker/types' +import type { File, FolderPickerEntry } from 'components/FolderPicker/types' import { ROOT_DIR_ID } from 'constants/config' import { isEncryptedFolder } from 'lib/encryption' -import { AddFolderWithoutState } from 'modules/filelist/AddFolder' -import { DumbFile as File } from 'modules/filelist/File' import { FolderUnlocker } from 'modules/folder/components/FolderUnlocker' import { buildMoveOrImportQuery, @@ -92,12 +93,13 @@ const FolderPickerContentCozy: React.FC = ({ navigateTo(parentFolder.data) } - const handleFolderOpen = (folder: IOCozyFile): void => { - navigateTo(folder) - } + const handleClick = (file: File): void => { + if (isDirectory(file)) { + navigateTo(file) + } - const handleFileOpen = ({ file }: { file: IOCozyFile }): void => { if ( + file._type === 'io.cozy.files' && file.cozyMetadata?.createdByApp === 'nextcloud' && file.cozyMetadata.sourceAccount ) { @@ -110,8 +112,8 @@ const FolderPickerContentCozy: React.FC = ({ } return ( - - + = ({ hasNoData={files.length === 0} > - {files.map(file => ( - ( + ))} - + ) } diff --git a/src/components/FolderPicker/FolderPickerContentExplorer.tsx b/src/components/FolderPicker/FolderPickerContentExplorer.tsx deleted file mode 100644 index 51b020b1ac..0000000000 --- a/src/components/FolderPicker/FolderPickerContentExplorer.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { ReactNode } from 'react' - -import { Table } from 'cozy-ui/transpiled/react/deprecated/Table' - -import FileListBody from 'modules/filelist/FileListBody' -import { FileListHeader } from 'modules/filelist/FileListHeader' - -interface FolderPickerContentExplorerProps { - children: ReactNode -} - -const FolderPickerContentExplorer: React.FC< - FolderPickerContentExplorerProps -> = ({ children }) => ( - - - {children} -
-) - -export { FolderPickerContentExplorer } diff --git a/src/components/FolderPicker/FolderPickerContentLoader.tsx b/src/components/FolderPicker/FolderPickerContentLoader.tsx index 1b83701eb6..1d01331ab9 100644 --- a/src/components/FolderPicker/FolderPickerContentLoader.tsx +++ b/src/components/FolderPicker/FolderPickerContentLoader.tsx @@ -1,8 +1,10 @@ import React, { ReactNode } from 'react' +import ListItemSkeleton from 'cozy-ui/transpiled/react/Skeletons/ListItemSkeleton' +import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints' + import { EmptyDrive } from 'components/Error/Empty' import Oops from 'components/Error/Oops' -import FileListRowsPlaceholder from 'modules/filelist/FileListRowsPlaceholder' interface FolderPickerContentLoaderProps { fetchStatus: string @@ -15,7 +17,22 @@ const FolderPickerContentLoader: React.FC = ({ hasNoData, children }) => { - if (fetchStatus === 'loading') return + const { isMobile } = useBreakpoints() + const gutters = isMobile ? 'default' : 'double' + + if (fetchStatus === 'loading') + return ( + <> + {Array.from({ length: 8 }, (_, index) => ( + + ))} + + ) else if (fetchStatus === 'failed') return else if (fetchStatus === 'loaded' && hasNoData) return diff --git a/src/components/FolderPicker/FolderPickerContentNextcloud.tsx b/src/components/FolderPicker/FolderPickerContentNextcloud.tsx index 508186b0ce..f465ae50e2 100644 --- a/src/components/FolderPicker/FolderPickerContentNextcloud.tsx +++ b/src/components/FolderPicker/FolderPickerContentNextcloud.tsx @@ -1,18 +1,20 @@ import React from 'react' import { useQuery } from 'cozy-client' +import { isDirectory } from 'cozy-client/dist/models/file' import { NextcloudFile } from 'cozy-client/types/types' +import List from 'cozy-ui/transpiled/react/List' -import { FolderPickerContentExplorer } from 'components/FolderPicker/FolderPickerContentExplorer' +import { FolderPickerListItem } from './FolderPickerListItem' import { FolderPickerContentLoader } from 'components/FolderPicker/FolderPickerContentLoader' import { isInvalidMoveTarget } from 'components/FolderPicker/helpers' -import { DumbFile as File } from 'modules/filelist/File' +import type { File, FolderPickerEntry } from 'components/FolderPicker/types' import { buildNextcloudFolderQuery } from 'queries' interface Props { folder: NextcloudFile - entries: import('./types').File[] // Update with the appropriate type - navigateTo: (folder?: import('./types').File) => void // Update with the appropriate type + entries: FolderPickerEntry[] // Update with the appropriate type + navigateTo: (folder: import('./types').File) => void // Update with the appropriate type } const FolderPickerContentNextcloud: React.FC = ({ @@ -33,41 +35,31 @@ const FolderPickerContentNextcloud: React.FC = ({ data?: NextcloudFile[] } - const handleFolderOpen = (folder: import('./types').File): void => { - navigateTo(folder) - } + const files = data ?? [] - const handleFileOpen = (): void => { - // Do nothing + const handleClick = (file: File): void => { + if (isDirectory(file)) { + navigateTo(file) + } } - const files = data ?? [] - return ( - + - {files.map(file => ( - ( + ))} - + ) } diff --git a/src/components/FolderPicker/FolderPickerListItem.tsx b/src/components/FolderPicker/FolderPickerListItem.tsx new file mode 100644 index 0000000000..6f62dece4f --- /dev/null +++ b/src/components/FolderPicker/FolderPickerListItem.tsx @@ -0,0 +1,72 @@ +import { filesize } from 'filesize' +import React, { FC } from 'react' + +import { isDirectory } from 'cozy-client/dist/models/file' +import Divider from 'cozy-ui/transpiled/react/Divider' +import ListItem from 'cozy-ui/transpiled/react/ListItem' +import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon' +import ListItemText from 'cozy-ui/transpiled/react/ListItemText' +import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints' +import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' + +import type { File } from 'components/FolderPicker/types' +import FileThumbnail from 'modules/filelist/icons/FileThumbnail' + +import styles from 'styles/folder-picker.styl' + +interface FolderPickerListItemProps { + file: File + disabled?: boolean + onClick: (file: File) => void + showDivider?: boolean +} + +const FolderPickerListItem: FC = ({ + file, + disabled = false, + onClick, + showDivider = false +}) => { + const { f, t } = useI18n() + const { isMobile } = useBreakpoints() + const gutters = isMobile ? 'default' : 'double' + + const handleClick = (): void => { + onClick(file) + } + + const formattedUpdatedAt = f(file.updated_at, t('table.row_update_format')) + const formattedSize = file.size + ? filesize(file.size, { base: 10 }) + : undefined + const secondaryText = !isDirectory(file) + ? `${formattedUpdatedAt}${formattedSize ? ` - ${formattedSize}` : ''}` + : undefined + + return ( + <> + + + + + + + {showDivider && } + + ) +} + +export { FolderPickerListItem } diff --git a/src/components/FolderPicker/helpers.ts b/src/components/FolderPicker/helpers.ts index 30bfdabe16..4ab38181af 100644 --- a/src/components/FolderPicker/helpers.ts +++ b/src/components/FolderPicker/helpers.ts @@ -101,7 +101,9 @@ export const computeNextcloudRootFolder = ({ type: 'directory', links: { self: 'unknown' - } + }, + size: 0, + updated_at: new Date().toISOString() }) /** diff --git a/src/declarations.d.ts b/src/declarations.d.ts index 9be35508f3..0956a72bb5 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -25,6 +25,7 @@ declare module 'cozy-ui/transpiled/react' { declare module 'cozy-ui/transpiled/react/providers/I18n' { export const useI18n: () => { t: (key: string, options?: Record) => string + f: (date: string, format: string) => string lang: string } } @@ -76,6 +77,9 @@ declare module 'cozy-client/dist/models/file' { file: Partial, destination: import('components/FolderPicker/types').File ) => Promise + export const isDirectory: ( + file: import('components/FolderPicker/types').File + ) => boolean } declare module '*.svg' { @@ -124,3 +128,22 @@ declare module 'cozy-ui/transpiled/react/Nav' { export const NavItem: React.ComponentType export const NavLink: { className: string; activeClassName: string } } + +declare module 'cozy-ui/transpiled/react/Typography' { + const Typography: React.ComponentType<{ + variant?: string + color?: string + noWrap?: boolean + className?: string + }> + export default Typography +} + +declare module 'cozy-keys-lib' { + export const useVaultClient: () => object +} + +declare module '*.styl' { + const content: Record + export default content +} diff --git a/src/modules/filelist/AddFolder.jsx b/src/modules/filelist/AddFolder.jsx index 0a45c6e1a5..b45fcc084f 100644 --- a/src/modules/filelist/AddFolder.jsx +++ b/src/modules/filelist/AddFolder.jsx @@ -66,7 +66,7 @@ const createFolderAfterSubmit = }) } -const mapDispatchToProps = (dispatch, ownProps) => ({ +export const addFolderDispatch = (dispatch, ownProps) => ({ onSubmit: (name, showAlert, t) => dispatch(createFolderAfterSubmit(ownProps, name, { showAlert, t })), onAbort: (accidental, showAlert, t) => { @@ -84,12 +84,12 @@ const mapDispatchToProps = (dispatch, ownProps) => ({ const AddFolderWithoutState = compose( withClient, - connect(null, mapDispatchToProps) + connect(null, addFolderDispatch) )(AddFolder) const AddFolderWithState = compose( withClient, - connect(mapStateToProps, mapDispatchToProps) + connect(mapStateToProps, addFolderDispatch) )(AddFolder) const AddFolderWithAfter = ({ refreshFolderContent, ...props }) => { diff --git a/src/styles/folder-picker.styl b/src/styles/folder-picker.styl new file mode 100644 index 0000000000..03f24170d7 --- /dev/null +++ b/src/styles/folder-picker.styl @@ -0,0 +1,4 @@ +.icon-shared + position absolute + bottom -.375rem + right -.375rem