1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

apply suggestion

This commit is contained in:
grafixeyehero 2022-10-27 00:59:46 +03:00
parent 7805e86f70
commit d7e48d30b6
21 changed files with 172 additions and 140 deletions

View file

@ -0,0 +1,53 @@
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import AlphaPicker from '../alphaPicker/alphaPicker';
import { ViewQuerySettings } from '../../types/interface';
interface AlphaPickerContainerProps {
viewQuerySettings: ViewQuerySettings;
setViewQuerySettings: React.Dispatch<React.SetStateAction<ViewQuerySettings>>;
}
const AlphaPickerContainer: FC<AlphaPickerContainerProps> = ({ viewQuerySettings, setViewQuerySettings }) => {
const [ alphaPicker, setAlphaPicker ] = useState<AlphaPicker>();
const element = useRef<HTMLDivElement>(null);
alphaPicker?.updateControls(viewQuerySettings);
const onAlphaPickerChange = useCallback((e) => {
const newValue = (e as CustomEvent).detail.value;
let updatedValue: React.SetStateAction<ViewQuerySettings>;
if (newValue === '#') {
updatedValue = {NameLessThan: 'A'};
} else {
updatedValue = {NameStartsWith: newValue};
}
setViewQuerySettings((prevState) => ({
...prevState,
StartIndex: 0,
...updatedValue
}));
}, [setViewQuerySettings]);
useEffect(() => {
const alphaPickerElement = element.current;
setAlphaPicker(new AlphaPicker({
element: alphaPickerElement,
valueChangeEvent: 'click'
}));
if (alphaPickerElement) {
alphaPickerElement.addEventListener('alphavaluechanged', onAlphaPickerChange);
}
return () => {
alphaPickerElement?.removeEventListener('alphavaluechanged', onAlphaPickerChange);
};
}, [onAlphaPickerChange]);
return (
<div ref={element} className='alphaPicker alphaPicker-fixed alphaPicker-fixed-right alphaPicker-vertical alphabetPicker-right' />
);
};
export default AlphaPickerContainer;

View file

@ -0,0 +1,61 @@
import React, { FC, useCallback, useEffect, useRef } from 'react';
import IconButtonElement from '../../elements/IconButtonElement';
import { ViewQuerySettings } from '../../types/interface';
interface FilterProps {
topParentId?: string | null;
getItemTypes: () => string[];
getFilterMenuOptions: () => Record<string, never>;
getVisibleFilters: () => string[];
viewQuerySettings: ViewQuerySettings;
setViewQuerySettings: React.Dispatch<React.SetStateAction<ViewQuerySettings>>;
}
const Filter: FC<FilterProps> = ({
topParentId,
getItemTypes,
getVisibleFilters,
getFilterMenuOptions,
viewQuerySettings,
setViewQuerySettings
}) => {
const element = useRef<HTMLDivElement>(null);
const showFilterMenu = useCallback(() => {
import('../filtermenu/filtermenu').then(({default: FilterMenu}) => {
const filterMenu = new FilterMenu();
filterMenu.show({
settings: viewQuerySettings,
visibleSettings: getVisibleFilters(),
parentId: topParentId,
itemTypes: getItemTypes(),
serverId: window.ApiClient.serverId(),
filterMenuOptions: getFilterMenuOptions(),
setfilters: setViewQuerySettings
});
});
}, [viewQuerySettings, getVisibleFilters, topParentId, getItemTypes, getFilterMenuOptions, setViewQuerySettings]);
useEffect(() => {
const btnFilter = element.current?.querySelector('.btnFilter');
btnFilter?.addEventListener('click', showFilterMenu);
return () => {
btnFilter?.removeEventListener('click', showFilterMenu);
};
}, [showFilterMenu]);
return (
<div ref={element}>
<IconButtonElement
is='paper-icon-button-light'
className='btnFilter autoSize'
title='Filter'
icon='material-icons filter_list'
/>
</div>
);
};
export default Filter;

View file

@ -0,0 +1,159 @@
import '../../elements/emby-button/emby-button';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
import escapeHTML from 'escape-html';
import React, { FC, useCallback, useEffect, useRef } from 'react';
import { appRouter } from '../appRouter';
import cardBuilder from '../cardbuilder/cardBuilder';
import layoutManager from '../layoutManager';
import lazyLoader from '../lazyLoader/lazyLoaderIntersectionObserver';
import globalize from '../../scripts/globalize';
interface GenresItemsContainerProps {
topParentId?: string | null;
getCurrentViewStyle: () => string;
itemsResult?: BaseItemDtoQueryResult;
}
const GenresItemsContainer: FC<GenresItemsContainerProps> = ({ topParentId, getCurrentViewStyle, itemsResult = {} }) => {
const element = useRef<HTMLDivElement>(null);
const enableScrollX = useCallback(() => {
return !layoutManager.desktop;
}, []);
const getPortraitShape = useCallback(() => {
return enableScrollX() ? 'overflowPortrait' : 'portrait';
}, [enableScrollX]);
const getThumbShape = useCallback(() => {
return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
}, [enableScrollX]);
const fillItemsContainer = useCallback((entry) => {
const elem = entry.target;
const id = elem.getAttribute('data-id');
const viewStyle = getCurrentViewStyle();
let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9;
if (enableScrollX()) {
limit = 10;
}
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
const query = {
SortBy: 'Random',
SortOrder: 'Ascending',
IncludeItemTypes: 'Movie',
Recursive: true,
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
ImageTypeLimit: 1,
EnableImageTypes: enableImageTypes,
Limit: limit,
GenreIds: id,
EnableTotalRecordCount: false,
ParentId: topParentId
};
window.ApiClient.getItems(window.ApiClient.getCurrentUserId(), query).then((result) => {
const items = result.Items || [];
if (viewStyle == 'Thumb') {
cardBuilder.buildCards(items, {
itemsContainer: elem,
shape: getThumbShape(),
preferThumb: true,
showTitle: true,
scalable: true,
centerText: true,
overlayMoreButton: true,
allowBottomPadding: false
});
} else if (viewStyle == 'ThumbCard') {
cardBuilder.buildCards(items, {
itemsContainer: elem,
shape: getThumbShape(),
preferThumb: true,
showTitle: true,
scalable: true,
centerText: false,
cardLayout: true,
showYear: true
});
} else if (viewStyle == 'PosterCard') {
cardBuilder.buildCards(items, {
itemsContainer: elem,
shape: getPortraitShape(),
showTitle: true,
scalable: true,
centerText: false,
cardLayout: true,
showYear: true
});
} else if (viewStyle == 'Poster') {
cardBuilder.buildCards(items, {
itemsContainer: elem,
shape: getPortraitShape(),
scalable: true,
overlayMoreButton: true,
allowBottomPadding: true,
showTitle: true,
centerText: true,
showYear: true
});
}
});
}, [enableScrollX, getCurrentViewStyle, getPortraitShape, getThumbShape, topParentId]);
useEffect(() => {
const elem = element.current?.querySelector('#items') as HTMLDivElement;
let html = '';
const items = itemsResult.Items || [];
for (let i = 0, length = items.length; i < length; i++) {
const item = items[i];
html += '<div class="verticalSection">';
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item, {
context: 'movies',
parentId: topParentId
}) + '" class="more button-flat button-flat-mini sectionTitleTextButton btnMoreFromGenre' + item.Id + '">';
html += '<h2 class="sectionTitle sectionTitle-cards">';
html += escapeHTML(item.Name);
html += '</h2>';
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
html += '</a>';
html += '</div>';
if (enableScrollX()) {
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x lazy" data-id="' + item.Id + '">';
} else {
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap lazy padded-left padded-right" data-id="' + item.Id + '">';
}
html += '</div>';
html += '</div>';
}
if (!itemsResult.Items?.length) {
html = '';
html += '<div class="noItemsMessage centerMessage">';
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
html += '<p>' + globalize.translate('MessageNoGenresAvailable') + '</p>';
html += '</div>';
}
elem.innerHTML = html;
lazyLoader.lazyChildren(elem, fillItemsContainer);
}, [getCurrentViewStyle, itemsResult.Items, fillItemsContainer, topParentId, enableScrollX]);
return (
<div ref={element}>
<div id='items'/>
</div>
);
};
export default GenresItemsContainer;

View file

@ -0,0 +1,33 @@
import React, { FC, useEffect, useRef } from 'react';
import ItemsContainerElement from '../../elements/ItemsContainerElement';
import imageLoader from '../images/imageLoader';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import { ViewQuerySettings } from '../../types/interface';
interface ItemsContainerI {
viewQuerySettings: ViewQuerySettings;
getItemsHtml: () => string
}
const ItemsContainer: FC<ItemsContainerI> = ({ viewQuerySettings, getItemsHtml }) => {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
const itemsContainer = element.current?.querySelector('.itemsContainer') as HTMLDivElement;
itemsContainer.innerHTML = getItemsHtml();
imageLoader.lazyChildren(itemsContainer);
}, [getItemsHtml]);
const cssClass = viewQuerySettings.imageType == 'list' ? 'vertical-list' : 'vertical-wrap';
return (
<div ref={element}>
<ItemsContainerElement
className={`itemsContainer ${cssClass} centered padded-left padded-right padded-right-withalphapicker`}
/>
</div>
);
};
export default ItemsContainer;

View file

@ -0,0 +1,38 @@
import React, { FC, useCallback, useEffect, useRef } from 'react';
import IconButtonElement from '../../elements/IconButtonElement';
const NewCollection: FC = () => {
const element = useRef<HTMLDivElement>(null);
const showCollectionEditor = useCallback(() => {
import('../collectionEditor/collectionEditor').then(({default: CollectionEditor}) => {
const serverId = window.ApiClient.serverId();
const collectionEditor = new CollectionEditor();
collectionEditor.show({
items: [],
serverId: serverId
});
});
}, []);
useEffect(() => {
const btnNewCollection = element.current?.querySelector('.btnNewCollection');
if (btnNewCollection) {
btnNewCollection.addEventListener('click', showCollectionEditor);
}
}, [showCollectionEditor]);
return (
<div ref={element}>
<IconButtonElement
is='paper-icon-button-light'
className='btnNewCollection autoSize'
title='Add'
icon='material-icons add'
/>
</div>
);
};
export default NewCollection;

View file

@ -0,0 +1,96 @@
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
import React, { FC, useCallback, useEffect, useRef } from 'react';
import IconButtonElement from '../../elements/IconButtonElement';
import globalize from '../../scripts/globalize';
import * as userSettings from '../../scripts/settings/userSettings';
import { ViewQuerySettings } from '../../types/interface';
interface PaginationProps {
viewQuerySettings: ViewQuerySettings;
setViewQuerySettings: React.Dispatch<React.SetStateAction<ViewQuerySettings>>;
itemsResult?: BaseItemDtoQueryResult;
}
const Pagination: FC<PaginationProps> = ({ viewQuerySettings, setViewQuerySettings, itemsResult = {} }) => {
const limit = userSettings.libraryPageSize(undefined);
const totalRecordCount = itemsResult.TotalRecordCount || 0;
const startIndex = viewQuerySettings.StartIndex || 0;
const recordsEnd = Math.min(startIndex + limit, totalRecordCount);
const showControls = limit < totalRecordCount;
const element = useRef<HTMLDivElement>(null);
const onNextPageClick = useCallback(() => {
if (limit > 0) {
const newIndex = startIndex + limit;
setViewQuerySettings((prevState) => ({
...prevState,
StartIndex: newIndex
}));
}
}, [limit, setViewQuerySettings, startIndex]);
const onPreviousPageClick = useCallback(() => {
if (limit > 0) {
const newIndex = Math.max(0, startIndex - limit);
setViewQuerySettings((prevState) => ({
...prevState,
StartIndex: newIndex
}));
}
}, [limit, setViewQuerySettings, startIndex]);
useEffect(() => {
const btnNextPage = element.current?.querySelector('.btnNextPage') as HTMLButtonElement;
if (btnNextPage) {
if (startIndex + limit >= totalRecordCount) {
btnNextPage.disabled = true;
} else {
btnNextPage.disabled = false;
}
btnNextPage.addEventListener('click', onNextPageClick);
}
const btnPreviousPage = element.current?.querySelector('.btnPreviousPage') as HTMLButtonElement;
if (btnPreviousPage) {
if (startIndex) {
btnPreviousPage.disabled = false;
} else {
btnPreviousPage.disabled = true;
}
btnPreviousPage.addEventListener('click', onPreviousPageClick);
}
return () => {
btnNextPage?.removeEventListener('click', onNextPageClick);
btnPreviousPage?.removeEventListener('click', onPreviousPageClick);
};
}, [totalRecordCount, onNextPageClick, onPreviousPageClick, limit, startIndex]);
return (
<div ref={element}>
<div className='paging'>
{showControls && (
<div className='listPaging' style={{ display: 'flex', alignItems: 'center' }}>
<span>
{globalize.translate('ListPaging', (totalRecordCount ? startIndex + 1 : 0), recordsEnd, totalRecordCount)}
</span>
<IconButtonElement
is='paper-icon-button-light'
className='btnPreviousPage autoSize'
icon='material-icons arrow_back'
/>
<IconButtonElement
is='paper-icon-button-light'
className='btnNextPage autoSize'
icon='material-icons arrow_forward'
/>
</div>
)}
</div>
</div>
);
};
export default Pagination;

View file

@ -0,0 +1,48 @@
import type { RecommendationDto } from '@jellyfin/sdk/lib/generated-client';
import React, { FC } from 'react';
import globalize from '../../scripts/globalize';
import escapeHTML from 'escape-html';
import SectionContainer from './SectionContainer';
interface RecommendationContainerProps {
getPortraitShape: () => string;
enableScrollX: () => boolean;
recommendation?: RecommendationDto;
}
const RecommendationContainer: FC<RecommendationContainerProps> = ({ getPortraitShape, enableScrollX, recommendation = {} }) => {
let title = '';
switch (recommendation.RecommendationType) {
case 'SimilarToRecentlyPlayed':
title = globalize.translate('RecommendationBecauseYouWatched', recommendation.BaselineItemName);
break;
case 'SimilarToLikedItem':
title = globalize.translate('RecommendationBecauseYouLike', recommendation.BaselineItemName);
break;
case 'HasDirectorFromRecentlyPlayed':
case 'HasLikedDirector':
title = globalize.translate('RecommendationDirectedBy', recommendation.BaselineItemName);
break;
case 'HasActorFromRecentlyPlayed':
case 'HasLikedActor':
title = globalize.translate('RecommendationStarring', recommendation.BaselineItemName);
break;
}
return <SectionContainer
sectionTitle={escapeHTML(title)}
enableScrollX={enableScrollX}
items={recommendation.Items || []}
cardOptions={{
shape: getPortraitShape(),
showYear: true
}}
/>;
};
export default RecommendationContainer;

View file

@ -0,0 +1,62 @@
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import React, { FC, useEffect, useRef } from 'react';
import cardBuilder from '../cardbuilder/cardBuilder';
import ItemsContainerElement from '../../elements/ItemsContainerElement';
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
import { CardOptions } from '../../types/interface';
interface SectionContainerProps {
sectionTitle: string;
enableScrollX: () => boolean;
items?: BaseItemDto[];
cardOptions?: CardOptions;
}
const SectionContainer: FC<SectionContainerProps> = ({
sectionTitle,
enableScrollX,
items = [],
cardOptions = {}
}) => {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
cardBuilder.buildCards(items, {
itemsContainer: element.current?.querySelector('.itemsContainer'),
parentContainer: element.current?.querySelector('.verticalSection'),
scalable: true,
overlayPlayButton: true,
showTitle: true,
centerText: true,
cardLayout: false,
...cardOptions
});
}, [cardOptions, enableScrollX, items]);
return (
<div ref={element}>
<div className='verticalSection hide'>
<div className='sectionTitleContainer sectionTitleContainer-cards'>
<h2 className='sectionTitle sectionTitle-cards padded-left'>
{sectionTitle}
</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 SectionContainer;

View file

@ -0,0 +1,50 @@
import React, { FC, useCallback, useEffect, useRef } from 'react';
import IconButtonElement from '../../elements/IconButtonElement';
import { ViewQuerySettings } from '../../types/interface';
interface SelectViewProps {
getVisibleViewSettings: () => string[];
viewQuerySettings: ViewQuerySettings;
setViewQuerySettings: React.Dispatch<React.SetStateAction<ViewQuerySettings>>;
}
const SelectView: FC<SelectViewProps> = ({
getVisibleViewSettings,
viewQuerySettings,
setViewQuerySettings
}) => {
const element = useRef<HTMLDivElement>(null);
const showViewSettingsMenu = useCallback(() => {
import('../viewSettings/viewSettings').then(({default: ViewSettings}) => {
const viewsettings = new ViewSettings();
viewsettings.show({
settings: viewQuerySettings,
visibleSettings: getVisibleViewSettings(),
setviewsettings: setViewQuerySettings
});
});
}, [getVisibleViewSettings, viewQuerySettings, setViewQuerySettings]);
useEffect(() => {
const btnSelectView = element.current?.querySelector('.btnSelectView') as HTMLButtonElement;
btnSelectView?.addEventListener('click', showViewSettingsMenu);
return () => {
btnSelectView?.removeEventListener('click', showViewSettingsMenu);
};
}, [showViewSettingsMenu]);
return (
<div ref={element}>
<IconButtonElement
is='paper-icon-button-light'
className='btnSelectView autoSize'
title='ButtonSelectView'
icon='material-icons view_comfy'
/>
</div>
);
};
export default SelectView;

View file

@ -0,0 +1,43 @@
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
import React, { FC, useCallback, useEffect, useRef } from 'react';
import { playbackManager } from '../playback/playbackmanager';
import IconButtonElement from '../../elements/IconButtonElement';
interface ShuffleProps {
itemsResult?: BaseItemDtoQueryResult;
topParentId: string | null;
}
const Shuffle: FC<ShuffleProps> = ({ itemsResult = {}, topParentId }) => {
const element = useRef<HTMLDivElement>(null);
const shuffle = useCallback(() => {
window.ApiClient.getItem(
window.ApiClient.getCurrentUserId(),
topParentId as string
).then((item) => {
playbackManager.shuffle(item);
});
}, [topParentId]);
useEffect(() => {
const btnShuffle = element.current?.querySelector('.btnShuffle');
if (btnShuffle) {
btnShuffle.addEventListener('click', shuffle);
}
}, [itemsResult.TotalRecordCount, shuffle]);
return (
<div ref={element}>
<IconButtonElement
is='paper-icon-button-light'
className='btnShuffle autoSize'
title='Shuffle'
icon='material-icons shuffle'
/>
</div>
);
};
export default Shuffle;

View file

@ -0,0 +1,54 @@
import React, { FC, useCallback, useEffect, useRef } from 'react';
import IconButtonElement from '../../elements/IconButtonElement';
import { ViewQuerySettings } from '../../types/interface';
interface SortProps {
getSortMenuOptions: () => {
name: string;
value: string;
}[];
viewQuerySettings: ViewQuerySettings;
setViewQuerySettings: React.Dispatch<React.SetStateAction<ViewQuerySettings>>;
}
const Sort: FC<SortProps> = ({
getSortMenuOptions,
viewQuerySettings,
setViewQuerySettings
}) => {
const element = useRef<HTMLDivElement>(null);
const showSortMenu = useCallback(() => {
import('../sortmenu/sortmenu').then(({default: SortMenu}) => {
const sortMenu = new SortMenu();
sortMenu.show({
settings: viewQuerySettings,
sortOptions: getSortMenuOptions(),
setSortValues: setViewQuerySettings
});
});
}, [getSortMenuOptions, viewQuerySettings, setViewQuerySettings]);
useEffect(() => {
const btnSort = element.current?.querySelector('.btnSort');
btnSort?.addEventListener('click', showSortMenu);
return () => {
btnSort?.removeEventListener('click', showSortMenu);
};
}, [showSortMenu]);
return (
<div ref={element}>
<IconButtonElement
is='paper-icon-button-light'
className='btnSort autoSize'
title='Sort'
icon='material-icons sort_by_alpha'
/>
</div>
);
};
export default Sort;

View file

@ -0,0 +1,417 @@
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import loading from '../loading/loading';
import * as userSettings from '../../scripts/settings/userSettings';
import AlphaPickerContainer from './AlphaPickerContainer';
import Filter from './Filter';
import ItemsContainer from './ItemsContainer';
import Pagination from './Pagination';
import SelectView from './SelectView';
import Shuffle from './Shuffle';
import Sort from './Sort';
import NewCollection from './NewCollection';
import globalize from '../../scripts/globalize';
import { CardOptions, ViewQuerySettings } from '../../types/interface';
import ServerConnections from '../ServerConnections';
import { useLocalStorage } from '../../hooks/useLocalStorage';
import listview from '../listview/listview';
import cardBuilder from '../cardbuilder/cardBuilder';
interface ViewItemsContainerProps {
topParentId: string | null;
isBtnShuffleEnabled?: boolean;
isBtnFilterEnabled?: boolean;
isBtnNewCollectionEnabled?: boolean;
isAlphaPickerEnabled?: boolean;
getBasekey: () => string;
getItemTypes: () => string[];
getNoItemsMessage: () => string;
}
const getDefaultSortBy = () => {
return 'SortName';
};
const getVisibleViewSettings = () => {
return [
'showTitle',
'showYear',
'imageType',
'cardLayout'
];
};
const getFilterMenuOptions = () => {
return {};
};
const getVisibleFilters = () => {
return [
'IsUnplayed',
'IsPlayed',
'IsFavorite',
'IsResumable',
'VideoType',
'HasSubtitles',
'HasTrailer',
'HasSpecialFeature',
'HasThemeSong',
'HasThemeVideo'
];
};
const getSortMenuOptions = () => {
return [{
name: globalize.translate('Name'),
value: 'SortName,ProductionYear'
}, {
name: globalize.translate('OptionRandom'),
value: 'Random'
}, {
name: globalize.translate('OptionImdbRating'),
value: 'CommunityRating,SortName,ProductionYear'
}, {
name: globalize.translate('OptionCriticRating'),
value: 'CriticRating,SortName,ProductionYear'
}, {
name: globalize.translate('OptionDateAdded'),
value: 'DateCreated,SortName,ProductionYear'
}, {
name: globalize.translate('OptionDatePlayed'),
value: 'DatePlayed,SortName,ProductionYear'
}, {
name: globalize.translate('OptionParentalRating'),
value: 'OfficialRating,SortName,ProductionYear'
}, {
name: globalize.translate('OptionPlayCount'),
value: 'PlayCount,SortName,ProductionYear'
}, {
name: globalize.translate('OptionReleaseDate'),
value: 'PremiereDate,SortName,ProductionYear'
}, {
name: globalize.translate('Runtime'),
value: 'Runtime,SortName,ProductionYear'
}];
};
const defaultViewQuerySettings: ViewQuerySettings = {
showTitle: true,
showYear: true,
imageType: 'primary',
viewType: '',
cardLayout: false,
SortBy: getDefaultSortBy(),
SortOrder: 'Ascending',
IsPlayed: false,
IsUnplayed: false,
IsFavorite: false,
IsResumable: false,
Is4K: null,
IsHD: null,
IsSD: null,
Is3D: null,
VideoTypes: '',
SeriesStatus: '',
HasSubtitles: null,
HasTrailer: null,
HasSpecialFeature: null,
HasThemeSong: null,
HasThemeVideo: null,
GenreIds: '',
StartIndex: 0
};
const ViewItemsContainer: FC<ViewItemsContainerProps> = ({
topParentId,
isBtnShuffleEnabled = false,
isBtnFilterEnabled = true,
isBtnNewCollectionEnabled = false,
isAlphaPickerEnabled = true,
getBasekey,
getItemTypes,
getNoItemsMessage
}) => {
const getSettingsKey = useCallback(() => {
return `${topParentId} - ${getBasekey()}`;
}, [getBasekey, topParentId]);
const [isLoading, setisLoading] = useState(false);
const [viewQuerySettings, setViewQuerySettings] = useLocalStorage<ViewQuerySettings>(
`viewQuerySettings - ${getSettingsKey()}`,
defaultViewQuerySettings
);
const [ itemsResult, setItemsResult ] = useState<BaseItemDtoQueryResult>({});
const element = useRef<HTMLDivElement>(null);
const getContext = useCallback(() => {
const itemType = getItemTypes().join(',');
if (itemType === 'Movie' || itemType === 'BoxSet') {
return 'movies';
}
return null;
}, [getItemTypes]);
const getCardOptions = useCallback(() => {
let shape;
let preferThumb;
let preferDisc;
let preferLogo;
if (viewQuerySettings.imageType === 'banner') {
shape = 'banner';
} else if (viewQuerySettings.imageType === 'disc') {
shape = 'square';
preferDisc = true;
} else if (viewQuerySettings.imageType === 'logo') {
shape = 'backdrop';
preferLogo = true;
} else if (viewQuerySettings.imageType === 'thumb') {
shape = 'backdrop';
preferThumb = true;
} else {
shape = 'autoVertical';
}
const cardOptions: CardOptions = {
shape: shape,
showTitle: viewQuerySettings.showTitle,
showYear: viewQuerySettings.showYear,
cardLayout: viewQuerySettings.cardLayout,
centerText: true,
context: getContext(),
coverImage: true,
preferThumb: preferThumb,
preferDisc: preferDisc,
preferLogo: preferLogo,
overlayPlayButton: false,
overlayMoreButton: true,
overlayText: !viewQuerySettings.showTitle
};
cardOptions.items = itemsResult.Items || [];
return cardOptions;
}, [
getContext,
itemsResult.Items,
viewQuerySettings.cardLayout,
viewQuerySettings.imageType,
viewQuerySettings.showTitle,
viewQuerySettings.showYear
]);
const getItemsHtml = useCallback(() => {
let html = '';
if (viewQuerySettings.imageType === 'list') {
html = listview.getListViewHtml({
items: itemsResult.Items || [],
context: getContext()
});
} else {
html = cardBuilder.getCardsHtml(itemsResult.Items || [], getCardOptions());
}
if (!itemsResult.Items?.length) {
html += '<div class="noItemsMessage centerMessage">';
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
html += '<p>' + globalize.translate(getNoItemsMessage()) + '</p>';
html += '</div>';
}
return html;
}, [getCardOptions, getContext, itemsResult.Items, getNoItemsMessage, viewQuerySettings.imageType]);
const getQuery = useCallback(() => {
let fields = 'BasicSyncInfo,MediaSourceCount';
if (viewQuerySettings.imageType === 'primary') {
fields += ',PrimaryImageAspectRatio';
}
if (viewQuerySettings.showYear) {
fields += ',ProductionYear';
}
const queryFilters: string[] = [];
if (viewQuerySettings.IsPlayed) {
queryFilters.push('IsPlayed');
}
if (viewQuerySettings.IsUnplayed) {
queryFilters.push('IsUnplayed');
}
if (viewQuerySettings.IsFavorite) {
queryFilters.push('IsFavorite');
}
if (viewQuerySettings.IsResumable) {
queryFilters.push('IsResumable');
}
let queryIsHD;
if (viewQuerySettings.IsHD) {
queryIsHD = true;
}
if (viewQuerySettings.IsSD) {
queryIsHD = false;
}
return {
SortBy: viewQuerySettings.SortBy,
SortOrder: viewQuerySettings.SortOrder,
IncludeItemTypes: getItemTypes().join(','),
Recursive: true,
Fields: fields,
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb,Disc,Logo',
Limit: userSettings.libraryPageSize(undefined),
IsFavorite: getBasekey() === 'favorites' ? true : null,
VideoTypes: viewQuerySettings.VideoTypes,
GenreIds: viewQuerySettings.GenreIds,
Is4K: viewQuerySettings.Is4K ? true : null,
IsHD: queryIsHD,
Is3D: viewQuerySettings.Is3D ? true : null,
HasSubtitles: viewQuerySettings.HasSubtitles ? true : null,
HasTrailer: viewQuerySettings.HasTrailer ? true : null,
HasSpecialFeature: viewQuerySettings.HasSpecialFeature ? true : null,
HasThemeSong: viewQuerySettings.HasThemeSong ? true : null,
HasThemeVideo: viewQuerySettings.HasThemeVideo ? true : null,
Filters: queryFilters.length ? queryFilters.join(',') : null,
StartIndex: viewQuerySettings.StartIndex,
NameLessThan: viewQuerySettings.NameLessThan,
NameStartsWith: viewQuerySettings.NameStartsWith,
ParentId: topParentId
};
}, [
viewQuerySettings.imageType,
viewQuerySettings.showYear,
viewQuerySettings.IsPlayed,
viewQuerySettings.IsUnplayed,
viewQuerySettings.IsFavorite,
viewQuerySettings.IsResumable,
viewQuerySettings.IsHD,
viewQuerySettings.IsSD,
viewQuerySettings.SortBy,
viewQuerySettings.SortOrder,
viewQuerySettings.VideoTypes,
viewQuerySettings.GenreIds,
viewQuerySettings.Is4K,
viewQuerySettings.Is3D,
viewQuerySettings.HasSubtitles,
viewQuerySettings.HasTrailer,
viewQuerySettings.HasSpecialFeature,
viewQuerySettings.HasThemeSong,
viewQuerySettings.HasThemeVideo,
viewQuerySettings.StartIndex,
viewQuerySettings.NameLessThan,
viewQuerySettings.NameStartsWith,
getItemTypes,
getBasekey,
topParentId
]);
const fetchData = useCallback(() => {
loading.show();
const apiClient = ServerConnections.getApiClient(window.ApiClient.serverId());
return apiClient.getItems(
apiClient.getCurrentUserId(),
{
...getQuery()
}
);
}, [getQuery]);
const reloadItems = useCallback(() => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
setisLoading(false);
fetchData().then((result) => {
setItemsResult(result);
window.scrollTo(0, 0);
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
autoFocuser.autoFocus(page);
});
loading.hide();
setisLoading(true);
});
}, [fetchData]);
useEffect(() => {
reloadItems();
}, [reloadItems]);
return (
<div ref={element}>
<div className='flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x'>
<Pagination
itemsResult= {itemsResult}
viewQuerySettings={viewQuerySettings}
setViewQuerySettings={setViewQuerySettings}
/>
{isBtnShuffleEnabled && <Shuffle itemsResult={itemsResult} topParentId={topParentId} />}
<SelectView
getVisibleViewSettings={getVisibleViewSettings}
viewQuerySettings={viewQuerySettings}
setViewQuerySettings={setViewQuerySettings}
/>
<Sort
getSortMenuOptions={getSortMenuOptions}
viewQuerySettings={viewQuerySettings}
setViewQuerySettings={setViewQuerySettings}
/>
{isBtnFilterEnabled && <Filter
topParentId={topParentId}
getItemTypes={getItemTypes}
getVisibleFilters={getVisibleFilters}
getFilterMenuOptions={getFilterMenuOptions}
viewQuerySettings={viewQuerySettings}
setViewQuerySettings={setViewQuerySettings}
/>}
{isBtnNewCollectionEnabled && <NewCollection />}
</div>
{isAlphaPickerEnabled && <AlphaPickerContainer
viewQuerySettings={viewQuerySettings}
setViewQuerySettings={setViewQuerySettings}
/>}
{isLoading && <ItemsContainer
viewQuerySettings={viewQuerySettings}
getItemsHtml={getItemsHtml}
/>}
<div className='flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x'>
<Pagination
itemsResult= {itemsResult}
viewQuerySettings={viewQuerySettings}
setViewQuerySettings={setViewQuerySettings}
/>
</div>
</div>
);
};
export default ViewItemsContainer;