From ab95c548aff52d61d3b3e9c6ee3527c04a80b78f Mon Sep 17 00:00:00 2001 From: Ram Prasad Agarwal Date: Wed, 16 Oct 2024 16:01:03 +0530 Subject: [PATCH 1/2] [ui-storagebrowser] adds file preview and save for files --- .../StorageFilePage/StorageFilePage.scss | 54 +++-- .../StorageFilePage/StorageFilePage.test.tsx | 77 +++++++ .../StorageFilePage/StorageFilePage.tsx | 213 ++++++++++++------ .../js/reactComponents/FileChooser/api.ts | 1 + .../js/utils/constants/storageBrowser.ts | 37 +++ 5 files changed, 301 insertions(+), 81 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.scss b/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.scss index b67f76ff49f..eb39508564c 100644 --- a/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.scss +++ b/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.scss @@ -20,6 +20,7 @@ .hue-storage-file-page { display: flex; flex: 1; + height: 100%; flex-direction: column; gap: vars.$fluidx-spacing-s; padding: vars.$fluidx-spacing-s 0; @@ -84,20 +85,47 @@ gap: vars.$fluidx-spacing-s; } - .preview__textarea { - resize: none; - width: 100%; - height: 100%; - border: none; - border-radius: 0; - padding: vars.$fluidx-spacing-s; - box-shadow: none; - } + .preview__content { + display: flex; + flex: 1; + justify-content: center; + align-items: center; + background-color: vars.$fluidx-gray-040; + + .preview__textarea { + resize: none; + width: 100%; + height: 100%; + border: none; + border-radius: 0; + padding: vars.$fluidx-spacing-s; + box-shadow: none; + } - .preview__textarea[readonly] { - cursor: text; - color: vars.$fluidx-black; - background-color: vars.$fluidx-white; + .preview__textarea[readonly] { + cursor: text; + color: vars.$fluidx-black; + background-color: vars.$fluidx-white; + } + + .preview__document { + display: flex; + align-items: center; + flex-direction: column; + gap: 16px; + } + + audio { + width: 90%; + } + + video { + height: 90%; + } + + .preview__unsupported { + font-size: vars.$font-size-lg; + } } } } \ No newline at end of file diff --git a/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.test.tsx b/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.test.tsx index 31811a6b42c..e5ebc09d225 100644 --- a/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.test.tsx +++ b/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.test.tsx @@ -34,6 +34,11 @@ jest.mock('../../../utils/huePubSub', () => ({ publish: jest.fn() })); +const mockSave = jest.fn(); +jest.mock('../../../api/utils', () => ({ + post: () => mockSave() +})); + // Mock data for fileData const mockFileData: PathAndFileData = { path: '/path/to/file.txt', @@ -191,4 +196,76 @@ describe('StorageFilePage', () => { expect(screen.queryByRole('button', { name: 'Download' })).toBeNull(); expect(screen.queryByRole('link', { name: 'Download' })).toBeNull(); }); + + it('renders a textarea for text files', () => { + render( + + ); + + const textarea = screen.getByRole('textbox'); + expect(textarea).toBeInTheDocument(); + expect(textarea).toHaveValue('Text file content'); + }); + + it('renders an image for image files', () => { + render( + + ); + + const img = screen.getByRole('img'); + expect(img).toBeInTheDocument(); + expect(img).toHaveAttribute('src', expect.stringContaining('imagefile.png')); + }); + + it('renders a preview button for document files', () => { + render( + + ); + + expect(screen.getByRole('button', { name: /preview document/i })).toBeInTheDocument(); + }); + + it('renders an audio player for audio files', () => { + render( + + ); + + const audio = screen.getByTestId('preview__content__audio'); // audio tag can't be access using getByRole + expect(audio).toBeInTheDocument(); + expect(audio.children[0]).toHaveAttribute('src', expect.stringContaining('audiofile.mp3')); + }); + + it('renders a video player for video files', () => { + render( + + ); + + const video = screen.getByTestId('preview__content__video'); // video tag can't be access using getByRole + expect(video).toBeInTheDocument(); + expect(video.children[0]).toHaveAttribute('src', expect.stringContaining('videofile.mp4')); + }); + + it('displays a message for unsupported file types', () => { + render( + + ); + + expect(screen.getByText(/preview not available for this file/i)).toBeInTheDocument(); + }); }); diff --git a/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.tsx b/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.tsx index fb41d37df45..b3b65fa648f 100644 --- a/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.tsx +++ b/desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.tsx @@ -20,8 +20,15 @@ import './StorageFilePage.scss'; import { i18nReact } from '../../../utils/i18nReact'; import Button, { PrimaryButton } from 'cuix/dist/components/Button'; import { getFileMetaData } from './StorageFilePage.util'; -import { DOWNLOAD_API_URL } from '../../../reactComponents/FileChooser/api'; +import { DOWNLOAD_API_URL, SAVE_FILE_API_URL } from '../../../reactComponents/FileChooser/api'; import huePubSub from '../../../utils/huePubSub'; +import useSaveData from '../../../utils/hooks/useSaveData'; +import { + EDITABLE_FILE_FORMATS, + SUPPORTED_FILE_EXTENSIONS, + SupportedFileTypes +} from '../../../utils/constants/storageBrowser'; +import { Spin } from 'antd'; const StorageFilePage = ({ fileData }: { fileData: PathAndFileData }): JSX.Element => { const { t } = i18nReact.useTranslation(); @@ -29,89 +36,159 @@ const StorageFilePage = ({ fileData }: { fileData: PathAndFileData }): JSX.Eleme const [fileContent, setFileContent] = React.useState(fileData.view?.contents); const fileMetaData = useMemo(() => getFileMetaData(t, fileData), [t, fileData]); + const { loading: isSaving, save } = useSaveData(SAVE_FILE_API_URL, { + onSuccess: () => { + setIsEditing(false); + } + }); + const handleEdit = () => { setIsEditing(true); }; + const handleCancel = () => { + setIsEditing(false); + setFileContent(fileData.view?.contents); + }; + + const handleSave = () => + save({ + path: fileData.path, + encoding: 'utf-8', + contents: fileContent + }); + const handleDownload = () => { huePubSub.publish('hue.global.info', { message: t('Downloading your file, Please wait...') }); }; - const handleSave = () => { - // TODO: Save file content to API - setIsEditing(false); - }; + const filePreviewUrl = `${DOWNLOAD_API_URL}${fileData.path}?disposition=inline`; - const handleCancel = () => { - setIsEditing(false); - setFileContent(fileData.view?.contents); - }; + const fileName = fileData?.path?.split('/')?.pop(); + const fileType = useMemo(() => { + const fileExtension = fileName?.split('.')?.pop()?.toLocaleLowerCase(); + if (!fileExtension) { + return SupportedFileTypes.OTHER; + } + return SUPPORTED_FILE_EXTENSIONS[fileExtension] ?? SupportedFileTypes.OTHER; + }, [fileName]); return ( -
-
- {fileMetaData.map((row, index) => ( -
- {row.map(item => ( -
-
{item.label}
-
{item.value}
-
- ))} + +
+
+ {fileMetaData.map((row, index) => ( +
+ {row.map(item => ( +
+
{item.label}
+
{item.value}
+
+ ))} +
+ ))} +
+ +
+
+ {t('Content')} +
+ {!isEditing && EDITABLE_FILE_FORMATS[fileType] && ( + + {t('Edit')} + + )} + {isEditing && ( + <> + + {t('Save')} + + + + )} + {fileData.show_download_button && ( + + + {t('Download')} + + + )} +
- ))} -
-
-
- {t('Content')} -
- - - - +
+ {fileType === SupportedFileTypes.TEXT && ( +