Skip to content

Commit

Permalink
馃搧 added drive file preview modal (#2748)
Browse files Browse the repository at this point in the history
* added drive file preview modal

* link components

* use drive display

* link the views

* added controls

* revert the message viewer changes
  • Loading branch information
rezk2ll committed Feb 20, 2023
1 parent ab8df96 commit 8149a77
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 148 deletions.
80 changes: 80 additions & 0 deletions twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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) => void = (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 modal = useDrivePreviewModal();

useGlobalEffect(
'useDrivePreview',
async () => {
if (modal.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 {
...modal,
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 }
};
14 changes: 14 additions & 0 deletions twake/frontend/src/app/features/drive/state/viewer.ts
Original file line number Diff line number Diff line change
@@ -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
}
});
52 changes: 0 additions & 52 deletions twake/frontend/src/app/features/viewer/api/viewer-api-client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { ChannelType } from 'app/features/channels/types/channel';
import { Thumbnail } from 'app/features/drive/types';
import { MessageFileType, MessageWithReplies } from 'app/features/messages/types/message';
import { UserType } from 'app/features/users/types/user';
import Api from '../../global/framework/api-service';
import { TwakeService } from '../../global/framework/registry-decorator-service';

const MESSAGES_PREFIX = '/internal/services/messages/v1/companies';
const FILES_PREFIX = '/internal/services/files/v1/companies';

export type MessageFileDetails = MessageFileType & {
user: UserType;
Expand All @@ -24,62 +22,12 @@ export type MessageFileDetails = MessageFileType & {
};
};

export type DrivePublicFile = {
company_id: string;
id: string;
user_id: string;
application_id: null | string;
updated_at: number;
created_at: number;
metadata: null | {
name?: string;
mime?: string;
thumbnails_status?: 'done' | 'error' | 'waiting';
external_id?: string;
size?: number;
};
thumbnails: Thumbnail[];
upload_data: null | {
size: number;
chunks: number;
};
};

export type DriveFileDetails = DrivePublicFile & {
navigation: {
previous: null | {
message_id: string;
id: string;
};
next: null | {
message_id: string;
id: string;
};
};
message?: null | any;
user?: null | any;
};

@TwakeService('ViewerAPIClientService')
class ViewerAPIClient {
async getMessageFile(companyId: string, messageId: string, msgFileId: string) {
const route = `${MESSAGES_PREFIX}/${companyId}/messages/${messageId}/files/${msgFileId}`;
return await Api.get<{ resource: MessageFileDetails }>(route);
}

async getPublicFile(companyId: string, fileId: string): Promise<{ resource: DriveFileDetails }> {
return await Api.get<{ resource: DrivePublicFile }>(
`${FILES_PREFIX}/${companyId}/files/${fileId}`,
).then(({ resource }) => ({
resource: {
...resource,
navigation: {
next: null,
previous: null,
},
},
}));
}
}

export default new ViewerAPIClient();
33 changes: 5 additions & 28 deletions twake/frontend/src/app/features/viewer/hooks/use-viewer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { useGlobalEffect } from 'app/features/global/hooks/use-global-effect';
import { MessageFileType } from 'app/features/messages/types/message';
import ViewerAPIClient, { DriveFileDetails, MessageFileDetails } from '../api/viewer-api-client';
import ViewerAPIClient, { MessageFileDetails } from '../api/viewer-api-client';
import { atom, useRecoilState } from 'recoil';
import FileUploadApiClient from 'app/features/files/api/file-upload-api-client';
import FileUploadService from 'app/features/files/services/file-upload-service';
import { LoadingState } from 'app/features/global/state/atoms/Loading';
import UserAPIClient from 'app/features/users/api/user-api-client';

export const FileViewerState = atom<{
file: null | { company_id?: string; message_id?: string; id?: string };
details?: MessageFileDetails | DriveFileDetails;
details?: MessageFileDetails;
loading: boolean;
}>({
key: 'FileViewerState',
Expand Down Expand Up @@ -45,7 +44,6 @@ export const useFileViewer = () => {
loading: true,
});

if (status.file.message_id) {
const details = await ViewerAPIClient.getMessageFile(
status.file.company_id || '',
status.file.message_id || '',
Expand All @@ -57,24 +55,6 @@ export const useFileViewer = () => {
details: details.resource || (details as unknown as MessageFileDetails),
loading: false,
});
} else {
const details = await ViewerAPIClient.getPublicFile(
status.file.company_id || '',
status.file.id || '',
);

const user = (await UserAPIClient.list([details.resource.user_id])).pop();

setStatus({
...status,
details: {
...status.file,
...details.resource,
user
},
loading: false,
});
}
}
},
[status.file?.id],
Expand Down Expand Up @@ -125,15 +105,12 @@ export const useViewerDisplayData = () => {
const extension = name?.split('.').pop();

const download = FileUploadService.getDownloadRoute({
companyId:
(status?.details as MessageFileDetails)?.metadata?.external_id?.company_id ||
status.file?.company_id,
fileId: (status?.details as MessageFileDetails)?.metadata?.external_id?.id || status.file?.id,
companyId: status?.details?.metadata?.external_id?.company_id,
fileId: status?.details?.metadata?.external_id?.id,
});

const type = FileUploadApiClient.mimeToType(status?.details?.metadata?.mime || '', extension);

const id = (status?.details as MessageFileDetails)?.metadata?.external_id?.id || status.file?.id;
const id = status?.details?.metadata?.external_id?.id;

return { download, type, name, id };
};
9 changes: 6 additions & 3 deletions twake/frontend/src/app/views/applications/drive/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { DriveItemSelectedList } from 'app/features/drive/state/store';
import { formatBytes } from 'app/features/drive/utils';
import useRouterCompany from 'app/features/router/hooks/use-router-company';
import _ from 'lodash';
import { useCallback, useEffect, useRef } from 'react';
import { Suspense, useCallback, useEffect, useRef } from 'react';
import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil';
import { DrivePreview } from '../viewer/drive-preview';
import HeaderPath from './header-path';
import { DocumentRow } from './item-row/document-row';
import { FolderRow } from './item-row/folder-row';
Expand Down Expand Up @@ -53,7 +54,7 @@ export default ({ initialParentId }: { initialParentId?: string }) => {
useEffect(() => {
setChecked({});
refresh(parentId);
refresh("trash");
refresh('trash');
}, [parentId, refresh]);

const openItemModal = useCallback(() => {
Expand Down Expand Up @@ -99,7 +100,9 @@ export default ({ initialParentId }: { initialParentId?: string }) => {
<PropertiesModal />
<ConfirmDeleteModal />
<ConfirmTrashModal />

<Suspense fallback={<></>}>
<DrivePreview />
</Suspense>
<div
className={
'flex flex-col p-4 grow h-full overflow-auto ' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import {
import { Base, BaseSmall } from 'app/atoms/text';
import Menu from 'app/components/menus/menu';
import { useDriveActions } from 'app/features/drive/hooks/use-drive-actions';
import { useDrivePreview } from 'app/features/drive/hooks/use-drive-preview';
import { formatBytes } from 'app/features/drive/utils';
import fileUploadApiClient from 'app/features/files/api/file-upload-api-client';
import { useFileViewerModal } from 'app/features/viewer/hooks/use-viewer';
import { useState } from 'react';
import { useSetRecoilState } from 'recoil';
import Avatar from '../../../../atoms/avatar';
Expand All @@ -38,7 +38,7 @@ export const DocumentRow = ({
}: DriveItemProps) => {
const [hover, setHover] = useState(false);
const { download, update } = useDriveActions();
const { open } = useFileViewerModal();
const { open } = useDrivePreview();

const setVersionModal = useSetRecoilState(VersionsModalAtom);
const setSelectorModalState = useSetRecoilState(SelectorModalAtom);
Expand All @@ -55,12 +55,7 @@ export const DocumentRow = ({
const hasThumbnails = !!metadata.thumbnails?.length || false;

const preview = () => {
open({
...item.last_version_cache,
company_id: item.company_id,
id: metadata.external_id,
metadata,
});
open(item);
};

return (
Expand Down
8 changes: 5 additions & 3 deletions twake/frontend/src/app/views/applications/viewer/controls.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useViewerDisplayData } from 'app/features/viewer/hooks/use-viewer';
import ImageControls from './images/controls';
import VideoControls from './videos/controls';
import PdfControls from './pdf/controls';
import ArchiveControls from './archive/controls';
import CodeControls from './code/controls';

export default () => {
const { type } = useViewerDisplayData();
type PropsType = {
type: string;
}

export default ({ type }: PropsType) => {

if (!type) {
return <></>;
Expand Down
4 changes: 3 additions & 1 deletion twake/frontend/src/app/views/applications/viewer/display.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
useFileViewerModal,
useViewerDataLoading,
useViewerDisplayData,
} from 'app/features/viewer/hooks/use-viewer';
import ImageDisplay from './images/display';
Expand All @@ -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 <ImageDisplay download={download} />;
return <ImageDisplay loading={loading} setLoading={setLoading} download={download} />;
}

if (type === 'video' || type === 'audio') {
Expand Down
50 changes: 50 additions & 0 deletions twake/frontend/src/app/views/applications/viewer/drive-display.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="text-white m-auto w-full text-center h-full flex items-center">
<span className="block w-full text-center">We can't display this document.</span>
</div>
);
}

switch (type) {
case 'image':
return <ImageDisplay loading={loading} setLoading={setLoading} download={download} />;

case 'video':
case 'audio':
return <VideoDisplay download={download} />;

case 'code':
return <CodeDisplay download={download} name={name} />;

case 'archive':
return <ArchiveDisplay download={download} name={name} />;

case 'pdf':
return <PdfDisplay download={download} name={name} />;

default:
return <OtherDisplay download={download} name={name} id={id} />;
}
};

0 comments on commit 8149a77

Please sign in to comment.