Skip to content

Commit

Permalink
Merge pull request #516 from fairDataSociety/feat/file-preview
Browse files Browse the repository at this point in the history
feat: new file preview
  • Loading branch information
tfius authored Sep 20, 2023
2 parents d1ef24e + b044bac commit afdcfb2
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 32 deletions.
133 changes: 113 additions & 20 deletions src/components/FilePreview/FilePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button } from '@components/Buttons';
import ConsentViewer from '@components/ConsentViewer/ConsentViewer';
import { useLocales } from '@context/LocalesContext';
import { FileItem } from '@fairdatasociety/fdp-storage';
import { FC } from 'react';
import { FC, useEffect, useState } from 'react';

interface FilePreviewProps {
file: FileItem;
Expand All @@ -13,6 +13,28 @@ interface FilePreviewProps {
onError: () => void;
}

const MAX_TEXT_PREVIEW_LENGTH = 500;

enum PreviewType {
Consent,
Text,
Video,
Image,
Unknown,
}

const TEXT_FILE_EXTENSIONS = [
'.txt',
'.srt',
'.log',
'.csv',
'.sub',
'.md',
'.json',
];

const IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.svg', '.gif', '.bmp'];

const VIDEO_FILE_EXTENSIONS = [
'.mpg',
'.mp2',
Expand All @@ -32,10 +54,31 @@ const VIDEO_FILE_EXTENSIONS = [
'.swf',
];

export function isFilePreviewSupported(fileName: string): boolean {
return isTextFile(fileName) || isFileImage(fileName) || isFileVideo(fileName);
}

function isString(data: unknown): boolean {
return typeof data === 'string';
}

function checkFileExtension(fileName: string, extensions: string[]): boolean {
const lowercaseFileName = fileName.toLowerCase();
return extensions.some((extension) => lowercaseFileName.endsWith(extension));
}

function isTextFile(fileName: string): boolean {
return checkFileExtension(fileName, TEXT_FILE_EXTENSIONS);
}

function isFileImage(fileName: string): boolean {
return checkFileExtension(fileName, IMAGE_FILE_EXTENSIONS);
}

function isFileVideo(fileName: string): boolean {
return checkFileExtension(fileName, VIDEO_FILE_EXTENSIONS);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isFileConsent(data: any): boolean {
return (
Expand All @@ -54,13 +97,6 @@ function isFileConsent(data: any): boolean {
);
}

function isFileVideo(fileName: string): boolean {
const lowercaseFileName = fileName.toLowerCase();
return VIDEO_FILE_EXTENSIONS.some((extension) =>
lowercaseFileName.endsWith(extension)
);
}

const FilePreview: FC<FilePreviewProps> = ({
file,
pod,
Expand All @@ -69,12 +105,59 @@ const FilePreview: FC<FilePreviewProps> = ({
onError,
}) => {
const { intl } = useLocales();
const [type, setType] = useState<PreviewType | null>(null);
const [content, setContent] = useState<unknown>(source);

const preparePreview = async () => {
try {
const fileName = file.name;

if (isFileVideo(fileName)) {
setContent(window.URL.createObjectURL(source));
return setType(PreviewType.Video);
}

if (isFileImage(fileName)) {
setContent(window.URL.createObjectURL(source));
return setType(PreviewType.Image);
}

if (isTextFile(fileName)) {
const text = await source.text();

if (fileName.endsWith('.json')) {
const json = JSON.parse(text);

if (isFileConsent(json)) {
setType(PreviewType.Consent);
return setContent(json);
}
}

setContent(
text.substring(0, MAX_TEXT_PREVIEW_LENGTH) +
(text.length > MAX_TEXT_PREVIEW_LENGTH ? '...' : '')
);
return setType(PreviewType.Text);
}

throw new Error('Unknown type');
} catch (error) {
setType(PreviewType.Unknown);

onError();
}
};

useEffect(() => {
preparePreview();
}, [file, source]);

if (isFileConsent(source)) {
if (type === PreviewType.Consent) {
return (
<>
<div className="w-full h-auto my-10 rounded">
<ConsentViewer data={source} />
<ConsentViewer data={content} />
</div>
{(!directory || !directory.includes('/')) && (
<div className="mb-4">
Expand All @@ -97,22 +180,32 @@ const FilePreview: FC<FilePreviewProps> = ({
);
}

if (isFileVideo(file.name || '')) {
if (type === PreviewType.Text) {
return (
<p className="w-full h-auto my-10 max-h-48 overflow-auto">{content}</p>
);
}

if (type === PreviewType.Video) {
return (
<video className="w-full h-auto my-10 rounded" controls>
<source src={source} />
<source src={content as string} />
</video>
);
}

return (
<img
src={source}
alt="Preview Image"
className="w-full h-auto my-10 rounded"
onError={onError}
/>
);
if (type === PreviewType.Image) {
return (
<img
src={content as string}
alt="Preview Image"
className="w-full h-auto my-10 rounded"
onError={onError}
/>
);
}

return null;
};

export default FilePreview;
25 changes: 13 additions & 12 deletions src/components/Modals/PreviewFileModal/PreviewFileModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import ShareDarkIcon from '@media/UI/share-dark.svg';
import DeleteLightIcon from '@media/UI/delete-light.svg';
import DeleteDarkIcon from '@media/UI/delete-dark.svg';
import Spinner from '@components/Spinner/Spinner';
import FilePreview from '@components/FilePreview/FilePreview';
import FilePreview, {
isFilePreviewSupported,
} from '@components/FilePreview/FilePreview';
import { FileItem } from '@fairdatasociety/fdp-storage';
import { extractFileExtension } from '@utils/filename';
import { useLocales } from '@context/LocalesContext';
Expand All @@ -54,13 +56,17 @@ const PreviewFileModal: FC<PreviewModalProps> = ({
const { activePod, directoryName } = useContext(PodContext);
const [loading, setLoading] = useState(false);

const [imageSource, setImageSource] = useState('');
const [fileContent, setFileContent] = useState<Blob | null>(null);
const [errorMessage, setErrorMessage] = useState('');
const [showShareFileModal, setShowShareFileModal] = useState(false);
const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false);
const { intl } = useLocales();

useEffect(() => {
if (!isFilePreviewSupported(previewFile?.name)) {
return;
}

setLoading(true);
downloadFile(fdpClientRef.current, {
filename: previewFile?.name,
Expand All @@ -71,12 +77,7 @@ const PreviewFileModal: FC<PreviewModalProps> = ({
const blob = await response.arrayBuffer();
const content = new Blob([blob]);

if (previewFile?.name.endsWith('.json')) {
const json = await content.text();
return setImageSource(JSON.parse(json));
}

setImageSource(window.URL.createObjectURL(content));
setFileContent(content);
})
.catch((e) => {
setErrorMessage(intl.get('FILE_PREVIEW_ERROR'));
Expand Down Expand Up @@ -152,24 +153,24 @@ const PreviewFileModal: FC<PreviewModalProps> = ({
headerTitle={intl.get('PREVIEW_FILE')}
className="w-full md:w-98"
>
{imageSource ? (
{fileContent ? (
<FilePreview
file={previewFile}
source={imageSource}
source={fileContent}
pod={activePod}
directory={directoryName}
onError={() => setErrorMessage(intl.get('FILE_PREVIEW_ERROR'))}
/>
) : null}
<Spinner isLoading={loading} />
<Spinner className="my-8" isLoading={loading} />

{errorMessage ? (
<div className="my-28 text-color-status-negative-day text-xs text-center leading-none">
{errorMessage}
</div>
) : null}

<h2 className="text-base text-color-accents-purple-black dark:text-color-shade-white-night">
<h2 className="text-base mt-8 text-color-accents-purple-black dark:text-color-shade-white-night">
{previewFile?.name}
</h2>

Expand Down

0 comments on commit afdcfb2

Please sign in to comment.