';
- }
-
- elem.innerHTML = html;
- imageLoader.lazyChildren(elem);
-}
-
-const dataMonitorHints = {
- 'Audio': 'audioplayback,markplayed',
- 'Video': 'videoplayback,markplayed'
-};
-
-function loadResume(elem, apiClient, headerText, mediaType, userSettings) {
- let html = '';
-
- const dataMonitor = dataMonitorHints[mediaType] || 'markplayed';
-
- html += '
';
- html += `
';
-
- elem.classList.add('hide');
- elem.innerHTML = html;
-
- const itemsContainer = elem.querySelector('.itemsContainer');
- itemsContainer.fetchData = getItemsToResumeFn(mediaType, apiClient.serverId());
- itemsContainer.getItemsHtml = getItemsToResumeHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume(), mediaType);
- itemsContainer.parentContainer = elem;
-}
-
-function getItemsToResumeFn(mediaType, serverId) {
- return function () {
- const apiClient = ServerConnections.getApiClient(serverId);
-
- const limit = enableScrollX() ? 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, mediaType) {
- return function (items) {
- const cardLayout = false;
- return cardBuilder.getCardsHtml({
- items: items,
- preferThumb: true,
- inheritThumb: !useEpisodeImages,
- shape: (mediaType === 'Book') ?
- getPortraitShape(enableScrollX()) :
- getBackdropShape(enableScrollX()),
- overlayText: false,
- showTitle: true,
- showParentTitle: true,
- lazy: true,
- showDetailsMenu: true,
- overlayPlayButton: true,
- context: 'home',
- centerText: !cardLayout,
- allowBottomPadding: false,
- cardLayout: cardLayout,
- showYear: true,
- lines: 2
- });
- };
-}
-
-function getOnNowFetchFn(serverId) {
- 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 getOnNowItemsHtml(items) {
- return cardBuilder.getCardsHtml({
- items: items,
- preferThumb: 'auto',
- inheritThumb: false,
- shape: (enableScrollX() ? 'autooverflow' : 'auto'),
- showParentTitleOrTitle: true,
- showTitle: true,
- centerText: true,
- coverImage: true,
- overlayText: false,
- allowBottomPadding: !enableScrollX(),
- showAirTime: true,
- showChannelName: false,
- showAirDateTime: false,
- showAirEndTime: true,
- defaultShape: getBackdropShape(enableScrollX()),
- lines: 3,
- overlayPlayButton: true
- });
-}
-
-function loadOnNow(elem, apiClient, user) {
- 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) {
- let html = '';
- if (result.Items.length) {
- elem.classList.remove('padded-left');
- elem.classList.remove('padded-right');
- elem.classList.remove('padded-bottom');
- elem.classList.remove('verticalSection');
-
- html += '
';
- html += '
';
- html += '
' + globalize.translate('LiveTV') + '
';
- html += '';
-
- if (enableScrollX()) {
- html += '
';
- html += '
';
- }
- html += '
';
- html += '
';
-
- html += '
';
- html += '
';
-
- if (enableScrollX()) {
- html += '
';
- html += '
';
- html += '
';
-
- elem.innerHTML = html;
-
- const itemsContainer = elem.querySelector('.itemsContainer');
- itemsContainer.parentContainer = elem;
- itemsContainer.fetchData = getOnNowFetchFn(apiClient.serverId());
- itemsContainer.getItemsHtml = getOnNowItemsHtml;
- }
- });
-}
-
-function getNextUpFetchFn(serverId, userSettings) {
- return function () {
- const apiClient = ServerConnections.getApiClient(serverId);
- const oldestDateForNextUp = new Date();
- oldestDateForNextUp.setDate(oldestDateForNextUp.getDate() - userSettings.maxDaysForNextUp());
- return apiClient.getNextUpEpisodes({
- Limit: enableScrollX() ? 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) {
- return function (items) {
- const cardLayout = false;
- return cardBuilder.getCardsHtml({
- items: items,
- preferThumb: true,
- inheritThumb: !useEpisodeImages,
- shape: getBackdropShape(enableScrollX()),
- overlayText: false,
- showTitle: true,
- showParentTitle: true,
- lazy: true,
- overlayPlayButton: true,
- context: 'home',
- centerText: !cardLayout,
- allowBottomPadding: !enableScrollX(),
- cardLayout: cardLayout
- });
- };
-}
-
-function loadNextUp(elem, apiClient, userSettings) {
- let html = '';
-
- html += '
';
-
- if (enableScrollX()) {
- html += '
';
- html += '
';
-
- elem.classList.add('hide');
- elem.innerHTML = html;
-
- const itemsContainer = elem.querySelector('.itemsContainer');
- itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings);
- itemsContainer.getItemsHtml = getNextUpItemsHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume());
- itemsContainer.parentContainer = elem;
-}
-
-function getLatestRecordingsFetchFn(serverId, activeRecordingsOnly) {
- return function () {
- const apiClient = ServerConnections.getApiClient(serverId);
- return apiClient.getLiveTvRecordings({
- userId: apiClient.getCurrentUserId(),
- Limit: enableScrollX() ? 12 : 5,
- Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
- EnableTotalRecordCount: false,
- IsLibraryItem: activeRecordingsOnly ? null : false,
- IsInProgress: activeRecordingsOnly ? true : null
- });
- };
-}
-
-function getLatestRecordingItemsHtml(activeRecordingsOnly) {
- return function (items) {
- return cardBuilder.getCardsHtml({
- items: items,
- shape: enableScrollX() ? 'autooverflow' : 'auto',
- showTitle: true,
- showParentTitle: true,
- coverImage: true,
- lazy: true,
- showDetailsMenu: true,
- centerText: true,
- overlayText: false,
- showYear: true,
- lines: 2,
- overlayPlayButton: !activeRecordingsOnly,
- allowBottomPadding: !enableScrollX(),
- preferThumb: true,
- cardLayout: false,
- overlayMoreButton: activeRecordingsOnly,
- action: activeRecordingsOnly ? 'none' : null,
- centerPlayButton: activeRecordingsOnly
- });
- };
-}
-
-function loadLatestLiveTvRecordings(elem, activeRecordingsOnly, apiClient) {
- const title = activeRecordingsOnly ?
- globalize.translate('HeaderActiveRecordings') :
- globalize.translate('HeaderLatestRecordings');
-
- let html = '';
-
- html += '
';
- html += '
' + title + '
';
- html += '';
-
- if (enableScrollX()) {
- html += '
';
- html += '
';
-
- elem.classList.add('hide');
- elem.innerHTML = html;
-
- const itemsContainer = elem.querySelector('.itemsContainer');
- itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly);
- itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly);
- itemsContainer.parentContainer = elem;
-}
-
export default {
- loadLibraryTiles: loadLibraryTiles,
- getDefaultSection: getDefaultSection,
- loadSections: loadSections,
- destroySections: destroySections,
- pause: pause,
- resume: resume
+ getDefaultSection,
+ loadSections,
+ destroySections,
+ pause,
+ resume
};
diff --git a/src/components/homesections/sections/activeRecordings.ts b/src/components/homesections/sections/activeRecordings.ts
new file mode 100644
index 000000000..8b8129f78
--- /dev/null
+++ b/src/components/homesections/sections/activeRecordings.ts
@@ -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 += '
';
+ html += '
' + title + '
';
+ html += '';
+
+ if (options.enableOverflow) {
+ html += '
';
+ html += '
';
+
+ 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;
+}
diff --git a/src/components/homesections/sections/libraryButtons.ts b/src/components/homesections/sections/libraryButtons.ts
new file mode 100644
index 000000000..06656c343
--- /dev/null
+++ b/src/components/homesections/sections/libraryButtons.ts
@@ -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 += '';
+
+ return html;
+}
+
+export function loadLibraryButtons(elem: HTMLElement, userViews: BaseItemDto[]) {
+ elem.classList.remove('verticalSection');
+ const html = getLibraryButtonsHtml(userViews);
+
+ elem.innerHTML = html;
+ imageLoader.lazyChildren(elem);
+}
diff --git a/src/components/homesections/sections/libraryTiles.ts b/src/components/homesections/sections/libraryTiles.ts
new file mode 100644
index 000000000..6ccfc528c
--- /dev/null
+++ b/src/components/homesections/sections/libraryTiles.ts
@@ -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 += '
' + globalize.translate('HeaderMyMedia') + '
';
+ if (enableOverflow) {
+ html += '
';
+ html += '
';
+ }
+
+ elem.innerHTML = html;
+ imageLoader.lazyChildren(elem);
+}
diff --git a/src/components/homesections/sections/liveTv.ts b/src/components/homesections/sections/liveTv.ts
new file mode 100644
index 000000000..7c8606c12
--- /dev/null
+++ b/src/components/homesections/sections/liveTv.ts
@@ -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 += '
';
+ html += '
';
+ html += '
' + globalize.translate('LiveTV') + '
';
+ html += '';
+
+ if (options.enableOverflow) {
+ html += '
';
+ html += '
';
+ }
+ html += '
';
+ html += '
';
+
+ html += '
';
+ html += '
';
+
+ if (options.enableOverflow) {
+ html += '
';
+ html += '
';
+ html += '
';
+
+ 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);
+ }
+ });
+}
diff --git a/src/components/homesections/sections/nextUp.ts b/src/components/homesections/sections/nextUp.ts
new file mode 100644
index 000000000..4ab401ab8
--- /dev/null
+++ b/src/components/homesections/sections/nextUp.ts
@@ -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 += '
';
+
+ if (options.enableOverflow) {
+ html += '
';
+ html += '
';
+
+ 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;
+}
diff --git a/src/components/homesections/sections/recentlyAdded.ts b/src/components/homesections/sections/recentlyAdded.ts
new file mode 100644
index 000000000..3c46ecbbe
--- /dev/null
+++ b/src/components/homesections/sections/recentlyAdded.ts
@@ -0,0 +1,158 @@
+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 += '
';
+
+ if (options.enableOverflow) {
+ html += '
';
+ html += '
';
+
+ 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 ?? [];
+
+ userViews.forEach(item => {
+ if (!item.Id || userExcludeItems.indexOf(item.Id) !== -1) {
+ return;
+ }
+
+ if (!item.CollectionType || excludeViewTypes.indexOf(item.CollectionType) !== -1) {
+ return;
+ }
+
+ const frag = document.createElement('div');
+ frag.classList.add('verticalSection');
+ frag.classList.add('hide');
+ elem.appendChild(frag);
+
+ renderLatestSection(frag, apiClient, user, item, options);
+ });
+}
diff --git a/src/components/homesections/sections/resume.ts b/src/components/homesections/sections/resume.ts
new file mode 100644
index 000000000..e96dde3ee
--- /dev/null
+++ b/src/components/homesections/sections/resume.ts
@@ -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
= {
+ 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 += '' + globalize.translate(titleLabel) + '
';
+ if (options.enableOverflow) {
+ html += '';
+ html += `
';
+
+ 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;
+}
diff --git a/src/components/homesections/sections/section.d.ts b/src/components/homesections/sections/section.d.ts
new file mode 100644
index 000000000..9307ba7c1
--- /dev/null
+++ b/src/components/homesections/sections/section.d.ts
@@ -0,0 +1,12 @@
+import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
+import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto-query-result';
+
+export interface SectionOptions {
+ enableOverflow: boolean
+}
+
+export type SectionContainerElement = {
+ fetchData: () => Promise
+ getItemsHtml: (items: BaseItemDto[]) => void
+ parentContainer: HTMLElement
+} & Element;
diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js
index 0ec982f15..0416ce471 100644
--- a/src/components/itemContextMenu.js
+++ b/src/components/itemContextMenu.js
@@ -444,7 +444,7 @@ function executeCommand(item, id, options) {
});
break;
case 'multiSelect':
- import('./multiSelect/multiSelect').then(({ startMultiSelect: startMultiSelect }) => {
+ import('./multiSelect/multiSelect').then(({ startMultiSelect }) => {
const card = dom.parentWithClass(options.positionTo, 'card');
startMultiSelect(card);
});
diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx
index f8e5a12fb..ebb5240b2 100644
--- a/src/components/search/SearchResults.tsx
+++ b/src/components/search/SearchResults.tsx
@@ -49,7 +49,7 @@ const SearchResults: FunctionComponent = ({ serverId = windo
const getDefaultParameters = useCallback(() => ({
ParentId: parentId,
searchTerm: query,
- Limit: 24,
+ Limit: 100,
Fields: 'PrimaryImageAspectRatio,CanDelete,BasicSyncInfo,MediaSourceCount',
Recursive: true,
EnableTotalRecordCount: false,
diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js
index 691b07cb4..9dc065621 100644
--- a/src/scripts/settings/userSettings.js
+++ b/src/scripts/settings/userSettings.js
@@ -301,7 +301,7 @@ export class UserSettings {
/**
* Get or set 'Use Episode Images in Next Up and Continue Watching' state.
- * @param {string|boolean|undefined} val - Flag to enable 'Use Episode Images in Next Up and Continue Watching' or undefined.
+ * @param {string|boolean|undefined} [val] - Flag to enable 'Use Episode Images in Next Up and Continue Watching' or undefined.
* @return {boolean} 'Use Episode Images in Next Up' state.
*/
useEpisodeImagesInNextUpAndResume(val) {
@@ -463,7 +463,7 @@ export class UserSettings {
/**
* Get or set max days for next up list.
- * @param {number|undefined} val - Max days for next up.
+ * @param {number|undefined} [val] - Max days for next up.
* @return {number} Max days for a show to stay in next up without being watched.
*/
maxDaysForNextUp(val) {
@@ -482,7 +482,7 @@ export class UserSettings {
/**
* Get or set rewatching in next up.
- * @param {boolean|undefined} val - If rewatching items should be included in next up.
+ * @param {boolean|undefined} [val] - If rewatching items should be included in next up.
* @returns {boolean} Rewatching in next up state.
*/
enableRewatchingInNextUp(val) {
diff --git a/src/strings/da.json b/src/strings/da.json
index 82c8a8cc7..0b4176d23 100644
--- a/src/strings/da.json
+++ b/src/strings/da.json
@@ -1336,7 +1336,7 @@
"EnableFasterAnimations": "Hurtigere animationer",
"DisablePlugin": "Deaktiver",
"EnablePlugin": "Aktiver",
- "DirectPlayHelp": "Kilde filen er kompatibel med denne klient, og modtager filen uden brug af omkodning.",
+ "DirectPlayHelp": "Kilde filen er kompatibel med denne klient og modtager filen uden brug af omkodning.",
"EnableEnhancedNvdecDecoder": "Aktiver forbedret NVDEC-dekoder",
"MessagePlaybackError": "Der opstod en fejl under afspilning af denne fil på din Google Cast modtager.",
"MessageChromecastConnectionError": "Din Google Cast modtager kan ikke komme i kontakt med Jellyfin serveren. Undersøg venligst forbindelsen og prøv igen.",
@@ -1702,5 +1702,19 @@
"SubtitleCyan": "Cyan",
"SubtitleMagenta": "Magenta",
"AllowCollectionManagement": "Tillad denne bruger at administrere samlinger",
- "AllowSegmentDeletion": "Slet segmenter"
+ "AllowSegmentDeletion": "Slet segmenter",
+ "HeaderEpisodesStatus": "Episodestatus",
+ "GoHome": "Gå Hjem",
+ "EnableAudioNormalizationHelp": "Audionormalisering tilføjer en konstant forstærkning for at holde gennemsnittet på et ønsket niveau (-18 dB).",
+ "EnableAudioNormalization": "Audio Normalisering",
+ "GridView": "Gittervisning",
+ "HeaderConfirmRepositoryInstallation": "Bekræft installation af plugin-repositorium",
+ "BackdropScreensaver": "Screensaver baggrund",
+ "GetThePlugin": "Få pluginnet",
+ "AllowSegmentDeletionHelp": "Slet gamle segmenter, når de er blevet sendt til klienten. Dette forhindrer, at man skal gemme hele den transkodede fil på disken. Fungerer kun med throttling aktiveret. Slå dette fra, hvis du oplever afspilningsproblemer.",
+ "LabelThrottleDelaySeconds": "Begræns efter",
+ "LabelThrottleDelaySecondsHelp": "Tid i sekunder, hvorefter transcoderen vil blive begrænset. Skal være stor nok til, at klienten kan opretholde en sund buffer. Virker kun, hvis throttling er aktiveret.",
+ "LabelSegmentKeepSeconds": "Tid at gemme segmenter i",
+ "LabelSegmentKeepSecondsHelp": "Tid i sekunder, som segmenter skal gemmes i, før de overskrives. Skal være større end \"Begræns efter\". Virker kun, hvis sletning af segmenter er aktiveret.",
+ "HeaderGuestCast": "Gæstestjerner"
}
diff --git a/src/strings/de.json b/src/strings/de.json
index 4ec655c88..0e2dd7749 100644
--- a/src/strings/de.json
+++ b/src/strings/de.json
@@ -1733,7 +1733,7 @@
"PasswordRequiredForAdmin": "Für Admin Konten wird ein Passwort benötigt.",
"LabelEnableLUFSScan": "LUFS-Scan aktivieren",
"LabelSyncPlayNoGroups": "Keine Gruppen verfügbar",
- "LabelEnableLUFSScanHelp": "Clients können die Audio Wiedergabe normalisieren um die selbe Lautstärke für mehrere Stücke zu bekommen.\nDie verlängert den Bibiliotheks Scan und benötigt mehr Ressourcen.",
+ "LabelEnableLUFSScanHelp": "Clients können die Audio Wiedergabe normalisieren, um die selbe Lautstärke für mehrere Stücke zu bekommen.\nDies verlängert den Bibliotheksscan und benötigt mehr Ressourcen.",
"Notifications": "Benachrichtigungen",
"NotificationsMovedMessage": "Die Benachrichtigungsfunktion wurde zum Webhook Plugin verschoben.",
"EnableAudioNormalizationHelp": "Die Audionormalisierung fügt eine konstante Verstärkung hinzu, um den Durchschnitt auf einem gewünschten Pegel zu halten (-18 dB).",
@@ -1758,8 +1758,8 @@
"HeaderEpisodesStatus": "Episodenstatus",
"AllowSegmentDeletion": "Segmente löschen",
"AllowSegmentDeletionHelp": "Alte Segmente löschen, nachdem sie zum Client gesendet wurden. Damit muss nicht die gesamte transkodierte Datei zwischengespeichert werden. Sollten Wiedergabeprobleme auftreten, kann diese Einstellung deaktiviert werden.",
- "LabelThrottleDelaySeconds": "Limitieren nach",
- "LabelThrottleDelaySecondsHelp": "Zeit, in Sekunden, nach der die Transkodierung limitiert wird. Muss groß genug sein um dem Client eine problemlose Wiedergabe zu ermöglichen. Funktioniert nur wenn \"Transkodierung drosseln\" aktiviert ist.",
+ "LabelThrottleDelaySeconds": "Drosseln nach",
+ "LabelThrottleDelaySecondsHelp": "Zeit, in Sekunden, nach der die Transkodierung gedrosselt wird. Muss groß genug sein um dem Client eine problemlose Wiedergabe zu ermöglichen. Funktioniert nur wenn \"Transkodierung drosseln\" aktiviert ist.",
"LabelSegmentKeepSeconds": "Zeit um Segmente zu behalten",
"LabelSegmentKeepSecondsHelp": "Zeit, in Sekunden, in der Segmente nicht überschrieben werden dürfen. Muss größer sein als \"Limitieren nach\". Funktioniert nur wenn \"Segmente löschen\" aktiviert ist.",
"LogoScreensaver": "Logo Bildschirmschoner",
@@ -1776,5 +1776,5 @@
"ForeignPartsOnly": "Erzwungen/Nur ausländische Teile",
"HearingImpairedShort": "BaFa/SDH",
"HeaderGuestCast": "Gast Stars",
- "LabelBackdropScreensaverIntervalHelp": "Die Zeit in Sekunden zwischen dem Wechsel verschiedener Hintergrundbilder im Bildschirmschoner"
+ "LabelBackdropScreensaverIntervalHelp": "Die Zeit in Sekunden zwischen dem Wechsel verschiedener Hintergrundbilder im Bildschirmschoner."
}
diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json
index 3fb0abb6a..7431d0fdf 100644
--- a/src/strings/es-mx.json
+++ b/src/strings/es-mx.json
@@ -1264,7 +1264,7 @@
"AskAdminToCreateLibrary": "Pide a un administrador crear una biblioteca.",
"Artist": "Artista",
"AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.",
- "AllowFfmpegThrottling": "Regular transcodificaciones",
+ "AllowFfmpegThrottling": "Limitar transcodificaciones",
"AlbumArtist": "Artista del álbum",
"Album": "Album",
"Yadif": "YADIF",
@@ -1756,10 +1756,10 @@
"AllowSegmentDeletion": "Borrar segmentos",
"HeaderEpisodesStatus": "Estatus de los Episodios",
"AllowSegmentDeletionHelp": "Borrar los viejos segmentos después de que hayan sido enviados al cliente. Esto previene que se tenga almacenado la totalidad de la transcodificación en el disco. Esto funciona unicamente cuando se tenga habilitado el throttling. Apagar esta opción cuando se tengan problemas de reproducción.",
- "LabelThrottleDelaySeconds": "Acelerar después",
+ "LabelThrottleDelaySeconds": "Limitar después de",
"LabelThrottleDelaySecondsHelp": "Tiempo en segundos después de que la transcodificación entre en aceleración. Deben ser los suficientes para que el buffer del cliente siga operando. Unicamente funciona si la aceleración está habilitada.",
"LabelSegmentKeepSeconds": "Tiempo para guardar segmentos",
- "LabelSegmentKeepSecondsHelp": "Tiempo en segundos en los que los segmentos deben permanecer antes de que sean sobrescritos. Estos deben de ser mayores a los indicados en \"Acelerar despues de\". Esto funciona unicamente si esta habilitada la opción de eliminar el segmento.",
+ "LabelSegmentKeepSecondsHelp": "Tiempo en segundos en los que los segmentos deben permanecer antes de que sean sobrescritos. Estos deben de ser mayores a los indicados en \"Limitar después de\". Esto funciona unicamente si esta habilitada la opción de eliminar el segmento.",
"AllowAv1Encoding": "Permitir encodificación en formato AV1",
"GoHome": "Ir a Inicio",
"UnknownError": "Un error desconocido ocurrió.",
diff --git a/src/strings/es.json b/src/strings/es.json
index ab3ac87d9..9c28914c1 100644
--- a/src/strings/es.json
+++ b/src/strings/es.json
@@ -1246,7 +1246,7 @@
"LabelDroppedFrames": "Frames perdidos",
"LabelCorruptedFrames": "Frames corruptos",
"AskAdminToCreateLibrary": "Pídele a un administrador que cree una biblioteca.",
- "AllowFfmpegThrottling": "Acelerar las conversiones",
+ "AllowFfmpegThrottling": "Limitar transcodificaciones",
"ClientSettings": "Ajustes de cliente",
"PreferEmbeddedEpisodeInfosOverFileNames": "Priorizar la información embebida sobre los nombres de archivos",
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Usar la información de episodio de los metadatos embebidos si está disponible.",
diff --git a/src/strings/es_419.json b/src/strings/es_419.json
index 0a208c94e..682ad9b6d 100644
--- a/src/strings/es_419.json
+++ b/src/strings/es_419.json
@@ -1307,7 +1307,7 @@
"AllowRemoteAccessHelp": "Si no se marca, se bloquearán todas las conexiones remotas.",
"AllowRemoteAccess": "Permitir conexiones remotas a este servidor",
"AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.",
- "AllowFfmpegThrottling": "Regular transcodificaciones",
+ "AllowFfmpegThrottling": "Limitar transcodificaciones",
"AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los clientes en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.",
"AllowOnTheFlySubtitleExtraction": "Permitir la extracción de subtítulos sobre la marcha",
"AllowMediaConversionHelp": "Permitir o denegar acceso a la función de convertir medios.",
@@ -1578,5 +1578,62 @@
"LabelMaxDaysForNextUp": "Días máximos en «A continuación»",
"LabelEnableAudioVbrHelp": "La tasa de bits variable ofrece mejor calidad a la tasa media de bits promedio, pero en algunos raros casos puede causar almacenamiento en búfer y problemas de compatibilidad.",
"LabelEnableAudioVbr": "Habilitar codificación VBR de audio",
- "HeaderPerformance": "Rendimiento"
+ "HeaderPerformance": "Rendimiento",
+ "HeaderEpisodesStatus": "Status de los Episodios",
+ "LabelBackdropScreensaverInterval": "Intervalo del Protector de Pantalla de Fondo",
+ "LabelBackdropScreensaverIntervalHelp": "Intervalo en segundos entre los diferentes fondos cuando se usa el protector de pantalla de fondo.",
+ "LabelEnableLUFSScanHelp": "Permite a los clientes normalizar la reproducción de audio para tener el mismo volumen en todas las pistas. Esto hará que los escaneos de biblioteca tomen más tiempo y utilicen más recursos.",
+ "LabelSyncPlaySettingsSpeedToSyncHelp": "Método de corrección de sincronización que consiste en acelerar la reproducción. La corrección de sincronización debe estar activada.",
+ "LabelSyncPlaySettingsSkipToSyncHelp": "Método de corrección de sincronización que consiste en saltar a la posición de reproducción estimada. La Corrección de Sincronización debe estar activada.",
+ "ListView": "Vista en Lista",
+ "MessageNoItemsAvailable": "No hay elementos disponibles actualmente.",
+ "Bold": "Negrita",
+ "Larger": "Más grande",
+ "LabelSyncPlaySettingsMinDelaySkipToSyncHelp": "Retraso mínimo (en milisegundos) después del cual SkipToSync intentará corregir la posición de reproducción.",
+ "Localization": "Localización",
+ "GoHome": "Ir a Inicio",
+ "LabelSyncPlaySettingsSpeedToSync": "Activar SpeedToSync",
+ "LabelDeveloper": "Desarrollador",
+ "LabelLevel": "Nivel",
+ "LabelDate": "Fecha",
+ "EnableCardLayout": "Mostrar CardBox visual",
+ "LabelMediaDetails": "Detalles de multimedia",
+ "LabelSyncPlaySettingsSkipToSync": "Activar SkipToSync",
+ "LabelSystem": "Sistema",
+ "LogLevel.Debug": "Depurar",
+ "LogLevel.Trace": "Rastreo",
+ "LogLevel.Warning": "Advertencia",
+ "LogLevel.Error": "Error",
+ "LogLevel.Critical": "Crítico",
+ "GridView": "Vista en Cuadrícula",
+ "GetThePlugin": "Obtener el Plugin",
+ "LabelSyncPlayNoGroups": "No hay grupos disponibles",
+ "LabelTextWeight": "Grosor del texto",
+ "LogLevel.None": "Ningún",
+ "LabelSyncPlaySettingsSpeedToSyncDuration": "Duración de SpeedToSync",
+ "LabelSyncPlaySettingsMinDelaySkipToSync": "Retraso mínimo de SkipToSync",
+ "EnableAudioNormalizationHelp": "La normalización de audio añadirá una ganancia constante para mantener la media en el nivel deseado (-18dB).",
+ "LabelEnableLUFSScan": "Activar escaneo LUFS",
+ "MenuOpen": "Abrir Menú",
+ "MenuClose": "Cerrar Menú",
+ "BackdropScreensaver": "Protector de Pantalla de Fondo",
+ "LabelSyncPlaySettingsMaxDelaySpeedToSyncHelp": "Retraso máximo de reproducción (en milisegundos) después del cual SkipToSync se usará en lugar de SpeedToSync.",
+ "LogoScreensaver": "Protector de Pantalla de Logo",
+ "MessageNoFavoritesAvailable": "No hay favoritos disponibles actualmente.",
+ "LabelSyncPlaySettingsSpeedToSyncDurationHelp": "Cantidad de milisegundos que SpeedToSync utilizará para corregir la posición de reproducción.",
+ "HeaderConfirmRepositoryInstallation": "Confirma la instalación del repositorio de plugins",
+ "EnableAudioNormalization": "Normalización de audio",
+ "LogLevel.Information": "Información",
+ "AllowCollectionManagement": "Permitir a este usuario gestionar colecciones",
+ "Lyricist": "Letrista",
+ "AllowSegmentDeletion": "Borrar segmentos",
+ "AllowSegmentDeletionHelp": "Borrar segmentos viejos que ya hayan sido enviados al cliente. Esto evita tener que guardar la totalidad del archivo transcodificado en el disco. Funciona solamente si la opción \"regular transcodificaciones\" también está activada. Si se experimentan problemas en la reproducción, desactivar esta opción.",
+ "LabelThrottleDelaySeconds": "Limitar después de",
+ "LabelThrottleDelaySecondsHelp": "Duración en segundos después de la cual la transcodificación será limitada. Debe ser suficiente para permitirle al cliente mantener un buffer adecuado. Solo funciona si la limitación de la transcodificación está activada.",
+ "LabelSegmentKeepSeconds": "Tiempo de retención de segmentos",
+ "LabelSegmentKeepSecondsHelp": "Duración en segundos que se retendrán los segmentos antes de ser sobreescritos. Debe ser mayor que \"Limitar después de\". Solo funciona si la opción \"Borrar segmentos\" está activada.",
+ "LabelParallelImageEncodingLimit": "Límite de codificaciones paralelas de imágenes",
+ "LabelParallelImageEncodingLimitHelp": "Cantidad máxima de codificaciones de imágenes que pueden ejecutarse en paralelo. Al establecer 0 se elegirá un límite basado en las especificaciones de su sistema.",
+ "MediaInfoTitle": "Título",
+ "HeaderGuestCast": "Estrellas Invitadas"
}
diff --git a/src/strings/fo.json b/src/strings/fo.json
index 6532f3e13..c98afcf76 100644
--- a/src/strings/fo.json
+++ b/src/strings/fo.json
@@ -212,5 +212,8 @@
"ValueMusicVideoCount": "{0} tónleika sjónbond",
"ValueOneAlbum": "1 album",
"ValueOneSong": "1 sangur",
- "ValueSongCount": "{0} sangir"
+ "ValueSongCount": "{0} sangir",
+ "ButtonForgotPassword": "Gloymt loyniorð",
+ "ButtonSignIn": "Innrita",
+ "ButtonSignOut": "Útrita"
}
diff --git a/src/strings/ms.json b/src/strings/ms.json
index 6a000566e..d950d6ff6 100644
--- a/src/strings/ms.json
+++ b/src/strings/ms.json
@@ -251,5 +251,12 @@
"AllowSegmentDeletionHelp": "Padam segmen lama setelah ia dihantar ke pelayan. Ini menghalang file transcode disimpan dalam disk. Ia akan berfungsi dengan penghad haju dihidupkan. Matikan tetapan ini jika anda mengalami isu dengan pemain video.",
"LabelThrottleDelaySeconds": "Penghad laju setelah",
"Settings": "Tetapan",
- "SelectServer": "Pilih pelayan"
+ "SelectServer": "Pilih pelayan",
+ "ButtonBackspace": "pendikit",
+ "ButtonSpace": "Ruang",
+ "BackdropScreensaver": "Penyimpan skrin latar belakang",
+ "Cursive": "Sambung",
+ "DefaultSubtitlesHelp": "Sari kata dimuatkan berdasarkan bendera lalai dan paksa dalam metadata yang disematkan. Keutamaan bahasa diambil kira apabila terdapat beberapa pilihan.",
+ "LabelThrottleDelaySecondsHelp": "Masa dalam saat selepas mana transkoder akan dihadkan. Mesti cukup besar untuk klien mengekalkan penimbal yang sihat. Hanya berfungsi jika penghadan diaktifkan.",
+ "LabelSegmentKeepSeconds": "Masa untuk mengekalkan segmen"
}
diff --git a/src/strings/nb.json b/src/strings/nb.json
index eaf915e98..91f77b623 100644
--- a/src/strings/nb.json
+++ b/src/strings/nb.json
@@ -1770,5 +1770,10 @@
"MachineTranslated": "Maskinoversatt",
"HeaderGuestCast": "Gjestestjerner",
"HearingImpairedShort": "HI/SDH",
- "LogoScreensaver": "Logoskjermsparer"
+ "LogoScreensaver": "Logoskjermsparer",
+ "LabelIsHearingImpaired": "For hørselshemmede (SDH)",
+ "LabelBackdropScreensaverInterval": "Skjermsparingsbakgrunn-intervall",
+ "LabelBackdropScreensaverIntervalHelp": "Tid i milisekunder mellom forskjellige bakgrunner ved bruk av skjermsparingsbakgrunner.",
+ "BackdropScreensaver": "Skjermsparingsbakgrunn",
+ "ForeignPartsOnly": "Kun tvungne/fremmede deler"
}
diff --git a/src/strings/ru.json b/src/strings/ru.json
index 4b1cd2c2a..d584214bd 100644
--- a/src/strings/ru.json
+++ b/src/strings/ru.json
@@ -1,6 +1,6 @@
{
"Absolute": "Абсолютный",
- "AccessRestrictedTryAgainLater": "В настоящее время доступ запрещён. Повторите попытку позже.",
+ "AccessRestrictedTryAgainLater": "В настоящее время доступ ограничен. Повторите попытку позже.",
"Actor": "Актёр",
"Add": "Добавить",
"AddToCollection": "Добавить в коллекцию",
@@ -1763,5 +1763,8 @@
"LabelSegmentKeepSeconds": "Время сохранения сегментов",
"LogLevel.Error": "Ошибка",
"LabelBackdropScreensaverInterval": "Интервал между фонами у заставки",
- "LabelBackdropScreensaverIntervalHelp": "Время в секундах между разными фонами, когда используется заставка."
+ "LabelBackdropScreensaverIntervalHelp": "Время в секундах между разными фонами, когда используется заставка.",
+ "BackdropScreensaver": "Заставка",
+ "GoHome": "Домой",
+ "GridView": "Сеткой"
}
diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json
index d583cf90d..34164f0f6 100644
--- a/src/strings/sl-si.json
+++ b/src/strings/sl-si.json
@@ -648,7 +648,7 @@
"MessageNoMovieSuggestionsAvailable": "Trenutno ni na voljo nobenih predlogov za filme. Začnite gledati in ocenjevati vaše filme, ter se nato vrnite sem in si oglejte predloge.",
"LabelSelectFolderGroups": "Samodejno združi vsebine iz spodnjih map v poglede kot so 'Filmi', 'Glasba' in 'TV'",
"TitlePlayback": "Predvajanje",
- "MessagePasswordResetForUsers": "Gesla naslednjih uporabnikov so bila ponastavljena. Zdaj se lahko prijavijo z Enostavnimi PIN kodami, ki so bile uporabljene za ponastavitev.",
+ "MessagePasswordResetForUsers": "Gesla naslednjih uporabnikov so bila ponastavljena. Zdaj se lahko prijavijo s PIN kodami, ki so bile uporabljene za ponastavitev.",
"OptionHideUserFromLoginHelp": "Koristno za zasebne ali skrite skrbniške račune. Uporabnik se bo moral prijaviti ročno z vpisom svojega uporabniškega imena in gesla.",
"OnlyForcedSubtitlesHelp": "Naložijo se zgolj podnapisi, ki so označeni kot prisiljeni.",
"OptionEnableExternalContentInSuggestionsHelp": "Dovoli, da so spletni napovedniki in TV kanali v živo vključeni med priporočenimi vsebinami.",
@@ -810,7 +810,7 @@
"OptionAutomaticallyGroupSeries": "Samodejno združi serije, ki so razdeljene po več mapah",
"OptionAllowUserToManageServer": "Dovoli temu uporabniku upravljanje strežnika",
"MessageTheFollowingLocationWillBeRemovedFromLibrary": "Naslednja mesta predstavnosti bodo odstranjena iz vaše knjižnice",
- "MessagePluginInstallDisclaimer": "Dodatki ustvarjeni s strani članov skupnosti so odličen način za izboljšanje vaše izkušnje z dodatnimi funkcijami in prednostmi. Preden namestite dodatke se zavedajte, da imajo lahko ti različne vplive na vaš strežnik, kot na primer počasnejše preiskovanje knjižnic, dodatna obdelava podatkov v ozadju in zmanjšana stabilnost sistema.",
+ "MessagePluginInstallDisclaimer": "POZOR: Nameščanje dodatkov tretjih oseb predstavlja tveganje. Vsebujejo lahko nestabilno ali zlonamerno kodo, ki se lahko kadarkoli spremeni. Namestite zgolj dodatke avtorjev ki jim zaupate. Zavedajte se morebitnih stranskih učinkov, kot na primer komunikacija z zunanjimi storitvami, podaljšan čas pregledovanja knjižnice in dodatno procesiranje v ozadju.",
"MessagePleaseWait": "Prosimo, počakajte. To lahko traja nekaj minut.",
"MessagePleaseEnsureInternetMetadata": "Prosimo poskrbite, da je prenašanje spletnih metapodatkov omogočeno.",
"MessageNothingHere": "Tu ni ničesar.",
@@ -1308,7 +1308,7 @@
"QuickConnectDescription": "Za vpis s Hitro povezavo izberi 'Hitra povezava' na napravi preko katere se vpisuješ in vnesi kodo.",
"QuickConnectDeactivated": "Hitra povezava je bila onemogočena pred dokončanjem vpisa",
"QuickConnectAuthorizeFail": "Neznana koda za Hitro povezavo",
- "QuickConnectAuthorizeSuccess": "Avtorizirano",
+ "QuickConnectAuthorizeSuccess": "Uspešno ste overili svojo napravo!",
"QuickConnectAuthorizeCode": "Vnesi kodo {0} za vpis",
"QuickConnectActivationSuccessful": "Aktivirano",
"QuickConnect": "Hitra povezava",
@@ -1402,7 +1402,7 @@
"LabelTonemappingAlgorithm": "Izberite algoritem za preslikavo barv",
"LabelOpenclDeviceHelp": "To je naprava OpenCL, ki bo uporabljena za preslikavo barv. Na levi strani pike je številka platforme, desno je številka naprave na tej platformi. Privzeta vrednost je 0.0. Zahtevana je datoteka FFmpeg, ki vsebuje metodo strojnega pospeševanja OpenCL.",
"LabelColorPrimaries": "Barvni prostor",
- "AllowTonemappingHelp": "Preslikava barv lahko preslika dinamični razpon videa HDR v SDR, pri tem pa ohranja podrobnosti in barve, kar je zelo pomembno za predstavitev izvorne scene. Trenutno deluje zgolj z HDR10 in HLG videi. Zahteva ustrezne OpenCL ali CUDA knjižnice.",
+ "AllowTonemappingHelp": "Preslikava barv lahko preslika dinamični razpon videa iz HDR v SDR, pri tem pa ohranja podrobnosti in barve, kar je zelo pomembno za predstavitev izvorne scene. Trenutno deluje zgolj z HDR10 in HLG videi. Zahteva ustrezne OpenCL ali CUDA knjižnice.",
"MediaInfoVideoRange": "Barvni razpon",
"LabelVideoRange": "Barvni razpon",
"LabelSonyAggregationFlags": "Sonyjeve agregacijske oznake",
@@ -1647,10 +1647,10 @@
"LabelUserMaxActiveSessions": "Največje število hkratnih uporabniških sej",
"LabelUDPPortRangeHelp": "Omeji Jellyfin na ta razpon vrat za UDP komunikacije. (Privzeto 1024 - 645535).
Opomba: Nekatere funkcije zahtevajo fiksna vrata, ki so lahko izven tega razpona.",
"LabelUDPPortRange": "Razpon komunikacij UDP",
- "LabelVppTonemappingContrastHelp": "Uporabi povečanje kontrasta pri VPP preslikavi barv. Priporočena in privzeta vrednost je 0.",
+ "LabelVppTonemappingContrastHelp": "Uporabi povečanje kontrasta pri VPP preslikavi barv. Priporočeni in privzeti vrednosti sta 1.",
"LabelVppTonemappingBrightness": "VPP preslikava barv povečanje svetlosti",
"LabelVppTonemappingContrast": "VPP preslikava barv povečanje kontrasta",
- "LabelVppTonemappingBrightnessHelp": "Uporabi povečanje svetlosti pri VPP preslikavi barv. Priporočena in privzeta vrednost je 0.",
+ "LabelVppTonemappingBrightnessHelp": "Uporabi povečanje svetlosti pri VPP preslikavi barv. Priporočeni in privzeti vrednosti sta 16 in 0.",
"AllowVppTonemappingHelp": "Preslikava barv v celoti na podlagi gonilnikov Intel. Trenutno deluje zgolj na določeni strojni opremi z HDR10 videi. Ima prednost pred drugimi OpenCL implementacijami.",
"TonemappingAlgorithmHelp": "Preslikavo barv lahko podrobno nastavite. Če teh možnosti ne poznate, pustite privzete vrednosti. Priporočena vrednost je 'BT.2390'.",
"LabelTonemappingThresholdHelp": "Parametri preslikave barv so natančno nastavljeni za vsak prizor. Prag je uporabljen za zaznavanje ali se je prizor spremenil ali ne. Če je razlika med povprečno svetlostjo trenutne sličice in tekočim povprečjem večja od nastavljenega praga, se vrednosti za povprečno in najvišjo svetlost prizora znova izračunajo. Priporočena in privzeta vrednost je 0,8 in 0,2.",
@@ -1693,11 +1693,11 @@
"PreferEmbeddedExtrasTitlesOverFileNames": "Raje uporabi vdelane naslove kot imena datotek",
"SaveRecordingNFOHelp": "Shrani metapodatke v isto mapo.",
"LabelDummyChapterDuration": "interval",
- "LabelDummyChapterDurationHelp": "Interval ekstrakcije slike poglavja v sekundah.",
+ "LabelDummyChapterDurationHelp": "Interval med navideznimi poglavji. Nastavite na 0 za onemogočanje ustvarjanja navideznih poglavij. Sprememba ne bo imela vpliva na obstoječa navidezna poglavja.",
"LabelDummyChapterCount": "Limit",
"LabelDummyChapterCountHelp": "Največje število slik poglavij, ki bodo ekstrahirane za vsako medijsko datoteko.",
"LabelChapterImageResolution": "Resolucija",
- "LabelChapterImageResolutionHelp": "Ločljivost slik poglavij.",
+ "LabelChapterImageResolutionHelp": "Ločljivost slik poglavij. Spreminjanje ne bo vplivalo na obstoječa lažna poglavja.",
"ResolutionMatchSource": "ujemanje z virom",
"HeaderRecordingMetadataSaving": "Snemanje metapodatkov",
"AllowCollectionManagement": "Dovoli uporabniku upravljanje zbirk",
@@ -1714,5 +1714,61 @@
"LabelSegmentKeepSeconds": "Čas ohranitve segmentov",
"AllowSegmentDeletionHelp": "Izbriši segmente, ki so če bili poslani odjemalcu. S tem se prepreči, da bi na disku bila shranjena celotna prekodirana datoteka. To deluje le, če je omogočeno zaviranje prekodiranja. Če se pojavijo težave pri predvajanju, onemogočite to možnost.",
"LabelThrottleDelaySecondsHelp": "Čas v sekundah, po katerem se bo prekodirnik upočasnil. Mora biti dovolj, da odjemalec ohranja ustrezen medpomnilnik. Deluje le, če je zaviranje prekodiranja omogočeno.",
- "LabelSegmentKeepSecondsHelp": "Čas v skundah, ko naj se segmenti ohranijo, preden se jih prepiše. Čas mora biti večji od \"Zaviraj po\". Deluje le, če je brisanje segmentov omogočeno."
+ "LabelSegmentKeepSecondsHelp": "Čas v skundah, ko naj se segmenti ohranijo, preden se jih prepiše. Čas mora biti večji od \"Zaviraj po\". Deluje le, če je brisanje segmentov omogočeno.",
+ "MessageRepositoryInstallDisclaimer": "POZOR: Nameščanje skladišča dodatkov tretjih oseb predstavlja tveganje. Lahko vsebuje nestabilno ali zlonamerno kodo, ki se lahko kadarkoli spremeni. Namestite zgolj skladišča avtorjev ki jim zaupate.",
+ "Studio": "Studio",
+ "TonemappingModeHelp": "Izberi način preslikave barv. Če opazite razbarvanje svetlih delov slike, poskusite uporabiti način RGB.",
+ "LabelIsHearingImpaired": "Za naglušne (SDH)",
+ "AllowAv1Encoding": "Dovoli kodiranje v AV1 format",
+ "BackdropScreensaver": "Ozadje ohranjevalnika zaslona",
+ "LogoScreensaver": "Ohranjevalnik zaslona z logotipom",
+ "Short": "Kratki film",
+ "PasswordRequiredForAdmin": "Administratorski računi zahtevajo geslo.",
+ "SaveRecordingImages": "Shrani posnetke slik EPG",
+ "SubtitleBlack": "Črna",
+ "SubtitleRed": "Rdeča",
+ "LabelSyncPlayNoGroups": "Nobena skupina ni na voljo",
+ "NotificationsMovedMessage": "Funkcionalnost obvestil se je premaknila v dodatek Webhook.",
+ "SecondarySubtitles": "Sekundarni podnapisi",
+ "LabelDeveloper": "Razvijalec",
+ "LabelEnableAudioVbrHelp": "Spremenljiva bitna hitrost omogoča boljše razmerje med kvaliteto zvoka in povprečno bitno hitrostjo vendar lahko povzroči težave z medpomnjenjem in kompatibilnostjo.",
+ "LogLevel.None": "Brez",
+ "ForeignPartsOnly": "Samo vsiljeni/tuji deli",
+ "MachineTranslated": "Strojno prevedeno",
+ "LabelDate": "Datum",
+ "LabelEnableLUFSScan": "Omogoči skeniranje LUFS",
+ "LabelEnableLUFSScanHelp": "Odjemalci lahko normalizirajo glasnost zvoka za enako glasnost med skladbami. Pregledovanje knjižnice bo počasnejše in bo porabilo več sistemskih virov.",
+ "LabelLevel": "Nivo",
+ "LabelSystem": "Sistem",
+ "LogLevel.Trace": "",
+ "LogLevel.Warning": "Opozorilo",
+ "LogLevel.Information": "Informacije",
+ "Select": "Izberi",
+ "LogLevel.Error": "Napaka",
+ "LogLevel.Critical": "Kritično",
+ "GoHome": "Domov",
+ "LabelMediaDetails": "Podrobnosti predstavnosti",
+ "SubtitleWhite": "Bela",
+ "UnknownError": "Prišlo je do neznane napake.",
+ "Unknown": "Neznano",
+ "LabelBackdropScreensaverInterval": "Interval ozadja ohranjevalnika zaslona",
+ "LabelBackdropScreensaverIntervalHelp": "Čas v sekundah med različnimi ozadji za ohranjevalnik zaslona.",
+ "Notifications": "Obvestila",
+ "LabelEnableAudioVbr": "Omogoči VBR kodiranje zvoka",
+ "ListView": "Pogled seznama",
+ "PleaseConfirmRepositoryInstallation": "S klikom na OK potrjujete, da ste prebrali zgornje opozorilo in želite nadaljevati z namestitvijo skladišča dodatkov.",
+ "MenuOpen": "Odpri meni",
+ "MenuClose": "Zapri meni",
+ "UserMenu": "Uporabniški meni",
+ "LabelTonemappingMode": "Način preslikave barv",
+ "LabelParallelImageEncodingLimit": "Omejitev vzporednega kodiranja slik",
+ "SubtitleYellow": "Rumena",
+ "SubtitleBlue": "Modra",
+ "SubtitleCyan": "Cian",
+ "SubtitleGray": "Siva",
+ "SubtitleGreen": "Zelena",
+ "SubtitleLightGray": "Svetlo siva",
+ "SubtitleMagenta": "Magenta",
+ "LabelParallelImageEncodingLimitHelp": "Največje dovoljeno število vzporednih kodiranj slik. Nastavite na 0 za samodejno omejitev glede na zmogljivost vašega sistema.",
+ "AiTranslated": "AI prevedeno"
}
diff --git a/src/strings/vi.json b/src/strings/vi.json
index a53d197c4..693eb272a 100644
--- a/src/strings/vi.json
+++ b/src/strings/vi.json
@@ -1,6 +1,6 @@
{
"Add": "Thêm",
- "All": "Tất cả",
+ "All": "Tất cả",
"MessageBrowsePluginCatalog": "Duyệt danh mục plugin của chúng tôi để xem các plugin có sẵn.",
"ButtonAddUser": "Thêm Người Dùng",
"ButtonCancel": "Hủy bỏ",
diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json
index 8a0f36db6..47986abc8 100644
--- a/src/strings/zh-cn.json
+++ b/src/strings/zh-cn.json
@@ -1773,5 +1773,8 @@
"ForeignPartsOnly": "仅限强制开启/外语部分",
"HearingImpairedShort": "听障/聋哑人士字幕",
"UnknownError": "发生未知错误。",
- "GoHome": "回家"
+ "GoHome": "回家",
+ "BackdropScreensaver": "背景屏保",
+ "LogoScreensaver": "徽标屏保",
+ "LabelIsHearingImpaired": "用于听障/聋哑人士"
}
diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json
index b4825af3b..d6362e2c0 100644
--- a/src/strings/zh-tw.json
+++ b/src/strings/zh-tw.json
@@ -822,7 +822,7 @@
"LabelFormat": "格式",
"LabelFriendlyName": "好聽的名字",
"LabelGroupMoviesIntoCollections": "將電影分組",
- "LabelKodiMetadataDateFormat": "釋出日期格式",
+ "LabelKodiMetadataDateFormat": "發行日期格式",
"LabelIconMaxWidth": "Icon 最寬寬度",
"LabelGroupMoviesIntoCollectionsHelp": "選擇檢視電影清單時,集合中的電影將作為一個分組項目顯示。",
"LabelEncoderPreset": "預設編碼",
diff --git a/src/types/homeSectionType.ts b/src/types/homeSectionType.ts
new file mode 100644
index 000000000..1a3e6eb87
--- /dev/null
+++ b/src/types/homeSectionType.ts
@@ -0,0 +1,27 @@
+// NOTE: This should be included in the OpenAPI spec ideally
+// https://github.com/jellyfin/jellyfin/blob/1b4394199a2f9883cd601bdb8c9d66015397aa52/Jellyfin.Data/Enums/HomeSectionType.cs
+export enum HomeSectionType {
+ None = 'none',
+ SmallLibraryTiles = 'smalllibrarytiles',
+ LibraryButtons = 'librarybuttons',
+ ActiveRecordings = 'activerecordings',
+ Resume = 'resume',
+ ResumeAudio = 'resumeaudio',
+ LatestMedia = 'latestmedia',
+ NextUp = 'nextup',
+ LiveTv = 'livetv',
+ ResumeBook = 'resumebook'
+}
+
+// NOTE: This needs to match the server defaults
+// https://github.com/jellyfin/jellyfin/blob/1b4394199a2f9883cd601bdb8c9d66015397aa52/Jellyfin.Api/Controllers/DisplayPreferencesController.cs#L120
+export const DEFAULT_SECTIONS: HomeSectionType[] = [
+ HomeSectionType.SmallLibraryTiles,
+ HomeSectionType.Resume,
+ HomeSectionType.ResumeAudio,
+ HomeSectionType.ResumeBook,
+ HomeSectionType.LiveTv,
+ HomeSectionType.NextUp,
+ HomeSectionType.LatestMedia,
+ HomeSectionType.None
+];
diff --git a/src/utils/jellyfin-apiclient/getItems.ts b/src/utils/jellyfin-apiclient/getItems.ts
index 4bbe711f8..37d35f840 100644
--- a/src/utils/jellyfin-apiclient/getItems.ts
+++ b/src/utils/jellyfin-apiclient/getItems.ts
@@ -67,7 +67,7 @@ function mergeResults(results: BaseItemDtoQueryResult[]) {
export function getItems(apiClient: ApiClient, userId: string, options?: GetItemsRequest) {
const ids = options?.Ids?.split(',');
if (!options || !ids || ids.length <= ITEMS_PER_REQUEST_LIMIT) {
- return apiClient.getItems(apiClient.getCurrentUserId(), options);
+ return apiClient.getItems(userId, options);
}
const results = getItemsSplit(apiClient, userId, options);