diff --git a/src/apps/experimental/components/library/GridListViewButton.tsx b/src/apps/experimental/components/library/GridListViewButton.tsx deleted file mode 100644 index bf6f48391..000000000 --- a/src/apps/experimental/components/library/GridListViewButton.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { FC, useCallback } from 'react'; -import ButtonGroup from '@mui/material/ButtonGroup/ButtonGroup'; -import IconButton from '@mui/material/IconButton'; -import ViewModuleIcon from '@mui/icons-material/ViewModule'; -import ViewListIcon from '@mui/icons-material/ViewList'; - -import globalize from 'lib/globalize'; -import { LibraryViewSettings, ViewMode } from 'types/library'; -import { LibraryTab } from 'types/libraryTab'; -import ViewSettingsButton from './ViewSettingsButton'; - -interface GridListViewButtonProps { - viewType: LibraryTab; - libraryViewSettings: LibraryViewSettings; - setLibraryViewSettings: React.Dispatch>; -} - -const GridListViewButton: FC = ({ - viewType, - libraryViewSettings, - setLibraryViewSettings -}) => { - const handleToggleCurrentView = useCallback(() => { - setLibraryViewSettings((prevState) => ({ - ...prevState, - ViewMode: - prevState.ViewMode === ViewMode.ListView ? ViewMode.GridView : ViewMode.ListView - })); - }, [setLibraryViewSettings]); - - const isGridView = libraryViewSettings.ViewMode === ViewMode.GridView; - - return ( - - {isGridView ? ( - - ) : ( - - - - )} - - - - - - ); -}; - -export default GridListViewButton; diff --git a/src/apps/experimental/components/library/ItemsView.tsx b/src/apps/experimental/components/library/ItemsView.tsx index aa6e8f881..ae985b22e 100644 --- a/src/apps/experimental/components/library/ItemsView.tsx +++ b/src/apps/experimental/components/library/ItemsView.tsx @@ -2,9 +2,12 @@ import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/bas import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'; -import React, { type FC, useCallback } from 'react'; import Box from '@mui/material/Box'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import type { Theme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; import classNames from 'classnames'; +import React, { type FC, useCallback } from 'react'; import { useApi } from 'hooks/useApi'; import { useLocalStorage } from 'hooks/useLocalStorage'; @@ -31,8 +34,8 @@ import PlayAllButton from './PlayAllButton'; import QueueButton from './QueueButton'; import ShuffleButton from './ShuffleButton'; import SortButton from './SortButton'; -import GridListViewButton from './GridListViewButton'; import LibraryViewMenu from './LibraryViewMenu'; +import ViewSettingsButton from './ViewSettingsButton'; interface ItemsViewProps { viewType: LibraryTab; @@ -72,10 +75,11 @@ const ItemsView: FC = ({ getSettingsKey(viewType, parentId), getDefaultLibraryViewSettings(viewType) ); + const isSmallScreen = useMediaQuery((t: Theme) => t.breakpoints.up('sm')); const { __legacyApiClient__ } = useApi(); const { - isLoading, + isPending, data: itemsResult, isPlaceholderData, refetch @@ -229,90 +233,142 @@ const ItemsView: FC = ({ ); return ( - + + + + - - - {isBtnPlayAllEnabled && ( - - )} - {isBtnShuffleEnabled && totalRecordCount > 1 && ( - - )} - {isBtnQueueEnabled - && item - && playbackManager.canQueue(item) && ( - - )} - {isBtnSortEnabled && ( - - )} - {isBtnFilterEnabled && ( - - )} - {isBtnNewCollectionEnabled && } - {isBtnGridListEnabled && ( - - )} - - {isPaginationEnabled && ( - + + {isBtnFilterEnabled && ( + + )} + + {isBtnSortEnabled && ( + + )} + + {isBtnGridListEnabled && ( + + )} + + + + + {!isPending && ( + <> + + {isBtnPlayAllEnabled && ( + + )} + + {isBtnShuffleEnabled && totalRecordCount > 1 && ( + + )} + + {isBtnQueueEnabled && item && playbackManager.canQueue(item) && ( + + )} + + + {isBtnNewCollectionEnabled && } + + )} + + + + {!isPending && isPaginationEnabled && ( - - )} + )} + {isAlphabetPickerEnabled && hasSortName && ( @@ -322,7 +378,7 @@ const ItemsView: FC = ({ /> )} - {isLoading ? ( + {isPending ? ( ) : ( = ({ )} - {isPaginationEnabled && ( + {!isPending && isPaginationEnabled && ( { +interface NewCollectionButtonProps { + isTextVisible: boolean +} + +const NewCollectionButton: FC = ({ + isTextVisible +}) => { const showCollectionEditor = useCallback(() => { import('components/collectionEditor/collectionEditor').then( ({ default: CollectionEditor }) => { @@ -21,13 +28,17 @@ const NewCollectionButton: FC = () => { }, []); return ( - : undefined} onClick={showCollectionEditor} > - - + {isTextVisible ? ( + globalize.translate('NewCollection') + ) : ( + + )} + ); }; diff --git a/src/apps/experimental/components/library/Pagination.tsx b/src/apps/experimental/components/library/Pagination.tsx index 477a98231..d803ded46 100644 --- a/src/apps/experimental/components/library/Pagination.tsx +++ b/src/apps/experimental/components/library/Pagination.tsx @@ -2,8 +2,12 @@ import React, { FC, useCallback } from 'react'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; import ButtonGroup from '@mui/material/ButtonGroup'; -import IconButton from '@mui/material/IconButton'; +import Stack from '@mui/material/Stack'; +import type { Theme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; +import useMediaQuery from '@mui/material/useMediaQuery'; import globalize from 'lib/globalize'; import * as userSettings from 'scripts/settings/userSettings'; @@ -22,6 +26,8 @@ const Pagination: FC = ({ totalRecordCount, isPlaceholderData }) => { + const isSmallScreen = useMediaQuery((t: Theme) => t.breakpoints.up('sm')); + const limit = userSettings.libraryPageSize(undefined); const startIndex = libraryViewSettings.StartIndex ?? 0; const recordsStart = totalRecordCount ? startIndex + 1 : 0; @@ -47,47 +53,88 @@ const Pagination: FC = ({ }, [limit, setLibraryViewSettings, startIndex]); return ( - + {!isSmallScreen && ( + + )} + - + {globalize.translate( 'ListPaging', recordsStart, recordsEnd, totalRecordCount )} - - {showControls && ( - - - - - - = totalRecordCount || isPlaceholderData } - onClick={onNextPageClick} - > - - - - )} + - + + {isSmallScreen && ( + + + + + + )} + + {!isSmallScreen && ( + + )} + ); }; diff --git a/src/apps/experimental/components/library/PlayAllButton.tsx b/src/apps/experimental/components/library/PlayAllButton.tsx index 16cef318a..d02fcea5e 100644 --- a/src/apps/experimental/components/library/PlayAllButton.tsx +++ b/src/apps/experimental/components/library/PlayAllButton.tsx @@ -1,6 +1,6 @@ import React, { FC, useCallback } from 'react'; -import IconButton from '@mui/material/IconButton'; -import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import Button from '@mui/material/Button'; +import PlayArrow from '@mui/icons-material/PlayArrow'; import { playbackManager } from 'components/playback/playbackmanager'; import globalize from 'lib/globalize'; @@ -10,14 +10,22 @@ import { LibraryTab } from 'types/libraryTab'; import type { ItemDto } from 'types/base/models/item-dto'; interface PlayAllButtonProps { - item: ItemDto | undefined; - items: ItemDto[]; - viewType: LibraryTab; - hasFilters: boolean; + item: ItemDto | undefined + items: ItemDto[] + viewType: LibraryTab + hasFilters: boolean + isTextVisible: boolean libraryViewSettings: LibraryViewSettings } -const PlayAllButton: FC = ({ item, items, viewType, hasFilters, libraryViewSettings }) => { +const PlayAllButton: FC = ({ + item, + items, + viewType, + hasFilters, + isTextVisible, + libraryViewSettings +}) => { const play = useCallback(() => { if (item && !hasFilters) { playbackManager.play({ @@ -47,19 +55,17 @@ const PlayAllButton: FC = ({ item, items, viewType, hasFilte }, [hasFilters, item, items, libraryViewSettings, viewType]); return ( - : undefined} onClick={play} - sx={{ - order: { - xs: 1, - sm: 'unset' - } - }} > - - + {isTextVisible ? ( + globalize.translate('HeaderPlayAll') + ) : ( + + )} + ); }; diff --git a/src/apps/experimental/components/library/QueueButton.tsx b/src/apps/experimental/components/library/QueueButton.tsx index c656e2b56..aa925e228 100644 --- a/src/apps/experimental/components/library/QueueButton.tsx +++ b/src/apps/experimental/components/library/QueueButton.tsx @@ -1,6 +1,6 @@ import React, { FC, useCallback } from 'react'; -import IconButton from '@mui/material/IconButton'; -import QueueIcon from '@mui/icons-material/Queue'; +import Button from '@mui/material/Button'; +import Queue from '@mui/icons-material/Queue'; import { playbackManager } from 'components/playback/playbackmanager'; import globalize from 'lib/globalize'; @@ -8,11 +8,17 @@ import type { ItemDto } from 'types/base/models/item-dto'; interface QueueButtonProps { item: ItemDto | undefined - items: ItemDto[]; - hasFilters: boolean; + items: ItemDto[] + hasFilters: boolean + isTextVisible: boolean } -const QueueButton: FC = ({ item, items, hasFilters }) => { +const QueueButton: FC = ({ + item, + items, + hasFilters, + isTextVisible +}) => { const queue = useCallback(() => { if (item && !hasFilters) { playbackManager.queue({ @@ -30,19 +36,17 @@ const QueueButton: FC = ({ item, items, hasFilters }) => { }, [hasFilters, item, items]); return ( - : undefined} onClick={queue} - sx={{ - order: { - xs: 3, - sm: 'unset' - } - }} > - - + {isTextVisible ? ( + globalize.translate('AddToPlayQueue') + ) : ( + + )} + ); }; diff --git a/src/apps/experimental/components/library/ShuffleButton.tsx b/src/apps/experimental/components/library/ShuffleButton.tsx index 44acdbc54..7f240df44 100644 --- a/src/apps/experimental/components/library/ShuffleButton.tsx +++ b/src/apps/experimental/components/library/ShuffleButton.tsx @@ -1,7 +1,7 @@ import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'; import React, { FC, useCallback } from 'react'; -import IconButton from '@mui/material/IconButton'; -import ShuffleIcon from '@mui/icons-material/Shuffle'; +import Shuffle from '@mui/icons-material/Shuffle'; +import Button from '@mui/material/Button'; import { playbackManager } from 'components/playback/playbackmanager'; import globalize from 'lib/globalize'; @@ -11,14 +11,22 @@ import { LibraryTab } from 'types/libraryTab'; import type { ItemDto } from 'types/base/models/item-dto'; interface ShuffleButtonProps { - item: ItemDto | undefined; - items: ItemDto[]; + item: ItemDto | undefined + items: ItemDto[] viewType: LibraryTab - hasFilters: boolean; + hasFilters: boolean + isTextVisible: boolean libraryViewSettings: LibraryViewSettings } -const ShuffleButton: FC = ({ item, items, viewType, hasFilters, libraryViewSettings }) => { +const ShuffleButton: FC = ({ + item, + items, + viewType, + hasFilters, + isTextVisible, + libraryViewSettings +}) => { const shuffle = useCallback(() => { if (item && !hasFilters) { playbackManager.shuffle(item); @@ -38,19 +46,17 @@ const ShuffleButton: FC = ({ item, items, viewType, hasFilte }, [hasFilters, item, items, libraryViewSettings, viewType]); return ( - : undefined} onClick={shuffle} - sx={{ - order: { - xs: 2, - sm: 'unset' - } - }} > - - + {isTextVisible ? ( + globalize.translate('Shuffle') + ) : ( + + )} + ); }; diff --git a/src/apps/experimental/components/library/SortButton.tsx b/src/apps/experimental/components/library/SortButton.tsx index 25f290b25..6e8acf8b9 100644 --- a/src/apps/experimental/components/library/SortButton.tsx +++ b/src/apps/experimental/components/library/SortButton.tsx @@ -1,12 +1,11 @@ import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'; import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; import React, { FC, useCallback } from 'react'; -import IconButton from '@mui/material/IconButton'; +import Button from '@mui/material/Button'; import MenuItem from '@mui/material/MenuItem'; import Popover from '@mui/material/Popover'; import Typography from '@mui/material/Typography'; import Divider from '@mui/material/Divider'; -import Box from '@mui/material/Box'; import InputLabel from '@mui/material/InputLabel'; import FormControl from '@mui/material/FormControl'; import Select, { SelectChangeEvent } from '@mui/material/Select'; @@ -149,16 +148,14 @@ const SortButton: FC = ({ const sortMenuOptions = getSortMenuOptions(viewType); return ( - - + = ({ - + ); }; diff --git a/src/apps/experimental/components/library/ViewSettingsButton.tsx b/src/apps/experimental/components/library/ViewSettingsButton.tsx index 2ee46b6f1..b61a41db6 100644 --- a/src/apps/experimental/components/library/ViewSettingsButton.tsx +++ b/src/apps/experimental/components/library/ViewSettingsButton.tsx @@ -1,25 +1,28 @@ import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; import React, { FC, useCallback } from 'react'; -import IconButton from '@mui/material/IconButton'; -import MenuItem from '@mui/material/MenuItem'; +import Check from '@mui/icons-material/Check'; +import MoreVert from '@mui/icons-material/MoreVert'; +import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; -import Typography from '@mui/material/Typography'; import Divider from '@mui/material/Divider'; -import Box from '@mui/material/Box'; -import InputLabel from '@mui/material/InputLabel'; import FormControl from '@mui/material/FormControl'; import FormControlLabel from '@mui/material/FormControlLabel'; import FormGroup from '@mui/material/FormGroup'; -import Select, { SelectChangeEvent } from '@mui/material/Select'; +import InputLabel from '@mui/material/InputLabel'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import MenuItem from '@mui/material/MenuItem'; +import MenuList from '@mui/material/MenuList'; import Popover from '@mui/material/Popover'; -import ViewComfyIcon from '@mui/icons-material/ViewComfy'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import Typography from '@mui/material/Typography'; import globalize from 'lib/globalize'; -import { LibraryViewSettings } from 'types/library'; +import { LibraryViewSettings, ViewMode } from 'types/library'; import { LibraryTab } from 'types/libraryTab'; -const excludedViewType = [ +const IMAGE_TYPE_EXCLUDED_VIEWS = [ LibraryTab.Episodes, LibraryTab.Artists, LibraryTab.AlbumArtists, @@ -71,6 +74,20 @@ const ViewSettingsButton: FC = ({ [setLibraryViewSettings] ); + const onGridViewClick = useCallback(() => { + setLibraryViewSettings(prevState => ({ + ...prevState, + ViewMode: ViewMode.GridView + })); + }, [ setLibraryViewSettings ]); + + const onListViewClick = useCallback(() => { + setLibraryViewSettings(prevState => ({ + ...prevState, + ViewMode: ViewMode.ListView + })); + }, [ setLibraryViewSettings ]); + const onSelectChange = useCallback( (event: SelectChangeEvent) => { setLibraryViewSettings((prevState) => ({ @@ -81,19 +98,18 @@ const ViewSettingsButton: FC = ({ [setLibraryViewSettings] ); - const isVisible = !excludedViewType.includes(viewType); + const isGridView = libraryViewSettings.ViewMode === ViewMode.GridView; + const isImageTypeVisible = !IMAGE_TYPE_EXCLUDED_VIEWS.includes(viewType); return ( - - + = ({ '& .MuiFormControl-root': { m: 1, width: 220 } }} > - {isVisible && ( - - - - {globalize.translate('LabelImageType')} - - - - - )} - - - - - } - label={globalize.translate('ShowTitle')} - /> - {isVisible && ( - - } - label={globalize.translate('ShowYear')} - /> + + + + {isGridView && ( + )} - - } - label={globalize.translate('EnableCardLayout')} - /> - - + + {globalize.translate('GridView')} + + + + {!isGridView && ( + + )} + + {globalize.translate('ListView')} + + + + {isGridView && ( + <> + + {isImageTypeVisible && ( + <> + + + + {globalize.translate('LabelImageType')} + + + + + + + )} + + + + } + label={globalize.translate('ShowTitle')} + /> + {isImageTypeVisible && ( + + } + label={globalize.translate('ShowYear')} + /> + )} + + } + label={globalize.translate('EnableCardLayout')} + /> + + + + )} + - + ); }; diff --git a/src/apps/experimental/components/library/filter/FilterButton.tsx b/src/apps/experimental/components/library/filter/FilterButton.tsx index 9442d8361..aed9d360b 100644 --- a/src/apps/experimental/components/library/filter/FilterButton.tsx +++ b/src/apps/experimental/components/library/filter/FilterButton.tsx @@ -1,8 +1,8 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import React, { FC, useCallback } from 'react'; import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'; -import Box from '@mui/material/Box'; -import FilterListIcon from '@mui/icons-material/FilterList'; +import FilterAlt from '@mui/icons-material/FilterAlt'; +import Button from '@mui/material/Button'; import Popover from '@mui/material/Popover'; import MuiAccordion, { AccordionProps } from '@mui/material/Accordion'; import MuiAccordionDetails from '@mui/material/AccordionDetails'; @@ -10,7 +10,6 @@ import MuiAccordionSummary, { AccordionSummaryProps } from '@mui/material/AccordionSummary'; import Badge from '@mui/material/Badge'; -import IconButton from '@mui/material/IconButton'; import { styled } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; @@ -158,18 +157,16 @@ const FilterButton: FC = ({ }; return ( - - + = ({ )} - + ); }; diff --git a/src/apps/experimental/routes/music/index.tsx b/src/apps/experimental/routes/music/index.tsx index 98e1b125a..566c63ea8 100644 --- a/src/apps/experimental/routes/music/index.tsx +++ b/src/apps/experimental/routes/music/index.tsx @@ -39,6 +39,7 @@ const playlistsTabContent: LibraryTabContent = { const songsTabContent: LibraryTabContent = { viewType: LibraryTab.Songs, + isBtnShuffleEnabled: true, isBtnGridListEnabled: false, isAlphabetPickerEnabled: false, itemType: [BaseItemKind.Audio] diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 6b6fd9324..a59ef8524 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1666,6 +1666,7 @@ "ViewAlbumArtist": "View album artist", "ViewLyrics": "View lyrics", "ViewPlaybackInfo": "View playback info", + "ViewSettings": "View settings", "Watched": "Watched", "Wednesday": "Wednesday", "WeeklyAt": "{0}s at {1}",