Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor home screen #4825

Merged
merged 3 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 134 additions & 134 deletions src/components/cardbuilder/cardBuilder.js

Large diffs are not rendered by default.

662 changes: 44 additions & 618 deletions src/components/homesections/homesections.js

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions src/components/homesections/sections/activeRecordings.ts
Original file line number Diff line number Diff line change
@@ -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;
}
36 changes: 36 additions & 0 deletions src/components/homesections/sections/libraryButtons.ts
Original file line number Diff line number Diff line change
@@ -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);
}
46 changes: 46 additions & 0 deletions src/components/homesections/sections/libraryTiles.ts
Original file line number Diff line number Diff line change
@@ -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);
}
181 changes: 181 additions & 0 deletions src/components/homesections/sections/liveTv.ts
Original file line number Diff line number Diff line change
@@ -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(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems rather un-reactive. I'm sure there's a better way to do this with JSX.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source files these are being split off from are legacy and unfortunately not React. Splitting the legacy pieces out this way should make it easier to divide work on migrating to React going forward, though!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! thanks for saying something

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @grhallenbeck that is exactly the idea! I just had not made it back to this to comment myself. 😅

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);
}
});
}
Loading