mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
clean up GenresView & GenresItemsContainer
This commit is contained in:
parent
1c6b1fc478
commit
071e7d15d9
4 changed files with 101 additions and 142 deletions
|
@ -10,14 +10,31 @@ import cardBuilder from '../cardbuilder/cardBuilder';
|
||||||
import layoutManager from '../layoutManager';
|
import layoutManager from '../layoutManager';
|
||||||
import lazyLoader from '../lazyLoader/lazyLoaderIntersectionObserver';
|
import lazyLoader from '../lazyLoader/lazyLoaderIntersectionObserver';
|
||||||
import globalize from '../../scripts/globalize';
|
import globalize from '../../scripts/globalize';
|
||||||
|
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
|
||||||
|
import ItemsContainerElement from '../../elements/ItemsContainerElement';
|
||||||
|
|
||||||
|
const createLinkElement = ({ className, title, href }: { className?: string, title?: string | null, href?: string }) => ({
|
||||||
|
__html: `<a
|
||||||
|
is="emby-linkbutton"
|
||||||
|
class="${className}"
|
||||||
|
href="${href}"
|
||||||
|
>
|
||||||
|
<h2 class='sectionTitle sectionTitle-cards'>
|
||||||
|
${title}
|
||||||
|
</h2>
|
||||||
|
<span class='material-icons chevron_right' aria-hidden='true'></span>
|
||||||
|
</a>`
|
||||||
|
});
|
||||||
|
|
||||||
interface GenresItemsContainerProps {
|
interface GenresItemsContainerProps {
|
||||||
topParentId?: string | null;
|
topParentId?: string | null;
|
||||||
getCurrentViewStyle: () => string;
|
itemsResult: BaseItemDtoQueryResult;
|
||||||
itemsResult?: BaseItemDtoQueryResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const GenresItemsContainer: FC<GenresItemsContainerProps> = ({ topParentId, getCurrentViewStyle, itemsResult = {} }) => {
|
const GenresItemsContainer: FC<GenresItemsContainerProps> = ({
|
||||||
|
topParentId,
|
||||||
|
itemsResult = {}
|
||||||
|
}) => {
|
||||||
const element = useRef<HTMLDivElement>(null);
|
const element = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const enableScrollX = useCallback(() => {
|
const enableScrollX = useCallback(() => {
|
||||||
|
@ -28,21 +45,10 @@ const GenresItemsContainer: FC<GenresItemsContainerProps> = ({ topParentId, getC
|
||||||
return enableScrollX() ? 'overflowPortrait' : 'portrait';
|
return enableScrollX() ? 'overflowPortrait' : 'portrait';
|
||||||
}, [enableScrollX]);
|
}, [enableScrollX]);
|
||||||
|
|
||||||
const getThumbShape = useCallback(() => {
|
|
||||||
return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
|
|
||||||
}, [enableScrollX]);
|
|
||||||
|
|
||||||
const fillItemsContainer = useCallback((entry) => {
|
const fillItemsContainer = useCallback((entry) => {
|
||||||
const elem = entry.target;
|
const elem = entry.target;
|
||||||
const id = elem.getAttribute('data-id');
|
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 = {
|
const query = {
|
||||||
SortBy: 'Random',
|
SortBy: 'Random',
|
||||||
SortOrder: 'Ascending',
|
SortOrder: 'Ascending',
|
||||||
|
@ -50,108 +56,69 @@ const GenresItemsContainer: FC<GenresItemsContainerProps> = ({ topParentId, getC
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||||
ImageTypeLimit: 1,
|
ImageTypeLimit: 1,
|
||||||
EnableImageTypes: enableImageTypes,
|
EnableImageTypes: 'Primary',
|
||||||
Limit: limit,
|
Limit: 12,
|
||||||
GenreIds: id,
|
GenreIds: id,
|
||||||
EnableTotalRecordCount: false,
|
EnableTotalRecordCount: false,
|
||||||
ParentId: topParentId
|
ParentId: topParentId
|
||||||
};
|
};
|
||||||
window.ApiClient.getItems(window.ApiClient.getCurrentUserId(), query).then((result) => {
|
window.ApiClient.getItems(window.ApiClient.getCurrentUserId(), query).then((result) => {
|
||||||
const items = result.Items || [];
|
cardBuilder.buildCards(result.Items || [], {
|
||||||
if (viewStyle == 'Thumb') {
|
itemsContainer: elem,
|
||||||
cardBuilder.buildCards(items, {
|
shape: getPortraitShape(),
|
||||||
itemsContainer: elem,
|
scalable: true,
|
||||||
shape: getThumbShape(),
|
overlayMoreButton: true,
|
||||||
preferThumb: true,
|
allowBottomPadding: true,
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
scalable: true,
|
centerText: true,
|
||||||
centerText: true,
|
showYear: 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]);
|
}, [getPortraitShape, topParentId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const elem = element.current?.querySelector('#items') as HTMLDivElement;
|
const elem = element.current;
|
||||||
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);
|
lazyLoader.lazyChildren(elem, fillItemsContainer);
|
||||||
}, [getCurrentViewStyle, itemsResult.Items, fillItemsContainer, topParentId, enableScrollX]);
|
}, [itemsResult.Items, fillItemsContainer]);
|
||||||
|
|
||||||
|
const items = itemsResult.Items || [];
|
||||||
return (
|
return (
|
||||||
<div ref={element}>
|
<div ref={element}>
|
||||||
<div id='items'/>
|
{
|
||||||
|
!items.length ? (
|
||||||
|
<div className='noItemsMessage centerMessage'>
|
||||||
|
<h1>{globalize.translate('MessageNothingHere')}</h1>
|
||||||
|
<p>{globalize.translate('MessageNoGenresAvailable')}</p>
|
||||||
|
</div>
|
||||||
|
) : items.map((item, index) => (
|
||||||
|
<div key={index} className='verticalSection'>
|
||||||
|
<div
|
||||||
|
className='sectionTitleContainer sectionTitleContainer-cards padded-left'
|
||||||
|
dangerouslySetInnerHTML={createLinkElement({
|
||||||
|
className: 'more button-flat button-flat-mini sectionTitleTextButton btnMoreFromGenre',
|
||||||
|
title: escapeHTML(item.Name),
|
||||||
|
href: appRouter.getRouteUrl(item, {
|
||||||
|
context: 'movies',
|
||||||
|
parentId: topParentId
|
||||||
|
})
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{enableScrollX() ?
|
||||||
|
<ItemsScrollerContainerElement
|
||||||
|
scrollerclassName='padded-top-focusscale padded-bottom-focusscale'
|
||||||
|
dataMousewheel='false'
|
||||||
|
dataCenterfocus='true'
|
||||||
|
className='itemsContainer scrollSlider focuscontainer-x lazy'
|
||||||
|
dataId={item.Id}
|
||||||
|
/> : <ItemsContainerElement
|
||||||
|
className='itemsContainer vertical-wrap lazy padded-left padded-right'
|
||||||
|
dataId={item.Id}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
const createElement = ({ className }: IProps) => ({
|
const createElement = ({ className, dataId }: IProps) => ({
|
||||||
__html: `<div
|
__html: `<div
|
||||||
is="emby-itemscontainer"
|
is="emby-itemscontainer"
|
||||||
class="${className}"
|
class="${className}"
|
||||||
|
${dataId}
|
||||||
>
|
>
|
||||||
</div>`
|
</div>`
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
dataId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ItemsContainerElement: FC<IProps> = ({ className }) => {
|
const ItemsContainerElement: FC<IProps> = ({ className, dataId }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={createElement({
|
dangerouslySetInnerHTML={createElement({
|
||||||
className: className
|
className: className,
|
||||||
|
dataId: dataId ? `data-id="${dataId}"` : ''
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
const createScroller = ({ scrollerclassName, dataHorizontal, dataMousewheel, dataCenterfocus, className }: IProps) => ({
|
const createScroller = ({ scrollerclassName, dataHorizontal, dataMousewheel, dataCenterfocus, dataId, className }: IProps) => ({
|
||||||
__html: `<div is="emby-scroller"
|
__html: `<div is="emby-scroller"
|
||||||
class="${scrollerclassName}"
|
class="${scrollerclassName}"
|
||||||
${dataHorizontal}
|
${dataHorizontal}
|
||||||
|
@ -10,6 +10,7 @@ const createScroller = ({ scrollerclassName, dataHorizontal, dataMousewheel, dat
|
||||||
<div
|
<div
|
||||||
is="emby-itemscontainer"
|
is="emby-itemscontainer"
|
||||||
class="${className}"
|
class="${className}"
|
||||||
|
${dataId}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
@ -20,10 +21,11 @@ interface IProps {
|
||||||
dataHorizontal?: string;
|
dataHorizontal?: string;
|
||||||
dataMousewheel?: string;
|
dataMousewheel?: string;
|
||||||
dataCenterfocus?: string;
|
dataCenterfocus?: string;
|
||||||
|
dataId?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ItemsScrollerContainerElement: FC<IProps> = ({ scrollerclassName, dataHorizontal, dataMousewheel, dataCenterfocus, className }) => {
|
const ItemsScrollerContainerElement: FC<IProps> = ({ scrollerclassName, dataHorizontal, dataMousewheel, dataCenterfocus, dataId, className }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={createScroller({
|
dangerouslySetInnerHTML={createScroller({
|
||||||
|
@ -31,6 +33,7 @@ const ItemsScrollerContainerElement: FC<IProps> = ({ scrollerclassName, dataHori
|
||||||
dataHorizontal: dataHorizontal ? `data-horizontal="${dataHorizontal}"` : '',
|
dataHorizontal: dataHorizontal ? `data-horizontal="${dataHorizontal}"` : '',
|
||||||
dataMousewheel: dataMousewheel ? `data-mousewheel="${dataMousewheel}"` : '',
|
dataMousewheel: dataMousewheel ? `data-mousewheel="${dataMousewheel}"` : '',
|
||||||
dataCenterfocus: dataCenterfocus ? `data-centerfocus="${dataCenterfocus}"` : '',
|
dataCenterfocus: dataCenterfocus ? `data-centerfocus="${dataCenterfocus}"` : '',
|
||||||
|
dataId: dataId ? `data-id="${dataId}"` : '',
|
||||||
className: className
|
className: className
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,54 +1,40 @@
|
||||||
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
|
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import loading from '../../components/loading/loading';
|
import loading from '../../components/loading/loading';
|
||||||
import * as userSettings from '../../scripts/settings/userSettings';
|
|
||||||
import GenresItemsContainer from '../../components/common/GenresItemsContainer';
|
import GenresItemsContainer from '../../components/common/GenresItemsContainer';
|
||||||
import { LibraryViewProps, Query } from '../../types/interface';
|
import { LibraryViewProps } from '../../types/interface';
|
||||||
|
|
||||||
const GenresView: FC<LibraryViewProps> = ({ topParentId }) => {
|
const GenresView: FC<LibraryViewProps> = ({ topParentId }) => {
|
||||||
const [ itemsResult, setItemsResult ] = useState<BaseItemDtoQueryResult>({});
|
const [ itemsResult, setItemsResult ] = useState<BaseItemDtoQueryResult>({});
|
||||||
const element = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const getSettingsKey = useCallback(() => {
|
|
||||||
return topParentId + '-genres';
|
|
||||||
}, [topParentId]);
|
|
||||||
|
|
||||||
const getViewSettings = useCallback(() => {
|
|
||||||
return getSettingsKey() + '-view';
|
|
||||||
}, [getSettingsKey]);
|
|
||||||
|
|
||||||
let query = useMemo<Query>(() => ({
|
|
||||||
SortBy: 'SortName',
|
|
||||||
SortOrder: 'Ascending',
|
|
||||||
IncludeItemTypes: 'Movie',
|
|
||||||
Recursive: true,
|
|
||||||
EnableTotalRecordCount: false,
|
|
||||||
Limit: userSettings.libraryPageSize(undefined),
|
|
||||||
StartIndex: 0,
|
|
||||||
ParentId: topParentId }), [topParentId]);
|
|
||||||
|
|
||||||
query = userSettings.loadQuerySettings(getSettingsKey(), query);
|
|
||||||
|
|
||||||
const getCurrentViewStyle = useCallback(() => {
|
|
||||||
return userSettings.get(getViewSettings(), false) || 'Poster';
|
|
||||||
}, [getViewSettings]);
|
|
||||||
|
|
||||||
const reloadItems = useCallback(() => {
|
const reloadItems = useCallback(() => {
|
||||||
loading.show();
|
loading.show();
|
||||||
window.ApiClient.getGenres(window.ApiClient.getCurrentUserId(), query).then((result) => {
|
window.ApiClient.getGenres(
|
||||||
|
window.ApiClient.getCurrentUserId(),
|
||||||
|
{
|
||||||
|
SortBy: 'SortName',
|
||||||
|
SortOrder: 'Ascending',
|
||||||
|
IncludeItemTypes: 'Movie',
|
||||||
|
Recursive: true,
|
||||||
|
EnableTotalRecordCount: false,
|
||||||
|
ParentId: topParentId
|
||||||
|
}
|
||||||
|
).then((result) => {
|
||||||
setItemsResult(result);
|
setItemsResult(result);
|
||||||
loading.hide();
|
loading.hide();
|
||||||
});
|
});
|
||||||
}, [query]);
|
}, [topParentId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reloadItems();
|
reloadItems();
|
||||||
}, [reloadItems]);
|
}, [reloadItems]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={element}>
|
<GenresItemsContainer
|
||||||
<GenresItemsContainer topParentId={topParentId} getCurrentViewStyle={getCurrentViewStyle} itemsResult={itemsResult} />
|
topParentId={topParentId}
|
||||||
</div>
|
itemsResult={itemsResult}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue