Merge pull request #6777 from thornbill/library-controls
This commit is contained in:
@@ -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<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const GridListViewButton: FC<GridListViewButtonProps> = ({
|
||||
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 (
|
||||
<ButtonGroup>
|
||||
{isGridView ? (
|
||||
<ViewSettingsButton
|
||||
viewType={viewType}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
title={globalize.translate('GridView')}
|
||||
className='paper-icon-button-light autoSize'
|
||||
disabled={isGridView}
|
||||
onClick={handleToggleCurrentView}
|
||||
>
|
||||
<ViewModuleIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
title={globalize.translate('ListView')}
|
||||
className='paper-icon-button-light autoSize'
|
||||
disabled={!isGridView}
|
||||
onClick={handleToggleCurrentView}
|
||||
>
|
||||
<ViewListIcon />
|
||||
</IconButton>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default GridListViewButton;
|
||||
@@ -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<ItemsViewProps> = ({
|
||||
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<ItemsViewProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box className='padded-bottom-page'>
|
||||
<Box
|
||||
className={classNames(
|
||||
'padded-top padded-left padded-right padded-bottom',
|
||||
'padded-top padded-left padded-right',
|
||||
{ 'padded-right-withalphapicker': isAlphabetPickerEnabled }
|
||||
)}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ marginRight: 1 }}
|
||||
>
|
||||
<LibraryViewMenu />
|
||||
</Box>
|
||||
|
||||
<LibraryViewMenu />
|
||||
|
||||
{isBtnPlayAllEnabled && (
|
||||
<PlayAllButton
|
||||
item={item}
|
||||
items={items}
|
||||
viewType={viewType}
|
||||
hasFilters={hasFilters}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
{isBtnShuffleEnabled && totalRecordCount > 1 && (
|
||||
<ShuffleButton
|
||||
item={item}
|
||||
items={items}
|
||||
viewType={viewType}
|
||||
hasFilters={hasFilters}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
{isBtnQueueEnabled
|
||||
&& item
|
||||
&& playbackManager.canQueue(item) && (
|
||||
<QueueButton
|
||||
item={item}
|
||||
items={items}
|
||||
hasFilters={hasFilters}
|
||||
/>
|
||||
)}
|
||||
{isBtnSortEnabled && (
|
||||
<SortButton
|
||||
viewType={viewType}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
{isBtnFilterEnabled && (
|
||||
<FilterButton
|
||||
parentId={parentId}
|
||||
itemType={itemType}
|
||||
viewType={viewType}
|
||||
hasFilters={hasFilters}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
{isBtnNewCollectionEnabled && <NewCollectionButton />}
|
||||
{isBtnGridListEnabled && (
|
||||
<GridListViewButton
|
||||
viewType={viewType}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isPaginationEnabled && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
flexGrow: 1,
|
||||
order: 10
|
||||
}}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: {
|
||||
xs: 1,
|
||||
sm: 0
|
||||
},
|
||||
marginRight: 1
|
||||
}}
|
||||
>
|
||||
<ButtonGroup
|
||||
color='inherit'
|
||||
variant='text'
|
||||
>
|
||||
{isBtnFilterEnabled && (
|
||||
<FilterButton
|
||||
parentId={parentId}
|
||||
itemType={itemType}
|
||||
viewType={viewType}
|
||||
hasFilters={hasFilters}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isBtnSortEnabled && (
|
||||
<SortButton
|
||||
viewType={viewType}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isBtnGridListEnabled && (
|
||||
<ViewSettingsButton
|
||||
viewType={viewType}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexGrow: {
|
||||
xs: 1,
|
||||
sm: 0
|
||||
},
|
||||
justifyContent: 'end'
|
||||
}}
|
||||
>
|
||||
{!isPending && (
|
||||
<>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
>
|
||||
{isBtnPlayAllEnabled && (
|
||||
<PlayAllButton
|
||||
item={item}
|
||||
items={items}
|
||||
viewType={viewType}
|
||||
hasFilters={hasFilters}
|
||||
isTextVisible={isSmallScreen}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isBtnShuffleEnabled && totalRecordCount > 1 && (
|
||||
<ShuffleButton
|
||||
item={item}
|
||||
items={items}
|
||||
viewType={viewType}
|
||||
hasFilters={hasFilters}
|
||||
isTextVisible={isSmallScreen && !isBtnPlayAllEnabled}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isBtnQueueEnabled && item && playbackManager.canQueue(item) && (
|
||||
<QueueButton
|
||||
item={item}
|
||||
items={items}
|
||||
hasFilters={hasFilters}
|
||||
isTextVisible={isSmallScreen && !isBtnPlayAllEnabled && !isBtnShuffleEnabled}
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
|
||||
{isBtnNewCollectionEnabled && <NewCollectionButton isTextVisible={isSmallScreen} />}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
flexBasis: {
|
||||
xs: '100%',
|
||||
sm: 'auto'
|
||||
},
|
||||
flexGrow: 1,
|
||||
marginTop: {
|
||||
xs: 0.5,
|
||||
sm: 0
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!isPending && isPaginationEnabled && (
|
||||
<Pagination
|
||||
totalRecordCount={totalRecordCount}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
isPlaceholderData={isPlaceholderData}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{isAlphabetPickerEnabled && hasSortName && (
|
||||
@@ -322,7 +378,7 @@ const ItemsView: FC<ItemsViewProps> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
{isPending ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<ItemsContainer
|
||||
@@ -335,10 +391,10 @@ const ItemsView: FC<ItemsViewProps> = ({
|
||||
</ItemsContainer>
|
||||
)}
|
||||
|
||||
{isPaginationEnabled && (
|
||||
{!isPending && isPaginationEnabled && (
|
||||
<Box
|
||||
className={classNames(
|
||||
'padded-top padded-left padded-right padded-bottom',
|
||||
'padded-left padded-right',
|
||||
{ 'padded-right-withalphapicker': isAlphabetPickerEnabled }
|
||||
)}
|
||||
sx={{
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import Add from '@mui/icons-material/Add';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
import globalize from 'lib/globalize';
|
||||
|
||||
const NewCollectionButton: FC = () => {
|
||||
interface NewCollectionButtonProps {
|
||||
isTextVisible: boolean
|
||||
}
|
||||
|
||||
const NewCollectionButton: FC<NewCollectionButtonProps> = ({
|
||||
isTextVisible
|
||||
}) => {
|
||||
const showCollectionEditor = useCallback(() => {
|
||||
import('components/collectionEditor/collectionEditor').then(
|
||||
({ default: CollectionEditor }) => {
|
||||
@@ -21,13 +28,17 @@ const NewCollectionButton: FC = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
title={globalize.translate('Add')}
|
||||
className='paper-icon-button-light btnNewCollection autoSize'
|
||||
<Button
|
||||
variant='contained'
|
||||
startIcon={isTextVisible ? <Add /> : undefined}
|
||||
onClick={showCollectionEditor}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
{isTextVisible ? (
|
||||
globalize.translate('NewCollection')
|
||||
) : (
|
||||
<Add />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<PaginationProps> = ({
|
||||
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<PaginationProps> = ({
|
||||
}, [limit, setLibraryViewSettings, startIndex]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className='paging'
|
||||
<Stack
|
||||
direction='row'
|
||||
spacing={0.5}
|
||||
sx={{
|
||||
display: 'flex'
|
||||
alignItems: 'center',
|
||||
flexGrow: {
|
||||
xs: 1,
|
||||
sm: 0
|
||||
},
|
||||
marginLeft: {
|
||||
xs: 0,
|
||||
sm: 0.5
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!isSmallScreen && (
|
||||
<Button
|
||||
color='inherit'
|
||||
variant='text'
|
||||
title={globalize.translate('Previous')}
|
||||
disabled={!showControls || startIndex == 0 || isPlaceholderData}
|
||||
onClick={onPreviousPageClick}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Box
|
||||
className='listPaging'
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 1,
|
||||
marginRight: 1
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<Typography variant='body2'>
|
||||
{globalize.translate(
|
||||
'ListPaging',
|
||||
recordsStart,
|
||||
recordsEnd,
|
||||
totalRecordCount
|
||||
)}
|
||||
</span>
|
||||
{showControls && (
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
title={globalize.translate('Previous')}
|
||||
className='paper-icon-button-light btnPreviousPage autoSize'
|
||||
disabled={startIndex == 0 || isPlaceholderData}
|
||||
onClick={onPreviousPageClick}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
title={globalize.translate('Next')}
|
||||
className='paper-icon-button-light btnNextPage autoSize'
|
||||
disabled={startIndex + limit >= totalRecordCount || isPlaceholderData }
|
||||
onClick={onNextPageClick}
|
||||
>
|
||||
<ArrowForwardIcon />
|
||||
</IconButton>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{isSmallScreen && (
|
||||
<ButtonGroup
|
||||
color='inherit'
|
||||
variant='text'
|
||||
>
|
||||
<Button
|
||||
title={globalize.translate('Previous')}
|
||||
disabled={!showControls || startIndex == 0 || isPlaceholderData}
|
||||
onClick={onPreviousPageClick}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
title={globalize.translate('Next')}
|
||||
disabled={!showControls || startIndex + limit >= totalRecordCount || isPlaceholderData }
|
||||
onClick={onNextPageClick}
|
||||
>
|
||||
<ArrowForwardIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
|
||||
{!isSmallScreen && (
|
||||
<Button
|
||||
color='inherit'
|
||||
variant='text'
|
||||
title={globalize.translate('Next')}
|
||||
disabled={!showControls || startIndex + limit >= totalRecordCount || isPlaceholderData }
|
||||
onClick={onNextPageClick}
|
||||
>
|
||||
<ArrowForwardIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<PlayAllButtonProps> = ({ item, items, viewType, hasFilters, libraryViewSettings }) => {
|
||||
const PlayAllButton: FC<PlayAllButtonProps> = ({
|
||||
item,
|
||||
items,
|
||||
viewType,
|
||||
hasFilters,
|
||||
isTextVisible,
|
||||
libraryViewSettings
|
||||
}) => {
|
||||
const play = useCallback(() => {
|
||||
if (item && !hasFilters) {
|
||||
playbackManager.play({
|
||||
@@ -47,19 +55,17 @@ const PlayAllButton: FC<PlayAllButtonProps> = ({ item, items, viewType, hasFilte
|
||||
}, [hasFilters, item, items, libraryViewSettings, viewType]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
<Button
|
||||
title={globalize.translate('HeaderPlayAll')}
|
||||
className='paper-icon-button-light btnPlay autoSize'
|
||||
startIcon={isTextVisible ? <PlayArrow /> : undefined}
|
||||
onClick={play}
|
||||
sx={{
|
||||
order: {
|
||||
xs: 1,
|
||||
sm: 'unset'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PlayArrowIcon />
|
||||
</IconButton>
|
||||
{isTextVisible ? (
|
||||
globalize.translate('HeaderPlayAll')
|
||||
) : (
|
||||
<PlayArrow />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<QueueButtonProps> = ({ item, items, hasFilters }) => {
|
||||
const QueueButton: FC<QueueButtonProps> = ({
|
||||
item,
|
||||
items,
|
||||
hasFilters,
|
||||
isTextVisible
|
||||
}) => {
|
||||
const queue = useCallback(() => {
|
||||
if (item && !hasFilters) {
|
||||
playbackManager.queue({
|
||||
@@ -30,19 +36,17 @@ const QueueButton: FC<QueueButtonProps> = ({ item, items, hasFilters }) => {
|
||||
}, [hasFilters, item, items]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
<Button
|
||||
title={globalize.translate('AddToPlayQueue')}
|
||||
className='paper-icon-button-light btnQueue autoSize'
|
||||
startIcon={isTextVisible ? <Queue /> : undefined}
|
||||
onClick={queue}
|
||||
sx={{
|
||||
order: {
|
||||
xs: 3,
|
||||
sm: 'unset'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<QueueIcon />
|
||||
</IconButton>
|
||||
{isTextVisible ? (
|
||||
globalize.translate('AddToPlayQueue')
|
||||
) : (
|
||||
<Queue />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<ShuffleButtonProps> = ({ item, items, viewType, hasFilters, libraryViewSettings }) => {
|
||||
const ShuffleButton: FC<ShuffleButtonProps> = ({
|
||||
item,
|
||||
items,
|
||||
viewType,
|
||||
hasFilters,
|
||||
isTextVisible,
|
||||
libraryViewSettings
|
||||
}) => {
|
||||
const shuffle = useCallback(() => {
|
||||
if (item && !hasFilters) {
|
||||
playbackManager.shuffle(item);
|
||||
@@ -38,19 +46,17 @@ const ShuffleButton: FC<ShuffleButtonProps> = ({ item, items, viewType, hasFilte
|
||||
}, [hasFilters, item, items, libraryViewSettings, viewType]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
<Button
|
||||
title={globalize.translate('Shuffle')}
|
||||
className='paper-icon-button-light btnShuffle autoSize'
|
||||
startIcon={isTextVisible ? <Shuffle /> : undefined}
|
||||
onClick={shuffle}
|
||||
sx={{
|
||||
order: {
|
||||
xs: 2,
|
||||
sm: 'unset'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ShuffleIcon />
|
||||
</IconButton>
|
||||
{isTextVisible ? (
|
||||
globalize.translate('Shuffle')
|
||||
) : (
|
||||
<Shuffle />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<SortButtonProps> = ({
|
||||
const sortMenuOptions = getSortMenuOptions(viewType);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<IconButton
|
||||
<>
|
||||
<Button
|
||||
title={globalize.translate('Sort')}
|
||||
sx={{ ml: 2 }}
|
||||
aria-describedby={id}
|
||||
className='paper-icon-button-light btnSort autoSize'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<SortByAlphaIcon />
|
||||
</IconButton>
|
||||
</Button>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
@@ -229,7 +226,7 @@ const SortButton: FC<SortButtonProps> = ({
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Popover>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<ViewSettingsButtonProps> = ({
|
||||
[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<ViewSettingsButtonProps> = ({
|
||||
[setLibraryViewSettings]
|
||||
);
|
||||
|
||||
const isVisible = !excludedViewType.includes(viewType);
|
||||
const isGridView = libraryViewSettings.ViewMode === ViewMode.GridView;
|
||||
const isImageTypeVisible = !IMAGE_TYPE_EXCLUDED_VIEWS.includes(viewType);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<IconButton
|
||||
title={globalize.translate('ButtonSelectView')}
|
||||
sx={{ ml: 2 }}
|
||||
<>
|
||||
<Button
|
||||
title={globalize.translate('ViewSettings')}
|
||||
aria-describedby={id}
|
||||
className='paper-icon-button-light btnSelectView autoSize'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ViewComfyIcon />
|
||||
</IconButton>
|
||||
<MoreVert />
|
||||
</Button>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
@@ -111,70 +127,101 @@ const ViewSettingsButton: FC<ViewSettingsButtonProps> = ({
|
||||
'& .MuiFormControl-root': { m: 1, width: 220 }
|
||||
}}
|
||||
>
|
||||
{isVisible && (
|
||||
<FormControl>
|
||||
<InputLabel id='select-sort-label'>
|
||||
<Typography component='span'>
|
||||
{globalize.translate('LabelImageType')}
|
||||
</Typography>
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={libraryViewSettings.ImageType}
|
||||
label={globalize.translate('LabelImageType')}
|
||||
onChange={onSelectChange}
|
||||
>
|
||||
{imageTypesOptions.map((imageType) => (
|
||||
<MenuItem
|
||||
key={imageType.value}
|
||||
value={imageType.value}
|
||||
>
|
||||
<Typography component='span'>
|
||||
{globalize.translate(imageType.label)}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<Divider />
|
||||
<FormControl>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.ShowTitle}
|
||||
onChange={handleChange}
|
||||
name='ShowTitle'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('ShowTitle')}
|
||||
/>
|
||||
{isVisible && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.ShowYear}
|
||||
onChange={handleChange}
|
||||
name='ShowYear'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('ShowYear')}
|
||||
/>
|
||||
<MenuList>
|
||||
|
||||
<MenuItem
|
||||
onClick={onGridViewClick}
|
||||
>
|
||||
{isGridView && (
|
||||
<ListItemIcon><Check /></ListItemIcon>
|
||||
)}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.CardLayout}
|
||||
onChange={handleChange}
|
||||
name='CardLayout'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('EnableCardLayout')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
<ListItemText inset={!isGridView}>
|
||||
{globalize.translate('GridView')}
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={onListViewClick}
|
||||
>
|
||||
{!isGridView && (
|
||||
<ListItemIcon><Check /></ListItemIcon>
|
||||
)}
|
||||
<ListItemText inset={isGridView}>
|
||||
{globalize.translate('ListView')}
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
{isGridView && (
|
||||
<>
|
||||
<Divider />
|
||||
{isImageTypeVisible && (
|
||||
<>
|
||||
<FormControl>
|
||||
<InputLabel>
|
||||
<Typography component='span'>
|
||||
{globalize.translate('LabelImageType')}
|
||||
</Typography>
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={libraryViewSettings.ImageType}
|
||||
label={globalize.translate('LabelImageType')}
|
||||
onChange={onSelectChange}
|
||||
>
|
||||
{imageTypesOptions.map((imageType) => (
|
||||
<MenuItem
|
||||
key={imageType.value}
|
||||
value={imageType.value}
|
||||
>
|
||||
<Typography component='span'>
|
||||
{globalize.translate(imageType.label)}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<FormControl>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.ShowTitle}
|
||||
onChange={handleChange}
|
||||
name='ShowTitle'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('ShowTitle')}
|
||||
/>
|
||||
{isImageTypeVisible && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.ShowYear}
|
||||
onChange={handleChange}
|
||||
name='ShowYear'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('ShowYear')}
|
||||
/>
|
||||
)}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.CardLayout}
|
||||
onChange={handleChange}
|
||||
name='CardLayout'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('EnableCardLayout')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
</>
|
||||
)}
|
||||
</MenuList>
|
||||
</Popover>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<FilterButtonProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<IconButton
|
||||
<>
|
||||
<Button
|
||||
title={globalize.translate('Filter')}
|
||||
sx={{ ml: 2 }}
|
||||
aria-describedby={id}
|
||||
className='paper-icon-button-light btnFilter autoSize'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Badge color='info' variant='dot' invisible={!hasFilters}>
|
||||
<FilterListIcon />
|
||||
<FilterAlt />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Button>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
@@ -451,7 +448,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
||||
</Accordion>
|
||||
)}
|
||||
</Popover>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const playlistsTabContent: LibraryTabContent = {
|
||||
|
||||
const songsTabContent: LibraryTabContent = {
|
||||
viewType: LibraryTab.Songs,
|
||||
isBtnShuffleEnabled: true,
|
||||
isBtnGridListEnabled: false,
|
||||
isAlphabetPickerEnabled: false,
|
||||
itemType: [BaseItemKind.Audio]
|
||||
|
||||
@@ -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}",
|
||||
|
||||
Reference in New Issue
Block a user