From 2b9a8fe94daf0a6862c2aec59d154bf277486f71 Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Tue, 4 Jun 2024 23:55:30 +0200 Subject: [PATCH] feat: added compact mode --- packages/web/src/books/bookList/BookList.tsx | 79 +++++++++++------- .../books/bookList/BookListCompactItem.tsx | 15 ++++ .../books/bookList/BookListCoverContainer.tsx | 26 +++--- .../src/books/bookList/BookListListItem.tsx | 81 +++++++++++++------ .../books/bookList/BookListWithControls.tsx | 4 +- .../src/books/bookList/useListItemHeight.ts | 53 ++++++++++++ .../collections/CollectionDetailsScreen.tsx | 27 ++++--- .../src/collections/list/CollectionList.tsx | 3 +- .../src/common/lists/ListActionsToolbar.tsx | 51 +++++++++--- .../web/src/common/lists/ReactWindowList.tsx | 41 +--------- packages/web/src/download/states.ts | 4 + .../web/src/library/LibraryBooksScreen.tsx | 20 ++--- .../web/src/library/collections/FilterBar.tsx | 18 ++--- 13 files changed, 268 insertions(+), 154 deletions(-) create mode 100644 packages/web/src/books/bookList/BookListCompactItem.tsx create mode 100644 packages/web/src/books/bookList/useListItemHeight.ts diff --git a/packages/web/src/books/bookList/BookList.tsx b/packages/web/src/books/bookList/BookList.tsx index 74097e40..5eab7e45 100644 --- a/packages/web/src/books/bookList/BookList.tsx +++ b/packages/web/src/books/bookList/BookList.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, FC, memo } from "react" +import React, { useCallback, FC, memo, ReactNode } from "react" import { Box, useTheme } from "@mui/material" import { useWindowSize } from "react-use" import { BookListGridItem } from "./BookListGridItem" @@ -6,6 +6,34 @@ import { LibrarySorting } from "../../library/states" import { BookListListItem } from "./BookListListItem" import { ReactWindowList } from "../../common/lists/ReactWindowList" import { ListActionViewMode } from "../../common/lists/ListActionsToolbar" +import { BookListCompactItem } from "./BookListCompactItem" +import { useListItemHeight } from "./useListItemHeight" + +const ItemListContainer = ({ + children, + isLast, + borders = false +}: { + children: ReactNode + isLast: boolean + borders?: boolean +}) => ( + + {children} + +) export const BookList: FC<{ viewMode?: ListActionViewMode @@ -43,17 +71,10 @@ export const BookList: FC<{ : dynamicNumberOfItems : 1 const adjustedRatioWhichConsiderBottom = theme.custom.coverAverageRatio - 0.1 - const densityMultiplier = density === "dense" ? 0.8 : 1 - const listItemMargin = - (windowSize.width > theme.breakpoints.values["sm"] ? 20 : 10) * - densityMultiplier - const itemHeight = - viewMode === "grid" - ? undefined - : ((windowSize.width > theme.breakpoints.values["sm"] ? 200 : 150) * - theme.custom.coverAverageRatio + - listItemMargin) * - densityMultiplier + const { itemHeight, itemMargin } = useListItemHeight({ + density, + viewMode + }) // const rowBorderColor = theme.palette.grey[100] @@ -61,29 +82,29 @@ export const BookList: FC<{ (item: string, _: number, isLast: boolean) => { return viewMode === "grid" ? ( - ) : ( - + ) : viewMode === "list" ? ( + + + ) : ( + + - + ) }, - [viewMode, itemHeight, listItemMargin, onItemClick, withBookActions] + [viewMode, itemHeight, itemMargin, onItemClick, withBookActions] ) if (props.static) { @@ -104,7 +125,7 @@ export const BookList: FC<{ } return ( - + ) => { + return ( + + ) + } +) diff --git a/packages/web/src/books/bookList/BookListCoverContainer.tsx b/packages/web/src/books/bookList/BookListCoverContainer.tsx index ca910dc9..c0c5106f 100644 --- a/packages/web/src/books/bookList/BookListCoverContainer.tsx +++ b/packages/web/src/books/bookList/BookListCoverContainer.tsx @@ -1,5 +1,5 @@ import React, { FC, memo } from "react" -import { Box, Chip, useTheme } from "@mui/material" +import { Box, BoxProps, Chip, useTheme } from "@mui/material" import { CheckOutlined, CloudDownloadRounded, @@ -18,15 +18,17 @@ import { CoverIconBadge } from "./CoverIconBadge" type Book = ReturnType["data"] -export const BookListCoverContainer: FC<{ - bookId: string - className?: string - style?: React.CSSProperties - withReadingProgressStatus?: boolean - withDownloadStatus?: boolean - withBadges: boolean - size?: "small" | "large" | "medium" -}> = memo( +export const BookListCoverContainer: FC< + { + bookId: string + className?: string + style?: React.CSSProperties + withReadingProgressStatus?: boolean + withDownloadStatus?: boolean + withBadges: boolean + size?: "small" | "large" | "medium" + } & BoxProps +> = memo( ({ bookId, className, @@ -34,7 +36,8 @@ export const BookListCoverContainer: FC<{ withDownloadStatus = true, withReadingProgressStatus = true, withBadges, - size = "small" + size = "small", + ...rest }) => { const { data: item } = useBook({ id: bookId }) const bookDownloadState = useBookDownloadState(bookId) @@ -45,6 +48,7 @@ export const BookListCoverContainer: FC<{ {item && } {bookDownloadState?.downloadState !== DownloadState.Downloaded && ( diff --git a/packages/web/src/books/bookList/BookListListItem.tsx b/packages/web/src/books/bookList/BookListListItem.tsx index 95b5da15..2eee1d96 100644 --- a/packages/web/src/books/bookList/BookListListItem.tsx +++ b/packages/web/src/books/bookList/BookListListItem.tsx @@ -1,10 +1,14 @@ -import { Box, Chip, Stack, Typography, useTheme } from "@mui/material" +import { Box, BoxProps, Chip, Stack, Typography, useTheme } from "@mui/material" import { FC, memo } from "react" import { useDefaultItemClickHandler } from "./helpers" import { useBook, useIsBookProtected } from "../states" import { ReadingStateState } from "@oboku/shared" import { + CloudDoneRounded, + CloudDownloadRounded, DoneRounded, + DownloadDoneRounded, + DownloadingRounded, ErrorRounded, LoopRounded, MenuBookRounded, @@ -16,21 +20,31 @@ import { bookActionDrawerSignal } from "../drawer/BookActionsDrawer" import { useCSS } from "../../common/utils" import { BookListCoverContainer } from "./BookListCoverContainer" import { getMetadataFromBook } from "../getMetadataFromBook" +import { useBookDownloadState } from "../../download/states" -export const BookListListItem: FC<{ - bookId: string - onItemClick?: (id: string) => void - isSelected?: (id: string) => boolean - size?: "small" | "large" - itemHeight?: number - withDrawerActions?: boolean -}> = memo( +export const BookListListItem: FC< + { + bookId: string + onItemClick?: (id: string) => void + isSelected?: (id: string) => boolean + size?: "small" | "large" + itemHeight?: number + withDrawerActions?: boolean + withCover?: boolean + withAuthors?: boolean + withDownloadIcons?: boolean + } & BoxProps +> = memo( ({ bookId, onItemClick, size = "large", itemHeight, - withDrawerActions = true + withDrawerActions = true, + withCover = true, + withAuthors = true, + withDownloadIcons = false, + ...rest }) => { const { data: book } = useBook({ id: bookId @@ -40,6 +54,7 @@ export const BookListListItem: FC<{ const computedHeight = itemHeight || (size === "small" ? 50 : 100) const coverWidth = computedHeight * theme.custom.coverAverageRatio const classes = useStyles({ coverWidth }) + const bookDownloadState = useBookDownloadState(bookId) const { data: isBookProtected = true } = useIsBookProtected(book) const metadata = getMetadataFromBook(book) @@ -57,20 +72,23 @@ export const BookListListItem: FC<{ cursor: "pointer", flexGrow: 1 }} + {...rest} > - + {withCover && ( + + )}
@@ -84,9 +102,11 @@ export const BookListListItem: FC<{ > {metadata?.title || "Unknown"} - - {(metadata?.authors ?? [])[0] || "Unknown"} - + {withAuthors && ( + + {(metadata?.authors ?? [])[0] || "Unknown"} + + )} - {isBookProtected && } - {book?.isNotInterested && } + {withDownloadIcons && ( + <> + {bookDownloadState?.isDownloading ? ( + + ) : bookDownloadState?.isDownloaded ? ( + + ) : ( + + )} + + )} + {isBookProtected && } + {book?.isNotInterested && } {book?.readingStateCurrentState === ReadingStateState.Finished && (
- +
)} {book?.readingStateCurrentState === ReadingStateState.Reading && (
- +
{ + const theme = useTheme() + const windowSize = useWindowSize() + const densityMultiplier = density === "dense" ? 0.8 : 1 + + const listItemMargin = + (windowSize.width > theme.breakpoints.values["sm"] ? 15 : 10) * + densityMultiplier + + const compactItemMargin = + (windowSize.width > theme.breakpoints.values["sm"] ? 15 : 10) * + densityMultiplier + + const listItemHeight = + ((windowSize.width > theme.breakpoints.values["sm"] ? 200 : 150) * + theme.custom.coverAverageRatio + + listItemMargin) * + densityMultiplier + + const compactItemHeight = + ((windowSize.width > theme.breakpoints.values["sm"] ? 80 : 80) * + theme.custom.coverAverageRatio + + compactItemMargin) * + densityMultiplier + + const itemHeight = + viewMode === "grid" + ? undefined + : viewMode === "list" + ? listItemHeight + : compactItemHeight + + const itemMargin = + viewMode === "grid" + ? 0 + : viewMode === "list" + ? listItemMargin + : compactItemMargin + + console.log({itemMargin}) + + return { itemHeight, itemMargin } +} diff --git a/packages/web/src/collections/CollectionDetailsScreen.tsx b/packages/web/src/collections/CollectionDetailsScreen.tsx index ba73a2a7..584e9160 100644 --- a/packages/web/src/collections/CollectionDetailsScreen.tsx +++ b/packages/web/src/collections/CollectionDetailsScreen.tsx @@ -8,7 +8,10 @@ import { BookListWithControls } from "../books/bookList/BookListWithControls" import { useVisibleBookIds } from "../books/states" import { useCollectionActionsDrawer } from "../library/collections/CollectionActionsDrawer/useCollectionActionsDrawer" import { signal, useSignalValue } from "reactjrx" -import { ListActionSorting, ListActionViewMode } from "../common/lists/ListActionsToolbar" +import { + ListActionSorting, + ListActionViewMode +} from "../common/lists/ListActionsToolbar" type ScreenParams = { id: string @@ -22,7 +25,7 @@ export const collectionDetailsScreenListControlsStateSignal = signal<{ default: { viewMode: "grid", sorting: "alpha" - }, + } }) export const CollectionDetailsScreen = () => { @@ -113,16 +116,20 @@ export const CollectionDetailsScreen = () => { sorting={sorting} viewMode={viewMode} onViewModeChange={(value) => { - collectionDetailsScreenListControlsStateSignal.setValue({ - ...collectionDetailsScreenListControlsStateSignal.getValue(), - viewMode: value - }) + collectionDetailsScreenListControlsStateSignal.setValue( + (state) => ({ + ...state, + viewMode: value + }) + ) }} onSortingChange={(value) => { - collectionDetailsScreenListControlsStateSignal.setValue({ - ...collectionDetailsScreenListControlsStateSignal.getValue(), - sorting: value - }) + collectionDetailsScreenListControlsStateSignal.setValue( + (state) => ({ + ...state, + sorting: value + }) + ) }} renderEmptyList={
) => void - viewMode?: "list" | "grid" + viewMode?: ListActionViewMode itemMode?: ComponentProps["viewMode"] static?: boolean } & Omit< diff --git a/packages/web/src/common/lists/ListActionsToolbar.tsx b/packages/web/src/common/lists/ListActionsToolbar.tsx index 5a43fa2c..8335758d 100644 --- a/packages/web/src/common/lists/ListActionsToolbar.tsx +++ b/packages/web/src/common/lists/ListActionsToolbar.tsx @@ -2,6 +2,7 @@ import { ComponentProps, FC, useState } from "react" import { Toolbar, IconButton, useTheme, Button, Badge } from "@mui/material" import { AppsRounded, + FormatListBulletedRounded, ListRounded, LockOpenRounded, SortRounded, @@ -14,10 +15,43 @@ import { libraryStateSignal } from "../../library/states" export type ListActionSorting = ComponentProps["value"] export type ListActionViewMode = "grid" | "list" | "compact" +export const ViewModeIconButton = ({ + viewMode, + onViewModeChange +}: { + viewMode: ListActionViewMode + onViewModeChange?: (viewMode: ListActionViewMode) => void +}) => { + return ( + { + const newViewMode = + viewMode === "compact" + ? "grid" + : viewMode === "list" + ? "compact" + : "list" + + onViewModeChange?.(newViewMode) + }} + size="large" + > + {viewMode === "grid" ? ( + + ) : viewMode === "list" ? ( + + ) : ( + + )} + + ) +} + export const ListActionsToolbar: FC<{ viewMode?: ListActionViewMode sorting?: ListActionSorting - onViewModeChange?: (viewMode: "list" | "grid") => void + onViewModeChange?: (viewMode: ListActionViewMode) => void onSortingChange?: (sorting: ListActionSorting) => void numberOfFiltersApplied?: number onFilterClick?: () => void @@ -27,7 +61,7 @@ export const ListActionsToolbar: FC<{ onSortingChange, sorting, onFilterClick, - numberOfFiltersApplied = 0 + numberOfFiltersApplied = 0, }) => { const theme = useTheme() const library = useSignalValue(libraryStateSignal) @@ -94,15 +128,10 @@ export const ListActionsToolbar: FC<{
)} {!!viewMode && ( - { - onViewModeChange?.(viewMode === "grid" ? "list" : "grid") - }} - size="large" - > - {viewMode === "grid" ? : } - + )} { - const theme = useTheme() - - return useCSS( - () => ({ - verticalScrollButton: { - position: "absolute", - padding: theme.spacing(2), - backgroundColor: "gray", - opacity: 0.5, - borderRadius: 50, - alignItems: "center", - justifyContent: "center", - bottom: theme.spacing(1), - display: "flex" - }, - verticalScrollButtonMore: { - right: theme.spacing(1) - }, - verticalScrollButtonLess: { - left: theme.spacing(1) - }, - horizontalButton: { - padding: theme.spacing(2), - backgroundColor: "gray", - opacity: 0.5, - borderRadius: 50, - alignItems: "center", - justifyContent: "center", - transform: "translateY(-50%)", - top: "50%", - display: "flex", - flexFlow: "column" - } - }), - [theme] - ) -} +) \ No newline at end of file diff --git a/packages/web/src/download/states.ts b/packages/web/src/download/states.ts index e716d907..dfcea952 100644 --- a/packages/web/src/download/states.ts +++ b/packages/web/src/download/states.ts @@ -48,6 +48,10 @@ export const useBookDownloadState = (bookId?: string | null) => { return { downloadState: DownloadState.None, downloadProgress: 0, + isDownloaded: + bookDownloadState[bookId]?.downloadState === DownloadState.Downloaded, + isDownloading: + bookDownloadState[bookId]?.downloadState === DownloadState.Downloading, ...bookDownloadState[bookId] } } diff --git a/packages/web/src/library/LibraryBooksScreen.tsx b/packages/web/src/library/LibraryBooksScreen.tsx index abe61be6..33407dcd 100644 --- a/packages/web/src/library/LibraryBooksScreen.tsx +++ b/packages/web/src/library/LibraryBooksScreen.tsx @@ -16,7 +16,8 @@ import { ListRounded, SortRounded, NoEncryptionRounded, - BlurOffRounded + BlurOffRounded, + FormatListBulletedRounded } from "@mui/icons-material" import { LibraryFiltersDrawer } from "./LibraryFiltersDrawer" import EmptyLibraryAsset from "../assets/empty-library.svg" @@ -33,6 +34,7 @@ import { useTranslation } from "react-i18next" import { useBooks } from "./useBooks" import { useSignalValue } from "reactjrx" import { isUploadBookFromDataSourceDialogOpenedSignal } from "../upload/state" +import { ViewModeIconButton } from "../common/lists/ListActionsToolbar" export const LibraryBooksScreen = () => { const styles = useStyles() @@ -155,21 +157,15 @@ export const LibraryBooksScreen = () => {
)} - { + { libraryStateSignal.setValue((state) => ({ ...state, - viewMode: - library.viewMode === "grid" - ? "list" - : "grid" + viewMode: value })) }} - size="large" - color="primary" - > - {library.viewMode === "grid" ? : } - + /> { +export const FilterBar = ({ + ...rest +}: ComponentProps) => { const [isFiltersDrawerOpen, setIsFiltersDrawerOpen] = useState(false) - // const viewMode = useSignalValue( - // collectionsListSignal, - // (state) => state.viewMode - // ) const onFiltersDrawerClose = useCallback(() => { setIsFiltersDrawerOpen(false) @@ -19,13 +17,7 @@ export const FilterBar = () => { onFilterClick={() => { setIsFiltersDrawerOpen(true) }} - // onViewModeChange={(value) => { - // collectionsListSignal.setValue((state) => ({ - // ...state, - // viewMode: value - // })) - // }} - // viewMode={viewMode} + {...rest} />