From bdfa3cb851461f39f6406e1d16448e86a5ce3b79 Mon Sep 17 00:00:00 2001 From: Romaric Mourgues Date: Tue, 28 Mar 2023 10:58:48 +0200 Subject: [PATCH] Fix upload files / folders --- twake/frontend/src/app/atoms/alert/index.tsx | 2 +- .../src/app/atoms/animated-height/index.tsx | 49 ++++++ .../atoms/input/input-decoration-label.tsx | 2 +- .../src/app/atoms/input/input-text.tsx | 4 +- twake/frontend/src/app/atoms/modal/index.tsx | 2 +- .../edit-channel/channel-information.tsx | 2 +- .../edit-channel/channel-settings-menu.tsx | 12 +- .../invitation/parts/reached-limit.tsx | 2 +- .../app/components/uploads/upload-zone.tsx | 17 +- .../app/views/client/body/drive/browser.tsx | 2 +- .../client/body/drive/modals/create/index.tsx | 24 ++- .../drive/modals/update-access/common.tsx | 2 +- .../src/app/views/client/side-bar/actions.tsx | 159 ++++++++++-------- .../src/app/views/client/side-bar/index.tsx | 2 +- twake/frontend/tailwind.config.js | 2 +- 15 files changed, 189 insertions(+), 94 deletions(-) create mode 100644 twake/frontend/src/app/atoms/animated-height/index.tsx diff --git a/twake/frontend/src/app/atoms/alert/index.tsx b/twake/frontend/src/app/atoms/alert/index.tsx index 37bc8902..01dcf76b 100644 --- a/twake/frontend/src/app/atoms/alert/index.tsx +++ b/twake/frontend/src/app/atoms/alert/index.tsx @@ -10,7 +10,7 @@ export const Alert = (props: { let color = 'blue'; let textColor = 'white'; if (props.theme === 'success') color = 'green-500'; - if (props.theme === 'danger') color = 'red-500'; + if (props.theme === 'danger') color = 'rose-500'; if (props.theme === 'warning') color = 'orange-500'; if (props.theme === 'gray') { color = 'zinc-50'; diff --git a/twake/frontend/src/app/atoms/animated-height/index.tsx b/twake/frontend/src/app/atoms/animated-height/index.tsx new file mode 100644 index 00000000..fe553783 --- /dev/null +++ b/twake/frontend/src/app/atoms/animated-height/index.tsx @@ -0,0 +1,49 @@ +import _ from "lodash"; +import { + InputHTMLAttributes, + memo, + ReactNode, + useCallback, + useEffect, + useRef, +} from "react"; + +let interval: any = null; + +export const AnimatedHeight = memo( + (props: { children: ReactNode } & InputHTMLAttributes) => { + const el = useRef(null); + + const updateSize = useCallback(() => { + if (el.current) { + const contentHeight = el.current.scrollHeight; + const parent = el.current.parentNode as HTMLDivElement; + parent.style.height = `${contentHeight}px`; + parent.style.overflow = `hidden`; + } + }, [el]); + + useEffect(() => { + interval = setInterval(() => { + updateSize(); + }, 200); + return () => { + clearInterval(interval); + }; + }, []); + + return ( +
+
+ {props.children} +
+
+ ); + } +); diff --git a/twake/frontend/src/app/atoms/input/input-decoration-label.tsx b/twake/frontend/src/app/atoms/input/input-decoration-label.tsx index 8af12ffc..97f0e508 100644 --- a/twake/frontend/src/app/atoms/input/input-decoration-label.tsx +++ b/twake/frontend/src/app/atoms/input/input-decoration-label.tsx @@ -16,7 +16,7 @@ export const InputLabel = (props: InputLabelProps) => {
{props.input}
{props.feedback && ( - + {props.feedback} )} diff --git a/twake/frontend/src/app/atoms/input/input-text.tsx b/twake/frontend/src/app/atoms/input/input-text.tsx index 49110e08..437a8e7a 100644 --- a/twake/frontend/src/app/atoms/input/input-text.tsx +++ b/twake/frontend/src/app/atoms/input/input-text.tsx @@ -35,8 +35,8 @@ export const errorInputClassName = (theme: 'plain' | 'outline' = 'plain') => { return ( baseInputClassName + (theme === 'plain' - ? 'bg-red-200 border-red-200 dark:bg-red-800 dark:border-red-800' - : 'bg-red-50 border-red-300 dark:bg-red-900 dark:border-red-800') + ? 'bg-rose-200 border-rose-200 dark:bg-rose-800 dark:border-rose-800' + : 'bg-rose-50 border-rose-300 dark:bg-rose-900 dark:border-rose-800') ); }; diff --git a/twake/frontend/src/app/atoms/modal/index.tsx b/twake/frontend/src/app/atoms/modal/index.tsx index c8570d07..09a25643 100644 --- a/twake/frontend/src/app/atoms/modal/index.tsx +++ b/twake/frontend/src/app/atoms/modal/index.tsx @@ -155,7 +155,7 @@ export const ModalContent = (props: { }) => { let color = 'blue'; if (props.theme === 'success') color = 'green'; - if (props.theme === 'danger') color = 'red'; + if (props.theme === 'danger') color = 'rose'; if (props.theme === 'warning') color = 'orange'; if (props.theme === 'gray') color = 'gray'; return ( diff --git a/twake/frontend/src/app/components/edit-channel/channel-information.tsx b/twake/frontend/src/app/components/edit-channel/channel-information.tsx index 27fca977..51fa20c2 100644 --- a/twake/frontend/src/app/components/edit-channel/channel-information.tsx +++ b/twake/frontend/src/app/components/edit-channel/channel-information.tsx @@ -143,7 +143,7 @@ export const ChannelInformationForm = (props: {
{!!icon && ( { setIcon(''); }} diff --git a/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx b/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx index b913a3c7..a632315b 100644 --- a/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx +++ b/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx @@ -210,9 +210,9 @@ const RemoveBlock = (props: { channel?: ChannelType; onLeave?: Function }) => { props.onLeave && props.onLeave(); }); }} - className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-red-50 dark:hover:bg-red-800" + className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-rose-50 dark:hover:bg-rose-800" title={ - + {Languages.t('scenes.app.channelsbar.channel_removing')} } @@ -221,7 +221,7 @@ const RemoveBlock = (props: { channel?: ChannelType; onLeave?: Function }) => { loading ? ( ) : ( - + ) } /> @@ -280,9 +280,9 @@ const LeaveBlock = (props: { channel?: ChannelType; onLeave?: Function }) => { return leaveChannel(); }} - className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-red-50 dark:hover:bg-red-800" + className="-mx-2 my-1 p-2 rounded-md cursor-pointer hover:bg-rose-50 dark:hover:bg-rose-800" title={ - + {Languages.t('scenes.app.channelsbar.channel_leaving')} } @@ -291,7 +291,7 @@ const LeaveBlock = (props: { channel?: ChannelType; onLeave?: Function }) => { loading ? ( ) : ( - + ) } /> diff --git a/twake/frontend/src/app/components/invitation/parts/reached-limit.tsx b/twake/frontend/src/app/components/invitation/parts/reached-limit.tsx index 3e9a63b1..e3a2a688 100644 --- a/twake/frontend/src/app/components/invitation/parts/reached-limit.tsx +++ b/twake/frontend/src/app/components/invitation/parts/reached-limit.tsx @@ -4,7 +4,7 @@ import React from 'react'; export default (): React.ReactElement => { return ( - + {Languages.t( 'components.invitation.reached_limit.text', [], diff --git a/twake/frontend/src/app/components/uploads/upload-zone.tsx b/twake/frontend/src/app/components/uploads/upload-zone.tsx index ef6e531b..f6b5d509 100755 --- a/twake/frontend/src/app/components/uploads/upload-zone.tsx +++ b/twake/frontend/src/app/components/uploads/upload-zone.tsx @@ -21,6 +21,8 @@ type FileInputType = any; type FileObjectType = { [key: string]: any }; let sharedFileInput: any = null; +let sharedFolderInput: any = null; + export default class UploadZone extends React.Component { file_input: FileInputType = {}; stopHoverTimeout: ReturnType | undefined; @@ -41,7 +43,10 @@ export default class UploadZone extends React.Component { componentDidMount() { this.node && this.watch(this.node, document.body); - if (!sharedFileInput) { + if ( + (this.props.directory && !sharedFolderInput) || + (!this.props.directory && !sharedFileInput) + ) { this.file_input = document.createElement('input'); this.file_input.type = 'file'; this.file_input.style.position = 'absolute'; @@ -49,14 +54,18 @@ export default class UploadZone extends React.Component { this.file_input.style.left = '-10000px'; this.file_input.style.width = '100px'; this.file_input.multiple = this.props.multiple ? true : false; - this.file_input.directory = this.props.multiple ? true : false; - this.file_input.webkitdirectory = this.props.multiple ? true : false; + this.file_input.directory = this.props.directory ? true : false; + this.file_input.webkitdirectory = this.props.directory ? true : false; this.setCallback(); document.body.appendChild(this.file_input); - sharedFileInput = this.file_input; + if (this.props.directory) { + sharedFolderInput = this.file_input; + } else { + sharedFileInput = this.file_input; + } } else { this.file_input = sharedFileInput; } diff --git a/twake/frontend/src/app/views/client/body/drive/browser.tsx b/twake/frontend/src/app/views/client/body/drive/browser.tsx index 02895ae6..f42665b6 100644 --- a/twake/frontend/src/app/views/client/body/drive/browser.tsx +++ b/twake/frontend/src/app/views/client/body/drive/browser.tsx @@ -21,7 +21,7 @@ import { FolderRow } from './documents/folder-row'; import HeaderPath from './header-path'; import { ConfirmDeleteModal } from './modals/confirm-delete'; import { ConfirmTrashModal } from './modals/confirm-trash'; -import { CreateModal, CreateModalAtom } from './modals/create'; +import { CreateModalAtom } from './modals/create'; import { PropertiesModal } from './modals/properties'; import { AccessModal } from './modals/update-access'; import { VersionsModal } from './modals/versions'; diff --git a/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx index c28d57b9..b73d9085 100644 --- a/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx +++ b/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx @@ -1,11 +1,16 @@ import { Transition } from '@headlessui/react'; -import { ChevronLeftIcon, DesktopComputerIcon } from '@heroicons/react/outline'; -import { FolderIcon } from '@heroicons/react/solid'; +import { + ChevronLeftIcon, + DesktopComputerIcon, + DocumentDownloadIcon, + FolderAddIcon, + FolderDownloadIcon, + FolderIcon, +} from '@heroicons/react/outline'; import Avatar from 'app/atoms/avatar'; import A from 'app/atoms/link'; import { Modal, ModalContent } from 'app/atoms/modal'; import { Base } from 'app/atoms/text'; -import { useApplications } from 'app/features/applications/hooks/use-applications'; import { useCompanyApplications } from 'app/features/applications/hooks/use-company-applications'; import { Application } from 'app/features/applications/types/application'; import { ReactNode } from 'react'; @@ -29,9 +34,11 @@ export const CreateModalAtom = atom({ export const CreateModal = ({ selectFromDevice, + selectFolderFromDevice, addFromUrl, }: { selectFromDevice: () => void; + selectFolderFromDevice: () => void; addFromUrl: (url: string, name: string) => void; }) => { const [state, setState] = useRecoilState(CreateModalAtom); @@ -72,15 +79,20 @@ export const CreateModal = ({ >
} + icon={} text="Create a folder" onClick={() => setState({ ...state, type: 'folder' })} /> } - text="Upload document from device" + icon={} + text="Upload files from device" onClick={() => selectFromDevice()} /> + } + text="Upload folders from device" + onClick={() => selectFolderFromDevice()} + /> {(applications || []) .filter(app => app.display?.twake?.files?.editor?.empty_files?.length) diff --git a/twake/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx b/twake/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx index 0507cba4..95f6671e 100644 --- a/twake/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx +++ b/twake/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx @@ -22,7 +22,7 @@ export const AccessLevel = ({ className={ className + ' w-auto ' + - (level === 'none' ? '!text-red-500 !bg-red-100 dark-bg-red-800' : '') + (level === 'none' ? '!text-rose-500 !bg-rose-100 dark-bg-rose-800' : '') } value={level || 'none'} onChange={e => onChange(e.target.value as DriveFileAccessLevel & 'remove')} diff --git a/twake/frontend/src/app/views/client/side-bar/actions.tsx b/twake/frontend/src/app/views/client/side-bar/actions.tsx index 0d583572..74b5af2e 100644 --- a/twake/frontend/src/app/views/client/side-bar/actions.tsx +++ b/twake/frontend/src/app/views/client/side-bar/actions.tsx @@ -2,6 +2,7 @@ import { Button } from '@atoms/button/button'; import { PlusIcon, TruckIcon, UploadIcon, XIcon } from '@heroicons/react/outline'; import { useCallback, useRef } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; +import { AnimatedHeight } from '../../../atoms/animated-height'; import { getFilesTree } from '../../../components/uploads/file-tree-utils'; import UploadZone from '../../../components/uploads/upload-zone'; import { useDriveItem } from '../../../features/drive/hooks/use-drive-item'; @@ -17,6 +18,7 @@ export default () => { const { access, item, inTrash } = useDriveItem(parentId); const { children: trashChildren } = useDriveItem('trash'); const uploadZoneRef = useRef(null); + const uploadFolderZoneRef = useRef(null); const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom); const setCreationModalState = useSetRecoilState(CreateModalAtom); @@ -27,73 +29,96 @@ export default () => { }, [item?.id, setCreationModalState]); return ( - <> - { - const tree = await getFilesTree(event); - setCreationModalState({ parent_id: '', open: false }); - uploadTree(tree, { - companyId, - parentId, - }); - }} - /> - uploadZoneRef.current?.open()} - addFromUrl={(url, name) => { - setCreationModalState({ parent_id: '', open: false }); - uploadFromUrl(url, name, { - companyId, - parentId, - }); - }} - /> +
+ +
+ { + const tree = await getFilesTree(event); + setCreationModalState({ parent_id: '', open: false }); + uploadTree(tree, { + companyId, + parentId, + }); + }} + /> + { + const tree = await getFilesTree(event); + setCreationModalState({ parent_id: '', open: false }); + uploadTree(tree, { + companyId, + parentId, + }); + }} + /> + uploadFolderZoneRef.current?.open()} + selectFromDevice={() => uploadZoneRef.current?.open()} + addFromUrl={(url, name) => { + setCreationModalState({ parent_id: '', open: false }); + uploadFromUrl(url, name, { + companyId, + parentId, + }); + }} + /> - {inTrash && access === 'manage' && ( - <> - - - )} - {!(inTrash || access === 'read') && ( - <> - - - - )} - + {inTrash && access === 'manage' && ( + <> + + + )} + {!(inTrash || access === 'read') && ( + <> + + + + )} +
+
+
); }; diff --git a/twake/frontend/src/app/views/client/side-bar/index.tsx b/twake/frontend/src/app/views/client/side-bar/index.tsx index 12bcb6b2..754cab11 100644 --- a/twake/frontend/src/app/views/client/side-bar/index.tsx +++ b/twake/frontend/src/app/views/client/side-bar/index.tsx @@ -76,7 +76,7 @@ export default () => { theme="white" className={'w-full mb-1 ' + (inTrash ? activeClass : '')} > - Trash + Trash )} diff --git a/twake/frontend/tailwind.config.js b/twake/frontend/tailwind.config.js index c700e62d..edeb7ea4 100644 --- a/twake/frontend/tailwind.config.js +++ b/twake/frontend/tailwind.config.js @@ -1,7 +1,7 @@ const defaultTheme = require('tailwindcss/defaultTheme'); let shades = []; -['zinc', 'red', 'orange', 'green', 'blue'].map(color => { +['zinc', 'rose', 'orange', 'green', 'blue'].map(color => { [50, 100, 200, 300, 400, 500, 600, 700, 800, 900].map(shade => { shades.push(`bg-${color}-${shade}`); shades.push(`border-${color}-${shade}`);