mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Refactor: viewitemcontainer
This commit is contained in:
parent
550ad476af
commit
c61df2eb92
28 changed files with 520 additions and 1001 deletions
|
@ -1,59 +0,0 @@
|
|||
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',
|
||||
NameStartsWith: undefined
|
||||
};
|
||||
} else {
|
||||
updatedValue = {
|
||||
NameLessThan: undefined,
|
||||
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;
|
|
@ -1,65 +0,0 @@
|
|||
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
|
||||
}).catch(() => {
|
||||
// filter menu closed
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('[Filter] failed to load filter menu', err);
|
||||
});
|
||||
}, [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;
|
|
@ -1,33 +0,0 @@
|
|||
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;
|
|
@ -1,42 +0,0 @@
|
|||
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
|
||||
}).catch(() => {
|
||||
// closed collection editor
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('[NewCollection] failed to load collection editor', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
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;
|
|
@ -1,97 +0,0 @@
|
|||
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 recordsStart = totalRecordCount ? startIndex + 1 : 0;
|
||||
const recordsEnd = limit ? Math.min(startIndex + limit, totalRecordCount) : totalRecordCount;
|
||||
const showControls = limit > 0 && 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'>
|
||||
<div className='listPaging' style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>
|
||||
{globalize.translate('ListPaging', recordsStart, recordsEnd, totalRecordCount)}
|
||||
</span>
|
||||
{showControls && (
|
||||
<>
|
||||
<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;
|
|
@ -1,54 +0,0 @@
|
|||
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
|
||||
}).catch(() => {
|
||||
// view settings closed
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('[SelectView] failed to load view settings', err);
|
||||
});
|
||||
}, [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;
|
|
@ -1,45 +0,0 @@
|
|||
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);
|
||||
}).catch(err => {
|
||||
console.error('[Shuffle] failed to fetch items', err);
|
||||
});
|
||||
}, [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;
|
|
@ -1,58 +0,0 @@
|
|||
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
|
||||
}).catch(() => {
|
||||
// sort menu closed
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('[Sort] failed to load sort menu', err);
|
||||
});
|
||||
}, [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;
|
|
@ -1,411 +0,0 @@
|
|||
import { type BaseItemDtoQueryResult, ItemFields, ItemFilter } 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 ServerConnections from '../ServerConnections';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
import listview from '../listview/listview';
|
||||
import cardBuilder from '../cardbuilder/cardBuilder';
|
||||
|
||||
import { ViewQuerySettings } from '../../types/interface';
|
||||
import { CardOptions } from '../../types/cardOptions';
|
||||
|
||||
interface ViewItemsContainerProps {
|
||||
topParentId: string | null;
|
||||
isBtnShuffleEnabled?: boolean;
|
||||
isBtnFilterEnabled?: boolean;
|
||||
isBtnNewCollectionEnabled?: boolean;
|
||||
isAlphaPickerEnabled?: boolean;
|
||||
getBasekey: () => string;
|
||||
getItemTypes: () => string[];
|
||||
getNoItemsMessage: () => string;
|
||||
}
|
||||
|
||||
const getDefaultSortBy = () => {
|
||||
return 'SortName';
|
||||
};
|
||||
|
||||
const getFields = (viewQuerySettings: ViewQuerySettings) => {
|
||||
const fields: ItemFields[] = [
|
||||
ItemFields.BasicSyncInfo,
|
||||
ItemFields.MediaSourceCount
|
||||
];
|
||||
|
||||
if (viewQuerySettings.imageType === 'primary') {
|
||||
fields.push(ItemFields.PrimaryImageAspectRatio);
|
||||
}
|
||||
|
||||
return fields.join(',');
|
||||
};
|
||||
|
||||
const getFilters = (viewQuerySettings: ViewQuerySettings) => {
|
||||
const filters: ItemFilter[] = [];
|
||||
|
||||
if (viewQuerySettings.IsPlayed) {
|
||||
filters.push(ItemFilter.IsPlayed);
|
||||
}
|
||||
|
||||
if (viewQuerySettings.IsUnplayed) {
|
||||
filters.push(ItemFilter.IsUnplayed);
|
||||
}
|
||||
|
||||
if (viewQuerySettings.IsFavorite) {
|
||||
filters.push(ItemFilter.IsFavorite);
|
||||
}
|
||||
|
||||
if (viewQuerySettings.IsResumable) {
|
||||
filters.push(ItemFilter.IsResumable);
|
||||
}
|
||||
|
||||
return filters;
|
||||
};
|
||||
|
||||
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(() => {
|
||||
const queryFilters = getFilters(viewQuerySettings);
|
||||
|
||||
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: getFields(viewQuerySettings),
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb,Disc,Logo',
|
||||
Limit: userSettings.libraryPageSize(undefined) || 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,
|
||||
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);
|
||||
}).catch(err => {
|
||||
console.error('[ViewItemsContainer] failed to load autofocuser', err);
|
||||
});
|
||||
loading.hide();
|
||||
setisLoading(true);
|
||||
}).catch(err => {
|
||||
console.error('[ViewItemsContainer] failed to fetch data', err);
|
||||
});
|
||||
}, [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;
|
Loading…
Add table
Add a link
Reference in a new issue