mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into trickplay-new
This commit is contained in:
commit
2dfc0aa061
137 changed files with 5909 additions and 2829 deletions
|
@ -1,26 +1,15 @@
|
|||
import { Devices, Analytics, Input, ExpandLess, ExpandMore } from '@mui/icons-material';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import { Devices, Analytics, Input } from '@mui/icons-material';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import ListItemLink from 'components/ListItemLink';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
const DLNA_PATHS = [
|
||||
'/dashboard/dlna',
|
||||
'/dashboard/dlna/profiles'
|
||||
];
|
||||
|
||||
const DevicesDrawerSection = () => {
|
||||
const location = useLocation();
|
||||
|
||||
const isDlnaSectionOpen = DLNA_PATHS.includes(location.pathname);
|
||||
|
||||
return (
|
||||
<List
|
||||
aria-labelledby='devices-subheader'
|
||||
|
@ -47,24 +36,13 @@ const DevicesDrawerSection = () => {
|
|||
</ListItemLink>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemLink to='/dashboard/dlna' selected={false}>
|
||||
<ListItemLink to='/dashboard/dlna'>
|
||||
<ListItemIcon>
|
||||
<Input />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={'DLNA'} />
|
||||
{isDlnaSectionOpen ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItemLink>
|
||||
</ListItem>
|
||||
<Collapse in={isDlnaSectionOpen} timeout='auto' unmountOnExit>
|
||||
<List component='div' disablePadding>
|
||||
<ListItemLink to='/dashboard/dlna' sx={{ pl: 4 }}>
|
||||
<ListItemText inset primary={globalize.translate('Settings')} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to='/dashboard/dlna/profiles' sx={{ pl: 4 }}>
|
||||
<ListItemText inset primary={globalize.translate('TabProfiles')} />
|
||||
</ListItemLink>
|
||||
</List>
|
||||
</Collapse>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ import { AsyncRouteType, type AsyncRoute } from 'components/router/AsyncRoute';
|
|||
|
||||
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
|
||||
{ path: 'activity', type: AsyncRouteType.Dashboard },
|
||||
{ path: 'dlna', type: AsyncRouteType.Dashboard },
|
||||
{ path: 'notifications', type: AsyncRouteType.Dashboard },
|
||||
{ path: 'users', type: AsyncRouteType.Dashboard },
|
||||
{ path: 'users/access', type: AsyncRouteType.Dashboard },
|
||||
|
|
|
@ -31,24 +31,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [
|
|||
controller: 'dashboard/devices/device',
|
||||
view: 'dashboard/devices/device.html'
|
||||
}
|
||||
}, {
|
||||
path: 'dlna/profiles/edit',
|
||||
pageProps: {
|
||||
controller: 'dashboard/dlna/profile',
|
||||
view: 'dashboard/dlna/profile.html'
|
||||
}
|
||||
}, {
|
||||
path: 'dlna/profiles',
|
||||
pageProps: {
|
||||
controller: 'dashboard/dlna/profiles',
|
||||
view: 'dashboard/dlna/profiles.html'
|
||||
}
|
||||
}, {
|
||||
path: 'dlna',
|
||||
pageProps: {
|
||||
controller: 'dashboard/dlna/settings',
|
||||
view: 'dashboard/dlna/settings.html'
|
||||
}
|
||||
}, {
|
||||
path: 'plugins/add',
|
||||
pageProps: {
|
||||
|
|
|
@ -8,8 +8,8 @@ export const REDIRECTS: Redirect[] = [
|
|||
{ from: 'dashboardgeneral.html', to: '/dashboard/settings' },
|
||||
{ from: 'device.html', to: '/dashboard/devices/edit' },
|
||||
{ from: 'devices.html', to: '/dashboard/devices' },
|
||||
{ from: 'dlnaprofile.html', to: '/dashboard/dlna/profiles/edit' },
|
||||
{ from: 'dlnaprofiles.html', to: '/dashboard/dlna/profiles' },
|
||||
{ from: 'dlnaprofile.html', to: '/dashboard/dlna' },
|
||||
{ from: 'dlnaprofiles.html', to: '/dashboard/dlna' },
|
||||
{ from: 'dlnasettings.html', to: '/dashboard/dlna' },
|
||||
{ from: 'edititemmetadata.html', to: '/metadata' },
|
||||
{ from: 'encodingsettings.html', to: '/dashboard/playback/transcoding' },
|
||||
|
|
33
src/apps/dashboard/routes/dlna.tsx
Normal file
33
src/apps/dashboard/routes/dlna.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import Alert from '@mui/material/Alert/Alert';
|
||||
import Box from '@mui/material/Box/Box';
|
||||
import Button from '@mui/material/Button/Button';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Page from 'components/Page';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
const DlnaPage = () => (
|
||||
<Page
|
||||
id='dlnaSettingsPage'
|
||||
title='DLNA'
|
||||
className='mainAnimatedPage type-interior'
|
||||
>
|
||||
<div className='content-primary'>
|
||||
<h2>DLNA</h2>
|
||||
<Alert severity='info'>
|
||||
<Box sx={{ marginBottom: 2 }}>
|
||||
{globalize.translate('DlnaMovedMessage')}
|
||||
</Box>
|
||||
<Button
|
||||
component={Link}
|
||||
to='/dashboard/plugins/add?name=DLNA&guid=33eba9cd7da14720967fdd7dae7b74a1'
|
||||
>
|
||||
{globalize.translate('GetThePlugin')}
|
||||
</Button>
|
||||
</Alert>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export default DlnaPage;
|
|
@ -1,23 +1,13 @@
|
|||
import Alert from '@mui/material/Alert/Alert';
|
||||
import Box from '@mui/material/Box/Box';
|
||||
import Button from '@mui/material/Button/Button';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Page from 'components/Page';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
const PluginLink = () => (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `<a
|
||||
is='emby-linkbutton'
|
||||
class='button-link'
|
||||
href='#/dashboard/plugins/add?name=Webhook&guid=71552a5a5c5c4350a2aeebe451a30173'
|
||||
>
|
||||
${globalize.translate('GetThePlugin')}
|
||||
</a>`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const Notifications = () => (
|
||||
const NotificationsPage = () => (
|
||||
<Page
|
||||
id='notificationSettingPage'
|
||||
title={globalize.translate('Notifications')}
|
||||
|
@ -25,12 +15,20 @@ const Notifications = () => (
|
|||
>
|
||||
<div className='content-primary'>
|
||||
<h2>{globalize.translate('Notifications')}</h2>
|
||||
<p>
|
||||
{globalize.translate('NotificationsMovedMessage')}
|
||||
</p>
|
||||
<PluginLink />
|
||||
|
||||
<Alert severity='info'>
|
||||
<Box sx={{ marginBottom: 2 }}>
|
||||
{globalize.translate('NotificationsMovedMessage')}
|
||||
</Box>
|
||||
<Button
|
||||
component={Link}
|
||||
to='/dashboard/plugins/add?name=Webhook&guid=71552a5a5c5c4350a2aeebe451a30173'
|
||||
>
|
||||
{globalize.translate('GetThePlugin')}
|
||||
</Button>
|
||||
</Alert>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export default Notifications;
|
||||
export default NotificationsPage;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import React, { FC } from 'react';
|
||||
import { useGetGenres } from 'hooks/useFetchItems';
|
||||
import globalize from 'scripts/globalize';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import GenresSectionContainer from './GenresSectionContainer';
|
||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import { ParentId } from 'types/library';
|
||||
import type { ParentId } from 'types/library';
|
||||
|
||||
interface GenresItemsContainerProps {
|
||||
parentId: ParentId;
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
|
||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
||||
import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
|
||||
import escapeHTML from 'escape-html';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import React, { type FC } from 'react';
|
||||
import { useGetItems } from 'hooks/useFetchItems';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import { ParentId } from 'types/library';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { ParentId } from 'types/library';
|
||||
|
||||
interface GenresSectionContainerProps {
|
||||
parentId: ParentId;
|
||||
|
@ -60,7 +59,7 @@ const GenresSectionContainer: FC<GenresSectionContainerProps> = ({
|
|||
}
|
||||
|
||||
return <SectionContainer
|
||||
sectionTitle={escapeHTML(genre.Name)}
|
||||
sectionTitle={genre.Name || ''}
|
||||
items={itemsResult?.Items || []}
|
||||
url={getRouteUrl(genre)}
|
||||
cardOptions={{
|
||||
|
@ -69,7 +68,7 @@ const GenresSectionContainer: FC<GenresSectionContainerProps> = ({
|
|||
showTitle: true,
|
||||
centerText: true,
|
||||
cardLayout: false,
|
||||
shape: collectionType === CollectionType.Music ? 'overflowSquare' : 'overflowPortrait',
|
||||
shape: collectionType === CollectionType.Music ? CardShape.SquareOverflow : CardShape.PortraitOverflow,
|
||||
showParentTitle: collectionType === CollectionType.Music,
|
||||
showYear: collectionType !== CollectionType.Music
|
||||
}}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import React, { FC } from 'react';
|
||||
import GenresItemsContainer from './GenresItemsContainer';
|
||||
import { ParentId } from 'types/library';
|
||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import type { ParentId } from 'types/library';
|
||||
|
||||
interface GenresViewProps {
|
||||
parentId: ParentId;
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client';
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import React, { type FC, useCallback } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import classNames from 'classnames';
|
||||
import { useLocalStorage } from 'hooks/useLocalStorage';
|
||||
import { useGetItem, useGetItemsViewByType } from 'hooks/useFetchItems';
|
||||
import { getDefaultLibraryViewSettings, getSettingsKey } from 'utils/items';
|
||||
import { CardShape } from 'utils/card';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import listview from 'components/listview/listview';
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import globalize from 'scripts/globalize';
|
||||
import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer';
|
||||
import AlphabetPicker from './AlphabetPicker';
|
||||
import FilterButton from './filter/FilterButton';
|
||||
|
@ -22,12 +21,13 @@ import QueueButton from './QueueButton';
|
|||
import ShuffleButton from './ShuffleButton';
|
||||
import SortButton from './SortButton';
|
||||
import GridListViewButton from './GridListViewButton';
|
||||
import { LibraryViewSettings, ParentId, ViewMode } from 'types/library';
|
||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import NoItemsMessage from 'components/common/NoItemsMessage';
|
||||
import Lists from 'components/listview/List/Lists';
|
||||
import Cards from 'components/cardbuilder/Card/Cards';
|
||||
import { LibraryTab } from 'types/libraryTab';
|
||||
|
||||
import { CardOptions } from 'types/cardOptions';
|
||||
import { ListOptions } from 'types/listOptions';
|
||||
import { type LibraryViewSettings, type ParentId, ViewMode } from 'types/library';
|
||||
import type { CardOptions } from 'types/cardOptions';
|
||||
import type { ListOptions } from 'types/listOptions';
|
||||
|
||||
interface ItemsViewProps {
|
||||
viewType: LibraryTab;
|
||||
|
@ -110,18 +110,18 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
let preferLogo;
|
||||
|
||||
if (libraryViewSettings.ImageType === ImageType.Banner) {
|
||||
shape = 'banner';
|
||||
shape = CardShape.Banner;
|
||||
} else if (libraryViewSettings.ImageType === ImageType.Disc) {
|
||||
shape = 'square';
|
||||
shape = CardShape.Square;
|
||||
preferDisc = true;
|
||||
} else if (libraryViewSettings.ImageType === ImageType.Logo) {
|
||||
shape = 'backdrop';
|
||||
shape = CardShape.Backdrop;
|
||||
preferLogo = true;
|
||||
} else if (libraryViewSettings.ImageType === ImageType.Thumb) {
|
||||
shape = 'backdrop';
|
||||
shape = CardShape.Backdrop;
|
||||
preferThumb = true;
|
||||
} else {
|
||||
shape = 'auto';
|
||||
shape = CardShape.Auto;
|
||||
}
|
||||
|
||||
const cardOptions: CardOptions = {
|
||||
|
@ -135,9 +135,9 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
preferThumb: preferThumb,
|
||||
preferDisc: preferDisc,
|
||||
preferLogo: preferLogo,
|
||||
overlayPlayButton: false,
|
||||
overlayMoreButton: true,
|
||||
overlayText: !libraryViewSettings.ShowTitle
|
||||
overlayText: !libraryViewSettings.ShowTitle,
|
||||
imageType: libraryViewSettings.ImageType,
|
||||
queryKey: ['ItemsViewByType']
|
||||
};
|
||||
|
||||
if (
|
||||
|
@ -146,20 +146,26 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
|| viewType === LibraryTab.Episodes
|
||||
) {
|
||||
cardOptions.showParentTitle = libraryViewSettings.ShowTitle;
|
||||
cardOptions.overlayPlayButton = true;
|
||||
} else if (viewType === LibraryTab.Artists) {
|
||||
cardOptions.lines = 1;
|
||||
cardOptions.showYear = false;
|
||||
cardOptions.overlayPlayButton = true;
|
||||
} else if (viewType === LibraryTab.Channels) {
|
||||
cardOptions.shape = 'square';
|
||||
cardOptions.shape = CardShape.Square;
|
||||
cardOptions.showDetailsMenu = true;
|
||||
cardOptions.showCurrentProgram = true;
|
||||
cardOptions.showCurrentProgramTime = true;
|
||||
} else if (viewType === LibraryTab.SeriesTimers) {
|
||||
cardOptions.defaultShape = 'portrait';
|
||||
cardOptions.preferThumb = 'auto';
|
||||
cardOptions.shape = CardShape.Backdrop;
|
||||
cardOptions.showSeriesTimerTime = true;
|
||||
cardOptions.showSeriesTimerChannel = true;
|
||||
cardOptions.overlayMoreButton = true;
|
||||
cardOptions.lines = 3;
|
||||
} else if (viewType === LibraryTab.Movies) {
|
||||
cardOptions.overlayPlayButton = true;
|
||||
} else if (viewType === LibraryTab.Series || viewType === LibraryTab.Networks) {
|
||||
cardOptions.overlayMoreButton = true;
|
||||
}
|
||||
|
||||
return cardOptions;
|
||||
|
@ -172,27 +178,32 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
viewType
|
||||
]);
|
||||
|
||||
const getItemsHtml = useCallback(() => {
|
||||
let html = '';
|
||||
const getItems = useCallback(() => {
|
||||
if (!itemsResult?.Items?.length) {
|
||||
return <NoItemsMessage noItemsMessage={noItemsMessage} />;
|
||||
}
|
||||
|
||||
if (libraryViewSettings.ViewMode === ViewMode.ListView) {
|
||||
html = listview.getListViewHtml(getListOptions());
|
||||
} else {
|
||||
html = cardBuilder.getCardsHtml(
|
||||
itemsResult?.Items ?? [],
|
||||
getCardOptions()
|
||||
return (
|
||||
<Lists
|
||||
items={itemsResult?.Items ?? []}
|
||||
listOptions={getListOptions()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!itemsResult?.Items?.length) {
|
||||
html += '<div class="noItemsMessage centerMessage">';
|
||||
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
|
||||
html += '<p>' + globalize.translate(noItemsMessage) + '</p>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}, [libraryViewSettings.ViewMode, itemsResult?.Items, getListOptions, getCardOptions, noItemsMessage]);
|
||||
return (
|
||||
<Cards
|
||||
items={itemsResult?.Items ?? []}
|
||||
cardOptions={getCardOptions()}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
libraryViewSettings.ViewMode,
|
||||
itemsResult?.Items,
|
||||
getListOptions,
|
||||
getCardOptions,
|
||||
noItemsMessage
|
||||
]);
|
||||
|
||||
const totalRecordCount = itemsResult?.TotalRecordCount ?? 0;
|
||||
const items = itemsResult?.Items ?? [];
|
||||
|
@ -289,8 +300,10 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
className={itemsContainerClass}
|
||||
parentId={parentId}
|
||||
reloadItems={refetch}
|
||||
getItemsHtml={getItemsHtml}
|
||||
/>
|
||||
queryKey={['ItemsViewByType']}
|
||||
>
|
||||
{getItems()}
|
||||
</ItemsContainer>
|
||||
)}
|
||||
|
||||
{isPaginationEnabled && (
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { type FC } from 'react';
|
||||
import SuggestionsSectionView from './SuggestionsSectionView';
|
||||
import UpcomingView from './UpcomingView';
|
||||
import GenresView from './GenresView';
|
||||
import ItemsView from './ItemsView';
|
||||
import { LibraryTab } from 'types/libraryTab';
|
||||
import { ParentId } from 'types/library';
|
||||
import { LibraryTabContent } from 'types/libraryTabContent';
|
||||
import GuideView from './GuideView';
|
||||
import ProgramsSectionView from './ProgramsSectionView';
|
||||
import { LibraryTab } from 'types/libraryTab';
|
||||
import type { ParentId } from 'types/library';
|
||||
import type { LibraryTabContent } from 'types/libraryTabContent';
|
||||
|
||||
interface PageTabContentProps {
|
||||
parentId: ParentId;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { type FC } from 'react';
|
||||
import { useGetProgramsSectionsWithItems, useGetTimers } from 'hooks/useFetchItems';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import globalize from 'scripts/globalize';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import { ParentId } from 'types/library';
|
||||
import { Section, SectionType } from 'types/sections';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { ParentId } from 'types/library';
|
||||
import type { Section, SectionType } from 'types/sections';
|
||||
|
||||
interface ProgramsSectionViewProps {
|
||||
parentId: ParentId;
|
||||
|
@ -18,7 +19,7 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
|
|||
sectionType,
|
||||
isUpcomingRecordingsEnabled = false
|
||||
}) => {
|
||||
const { isLoading, data: sectionsWithItems } = useGetProgramsSectionsWithItems(parentId, sectionType);
|
||||
const { isLoading, data: sectionsWithItems, refetch } = useGetProgramsSectionsWithItems(parentId, sectionType);
|
||||
const {
|
||||
isLoading: isUpcomingRecordingsLoading,
|
||||
data: upcomingRecordings
|
||||
|
@ -60,8 +61,10 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
|
|||
sectionTitle={globalize.translate(section.name)}
|
||||
items={items ?? []}
|
||||
url={getRouteUrl(section)}
|
||||
reloadItems={refetch}
|
||||
cardOptions={{
|
||||
...section.cardOptions
|
||||
...section.cardOptions,
|
||||
queryKey: ['ProgramSectionWithItems']
|
||||
}}
|
||||
/>
|
||||
|
||||
|
@ -73,7 +76,8 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
|
|||
sectionTitle={group.name}
|
||||
items={group.timerInfo ?? []}
|
||||
cardOptions={{
|
||||
shape: 'overflowBackdrop',
|
||||
queryKey: ['Timers'],
|
||||
shape: CardShape.BackdropOverflow,
|
||||
showTitle: true,
|
||||
showParentTitleOrTitle: true,
|
||||
showAirTime: true,
|
||||
|
|
|
@ -1,43 +1,29 @@
|
|||
import type { BaseItemDto, TimerInfoDto } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useEffect, useRef } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer';
|
||||
import Scroller from 'elements/emby-scroller/Scroller';
|
||||
import LinkButton from 'elements/emby-button/LinkButton';
|
||||
import imageLoader from 'components/images/imageLoader';
|
||||
|
||||
import { CardOptions } from 'types/cardOptions';
|
||||
import Cards from 'components/cardbuilder/Card/Cards';
|
||||
import type { CardOptions } from 'types/cardOptions';
|
||||
|
||||
interface SectionContainerProps {
|
||||
url?: string;
|
||||
sectionTitle: string;
|
||||
items: BaseItemDto[] | TimerInfoDto[];
|
||||
cardOptions: CardOptions;
|
||||
reloadItems?: () => void;
|
||||
}
|
||||
|
||||
const SectionContainer: FC<SectionContainerProps> = ({
|
||||
sectionTitle,
|
||||
url,
|
||||
items,
|
||||
cardOptions
|
||||
cardOptions,
|
||||
reloadItems
|
||||
}) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const itemsContainer = element.current?.querySelector('.itemsContainer');
|
||||
cardBuilder.buildCards(items, {
|
||||
itemsContainer: itemsContainer,
|
||||
parentContainer: element.current,
|
||||
|
||||
...cardOptions
|
||||
});
|
||||
|
||||
imageLoader.lazyChildren(itemsContainer);
|
||||
}, [cardOptions, items]);
|
||||
|
||||
return (
|
||||
<div ref={element} className='verticalSection hide'>
|
||||
<div className='verticalSection'>
|
||||
<div className='sectionTitleContainer sectionTitleContainer-cards padded-left'>
|
||||
{url && items.length > 5 ? (
|
||||
<LinkButton
|
||||
|
@ -66,7 +52,11 @@ const SectionContainer: FC<SectionContainerProps> = ({
|
|||
>
|
||||
<ItemsContainer
|
||||
className='itemsContainer scrollSlider focuscontainer-x'
|
||||
/>
|
||||
reloadItems={reloadItems}
|
||||
queryKey={cardOptions.queryKey}
|
||||
>
|
||||
<Cards items={items} cardOptions={cardOptions} />
|
||||
</ItemsContainer>
|
||||
</Scroller>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import {
|
||||
RecommendationDto,
|
||||
type RecommendationDto,
|
||||
RecommendationType
|
||||
} from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC } from 'react';
|
||||
import escapeHTML from 'escape-html';
|
||||
import React, { type FC } from 'react';
|
||||
import {
|
||||
useGetMovieRecommendations,
|
||||
useGetSuggestionSectionsWithItems
|
||||
|
@ -12,8 +11,9 @@ import { appRouter } from 'components/router/appRouter';
|
|||
import globalize from 'scripts/globalize';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import { ParentId } from 'types/library';
|
||||
import { Section, SectionType } from 'types/sections';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { ParentId } from 'types/library';
|
||||
import type { Section, SectionType } from 'types/sections';
|
||||
|
||||
interface SuggestionsSectionViewProps {
|
||||
parentId: ParentId;
|
||||
|
@ -89,7 +89,7 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
|
|||
);
|
||||
break;
|
||||
}
|
||||
return escapeHTML(title);
|
||||
return title;
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -102,6 +102,7 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
|
|||
url={getRouteUrl(section)}
|
||||
cardOptions={{
|
||||
...section.cardOptions,
|
||||
queryKey: ['SuggestionSectionWithItems'],
|
||||
showTitle: true,
|
||||
centerText: true,
|
||||
cardLayout: false,
|
||||
|
@ -117,7 +118,8 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
|
|||
sectionTitle={getRecommendationTittle(recommendation)}
|
||||
items={recommendation.Items ?? []}
|
||||
cardOptions={{
|
||||
shape: 'overflowPortrait',
|
||||
queryKey: ['MovieRecommendations'],
|
||||
shape: CardShape.PortraitOverflow,
|
||||
showYear: true,
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { type FC } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useGetGroupsUpcomingEpisodes } from 'hooks/useFetchItems';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import globalize from 'scripts/globalize';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import { LibraryViewProps } from 'types/library';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { LibraryViewProps } from 'types/library';
|
||||
|
||||
const UpcomingView: FC<LibraryViewProps> = ({ parentId }) => {
|
||||
const { isLoading, data: groupsUpcomingEpisodes } = useGetGroupsUpcomingEpisodes(parentId);
|
||||
|
@ -29,7 +30,7 @@ const UpcomingView: FC<LibraryViewProps> = ({ parentId }) => {
|
|||
sectionTitle={group.name}
|
||||
items={group.items ?? []}
|
||||
cardOptions={{
|
||||
shape: 'overflowBackdrop',
|
||||
shape: CardShape.BackdropOverflow,
|
||||
showLocationTypeIndicator: false,
|
||||
showParentTitle: true,
|
||||
preferThumb: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue