Refactor home screen
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
import type { ApiClient } from 'jellyfin-apiclient';
|
||||
|
||||
import ServerConnections from 'components/ServerConnections';
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
import type { SectionContainerElement, SectionOptions } from './section';
|
||||
|
||||
function getLatestRecordingsFetchFn(
|
||||
serverId: string,
|
||||
activeRecordingsOnly: boolean,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function () {
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
return apiClient.getLiveTvRecordings({
|
||||
userId: apiClient.getCurrentUserId(),
|
||||
Limit: enableOverflow ? 12 : 5,
|
||||
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
|
||||
EnableTotalRecordCount: false,
|
||||
IsLibraryItem: activeRecordingsOnly ? null : false,
|
||||
IsInProgress: activeRecordingsOnly ? true : null
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getLatestRecordingItemsHtml(
|
||||
activeRecordingsOnly: boolean,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function (items: BaseItemDto[]) {
|
||||
return cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
shape: enableOverflow ? 'autooverflow' : 'auto',
|
||||
showTitle: true,
|
||||
showParentTitle: true,
|
||||
coverImage: true,
|
||||
lazy: true,
|
||||
showDetailsMenu: true,
|
||||
centerText: true,
|
||||
overlayText: false,
|
||||
showYear: true,
|
||||
lines: 2,
|
||||
overlayPlayButton: !activeRecordingsOnly,
|
||||
allowBottomPadding: !enableOverflow,
|
||||
preferThumb: true,
|
||||
cardLayout: false,
|
||||
overlayMoreButton: activeRecordingsOnly,
|
||||
action: activeRecordingsOnly ? 'none' : null,
|
||||
centerPlayButton: activeRecordingsOnly
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function loadRecordings(
|
||||
elem: HTMLElement,
|
||||
activeRecordingsOnly: boolean,
|
||||
apiClient: ApiClient,
|
||||
options: SectionOptions
|
||||
) {
|
||||
const title = activeRecordingsOnly ?
|
||||
globalize.translate('HeaderActiveRecordings') :
|
||||
globalize.translate('HeaderLatestRecordings');
|
||||
|
||||
let html = '';
|
||||
|
||||
html += '<div class="sectionTitleContainer sectionTitleContainer-cards">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + title + '</h2>';
|
||||
html += '</div>';
|
||||
|
||||
if (options.enableOverflow) {
|
||||
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">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">';
|
||||
}
|
||||
|
||||
if (options.enableOverflow) {
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
elem.classList.add('hide');
|
||||
elem.innerHTML = html;
|
||||
|
||||
const itemsContainer: SectionContainerElement | null = elem.querySelector('.itemsContainer');
|
||||
if (!itemsContainer) return;
|
||||
itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly, options);
|
||||
itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly, options);
|
||||
itemsContainer.parentContainer = elem;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import imageLoader from 'components/images/imageLoader';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import globalize from 'scripts/globalize';
|
||||
import imageHelper from 'scripts/imagehelper';
|
||||
|
||||
function getLibraryButtonsHtml(items: BaseItemDto[]) {
|
||||
let html = '';
|
||||
|
||||
html += '<div class="verticalSection verticalSection-extrabottompadding">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderMyMedia') + '</h2>';
|
||||
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-multiselect="false">';
|
||||
|
||||
// library card background images
|
||||
for (let i = 0, length = items.length; i < length; i++) {
|
||||
const item = items[i];
|
||||
const icon = imageHelper.getLibraryIcon(item.CollectionType);
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><span class="material-icons homeLibraryIcon ' + icon + '" aria-hidden="true"></span><span class="homeLibraryText">' + escapeHtml(item.Name) + '</span></a>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
export function loadLibraryButtons(elem: HTMLElement, userViews: BaseItemDto[]) {
|
||||
elem.classList.remove('verticalSection');
|
||||
const html = getLibraryButtonsHtml(userViews);
|
||||
|
||||
elem.innerHTML = html;
|
||||
imageLoader.lazyChildren(elem);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import imageLoader from 'components/images/imageLoader';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { getBackdropShape } from 'utils/card';
|
||||
|
||||
import type { SectionOptions } from './section';
|
||||
|
||||
export function loadLibraryTiles(
|
||||
elem: HTMLElement,
|
||||
userViews: BaseItemDto[],
|
||||
{
|
||||
enableOverflow
|
||||
}: SectionOptions
|
||||
) {
|
||||
let html = '';
|
||||
if (userViews.length) {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderMyMedia') + '</h2>';
|
||||
if (enableOverflow) {
|
||||
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">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right focuscontainer-x vertical-wrap">';
|
||||
}
|
||||
|
||||
html += cardBuilder.getCardsHtml({
|
||||
items: userViews,
|
||||
shape: getBackdropShape(enableOverflow),
|
||||
showTitle: true,
|
||||
centerText: true,
|
||||
overlayText: false,
|
||||
lazy: true,
|
||||
transition: false,
|
||||
allowBottomPadding: !enableOverflow
|
||||
});
|
||||
|
||||
if (enableOverflow) {
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
elem.innerHTML = html;
|
||||
imageLoader.lazyChildren(elem);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
||||
import type { ApiClient } from 'jellyfin-apiclient';
|
||||
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import ServerConnections from 'components/ServerConnections';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { getBackdropShape } from 'utils/card';
|
||||
|
||||
import type { SectionContainerElement, SectionOptions } from './section';
|
||||
|
||||
function getOnNowFetchFn(
|
||||
serverId: string
|
||||
) {
|
||||
return function () {
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
return apiClient.getLiveTvRecommendedPrograms({
|
||||
userId: apiClient.getCurrentUserId(),
|
||||
IsAiring: true,
|
||||
limit: 24,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Thumb,Backdrop',
|
||||
EnableTotalRecordCount: false,
|
||||
Fields: 'ChannelInfo,PrimaryImageAspectRatio'
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getOnNowItemsHtmlFn(
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return (items: BaseItemDto[]) => (
|
||||
cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
preferThumb: 'auto',
|
||||
inheritThumb: false,
|
||||
shape: (enableOverflow ? 'autooverflow' : 'auto'),
|
||||
showParentTitleOrTitle: true,
|
||||
showTitle: true,
|
||||
centerText: true,
|
||||
coverImage: true,
|
||||
overlayText: false,
|
||||
allowBottomPadding: !enableOverflow,
|
||||
showAirTime: true,
|
||||
showChannelName: false,
|
||||
showAirDateTime: false,
|
||||
showAirEndTime: true,
|
||||
defaultShape: getBackdropShape(enableOverflow),
|
||||
lines: 3,
|
||||
overlayPlayButton: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function buildSection(
|
||||
elem: HTMLElement,
|
||||
serverId: string,
|
||||
options: SectionOptions
|
||||
) {
|
||||
let html = '';
|
||||
|
||||
elem.classList.remove('padded-left');
|
||||
elem.classList.remove('padded-right');
|
||||
elem.classList.remove('padded-bottom');
|
||||
elem.classList.remove('verticalSection');
|
||||
|
||||
html += '<div class="verticalSection">';
|
||||
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('LiveTV') + '</h2>';
|
||||
html += '</div>';
|
||||
|
||||
if (options.enableOverflow) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true" data-scrollbuttons="false">';
|
||||
html += '<div class="padded-top padded-bottom scrollSlider focuscontainer-x">';
|
||||
} else {
|
||||
html += '<div class="padded-top padded-bottom focuscontainer-x">';
|
||||
}
|
||||
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('livetv', {
|
||||
serverId,
|
||||
section: 'programs'
|
||||
}) + '" class="raised"><span>' + globalize.translate('Programs') + '</span></a>';
|
||||
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('livetv', {
|
||||
serverId,
|
||||
section: 'guide'
|
||||
}) + '" class="raised"><span>' + globalize.translate('Guide') + '</span></a>';
|
||||
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('livetv', {
|
||||
serverId,
|
||||
section: 'channels'
|
||||
}) + '" class="raised"><span>' + globalize.translate('Channels') + '</span></a>';
|
||||
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('recordedtv', {
|
||||
serverId
|
||||
}) + '" class="raised"><span>' + globalize.translate('Recordings') + '</span></a>';
|
||||
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('livetv', {
|
||||
serverId,
|
||||
section: 'dvrschedule'
|
||||
}) + '" class="raised"><span>' + globalize.translate('Schedule') + '</span></a>';
|
||||
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('livetv', {
|
||||
serverId,
|
||||
section: 'seriesrecording'
|
||||
}) + '" class="raised"><span>' + globalize.translate('Series') + '</span></a>';
|
||||
|
||||
html += '</div>';
|
||||
if (options.enableOverflow) {
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="verticalSection">';
|
||||
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
|
||||
|
||||
if (!layoutManager.tv) {
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('livetv', {
|
||||
serverId,
|
||||
section: 'onnow'
|
||||
}) + '" class="more button-flat button-flat-mini sectionTitleTextButton">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate('HeaderOnNow');
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('HeaderOnNow') + '</h2>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (options.enableOverflow) {
|
||||
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">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">';
|
||||
}
|
||||
|
||||
if (options.enableOverflow) {
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
elem.innerHTML = html;
|
||||
|
||||
const itemsContainer: SectionContainerElement | null = elem.querySelector('.itemsContainer');
|
||||
if (!itemsContainer) return;
|
||||
itemsContainer.parentContainer = elem;
|
||||
itemsContainer.fetchData = getOnNowFetchFn(serverId);
|
||||
itemsContainer.getItemsHtml = getOnNowItemsHtmlFn(options);
|
||||
}
|
||||
|
||||
export function loadLiveTV(
|
||||
elem: HTMLElement,
|
||||
apiClient: ApiClient,
|
||||
user: UserDto,
|
||||
options: SectionOptions
|
||||
) {
|
||||
if (!user.Policy?.EnableLiveTvAccess) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return apiClient.getLiveTvRecommendedPrograms({
|
||||
userId: apiClient.getCurrentUserId(),
|
||||
IsAiring: true,
|
||||
limit: 1,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Thumb,Backdrop',
|
||||
EnableTotalRecordCount: false,
|
||||
Fields: 'ChannelInfo,PrimaryImageAspectRatio'
|
||||
}).then(function (result) {
|
||||
if (result.Items?.length) {
|
||||
buildSection(elem, apiClient.serverId(), options);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
import type { ApiClient } from 'jellyfin-apiclient';
|
||||
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import ServerConnections from 'components/ServerConnections';
|
||||
import globalize from 'scripts/globalize';
|
||||
import type { UserSettings } from 'scripts/settings/userSettings';
|
||||
import { getBackdropShape } from 'utils/card';
|
||||
|
||||
import type { SectionContainerElement, SectionOptions } from './section';
|
||||
|
||||
function getNextUpFetchFn(
|
||||
serverId: string,
|
||||
userSettings: UserSettings,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function () {
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
const oldestDateForNextUp = new Date();
|
||||
oldestDateForNextUp.setDate(oldestDateForNextUp.getDate() - userSettings.maxDaysForNextUp());
|
||||
return apiClient.getNextUpEpisodes({
|
||||
Limit: enableOverflow ? 24 : 15,
|
||||
Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,Path,MediaSourceCount',
|
||||
UserId: apiClient.getCurrentUserId(),
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
EnableTotalRecordCount: false,
|
||||
DisableFirstEpisode: false,
|
||||
NextUpDateCutoff: oldestDateForNextUp.toISOString(),
|
||||
EnableRewatching: userSettings.enableRewatchingInNextUp()
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getNextUpItemsHtmlFn(
|
||||
useEpisodeImages: boolean,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function (items: BaseItemDto[]) {
|
||||
const cardLayout = false;
|
||||
return cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
preferThumb: true,
|
||||
inheritThumb: !useEpisodeImages,
|
||||
shape: getBackdropShape(enableOverflow),
|
||||
overlayText: false,
|
||||
showTitle: true,
|
||||
showParentTitle: true,
|
||||
lazy: true,
|
||||
overlayPlayButton: true,
|
||||
context: 'home',
|
||||
centerText: !cardLayout,
|
||||
allowBottomPadding: !enableOverflow,
|
||||
cardLayout: cardLayout
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function loadNextUp(
|
||||
elem: HTMLElement,
|
||||
apiClient: ApiClient,
|
||||
userSettings: UserSettings,
|
||||
options: SectionOptions
|
||||
) {
|
||||
let html = '';
|
||||
|
||||
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
|
||||
if (!layoutManager.tv) {
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('nextup', {
|
||||
serverId: apiClient.serverId()
|
||||
}) + '" class="button-flat button-flat-mini sectionTitleTextButton">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate('NextUp');
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate('NextUp');
|
||||
html += '</h2>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (options.enableOverflow) {
|
||||
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" data-monitor="videoplayback,markplayed">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="videoplayback,markplayed">';
|
||||
}
|
||||
|
||||
if (options.enableOverflow) {
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
elem.classList.add('hide');
|
||||
elem.innerHTML = html;
|
||||
|
||||
const itemsContainer: SectionContainerElement | null = elem.querySelector('.itemsContainer');
|
||||
if (!itemsContainer) return;
|
||||
itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings, options);
|
||||
itemsContainer.getItemsHtml = getNextUpItemsHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume(), options);
|
||||
itemsContainer.parentContainer = elem;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
||||
import escapeHtml from 'escape-html';
|
||||
import type { ApiClient } from 'jellyfin-apiclient';
|
||||
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import globalize from 'scripts/globalize';
|
||||
import ServerConnections from 'components/ServerConnections';
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
|
||||
|
||||
import type { SectionContainerElement, SectionOptions } from './section';
|
||||
|
||||
function getFetchLatestItemsFn(
|
||||
serverId: string,
|
||||
parentId: string | undefined,
|
||||
collectionType: string | null | undefined,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function () {
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
let limit = 16;
|
||||
|
||||
if (enableOverflow) {
|
||||
if (collectionType === 'music') {
|
||||
limit = 30;
|
||||
}
|
||||
} else if (collectionType === 'tvshows') {
|
||||
limit = 5;
|
||||
} else if (collectionType === 'music') {
|
||||
limit = 9;
|
||||
} else {
|
||||
limit = 8;
|
||||
}
|
||||
|
||||
const options = {
|
||||
Limit: limit,
|
||||
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path',
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Thumb',
|
||||
ParentId: parentId
|
||||
};
|
||||
|
||||
return apiClient.getLatestItems(options);
|
||||
};
|
||||
}
|
||||
|
||||
function getLatestItemsHtmlFn(
|
||||
itemType: BaseItemKind | undefined,
|
||||
viewType: string | null | undefined,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function (items: BaseItemDto[]) {
|
||||
const cardLayout = false;
|
||||
let shape;
|
||||
if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') {
|
||||
shape = getPortraitShape(enableOverflow);
|
||||
} else if (viewType === 'music' || viewType === 'homevideos') {
|
||||
shape = getSquareShape(enableOverflow);
|
||||
} else {
|
||||
shape = getBackdropShape(enableOverflow);
|
||||
}
|
||||
|
||||
return cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
shape: shape,
|
||||
preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null,
|
||||
showUnplayedIndicator: false,
|
||||
showChildCountIndicator: true,
|
||||
context: 'home',
|
||||
overlayText: false,
|
||||
centerText: !cardLayout,
|
||||
overlayPlayButton: viewType !== 'photos',
|
||||
allowBottomPadding: !enableOverflow && !cardLayout,
|
||||
cardLayout: cardLayout,
|
||||
showTitle: viewType !== 'photos',
|
||||
showYear: viewType === 'movies' || viewType === 'tvshows' || !viewType,
|
||||
showParentTitle: viewType === 'music' || viewType === 'tvshows' || !viewType || (cardLayout && (viewType === 'tvshows')),
|
||||
lines: 2
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function renderLatestSection(
|
||||
elem: HTMLElement,
|
||||
apiClient: ApiClient,
|
||||
user: UserDto,
|
||||
parent: BaseItemDto,
|
||||
options: SectionOptions
|
||||
) {
|
||||
let html = '';
|
||||
|
||||
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
|
||||
if (!layoutManager.tv) {
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(parent, {
|
||||
section: 'latest'
|
||||
}) + '" class="more button-flat button-flat-mini sectionTitleTextButton">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate('LatestFromLibrary', escapeHtml(parent.Name));
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)) + '</h2>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (options.enableOverflow) {
|
||||
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">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer focuscontainer-x padded-left padded-right vertical-wrap">';
|
||||
}
|
||||
|
||||
if (options.enableOverflow) {
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
elem.innerHTML = html;
|
||||
|
||||
const itemsContainer: SectionContainerElement | null = elem.querySelector('.itemsContainer');
|
||||
if (!itemsContainer) return;
|
||||
itemsContainer.fetchData = getFetchLatestItemsFn(apiClient.serverId(), parent.Id, parent.CollectionType, options);
|
||||
itemsContainer.getItemsHtml = getLatestItemsHtmlFn(parent.Type, parent.CollectionType, options);
|
||||
itemsContainer.parentContainer = elem;
|
||||
}
|
||||
|
||||
export function loadRecentlyAdded(
|
||||
elem: HTMLElement,
|
||||
apiClient: ApiClient,
|
||||
user: UserDto,
|
||||
userViews: BaseItemDto[],
|
||||
options: SectionOptions
|
||||
) {
|
||||
elem.classList.remove('verticalSection');
|
||||
const excludeViewTypes = ['playlists', 'livetv', 'boxsets', 'channels'];
|
||||
const userExcludeItems = user.Configuration?.LatestItemsExcludes ?? [];
|
||||
|
||||
for (let i = 0, length = userViews.length; i < length; i++) {
|
||||
const item = userViews[i];
|
||||
if (
|
||||
!item.Id
|
||||
|| userExcludeItems.indexOf(item.Id) !== -1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!item.CollectionType
|
||||
|| excludeViewTypes.indexOf(item.CollectionType) !== -1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const frag = document.createElement('div');
|
||||
frag.classList.add('verticalSection');
|
||||
frag.classList.add('hide');
|
||||
elem.appendChild(frag);
|
||||
|
||||
renderLatestSection(frag, apiClient, user, item, options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import type { ApiClient } from 'jellyfin-apiclient';
|
||||
|
||||
import ServerConnections from 'components/ServerConnections';
|
||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import globalize from 'scripts/globalize';
|
||||
import type { UserSettings } from 'scripts/settings/userSettings';
|
||||
import { getBackdropShape, getPortraitShape } from 'utils/card';
|
||||
|
||||
import type { SectionContainerElement, SectionOptions } from './section';
|
||||
|
||||
const dataMonitorHints: Record<string, string> = {
|
||||
Audio: 'audioplayback,markplayed',
|
||||
Video: 'videoplayback,markplayed'
|
||||
};
|
||||
|
||||
function getItemsToResumeFn(
|
||||
mediaType: BaseItemKind,
|
||||
serverId: string,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function () {
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
|
||||
const limit = enableOverflow ? 12 : 5;
|
||||
|
||||
const options = {
|
||||
Limit: limit,
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Thumb',
|
||||
EnableTotalRecordCount: false,
|
||||
MediaTypes: mediaType
|
||||
};
|
||||
|
||||
return apiClient.getResumableItems(apiClient.getCurrentUserId(), options);
|
||||
};
|
||||
}
|
||||
|
||||
function getItemsToResumeHtmlFn(
|
||||
useEpisodeImages: boolean,
|
||||
mediaType: BaseItemKind,
|
||||
{ enableOverflow }: SectionOptions
|
||||
) {
|
||||
return function (items: BaseItemDto[]) {
|
||||
const cardLayout = false;
|
||||
return cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
preferThumb: true,
|
||||
inheritThumb: !useEpisodeImages,
|
||||
shape: (mediaType === 'Book') ?
|
||||
getPortraitShape(enableOverflow) :
|
||||
getBackdropShape(enableOverflow),
|
||||
overlayText: false,
|
||||
showTitle: true,
|
||||
showParentTitle: true,
|
||||
lazy: true,
|
||||
showDetailsMenu: true,
|
||||
overlayPlayButton: true,
|
||||
context: 'home',
|
||||
centerText: !cardLayout,
|
||||
allowBottomPadding: false,
|
||||
cardLayout: cardLayout,
|
||||
showYear: true,
|
||||
lines: 2
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function loadResume(
|
||||
elem: HTMLElement,
|
||||
apiClient: ApiClient,
|
||||
titleLabel: string,
|
||||
mediaType: BaseItemKind,
|
||||
userSettings: UserSettings,
|
||||
options: SectionOptions
|
||||
) {
|
||||
let html = '';
|
||||
|
||||
const dataMonitor = dataMonitorHints[mediaType] ?? 'markplayed';
|
||||
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate(titleLabel) + '</h2>';
|
||||
if (options.enableOverflow) {
|
||||
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" data-monitor="${dataMonitor}">`;
|
||||
} else {
|
||||
html += `<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="${dataMonitor}">`;
|
||||
}
|
||||
|
||||
if (options.enableOverflow) {
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
elem.classList.add('hide');
|
||||
elem.innerHTML = html;
|
||||
|
||||
const itemsContainer: SectionContainerElement | null = elem.querySelector('.itemsContainer');
|
||||
if (!itemsContainer) return;
|
||||
itemsContainer.fetchData = getItemsToResumeFn(mediaType, apiClient.serverId(), options);
|
||||
itemsContainer.getItemsHtml = getItemsToResumeHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume(), mediaType, options);
|
||||
itemsContainer.parentContainer = elem;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
|
||||
export interface SectionOptions {
|
||||
enableOverflow: boolean
|
||||
}
|
||||
|
||||
export type SectionContainerElement = {
|
||||
fetchData: () => void
|
||||
getItemsHtml: (items: BaseItemDto[]) => void
|
||||
parentContainer: HTMLElement
|
||||
} & Element;
|
||||
Reference in New Issue
Block a user