Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📁 added drive file preview modal #2748

Merged
merged 6 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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} />;
}
};
Loading