From 145ffd3c8578822f6ed1fa5034f88d1c822fc28f Mon Sep 17 00:00:00 2001 From: Anass Bouassaba Date: Wed, 23 Oct 2024 18:51:18 +0200 Subject: [PATCH] feat(ui): file info modal (#364) --- ui/src/components/file/file-info.tsx | 53 +++++++++++++++++++ ui/src/components/file/file-menu.tsx | 30 +++++++++++ ui/src/components/file/file-rename.tsx | 2 +- .../info}/file-info-create-time.tsx | 0 .../info/file-info-embed.tsx} | 33 ++++++------ .../info}/file-info-extension.tsx | 0 .../info}/file-info-image.tsx | 0 .../file/info/file-info-item-count.tsx | 32 +++++++++++ .../info}/file-info-permission.tsx | 0 .../info}/file-info-size.tsx | 0 .../info}/file-info-storage-usage.tsx | 10 ++-- .../info}/file-info-update-time.tsx | 0 ui/src/components/file/list/index.tsx | 9 ++++ ui/src/components/snapshot/snapshot-list.tsx | 16 +++++- .../viewer/drawer/drawer-content.tsx | 4 +- .../pages/workspace/workspace-files-page.tsx | 5 ++ ui/src/store/ui/files.ts | 10 ++++ 17 files changed, 176 insertions(+), 28 deletions(-) create mode 100644 ui/src/components/file/file-info.tsx rename ui/src/components/{viewer/drawer/file-info => file/info}/file-info-create-time.tsx (100%) rename ui/src/components/{viewer/drawer/file-info/index.tsx => file/info/file-info-embed.tsx} (60%) rename ui/src/components/{viewer/drawer/file-info => file/info}/file-info-extension.tsx (100%) rename ui/src/components/{viewer/drawer/file-info => file/info}/file-info-image.tsx (100%) create mode 100644 ui/src/components/file/info/file-info-item-count.tsx rename ui/src/components/{viewer/drawer/file-info => file/info}/file-info-permission.tsx (100%) rename ui/src/components/{viewer/drawer/file-info => file/info}/file-info-size.tsx (100%) rename ui/src/components/{viewer/drawer/file-info => file/info}/file-info-storage-usage.tsx (82%) rename ui/src/components/{viewer/drawer/file-info => file/info}/file-info-update-time.tsx (100%) diff --git a/ui/src/components/file/file-info.tsx b/ui/src/components/file/file-info.tsx new file mode 100644 index 000000000..000f83622 --- /dev/null +++ b/ui/src/components/file/file-info.tsx @@ -0,0 +1,53 @@ +import { + Button, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import cx from 'classnames' +import FileAPI from '@/client/api/file' +import FileInfoEmbed from '@/components/file/info/file-info-embed' +import { useAppDispatch, useAppSelector } from '@/store/hook' +import { infoModalDidClose } from '@/store/ui/files' + +const FileInfo = () => { + const dispatch = useAppDispatch() + const isModalOpen = useAppSelector((state) => state.ui.files.isInfoModalOpen) + const id = useAppSelector((state) => state.ui.files.selection[0]) + const { data: file } = FileAPI.useGet(id) + + if (!file) { + return null + } + + return ( + dispatch(infoModalDidClose())}> + + + Info + + + + + +
+ +
+
+
+
+ ) +} + +export default FileInfo diff --git a/ui/src/components/file/file-menu.tsx b/ui/src/components/file/file-menu.tsx index f2648bfc9..75037411d 100644 --- a/ui/src/components/file/file-menu.tsx +++ b/ui/src/components/file/file-menu.tsx @@ -36,6 +36,7 @@ import { IconFileCopy, IconGroup, IconHistory, + IconInfo, IconModeHeat, IconMoreVert, IconSelectCheckBox, @@ -56,6 +57,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hook' import { copyModalDidOpen, deleteModalDidOpen, + infoModalDidOpen, moveModalDidOpen, renameModalDidOpen, selectionUpdated, @@ -160,6 +162,10 @@ const FileMenu = ({ () => file !== undefined && geEditorPermission(file.permission), [file], ) + const isInfoAuthorized = useMemo( + () => file !== undefined && geViewerPermission(file.permission), + [file], + ) const isToolsAuthorized = useMemo( () => isInsightsAuthorized || isMosaicAuthorized, [isInsightsAuthorized, isMosaicAuthorized], @@ -428,6 +434,30 @@ const FileMenu = ({ ) : null} + + + } + isDisabled={!isInfoAuthorized} + onClick={(event: MouseEvent) => { + event.stopPropagation() + dispatch(infoModalDidOpen()) + }} + > +
+ Info + {isMacOS ? ( +
+ +E +
+ ) : ( +
+ Ctrl+E +
+ )} +
+
+
diff --git a/ui/src/components/file/file-rename.tsx b/ui/src/components/file/file-rename.tsx index 51ae60776..e16b46dc6 100644 --- a/ui/src/components/file/file-rename.tsx +++ b/ui/src/components/file/file-rename.tsx @@ -85,7 +85,7 @@ const FileRename = () => { > - Rename File + Rename Item { - if (!file.snapshot?.original) { - return null - } - return ( -
- - - - - - - -
- ) -} +const FileInfoEmbed = ({ file }: FileInfoEmbedProps) => ( +
+ + + + + + + + +
+) -export default DrawerFileInfo +export default FileInfoEmbed diff --git a/ui/src/components/viewer/drawer/file-info/file-info-extension.tsx b/ui/src/components/file/info/file-info-extension.tsx similarity index 100% rename from ui/src/components/viewer/drawer/file-info/file-info-extension.tsx rename to ui/src/components/file/info/file-info-extension.tsx diff --git a/ui/src/components/viewer/drawer/file-info/file-info-image.tsx b/ui/src/components/file/info/file-info-image.tsx similarity index 100% rename from ui/src/components/viewer/drawer/file-info/file-info-image.tsx rename to ui/src/components/file/info/file-info-image.tsx diff --git a/ui/src/components/file/info/file-info-item-count.tsx b/ui/src/components/file/info/file-info-item-count.tsx new file mode 100644 index 000000000..4484ff1c1 --- /dev/null +++ b/ui/src/components/file/info/file-info-item-count.tsx @@ -0,0 +1,32 @@ +import { Stat, StatLabel, StatNumber } from '@chakra-ui/react' +import cx from 'classnames' +import FileAPI, { File, FileType } from '@/client/api/file' + +export type FileInfoItemCountProps = { + file: File +} + +const FileInfoItemCount = ({ file }: FileInfoItemCountProps) => { + const { data: count, error } = FileAPI.useGetCount(file.id) + + if (file.type !== FileType.Folder) { + return null + } + + return ( + + Item count + +
+ {error ? ( + Failed to load. + ) : null} + {count && !error ? {count} : null} + {!count && !error ? Calculating… : null} +
+
+
+ ) +} + +export default FileInfoItemCount diff --git a/ui/src/components/viewer/drawer/file-info/file-info-permission.tsx b/ui/src/components/file/info/file-info-permission.tsx similarity index 100% rename from ui/src/components/viewer/drawer/file-info/file-info-permission.tsx rename to ui/src/components/file/info/file-info-permission.tsx diff --git a/ui/src/components/viewer/drawer/file-info/file-info-size.tsx b/ui/src/components/file/info/file-info-size.tsx similarity index 100% rename from ui/src/components/viewer/drawer/file-info/file-info-size.tsx rename to ui/src/components/file/info/file-info-size.tsx diff --git a/ui/src/components/viewer/drawer/file-info/file-info-storage-usage.tsx b/ui/src/components/file/info/file-info-storage-usage.tsx similarity index 82% rename from ui/src/components/viewer/drawer/file-info/file-info-storage-usage.tsx rename to ui/src/components/file/info/file-info-storage-usage.tsx index 619d49330..c69781d6e 100644 --- a/ui/src/components/viewer/drawer/file-info/file-info-storage-usage.tsx +++ b/ui/src/components/file/info/file-info-storage-usage.tsx @@ -18,7 +18,7 @@ export type FileInfoStorageUsageProps = { } const FileInfoStorageUsage = ({ file }: FileInfoStorageUsageProps) => { - const { data, error } = StorageAPI.useGetFileUsage(file.id) + const { data: usage, error } = StorageAPI.useGetFileUsage(file.id) return ( Storage usage @@ -27,15 +27,15 @@ const FileInfoStorageUsage = ({ file }: FileInfoStorageUsageProps) => { {error ? ( Failed to load. ) : null} - {data && !error ? ( + {usage && !error ? ( <> - {prettyBytes(data.bytes)} of {prettyBytes(data.maxBytes)} used + {prettyBytes(usage.bytes)} of {prettyBytes(usage.maxBytes)} used - + ) : null} - {!data && !error ? ( + {!usage && !error ? ( <> Calculating… diff --git a/ui/src/components/viewer/drawer/file-info/file-info-update-time.tsx b/ui/src/components/file/info/file-info-update-time.tsx similarity index 100% rename from ui/src/components/viewer/drawer/file-info/file-info-update-time.tsx rename to ui/src/components/file/info/file-info-update-time.tsx diff --git a/ui/src/components/file/list/index.tsx b/ui/src/components/file/list/index.tsx index 3d6245e0d..a4c0d243c 100644 --- a/ui/src/components/file/list/index.tsx +++ b/ui/src/components/file/list/index.tsx @@ -28,6 +28,7 @@ import { contextMenuDidOpen, copyModalDidOpen, deleteModalDidOpen, + infoModalDidOpen, moveModalDidOpen, multiSelectKeyUpdated, rangeSelectKeyUpdated, @@ -63,6 +64,7 @@ const FileList = ({ list, scale }: FileListProps) => { state.ui.files.isCreateModalOpen || state.ui.files.isShareModalOpen || state.ui.files.isRenameModalOpen || + state.ui.files.isInfoModalOpen || state.ui.mosaic.isModalOpen || state.ui.insights.isModalOpen, ) @@ -170,6 +172,13 @@ const FileList = ({ list, scale }: FileListProps) => { if (selection.length > 0) { dispatch(moveModalDidOpen()) } + } else if ( + (keyName === 'command+e' && isMacOS()) || + (keyName === 'ctrl+e' && !isMacOS()) + ) { + if (selection.length > 0) { + dispatch(infoModalDidOpen()) + } } else if ( (keyName === 'command+e' && isMacOS()) || (keyName === 'f2' && !isMacOS()) diff --git a/ui/src/components/snapshot/snapshot-list.tsx b/ui/src/components/snapshot/snapshot-list.tsx index e2fc056c3..98bc811c6 100644 --- a/ui/src/components/snapshot/snapshot-list.tsx +++ b/ui/src/components/snapshot/snapshot-list.tsx @@ -25,10 +25,11 @@ import { Tr, } from '@chakra-ui/react' import cx from 'classnames' -import SnapshotAPI, { Snapshot, SortOrder } from '@/client/api/snapshot' +import SnapshotAPI, { Snapshot, SortBy, SortOrder } from '@/client/api/snapshot' import { swrConfig } from '@/client/options' import Pagination from '@/lib/components/pagination' import SectionSpinner from '@/lib/components/section-spinner' +import prettyBytes from '@/lib/helpers/pretty-bytes' import prettyDate from '@/lib/helpers/pretty-date' import { useAppDispatch, useAppSelector } from '@/store/hook' import { @@ -53,7 +54,13 @@ const SnapshotList = () => { error, mutate: snapshotMutate, } = SnapshotAPI.useList( - { fileId, page, size: 5, sortOrder: SortOrder.Desc }, + { + fileId, + page, + size: 5, + sortBy: SortBy.Version, + sortOrder: SortOrder.Desc, + }, swrConfig(), ) @@ -181,6 +188,11 @@ const SnapshotList = () => {
{`v${s.version}`} + {s.original.size ? ( + + {prettyBytes(s.original.size)} + + ) : null} {s.entities ? ( Insights ) : null} diff --git a/ui/src/components/viewer/drawer/drawer-content.tsx b/ui/src/components/viewer/drawer/drawer-content.tsx index af7190b06..6debb87ab 100644 --- a/ui/src/components/viewer/drawer/drawer-content.tsx +++ b/ui/src/components/viewer/drawer/drawer-content.tsx @@ -10,12 +10,12 @@ import { useContext } from 'react' import cx from 'classnames' import { File } from '@/client/api/file' +import FileInfoEmbed from '@/components/file/info/file-info-embed' import { DrawerContext } from '@/lib/components/drawer' import { IconInfo } from '@/lib/components/icons' import SwitchCard from '@/lib/components/switch-card' import DrawerDownloadButton from './drawer-download-button' import DrawerOpenNewTabButton from './drawer-open-new-tab-button' -import DrawerFileInfo from './file-info' export type DrawerContentProps = { file: File @@ -34,7 +34,7 @@ const DrawerContent = ({ file }: DrawerContentProps) => { localStorageNamespace="file_info" expandedMinWidth="200px" > - +
) diff --git a/ui/src/pages/workspace/workspace-files-page.tsx b/ui/src/pages/workspace/workspace-files-page.tsx index 513c3fa5b..3ed277a11 100644 --- a/ui/src/pages/workspace/workspace-files-page.tsx +++ b/ui/src/pages/workspace/workspace-files-page.tsx @@ -18,6 +18,7 @@ import Path from '@/components/common/path' import FileCopy from '@/components/file/file-copy' import FileCreate from '@/components/file/file-create' import FileDelete from '@/components/file/file-delete' +import FileInfo from '@/components/file/file-info' import FileMove from '@/components/file/file-move' import FileRename from '@/components/file/file-rename' import SearchFilter from '@/components/file/file-search-filter' @@ -69,6 +70,9 @@ const WorkspaceFilesPage = () => { const isRenameModalOpen = useAppSelector( (state) => state.ui.files.isRenameModalOpen, ) + const isInfoModalOpen = useAppSelector( + (state) => state.ui.files.isInfoModalOpen, + ) const isInsightsModalOpen = useAppSelector( (state) => state.ui.insights.isModalOpen, ) @@ -215,6 +219,7 @@ const WorkspaceFilesPage = () => { {isCreateModalOpen ? : null} {isDeleteModalOpen ? : null} {isRenameModalOpen ? : null} + {isInfoModalOpen ? : null} {isInsightsModalOpen ? : null} {isMosaicModalOpen ? : null} {isSearchFilterModalOpen ? : null} diff --git a/ui/src/store/ui/files.ts b/ui/src/store/ui/files.ts index ff5b1e5c3..f3e474509 100644 --- a/ui/src/store/ui/files.ts +++ b/ui/src/store/ui/files.ts @@ -34,6 +34,7 @@ export type FilesState = { isDeleteModalOpen: boolean isRenameModalOpen: boolean isShareModalOpen: boolean + isInfoModalOpen: boolean isContextMenuOpen: boolean isSelectionMode: boolean iconScale: number @@ -55,6 +56,7 @@ const initialState: FilesState = { isDeleteModalOpen: false, isRenameModalOpen: false, isShareModalOpen: false, + isInfoModalOpen: false, isContextMenuOpen: false, iconScale: loadIconScale() || 1, sortBy: loadFileSortBy() || SortBy.DateCreated, @@ -105,6 +107,9 @@ const slice = createSlice({ sharingModalDidOpen: (state) => { state.isShareModalOpen = true }, + infoModalDidOpen: (state) => { + state.isInfoModalOpen = true + }, contextMenuDidOpen: (state) => { state.isContextMenuOpen = true }, @@ -126,6 +131,9 @@ const slice = createSlice({ sharingModalDidClose: (state) => { state.isShareModalOpen = false }, + infoModalDidClose: (state) => { + state.isInfoModalOpen = false + }, contextMenuDidClose: (state) => { state.isContextMenuOpen = false }, @@ -180,6 +188,7 @@ export const { deleteModalDidOpen, renameModalDidOpen, sharingModalDidOpen, + infoModalDidOpen, contextMenuDidOpen, moveModalDidClose, copyModalDidClose, @@ -187,6 +196,7 @@ export const { deleteModalDidClose, renameModalDidClose, sharingModalDidClose, + infoModalDidClose, contextMenuDidClose, multiSelectKeyUpdated, rangeSelectKeyUpdated,