mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Refactoring Suggestions View
This commit is contained in:
parent
cf137497a0
commit
9d88af3dfe
5 changed files with 116 additions and 158 deletions
|
@ -1,13 +1,9 @@
|
|||
import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
|
||||
import { RecommendationDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useEffect, useRef } from 'react';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import ItemsContainerElement from '../../elements/ItemsContainerElement';
|
||||
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
|
||||
import escapeHTML from 'escape-html';
|
||||
import SectionContainer from './SectionContainer';
|
||||
|
||||
type RecommendationContainerProps = {
|
||||
getPortraitShape: () => string;
|
||||
|
@ -16,8 +12,6 @@ type RecommendationContainerProps = {
|
|||
}
|
||||
|
||||
const RecommendationContainer: FunctionComponent<RecommendationContainerProps> = ({ getPortraitShape, enableScrollX, recommendation = {} }: RecommendationContainerProps) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
let title = '';
|
||||
|
||||
switch (recommendation.RecommendationType) {
|
||||
|
@ -40,40 +34,15 @@ const RecommendationContainer: FunctionComponent<RecommendationContainerProps> =
|
|||
break;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
cardBuilder.buildCards(recommendation.Items || [], {
|
||||
itemsContainer: element.current?.querySelector('.itemsContainer'),
|
||||
return <SectionContainer
|
||||
sectionTitle={escapeHTML(title)}
|
||||
enableScrollX={enableScrollX}
|
||||
items={recommendation.Items || []}
|
||||
cardOptions={{
|
||||
shape: getPortraitShape(),
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
allowBottomPadding: true,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
}, [enableScrollX, getPortraitShape, recommendation]);
|
||||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<div className='verticalSection'>
|
||||
<div className='sectionTitleContainer sectionTitleContainer-cards'>
|
||||
<h2 className='sectionTitle sectionTitle-cards padded-left'>
|
||||
{escapeHTML(title)}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{enableScrollX() ? <ItemsScrollerContainerElement
|
||||
scrollerclassName='padded-top-focusscale padded-bottom-focusscale'
|
||||
dataMousewheel='false'
|
||||
dataCenterfocus='true'
|
||||
className='itemsContainer scrollSlider focuscontainer-x'
|
||||
/> : <ItemsContainerElement
|
||||
className='itemsContainer focuscontainer-x padded-left padded-right vertical-wrap'
|
||||
/>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
showYear: true
|
||||
}}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default RecommendationContainer;
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
|
||||
import { BaseItemDtoQueryResult } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useEffect, useRef } from 'react';
|
||||
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import ItemsContainerElement from '../../elements/ItemsContainerElement';
|
||||
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
|
||||
|
||||
type ResumableItemsContainerProps = {
|
||||
getThumbShape: () => string;
|
||||
enableScrollX: () => boolean;
|
||||
itemsResult?: BaseItemDtoQueryResult;
|
||||
}
|
||||
|
||||
const ResumableItemsContainer: FunctionComponent<ResumableItemsContainerProps> = ({ getThumbShape, enableScrollX, itemsResult = {} }: ResumableItemsContainerProps) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const allowBottomPadding = !enableScrollX();
|
||||
cardBuilder.buildCards(itemsResult.Items || [], {
|
||||
itemsContainer: element.current?.querySelector('.itemsContainer'),
|
||||
parentContainer: element.current?.querySelector('#resumableSection'),
|
||||
preferThumb: true,
|
||||
shape: getThumbShape(),
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
allowBottomPadding: allowBottomPadding,
|
||||
cardLayout: false,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
}, [enableScrollX, getThumbShape, itemsResult.Items]);
|
||||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<div id='resumableSection' className='verticalSection hide'>
|
||||
<div className='sectionTitleContainer sectionTitleContainer-cards'>
|
||||
<h2 className='sectionTitle sectionTitle-cards padded-left'>
|
||||
{globalize.translate('HeaderContinueWatching')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{enableScrollX() ? <ItemsScrollerContainerElement
|
||||
scrollerclassName='padded-top-focusscale padded-bottom-focusscale'
|
||||
dataMousewheel='false'
|
||||
dataCenterfocus='true'
|
||||
className='itemsContainer scrollSlider focuscontainer-x'
|
||||
/> : <ItemsContainerElement
|
||||
className='itemsContainer focuscontainer-x padded-left padded-right vertical-wrap'
|
||||
/>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResumableItemsContainer;
|
|
@ -4,39 +4,44 @@ import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
|||
import React, { FunctionComponent, useEffect, useRef } from 'react';
|
||||
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import ItemsContainerElement from '../../elements/ItemsContainerElement';
|
||||
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
|
||||
import { ICardOptions } from './type';
|
||||
|
||||
type RecentlyAddedItemsContainerProps = {
|
||||
getPortraitShape: () => string;
|
||||
type SectionContainerProps = {
|
||||
sectionTitle: string;
|
||||
enableScrollX: () => boolean;
|
||||
items?: BaseItemDto[];
|
||||
cardOptions?: ICardOptions;
|
||||
}
|
||||
|
||||
const RecentlyAddedItemsContainer: FunctionComponent<RecentlyAddedItemsContainerProps> = ({ getPortraitShape, enableScrollX, items = [] }: RecentlyAddedItemsContainerProps) => {
|
||||
const SectionContainer: FunctionComponent<SectionContainerProps> = ({
|
||||
sectionTitle,
|
||||
enableScrollX,
|
||||
items = [],
|
||||
cardOptions = {}
|
||||
}: SectionContainerProps) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
cardBuilder.buildCards(items, {
|
||||
itemsContainer: element.current?.querySelector('.itemsContainer'),
|
||||
parentContainer: element.current?.querySelector('#recentlyAddedItemsSection'),
|
||||
shape: getPortraitShape(),
|
||||
parentContainer: element.current?.querySelector('.verticalSection'),
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
allowBottomPadding: true,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
centerText: true,
|
||||
cardLayout: false,
|
||||
...cardOptions
|
||||
});
|
||||
}, [enableScrollX, getPortraitShape, items]);
|
||||
}, [cardOptions, enableScrollX, items]);
|
||||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<div id='recentlyAddedItemsSection' className='verticalSection hide'>
|
||||
<div className='verticalSection hide'>
|
||||
<div className='sectionTitleContainer sectionTitleContainer-cards'>
|
||||
<h2 className='sectionTitle sectionTitle-cards padded-left'>
|
||||
{globalize.translate('HeaderLatestMovies')}
|
||||
{sectionTitle}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
|
@ -54,4 +59,4 @@ const RecentlyAddedItemsContainer: FunctionComponent<RecentlyAddedItemsContainer
|
|||
);
|
||||
};
|
||||
|
||||
export default RecentlyAddedItemsContainer;
|
||||
export default SectionContainer;
|
|
@ -10,7 +10,40 @@ export type IQuery = {
|
|||
StartIndex: number;
|
||||
ParentId?: string | null;
|
||||
IsFavorite?: boolean;
|
||||
IsMissing?: boolean;
|
||||
Limit:number;
|
||||
NameLessThan?: string;
|
||||
NameStartsWith?: string;
|
||||
}
|
||||
|
||||
export type ICardOptions = {
|
||||
itemsContainer?: HTMLElement;
|
||||
parentContainer?: HTMLElement;
|
||||
allowBottomPadding?: boolean;
|
||||
centerText?: boolean;
|
||||
coverImage?: boolean;
|
||||
inheritThumb?: boolean;
|
||||
overlayMoreButton?: boolean;
|
||||
overlayPlayButton?: boolean;
|
||||
overlayText?: boolean;
|
||||
preferThumb?: boolean;
|
||||
scalable?: boolean;
|
||||
shape?: string;
|
||||
lazy?: boolean;
|
||||
cardLayout?: boolean;
|
||||
showParentTitle?: boolean;
|
||||
showParentTitleOrTitle?: boolean;
|
||||
showAirTime?: boolean;
|
||||
showAirDateTime?: boolean;
|
||||
showChannelName?: boolean;
|
||||
showTitle?: boolean;
|
||||
showYear?: boolean;
|
||||
showDetailsMenu?: boolean;
|
||||
missingIndicator?: boolean;
|
||||
showLocationTypeIndicator?: boolean;
|
||||
showSeriesYear?: boolean;
|
||||
showUnplayedIndicator?: boolean;
|
||||
showChildCountIndicator?: boolean;
|
||||
lines?: number;
|
||||
context?: string;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ import layoutManager from '../../components/layoutManager';
|
|||
import loading from '../../components/loading/loading';
|
||||
import dom from '../../scripts/dom';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import RecentlyAddedItemsContainer from '../components/RecentlyAddedItemsContainer';
|
||||
import RecommendationContainer from '../components/RecommendationContainer';
|
||||
import ResumableItemsContainer from '../components/ResumableItemsContainer';
|
||||
import SectionContainer from '../components/SectionContainer';
|
||||
|
||||
type IProps = {
|
||||
topParentId: string | null;
|
||||
|
@ -15,7 +14,7 @@ type IProps = {
|
|||
|
||||
const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
|
||||
const [ latestItems, setLatestItems ] = useState<BaseItemDto[]>([]);
|
||||
const [ resumeItemsResult, setResumeItemsResult ] = useState<BaseItemDtoQueryResult>();
|
||||
const [ resumeResult, setResumeResult ] = useState<BaseItemDtoQueryResult>({});
|
||||
const [ recommendations, setRecommendations ] = useState<RecommendationDto[]>([]);
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
@ -37,6 +36,31 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
|
|||
});
|
||||
}, []);
|
||||
|
||||
const loadResume = useCallback((page, userId, parentId) => {
|
||||
loading.show();
|
||||
const screenWidth = dom.getWindowSize().innerWidth;
|
||||
const options = {
|
||||
SortBy: 'DatePlayed',
|
||||
SortOrder: 'Descending',
|
||||
IncludeItemTypes: 'Movie',
|
||||
Filters: 'IsResumable',
|
||||
Limit: screenWidth >= 1600 ? 5 : 3,
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||
CollapseBoxSetItems: false,
|
||||
ParentId: parentId,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
EnableTotalRecordCount: false
|
||||
};
|
||||
window.ApiClient.getItems(userId, options).then(result => {
|
||||
setResumeResult(result);
|
||||
|
||||
loading.hide();
|
||||
autoFocus(page);
|
||||
});
|
||||
}, [autoFocus]);
|
||||
|
||||
const loadLatest = useCallback((page: HTMLDivElement, userId: string, parentId: string | null) => {
|
||||
const options = {
|
||||
IncludeItemTypes: 'Movie',
|
||||
|
@ -50,43 +74,16 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
|
|||
window.ApiClient.getJSON(window.ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(items => {
|
||||
setLatestItems(items);
|
||||
|
||||
// FIXME: Wait for all sections to load
|
||||
autoFocus(page);
|
||||
});
|
||||
}, [autoFocus]);
|
||||
|
||||
const loadResume = useCallback((page, userId, parentId) => {
|
||||
loading.show();
|
||||
const screenWidth = dom.getWindowSize();
|
||||
const options = {
|
||||
SortBy: 'DatePlayed',
|
||||
SortOrder: 'Descending',
|
||||
IncludeItemTypes: 'Movie',
|
||||
Filters: 'IsResumable',
|
||||
Limit: screenWidth.innerWidth >= 1600 ? 5 : 3,
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||
CollapseBoxSetItems: false,
|
||||
ParentId: parentId,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
EnableTotalRecordCount: false
|
||||
};
|
||||
window.ApiClient.getItems(userId, options).then(result => {
|
||||
setResumeItemsResult(result);
|
||||
|
||||
loading.hide();
|
||||
// FIXME: Wait for all sections to load
|
||||
autoFocus(page);
|
||||
});
|
||||
}, [autoFocus]);
|
||||
|
||||
const loadSuggestions = useCallback((page, userId) => {
|
||||
const screenWidth = dom.getWindowSize();
|
||||
const screenWidth = dom.getWindowSize().innerWidth;
|
||||
let itemLimit = 5;
|
||||
if (screenWidth.innerWidth >= 1600) {
|
||||
if (screenWidth >= 1600) {
|
||||
itemLimit = 8;
|
||||
} else if (screenWidth.innerWidth >= 1200) {
|
||||
} else if (screenWidth >= 1200) {
|
||||
itemLimit = 6;
|
||||
}
|
||||
const url = window.window.ApiClient.getUrl('Movies/Recommendations', {
|
||||
|
@ -100,7 +97,6 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
|
|||
window.ApiClient.getJSON(url).then(result => {
|
||||
setRecommendations(result);
|
||||
|
||||
// FIXME: Wait for all sections to load
|
||||
autoFocus(page);
|
||||
});
|
||||
}, [autoFocus]);
|
||||
|
@ -126,11 +122,27 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
|
|||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<ResumableItemsContainer getThumbShape={getThumbShape} enableScrollX={enableScrollX} itemsResult={resumeItemsResult} />
|
||||
<SectionContainer
|
||||
sectionTitle={globalize.translate('HeaderContinueWatching')}
|
||||
enableScrollX={enableScrollX}
|
||||
items={resumeResult.Items || []}
|
||||
cardOptions={{
|
||||
preferThumb: true,
|
||||
shape: getThumbShape(),
|
||||
showYear: true
|
||||
}}
|
||||
/>
|
||||
|
||||
<RecentlyAddedItemsContainer getPortraitShape={getPortraitShape} enableScrollX={enableScrollX} items={latestItems} />
|
||||
<SectionContainer
|
||||
sectionTitle={globalize.translate('HeaderLatestMovies')}
|
||||
enableScrollX={enableScrollX}
|
||||
items={latestItems}
|
||||
cardOptions={{
|
||||
shape: getPortraitShape(),
|
||||
showYear: true
|
||||
}}
|
||||
/>
|
||||
|
||||
<div id='recommendations'>
|
||||
{!recommendations.length ? <div className='noItemsMessage centerMessage'>
|
||||
<h1>{globalize.translate('MessageNothingHere')}</h1>
|
||||
<p>{globalize.translate('MessageNoMovieSuggestionsAvailable')}</p>
|
||||
|
@ -138,7 +150,6 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
|
|||
return <RecommendationContainer key={index} getPortraitShape={getPortraitShape} enableScrollX={enableScrollX} recommendation={recommendation} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue