From 05a76586559d6f35d39926c8e6216a8e6151e417 Mon Sep 17 00:00:00 2001 From: Khaled FERJANI Date: Wed, 15 Feb 2023 17:07:42 +0100 Subject: [PATCH 1/6] added drive file preview modal --- .../features/drive/hooks/use-drive-preview.ts | 79 +++++++++++++ .../src/app/features/drive/state/viewer.ts | 14 +++ .../applications/viewer/drive-preview.tsx | 110 ++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts create mode 100644 twake/frontend/src/app/features/drive/state/viewer.ts create mode 100644 twake/frontend/src/app/views/applications/viewer/drive-preview.tsx diff --git a/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts b/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts new file mode 100644 index 0000000000..6f05cb1e9a --- /dev/null +++ b/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts @@ -0,0 +1,79 @@ +import fileUploadApiClient from 'app/features/files/api/file-upload-api-client'; +import fileUploadService from 'app/features/files/services/file-upload-service'; +import { useGlobalEffect } from 'app/features/global/hooks/use-global-effect'; +import { LoadingState } from 'app/features/global/state/atoms/Loading'; +import { useRecoilState } from 'recoil'; +import { DriveApiClient } from '../api-client/api-client'; +import { DriveViewerState } from '../state/viewer'; +import { DriveItem } from '../types'; + +export const useDrivePreviewModal = () => { + const [status, setStatus] = useRecoilState(DriveViewerState); + + const open = (item: DriveItem) => { + if (item.last_version_cache?.file_metadata?.source === 'internal') { + setStatus({ item, loading: true }); + } + }; + + const close = () => setStatus({ item: null, loading: true }); + + return { open, close, isOpen: !!status.item }; +}; + +export const useDrivePreview = () => { + const [status, setStatus] = useRecoilState(DriveViewerState); + const { isOpen } = useDrivePreviewModal(); + + useGlobalEffect( + 'useDrivePreview', + async () => { + if (isOpen && status.item) { + setStatus({ + ...status, + loading: true, + }); + + const details = await DriveApiClient.get(status.item.company_id, status.item.id); + + setStatus({ + ...status, + details, + loading: false, + }); + } + }, + [status.item?.id], + ); + + return { + status, + loading: status.loading, + }; +}; + +export const useDrivePreviewLoading = () => { + const [loading, setLoading] = useRecoilState(LoadingState('useDrivePreviewLoading')); + + return { loading, setLoading }; +}; + +export const useDrivePreviewDisplayData = () => { + const { status } = useDrivePreview(); + + if (!status) { + return {}; + } + + const name = + status.details?.item.last_version_cache.file_metadata.name || status.details?.item.name || ''; + const extension = name.split('.').pop(); + const type = fileUploadApiClient.mimeToType(status.details?.item.last_version_cache.file_metadata.mime || '', extension); + const id = status.details?.item.last_version_cache.file_metadata.external_id; + const download = fileUploadService.getDownloadRoute({ + companyId: status.item?.company_id || '', + fileId: status.details?.item.last_version_cache.file_metadata.external_id || '' + }); + + return { download, id, name, type, extension } +}; diff --git a/twake/frontend/src/app/features/drive/state/viewer.ts b/twake/frontend/src/app/features/drive/state/viewer.ts new file mode 100644 index 0000000000..9d29efd6ff --- /dev/null +++ b/twake/frontend/src/app/features/drive/state/viewer.ts @@ -0,0 +1,14 @@ +import { atom } from 'recoil'; +import { DriveItem, DriveItemDetails } from '../types'; + +export const DriveViewerState = atom<{ + item: null | DriveItem; + details?: DriveItemDetails; + loading: boolean; +}>({ + key: "DriveViewerState", + default: { + item: null, + loading: true + } +}); diff --git a/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx b/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx new file mode 100644 index 0000000000..160ade415d --- /dev/null +++ b/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx @@ -0,0 +1,110 @@ +import { Transition } from '@headlessui/react'; +import { DownloadIcon, XIcon } from '@heroicons/react/outline'; +import { Modal } from 'app/atoms/modal'; +import { + useDrivePreview, + useDrivePreviewDisplayData, + useDrivePreviewLoading, + useDrivePreviewModal, +} from 'app/features/drive/hooks/use-drive-preview'; +import { addShortcut, removeShortcut } from 'app/features/global/services/shortcut-service'; +import { useEffect, useState } from 'react'; +import { fadeTransition } from 'src/utils/transitions'; +import Display from './display'; +import * as Text from '@atoms/text'; +import { formatDate } from 'app/features/global/utils/format-date'; +import { formatSize } from 'app/features/global/utils/format-file-size'; +import { Button } from 'app/atoms/button/button'; + +export const DrivePreview = (): React.ReactElement => { + const { isOpen, close } = useDrivePreviewModal(); + const { loading } = useDrivePreview(); + const [modalLoading, setModalLoading] = useState(true); + const { loading: loadingData } = useDrivePreviewLoading(); + let animationTimeout: number = setTimeout(() => undefined); + + useEffect(() => { + addShortcut({ shortcut: 'esc', handler: close }); + + return () => { + removeShortcut({ shortcut: 'esc', handler: close }); + }; + }, []); + + useEffect(() => { + clearTimeout(animationTimeout); + + if (loading) { + animationTimeout = window.setTimeout(() => { + setModalLoading(false); + }, 400); + } + }, [loading]); + + return ( + + close()} + /> + + + + + + ); +}; + +const Footer = (): React.ReactElement => { + const { status } = useDrivePreview(); + const { download, extension } = useDrivePreviewDisplayData(); + + const name = status.details?.item.name; + + return ( + <> +
+
+ + {name} + + + {formatDate( + +(status.details?.item.added || '') || + status.details?.item.last_version_cache.date_added, + )}{' '} + • {extension?.toLocaleUpperCase()},{' '} + {formatSize( + status.details?.item.last_version_cache.file_metadata.size || + status.details?.item.size, + )} + +
+
+ {/* controls here */} +
+
+ + ); +}; From 928eefdb55d41c48c139f07dc878f324a9ab9867 Mon Sep 17 00:00:00 2001 From: Khaled FERJANI Date: Wed, 15 Feb 2023 17:37:57 +0100 Subject: [PATCH 2/6] link components --- .../features/drive/hooks/use-drive-preview.ts | 2 +- .../app/views/applications/viewer/display.tsx | 4 +- .../applications/viewer/drive-display.tsx | 50 +++++++++++++++++++ .../applications/viewer/images/display.tsx | 12 +++-- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 twake/frontend/src/app/views/applications/viewer/drive-display.tsx diff --git a/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts b/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts index 6f05cb1e9a..2ea378a8c6 100644 --- a/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts +++ b/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts @@ -69,7 +69,7 @@ export const useDrivePreviewDisplayData = () => { status.details?.item.last_version_cache.file_metadata.name || status.details?.item.name || ''; const extension = name.split('.').pop(); const type = fileUploadApiClient.mimeToType(status.details?.item.last_version_cache.file_metadata.mime || '', extension); - const id = status.details?.item.last_version_cache.file_metadata.external_id; + const id = status.details?.item.last_version_cache.file_metadata.external_id || ''; const download = fileUploadService.getDownloadRoute({ companyId: status.item?.company_id || '', fileId: status.details?.item.last_version_cache.file_metadata.external_id || '' diff --git a/twake/frontend/src/app/views/applications/viewer/display.tsx b/twake/frontend/src/app/views/applications/viewer/display.tsx index d80ed60182..f31ad90019 100644 --- a/twake/frontend/src/app/views/applications/viewer/display.tsx +++ b/twake/frontend/src/app/views/applications/viewer/display.tsx @@ -1,5 +1,6 @@ import { useFileViewerModal, + useViewerDataLoading, useViewerDisplayData, } from 'app/features/viewer/hooks/use-viewer'; import ImageDisplay from './images/display'; @@ -12,13 +13,14 @@ import OtherDisplay from './other/display'; export default () => { const { download, type, name, id } = useViewerDisplayData(); const { isOpen } = useFileViewerModal(); + const { loading, setLoading } = useViewerDataLoading(); if (!download || !isOpen) { return <>; } if (type === 'image') { - return ; + return ; } if (type === 'video' || type === 'audio') { diff --git a/twake/frontend/src/app/views/applications/viewer/drive-display.tsx b/twake/frontend/src/app/views/applications/viewer/drive-display.tsx new file mode 100644 index 0000000000..19a506352f --- /dev/null +++ b/twake/frontend/src/app/views/applications/viewer/drive-display.tsx @@ -0,0 +1,50 @@ +import { + useDrivePreviewDisplayData, + useDrivePreviewLoading, + useDrivePreviewModal, +} from 'app/features/drive/hooks/use-drive-preview'; +import ImageDisplay from './images/display'; +import VideoDisplay from './videos/display'; +import PdfDisplay from './pdf/display'; +import CodeDisplay from './code/display'; +import ArchiveDisplay from './archive/display'; +import OtherDisplay from './other/display'; + +export default (): React.ReactElement => { + const { download, type, name, id } = useDrivePreviewDisplayData(); + const { isOpen } = useDrivePreviewModal(); + const { loading, setLoading } = useDrivePreviewLoading(); + + if (!download || !isOpen) { + return <>; + } + + if (!type) { + return ( +
+ We can't display this document. +
+ ); + } + + switch (type) { + case 'image': + return ; + + case 'video': + case 'audio': + return ; + + case 'code': + return ; + + case 'archive': + return ; + + case 'pdf': + return ; + + default: + return ; + } +}; diff --git a/twake/frontend/src/app/views/applications/viewer/images/display.tsx b/twake/frontend/src/app/views/applications/viewer/images/display.tsx index 8a3cf89140..9372ff782c 100644 --- a/twake/frontend/src/app/views/applications/viewer/images/display.tsx +++ b/twake/frontend/src/app/views/applications/viewer/images/display.tsx @@ -1,4 +1,3 @@ -import { useViewerDataLoading } from 'app/features/viewer/hooks/use-viewer'; import { useEffect, useState } from 'react'; import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; @@ -12,13 +11,18 @@ let imageControls: { rotateCw: () => undefined, }; +type PropsType = { + download: string; + loading: boolean; + setLoading: (state: boolean) => void +} + export const getImageControls = () => { return imageControls; }; -export default (props: { download: string }) => { +export default ({ download, loading, setLoading}: PropsType) => { const [rotated, setRotated] = useState(0); - const { loading, setLoading } = useViewerDataLoading(); useEffect(() => { setLoading(true); @@ -39,7 +43,7 @@ export default (props: { download: string }) => { height: '90vh', opacity: loading ? 0 : 1, }} - src={props.download} + src={download} onLoad={() => { zoomOut(); setLoading(false); From 1855715a0fd184d90c0de25f21503db2932bafcb Mon Sep 17 00:00:00 2001 From: Khaled FERJANI Date: Wed, 15 Feb 2023 17:39:05 +0100 Subject: [PATCH 3/6] use drive display --- .../src/app/views/applications/viewer/drive-preview.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx b/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx index 160ade415d..50f731c15b 100644 --- a/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx +++ b/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx @@ -10,11 +10,11 @@ import { import { addShortcut, removeShortcut } from 'app/features/global/services/shortcut-service'; import { useEffect, useState } from 'react'; import { fadeTransition } from 'src/utils/transitions'; -import Display from './display'; import * as Text from '@atoms/text'; import { formatDate } from 'app/features/global/utils/format-date'; import { formatSize } from 'app/features/global/utils/format-file-size'; import { Button } from 'app/atoms/button/button'; +import DriveDisplay from './drive-display'; export const DrivePreview = (): React.ReactElement => { const { isOpen, close } = useDrivePreviewModal(); @@ -60,7 +60,8 @@ export const DrivePreview = (): React.ReactElement => { className="absolute m-auto w-8 h-8 left-0 right-0 top-0 bottom-0" {...fadeTransition} > - + +