mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Reduce cognitive complexity in card builder component
This commit is contained in:
parent
8af76ca3e7
commit
c8a7c7040a
15 changed files with 735 additions and 391 deletions
|
@ -6,14 +6,12 @@
|
|||
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import cardBuilderUtils from './cardBuilderUtils';
|
||||
import browser from 'scripts/browser';
|
||||
import datetime from 'scripts/datetime';
|
||||
import dom from 'scripts/dom';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
|
||||
import imageHelper from 'utils/image';
|
||||
import { randomInt } from 'utils/number';
|
||||
|
||||
import focusManager from '../focusManager';
|
||||
import imageLoader from '../images/imageLoader';
|
||||
|
@ -29,6 +27,17 @@ import 'elements/emby-button/paper-icon-button-light';
|
|||
|
||||
import './card.scss';
|
||||
import '../guide/programs.scss';
|
||||
import {
|
||||
getDesiredAspect,
|
||||
getPostersPerRow,
|
||||
isResizable,
|
||||
isUsingLiveTvNaming,
|
||||
resolveAction,
|
||||
resolveCardBoxCssClasses,
|
||||
resolveCardCssClasses,
|
||||
resolveCardImageContainerCssClasses,
|
||||
resolveMixedShapeByAspectRatio
|
||||
} from './cardBuilderUtils';
|
||||
|
||||
const enableFocusTransform = !browser.slow && !browser.edge;
|
||||
|
||||
|
@ -47,24 +56,6 @@ export function getCardsHtml(items, options) {
|
|||
return buildCardsHtmlInternal(items, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the window is resizable.
|
||||
* @param {number} windowWidth - Width of the device's screen.
|
||||
* @returns {boolean} - Result of the check.
|
||||
*/
|
||||
function isResizable(windowWidth) {
|
||||
const screen = window.screen;
|
||||
if (screen) {
|
||||
const screenWidth = screen.availWidth;
|
||||
|
||||
if ((screenWidth - windowWidth) > 20) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of a card's image according to the shape and amount of cards per row.
|
||||
* @param {string} shape - Shape of the card.
|
||||
|
@ -73,7 +64,7 @@ function isResizable(windowWidth) {
|
|||
* @returns {number} Width of the image for a card.
|
||||
*/
|
||||
function getImageWidth(shape, screenWidth, isOrientationLandscape) {
|
||||
const imagesPerRow = cardBuilderUtils.getPostersPerRow(shape, screenWidth, isOrientationLandscape, layoutManager.tv);
|
||||
const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape, layoutManager.tv);
|
||||
return Math.round(screenWidth / imagesPerRow);
|
||||
}
|
||||
|
||||
|
@ -113,7 +104,7 @@ function setCardData(items, options) {
|
|||
options.preferThumb = options.shape === 'backdrop' || options.shape === 'overflowBackdrop';
|
||||
}
|
||||
|
||||
options.uiAspect = cardBuilderUtils.getDesiredAspect(options.shape);
|
||||
options.uiAspect = getDesiredAspect(options.shape);
|
||||
options.primaryImageAspectRatio = primaryImageAspectRatio;
|
||||
|
||||
if (!options.width && options.widths) {
|
||||
|
@ -280,7 +271,7 @@ function getCardImageUrl(item, apiClient, options, shape) {
|
|||
let imgUrl = null;
|
||||
let imgTag = null;
|
||||
let coverImage = false;
|
||||
const uiAspect = cardBuilderUtils.getDesiredAspect(shape);
|
||||
const uiAspect = getDesiredAspect(shape);
|
||||
let imgType = null;
|
||||
let itemId = null;
|
||||
|
||||
|
@ -411,29 +402,6 @@ function getCardImageUrl(item, apiClient, options, shape) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an index used to select the default color of a card based on a string.
|
||||
* @param {?string} [str] - String to use for generating the index.
|
||||
* @returns {number} Index of the color.
|
||||
*/
|
||||
function getDefaultColorIndex(str) {
|
||||
const numRandomColors = 5;
|
||||
|
||||
if (str) {
|
||||
const charIndex = Math.floor(str.length / 2);
|
||||
const character = String(str.slice(charIndex, charIndex + 1).charCodeAt());
|
||||
let sum = 0;
|
||||
for (let i = 0; i < character.length; i++) {
|
||||
sum += parseInt(character.charAt(i), 10);
|
||||
}
|
||||
const index = String(sum).slice(-1);
|
||||
|
||||
return (index % numRandomColors) + 1;
|
||||
} else {
|
||||
return randomInt(1, numRandomColors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the HTML markup for a card's text.
|
||||
* @param {Array} lines - Array containing the text lines.
|
||||
|
@ -487,15 +455,6 @@ function getCardTextLines(lines, cssClass, forceLines, isOuterFooter, cardLayout
|
|||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the item is live TV.
|
||||
* @param {Object} item - Item to use for the check.
|
||||
* @returns {boolean} Flag showing if the item is live TV.
|
||||
*/
|
||||
function isUsingLiveTvNaming(item) {
|
||||
return item.Type === 'Program' || item.Type === 'Timer' || item.Type === 'Recording';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the air time text for the item based on the given times.
|
||||
* @param {object} item - Item used to generate the air time text.
|
||||
|
@ -574,7 +533,7 @@ function getCardFooterText(item, apiClient, options, footerClass, progressHtml,
|
|||
} else {
|
||||
lines.push(escapeHtml(item.SeriesName));
|
||||
}
|
||||
} else if (isUsingLiveTvNaming(item)) {
|
||||
} else if (isUsingLiveTvNaming(item.Type)) {
|
||||
lines.push(escapeHtml(item.Name));
|
||||
|
||||
if (!item.EpisodeTitle && !item.IndexNumber) {
|
||||
|
@ -616,7 +575,7 @@ function getCardFooterText(item, apiClient, options, footerClass, progressHtml,
|
|||
item.AlbumArtists[0].IsFolder = true;
|
||||
lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId));
|
||||
} else {
|
||||
lines.push(escapeHtml(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || '')));
|
||||
lines.push(escapeHtml(isUsingLiveTvNaming(item.Type) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || '')));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -888,15 +847,6 @@ function importRefreshIndicator() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default background class for a card based on a string.
|
||||
* @param {?string} [str] - Text used to generate the background class.
|
||||
* @returns {string} CSS classes for default card backgrounds.
|
||||
*/
|
||||
export function getDefaultBackgroundClass(str) {
|
||||
return 'defaultCardBackground defaultCardBackground' + getDefaultColorIndex(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the HTML markup for an individual card.
|
||||
* @param {number} index - Index of the card
|
||||
|
@ -906,87 +856,32 @@ export function getDefaultBackgroundClass(str) {
|
|||
* @returns {string} HTML markup for the generated card.
|
||||
*/
|
||||
function buildCard(index, item, apiClient, options) {
|
||||
let action = options.action || 'link';
|
||||
|
||||
if (action === 'play' && item.IsFolder) {
|
||||
// If this hard-coding is ever removed make sure to test nested photo albums
|
||||
action = 'link';
|
||||
} else if (item.MediaType === 'Photo') {
|
||||
action = 'play';
|
||||
}
|
||||
const action = resolveAction({
|
||||
defaultAction: options.action || 'link',
|
||||
isFolder: item.IsFolder,
|
||||
isPhoto: item.MediaType === 'Photo'
|
||||
});
|
||||
|
||||
let shape = options.shape;
|
||||
|
||||
if (shape === 'mixed') {
|
||||
shape = null;
|
||||
|
||||
const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
|
||||
|
||||
if (primaryImageAspectRatio) {
|
||||
if (primaryImageAspectRatio >= 1.33) {
|
||||
shape = 'mixedBackdrop';
|
||||
} else if (primaryImageAspectRatio > 0.71) {
|
||||
shape = 'mixedSquare';
|
||||
} else {
|
||||
shape = 'mixedPortrait';
|
||||
}
|
||||
}
|
||||
|
||||
shape = shape || 'mixedSquare';
|
||||
shape = resolveMixedShapeByAspectRatio(item.PrimaryImageAspectRatio);
|
||||
}
|
||||
|
||||
// TODO move card creation code to Card component
|
||||
|
||||
let className = 'card';
|
||||
|
||||
if (shape) {
|
||||
className += ' ' + shape + 'Card';
|
||||
}
|
||||
|
||||
if (options.cardCssClass) {
|
||||
className += ' ' + options.cardCssClass;
|
||||
}
|
||||
|
||||
if (options.cardClass) {
|
||||
className += ' ' + options.cardClass;
|
||||
}
|
||||
|
||||
if (layoutManager.desktop) {
|
||||
className += ' card-hoverable';
|
||||
}
|
||||
|
||||
if (layoutManager.tv) {
|
||||
className += ' show-focus';
|
||||
|
||||
if (enableFocusTransform) {
|
||||
className += ' show-animation';
|
||||
}
|
||||
}
|
||||
|
||||
const imgInfo = getCardImageUrl(item, apiClient, options, shape);
|
||||
const imgUrl = imgInfo.imgUrl;
|
||||
const blurhash = imgInfo.blurhash;
|
||||
|
||||
const forceName = imgInfo.forceName;
|
||||
|
||||
const overlayText = options.overlayText;
|
||||
|
||||
let cardImageContainerClass = 'cardImageContainer';
|
||||
const coveredImage = options.coverImage || imgInfo.coverImage;
|
||||
|
||||
if (coveredImage) {
|
||||
cardImageContainerClass += ' coveredImage';
|
||||
|
||||
if (item.Type === 'TvChannel') {
|
||||
cardImageContainerClass += ' coveredImage-contain';
|
||||
}
|
||||
}
|
||||
|
||||
if (!imgUrl) {
|
||||
cardImageContainerClass += ' ' + getDefaultBackgroundClass(item.Name);
|
||||
}
|
||||
|
||||
let cardBoxClass = options.cardLayout ? 'cardBox visualCardBox' : 'cardBox';
|
||||
const cardImageContainerClasses = resolveCardImageContainerCssClasses({
|
||||
itemType: item.Type,
|
||||
itemName: item.Name,
|
||||
hasCoverImage: options.coverImage || imgInfo.coverImage,
|
||||
imgUrl
|
||||
});
|
||||
|
||||
let footerCssClass;
|
||||
let progressHtml = indicators.getProgressBarHtml(item);
|
||||
|
@ -1046,9 +941,10 @@ function buildCard(index, item, apiClient, options) {
|
|||
outerCardFooter = getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: true }, { imgUrl, logoUrl });
|
||||
}
|
||||
|
||||
if (outerCardFooter && !options.cardLayout) {
|
||||
cardBoxClass += ' cardBox-bottompadded';
|
||||
}
|
||||
const cardBoxClass = resolveCardBoxCssClasses({
|
||||
hasOuterCardFooter: outerCardFooter.length > 0,
|
||||
cardLayout: options.cardLayout
|
||||
});
|
||||
|
||||
let overlayButtons = '';
|
||||
if (layoutManager.mobile) {
|
||||
|
@ -1073,10 +969,6 @@ function buildCard(index, item, apiClient, options) {
|
|||
}
|
||||
}
|
||||
|
||||
if (options.showChildCountIndicator && item.ChildCount) {
|
||||
className += ' groupedCard';
|
||||
}
|
||||
|
||||
// cardBox can be it's own separate element if an outer footer is ever needed
|
||||
let cardImageContainerOpen;
|
||||
let cardImageContainerClose = '';
|
||||
|
@ -1092,7 +984,7 @@ function buildCard(index, item, apiClient, options) {
|
|||
|
||||
if (layoutManager.tv) {
|
||||
// Don't use the IMG tag with safari because it puts a white border around it
|
||||
cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + '">');
|
||||
cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClasses + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<div class="' + cardImageContainerClasses + ' ' + cardContentClass + '">');
|
||||
|
||||
cardImageContainerClose = '</div>';
|
||||
} else {
|
||||
|
@ -1100,7 +992,7 @@ function buildCard(index, item, apiClient, options) {
|
|||
|
||||
const url = appRouter.getRouteUrl(item);
|
||||
// Don't use the IMG tag with safari because it puts a white border around it
|
||||
cardImageContainerOpen = imgUrl ? ('<a href="' + url + '" data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + cardImageContainerAriaLabelAttribute + '>') : ('<a href="' + url + '" data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction"' + cardImageContainerAriaLabelAttribute + '>');
|
||||
cardImageContainerOpen = imgUrl ? ('<a href="' + url + '" data-action="' + action + '" class="' + cardImageContainerClasses + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + cardImageContainerAriaLabelAttribute + '>') : ('<a href="' + url + '" data-action="' + action + '" class="' + cardImageContainerClasses + ' ' + cardContentClass + ' itemAction"' + cardImageContainerAriaLabelAttribute + '>');
|
||||
|
||||
cardImageContainerClose = '</a>';
|
||||
}
|
||||
|
@ -1178,16 +1070,24 @@ function buildCard(index, item, apiClient, options) {
|
|||
let ariaLabelAttribute = '';
|
||||
|
||||
if (tagName === 'button') {
|
||||
className += ' itemAction';
|
||||
actionAttribute = ' data-action="' + action + '"';
|
||||
ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`;
|
||||
} else {
|
||||
actionAttribute = '';
|
||||
}
|
||||
|
||||
if (item.Type !== 'MusicAlbum' && item.Type !== 'MusicArtist' && item.Type !== 'Audio') {
|
||||
className += ' card-withuserdata';
|
||||
}
|
||||
const className = resolveCardCssClasses({
|
||||
shape: shape,
|
||||
cardCssClass: options.cardCssClass,
|
||||
cardClass: options.cardClass,
|
||||
isTV: layoutManager.tv,
|
||||
enableFocusTransform: enableFocusTransform,
|
||||
isDesktop: layoutManager.desktop,
|
||||
showChildCountIndicator: options.showChildCountIndicator,
|
||||
childCount: item.ChildCount,
|
||||
tagName: tagName,
|
||||
itemType: item.Type
|
||||
});
|
||||
|
||||
const positionTicksData = item.UserData?.PlaybackPositionTicks ? (' data-positionticks="' + item.UserData.PlaybackPositionTicks + '"') : '';
|
||||
const collectionIdData = options.collectionId ? (' data-collectionid="' + options.collectionId + '"') : '';
|
||||
|
@ -1296,7 +1196,7 @@ export function getDefaultText(item, options) {
|
|||
return '<span class="cardImageIcon material-icons ' + options.defaultCardImageIcon + '" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item);
|
||||
const defaultName = isUsingLiveTvNaming(item.Type) ? item.Name : itemHelper.getDisplayName(item);
|
||||
return '<div class="cardText cardDefaultText">' + escapeHtml(defaultName) + '</div>';
|
||||
}
|
||||
|
||||
|
@ -1515,7 +1415,6 @@ export function onSeriesTimerCancelled(cancelledTimerId, itemsContainer) {
|
|||
|
||||
export default {
|
||||
getCardsHtml: getCardsHtml,
|
||||
getDefaultBackgroundClass: getDefaultBackgroundClass,
|
||||
getDefaultText: getDefaultText,
|
||||
buildCards: buildCards,
|
||||
onUserDataChanged: onUserDataChanged,
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
const ASPECT_RATIOS = {
|
||||
portrait: (2 / 3),
|
||||
backdrop: (16 / 9),
|
||||
square: 1,
|
||||
banner: (1000 / 185)
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the aspect ratio for a card given its shape.
|
||||
* @param {string} shape - Shape for which to get the aspect ratio.
|
||||
* @returns {null|number} Ratio of the shape.
|
||||
*/
|
||||
function getDesiredAspect(shape) {
|
||||
if (!shape) {
|
||||
return null;
|
||||
}
|
||||
|
||||
shape = shape.toLowerCase();
|
||||
if (shape.indexOf('portrait') !== -1) {
|
||||
return ASPECT_RATIOS.portrait;
|
||||
}
|
||||
if (shape.indexOf('backdrop') !== -1) {
|
||||
return ASPECT_RATIOS.backdrop;
|
||||
}
|
||||
if (shape.indexOf('square') !== -1) {
|
||||
return ASPECT_RATIOS.square;
|
||||
}
|
||||
if (shape.indexOf('banner') !== -1) {
|
||||
return ASPECT_RATIOS.banner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the number of posters per row.
|
||||
* @param {string} shape - Shape of the cards.
|
||||
* @param {number} screenWidth - Width of the screen.
|
||||
* @param {boolean} isOrientationLandscape - Flag for the orientation of the screen.
|
||||
* @param {boolean} isTV - Flag to denote if posters are rendered on a television screen.
|
||||
* @returns {number} Number of cards per row for an itemsContainer.
|
||||
*/
|
||||
function getPostersPerRow(shape, screenWidth, isOrientationLandscape, isTV) {
|
||||
switch (shape) {
|
||||
case 'portrait': return postersPerRowPortrait(screenWidth, isTV);
|
||||
case 'square': return postersPerRowSquare(screenWidth, isTV);
|
||||
case 'banner': return postersPerRowBanner(screenWidth);
|
||||
case 'backdrop': return postersPerRowBackdrop(screenWidth, isTV);
|
||||
case 'smallBackdrop': return postersPerRowSmallBackdrop(screenWidth);
|
||||
case 'overflowSmallBackdrop': return postersPerRowOverflowSmallBackdrop(screenWidth, isOrientationLandscape, isTV);
|
||||
case 'overflowPortrait': return postersPerRowOverflowPortrait(screenWidth, isOrientationLandscape, isTV);
|
||||
case 'overflowSquare': return postersPerRowOverflowSquare(screenWidth, isOrientationLandscape, isTV);
|
||||
case 'overflowBackdrop': return postersPerRowOverflowBackdrop(screenWidth, isOrientationLandscape, isTV);
|
||||
default: return 4;
|
||||
}
|
||||
}
|
||||
|
||||
const postersPerRowPortrait = (screenWidth, isTV) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 16.66666667;
|
||||
case screenWidth >= 2200: return 10;
|
||||
case screenWidth >= 1920: return 100 / 11.1111111111;
|
||||
case screenWidth >= 1600: return 8;
|
||||
case screenWidth >= 1400: return 100 / 14.28571428571;
|
||||
case screenWidth >= 1200: return 100 / 16.66666667;
|
||||
case screenWidth >= 800: return 5;
|
||||
case screenWidth >= 700: return 4;
|
||||
case screenWidth >= 500: return 100 / 33.33333333;
|
||||
default: return 100 / 33.33333333;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowSquare = (screenWidth, isTV) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 16.66666667;
|
||||
case screenWidth >= 2200: return 10;
|
||||
case screenWidth >= 1920: return 100 / 11.1111111111;
|
||||
case screenWidth >= 1600: return 8;
|
||||
case screenWidth >= 1400: return 100 / 14.28571428571;
|
||||
case screenWidth >= 1200: return 100 / 16.66666667;
|
||||
case screenWidth >= 800: return 5;
|
||||
case screenWidth >= 700: return 4;
|
||||
case screenWidth >= 500: return 100 / 33.33333333;
|
||||
default: return 2;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowBanner = (screenWidth) => {
|
||||
switch (true) {
|
||||
case screenWidth >= 2200: return 4;
|
||||
case screenWidth >= 1200: return 100 / 33.33333333;
|
||||
case screenWidth >= 800: return 2;
|
||||
default: return 1;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowBackdrop = (screenWidth, isTV) => {
|
||||
switch (true) {
|
||||
case isTV: return 4;
|
||||
case screenWidth >= 2500: return 6;
|
||||
case screenWidth >= 1600: return 5;
|
||||
case screenWidth >= 1200: return 4;
|
||||
case screenWidth >= 770: return 3;
|
||||
case screenWidth >= 420: return 2;
|
||||
default: return 1;
|
||||
}
|
||||
};
|
||||
|
||||
function postersPerRowSmallBackdrop(screenWidth) {
|
||||
switch (true) {
|
||||
case screenWidth >= 1600: return 8;
|
||||
case screenWidth >= 1400: return 100 / 14.2857142857;
|
||||
case screenWidth >= 1200: return 100 / 16.66666667;
|
||||
case screenWidth >= 1000: return 5;
|
||||
case screenWidth >= 800: return 4;
|
||||
case screenWidth >= 500: return 100 / 33.33333333;
|
||||
default: return 2;
|
||||
}
|
||||
}
|
||||
|
||||
const postersPerRowOverflowSmallBackdrop = (screenWidth, isLandscape, isTV) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 18.9;
|
||||
case isLandscape && screenWidth >= 800: return 100 / 15.5;
|
||||
case isLandscape: return 100 / 23.3;
|
||||
case screenWidth >= 540: return 100 / 30;
|
||||
default: return 100 / 72;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowOverflowPortrait = (screenWidth, isLandscape, isTV) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 15.5;
|
||||
case isLandscape && screenWidth >= 1700: return 100 / 11.6;
|
||||
case isLandscape: return 100 / 15.5;
|
||||
case screenWidth >= 1400: return 100 / 15;
|
||||
case screenWidth >= 1200: return 100 / 18;
|
||||
case screenWidth >= 760: return 100 / 23;
|
||||
case screenWidth >= 400: return 100 / 31.5;
|
||||
default: return 100 / 42;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowOverflowSquare = (screenWidth, isLandscape, isTV) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 15.5;
|
||||
case isLandscape && screenWidth >= 1700: return 100 / 11.6;
|
||||
case isLandscape: return 100 / 15.5;
|
||||
case screenWidth >= 1400: return 100 / 15;
|
||||
case screenWidth >= 1200: return 100 / 18;
|
||||
case screenWidth >= 760: return 100 / 23;
|
||||
case screenWidth >= 540: return 100 / 31.5;
|
||||
default: return 100 / 42;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowOverflowBackdrop = (screenWidth, isLandscape, isTV) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 23.3;
|
||||
case isLandscape && screenWidth >= 1700: return 100 / 18.5;
|
||||
case isLandscape: return 100 / 23.3;
|
||||
case screenWidth >= 1800: return 100 / 23.5;
|
||||
case screenWidth >= 1400: return 100 / 30;
|
||||
case screenWidth >= 760: return 100 / 40;
|
||||
case screenWidth >= 640: return 100 / 56;
|
||||
default: return 100 / 72;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
getDesiredAspect,
|
||||
getPostersPerRow
|
||||
};
|
|
@ -1,47 +1,52 @@
|
|||
import { describe, expect, test } from 'vitest';
|
||||
import cardBuilderUtils from './cardBuilderUtils';
|
||||
import {
|
||||
getDefaultBackgroundClass,
|
||||
getDefaultColorIndex,
|
||||
getDesiredAspect,
|
||||
getPostersPerRow,
|
||||
isResizable,
|
||||
isUsingLiveTvNaming,
|
||||
resolveAction, resolveCardBoxCssClasses,
|
||||
resolveCardCssClasses,
|
||||
resolveCardImageContainerCssClasses,
|
||||
resolveMixedShapeByAspectRatio
|
||||
} from './cardBuilderUtils';
|
||||
|
||||
describe('getDesiredAspect', () => {
|
||||
test('"portrait" (case insensitive)', () => {
|
||||
expect(cardBuilderUtils.getDesiredAspect('portrait')).toEqual((2 / 3));
|
||||
expect(cardBuilderUtils.getDesiredAspect('PorTRaIt')).toEqual((2 / 3));
|
||||
expect(getDesiredAspect('portrait')).toEqual((2 / 3));
|
||||
expect(getDesiredAspect('PorTRaIt')).toEqual((2 / 3));
|
||||
});
|
||||
|
||||
test('"backdrop" (case insensitive)', () => {
|
||||
expect(cardBuilderUtils.getDesiredAspect('backdrop')).toEqual((16 / 9));
|
||||
expect(cardBuilderUtils.getDesiredAspect('BaCkDroP')).toEqual((16 / 9));
|
||||
expect(getDesiredAspect('backdrop')).toEqual((16 / 9));
|
||||
expect(getDesiredAspect('BaCkDroP')).toEqual((16 / 9));
|
||||
});
|
||||
|
||||
test('"square" (case insensitive)', () => {
|
||||
expect(cardBuilderUtils.getDesiredAspect('square')).toEqual(1);
|
||||
expect(cardBuilderUtils.getDesiredAspect('sQuArE')).toEqual(1);
|
||||
expect(getDesiredAspect('square')).toEqual(1);
|
||||
expect(getDesiredAspect('sQuArE')).toEqual(1);
|
||||
});
|
||||
|
||||
test('"banner" (case insensitive)', () => {
|
||||
expect(cardBuilderUtils.getDesiredAspect('banner')).toEqual((1000 / 185));
|
||||
expect(cardBuilderUtils.getDesiredAspect('BaNnEr')).toEqual((1000 / 185));
|
||||
expect(getDesiredAspect('banner')).toEqual((1000 / 185));
|
||||
expect(getDesiredAspect('BaNnEr')).toEqual((1000 / 185));
|
||||
});
|
||||
|
||||
test('invalid shape', () => {
|
||||
expect(cardBuilderUtils.getDesiredAspect('invalid')).toBeNull();
|
||||
});
|
||||
test('invalid shape', () => expect(getDesiredAspect('invalid')).toBeNull());
|
||||
|
||||
test('shape is not provided', () => {
|
||||
expect(cardBuilderUtils.getDesiredAspect('')).toBeNull();
|
||||
});
|
||||
test('shape is not provided', () => expect(getDesiredAspect('')).toBeNull());
|
||||
});
|
||||
|
||||
describe('getPostersPerRow', () => {
|
||||
test('resolves to default of 4 posters per row if shape is not provided', () => {
|
||||
expect(cardBuilderUtils.getPostersPerRow('', 0, false, false)).toEqual(4);
|
||||
expect(getPostersPerRow('', 0, false, false)).toEqual(4);
|
||||
});
|
||||
|
||||
describe('portrait', () => {
|
||||
const postersPerRowForPortrait = (screenWidth, isTV) => (cardBuilderUtils.getPostersPerRow('portrait', screenWidth, false, isTV));
|
||||
const postersPerRowForPortrait = (screenWidth: number, isTV: boolean) => (getPostersPerRow('portrait', screenWidth, false, isTV));
|
||||
|
||||
test('television', () => {
|
||||
expect(postersPerRowForPortrait(0, true)).toEqual(100 / 16.66666667);
|
||||
});
|
||||
test('television', () => expect(postersPerRowForPortrait(0, true)).toEqual(100 / 16.66666667));
|
||||
|
||||
test('screen width less than 500px', () => {
|
||||
expect(postersPerRowForPortrait(100, false)).toEqual(100 / 33.33333333);
|
||||
|
@ -90,11 +95,9 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('square', () => {
|
||||
const postersPerRowForSquare = (screenWidth, isTV) => (cardBuilderUtils.getPostersPerRow('square', screenWidth, false, isTV));
|
||||
const postersPerRowForSquare = (screenWidth: number, isTV: boolean) => (getPostersPerRow('square', screenWidth, false, isTV));
|
||||
|
||||
test('television', () => {
|
||||
expect(postersPerRowForSquare(0, true)).toEqual(100 / 16.66666667);
|
||||
});
|
||||
test('television', () => expect(postersPerRowForSquare(0, true)).toEqual(100 / 16.66666667));
|
||||
|
||||
test('screen width less than 500px', () => {
|
||||
expect(postersPerRowForSquare(100, false)).toEqual(2);
|
||||
|
@ -143,11 +146,9 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('banner', () => {
|
||||
const postersPerRowForBanner = (screenWidth) => (cardBuilderUtils.getPostersPerRow('banner', screenWidth, false, false));
|
||||
const postersPerRowForBanner = (screenWidth: number) => (getPostersPerRow('banner', screenWidth, false, false));
|
||||
|
||||
test('screen width less than 800px', () => {
|
||||
expect(postersPerRowForBanner(799)).toEqual(1);
|
||||
});
|
||||
test('screen width less than 800px', () => expect(postersPerRowForBanner(799)).toEqual(1));
|
||||
|
||||
test('screen width greater than or equal to 800px', () => {
|
||||
expect(postersPerRowForBanner(800)).toEqual(2);
|
||||
|
@ -166,11 +167,9 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('backdrop', () => {
|
||||
const postersPerRowForBackdrop = (screenWidth, isTV) => (cardBuilderUtils.getPostersPerRow('backdrop', screenWidth, false, isTV));
|
||||
const postersPerRowForBackdrop = (screenWidth: number, isTV: boolean) => (getPostersPerRow('backdrop', screenWidth, false, isTV));
|
||||
|
||||
test('television', () => {
|
||||
expect(postersPerRowForBackdrop(0, true)).toEqual(4);
|
||||
});
|
||||
test('television', () => expect(postersPerRowForBackdrop(0, true)).toEqual(4));
|
||||
|
||||
test('screen width less than 420px', () => {
|
||||
expect(postersPerRowForBackdrop(100, false)).toEqual(1);
|
||||
|
@ -204,7 +203,7 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('small backdrop', () => {
|
||||
const postersPerRowForSmallBackdrop = (screenWidth) => (cardBuilderUtils.getPostersPerRow('smallBackdrop', screenWidth, false, false));
|
||||
const postersPerRowForSmallBackdrop = (screenWidth: number) => (getPostersPerRow('smallBackdrop', screenWidth, false, false));
|
||||
|
||||
test('screen width less than 500px', () => {
|
||||
expect(postersPerRowForSmallBackdrop(100)).toEqual(2);
|
||||
|
@ -243,11 +242,9 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('overflow small backdrop', () => {
|
||||
const postersPerRowForOverflowSmallBackdrop = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowSmallBackdrop', screenWidth, isLandscape, isTV));
|
||||
const postersPerRowForOverflowSmallBackdrop = (screenWidth: number, isLandscape = false, isTV = false) => (getPostersPerRow('overflowSmallBackdrop', screenWidth, isLandscape, isTV));
|
||||
|
||||
test('television', () => {
|
||||
expect(postersPerRowForOverflowSmallBackdrop(0, false, true)).toEqual( 100 / 18.9);
|
||||
});
|
||||
test('television', () => expect(postersPerRowForOverflowSmallBackdrop(0, false, true)).toEqual(100 / 18.9));
|
||||
|
||||
describe('non-landscape', () => {
|
||||
test('screen width greater or equal to 540px', () => {
|
||||
|
@ -275,11 +272,9 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('overflow portrait', () => {
|
||||
const postersPerRowForOverflowPortrait = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowPortrait', screenWidth, isLandscape, isTV));
|
||||
const postersPerRowForOverflowPortrait = (screenWidth: number, isLandscape = false, isTV = false) => (getPostersPerRow('overflowPortrait', screenWidth, isLandscape, isTV));
|
||||
|
||||
test('television', () => {
|
||||
expect(postersPerRowForOverflowPortrait(0, false, true)).toEqual( 100 / 15.5);
|
||||
});
|
||||
test('television', () => expect(postersPerRowForOverflowPortrait(0, false, true)).toEqual(100 / 15.5));
|
||||
|
||||
describe('non-landscape', () => {
|
||||
test('screen width greater or equal to 1400px', () => {
|
||||
|
@ -322,11 +317,9 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('overflow square', () => {
|
||||
const postersPerRowForOverflowSquare = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowSquare', screenWidth, isLandscape, isTV));
|
||||
const postersPerRowForOverflowSquare = (screenWidth: number, isLandscape = false, isTV = false) => (getPostersPerRow('overflowSquare', screenWidth, isLandscape, isTV));
|
||||
|
||||
test('television', () => {
|
||||
expect(postersPerRowForOverflowSquare(0, false, true)).toEqual( 100 / 15.5);
|
||||
});
|
||||
test('television', () => expect(postersPerRowForOverflowSquare(0, false, true)).toEqual(100 / 15.5));
|
||||
|
||||
describe('non-landscape', () => {
|
||||
test('screen width greater or equal to 1400px', () => {
|
||||
|
@ -369,11 +362,9 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
|
||||
describe('overflow backdrop', () => {
|
||||
const postersPerRowForOverflowBackdrop = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowBackdrop', screenWidth, isLandscape, isTV));
|
||||
const postersPerRowForOverflowBackdrop = (screenWidth: number, isLandscape = false, isTV = false) => (getPostersPerRow('overflowBackdrop', screenWidth, isLandscape, isTV));
|
||||
|
||||
test('television', () => {
|
||||
expect(postersPerRowForOverflowBackdrop(0, false, true)).toEqual( 100 / 23.3);
|
||||
});
|
||||
test('television', () => expect(postersPerRowForOverflowBackdrop(0, false, true)).toEqual(100 / 23.3));
|
||||
|
||||
describe('non-landscape', () => {
|
||||
test('screen width greater or equal to 1800px', () => {
|
||||
|
@ -415,3 +406,312 @@ describe('getPostersPerRow', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('isUsingLiveTvNaming', () => {
|
||||
expect(isUsingLiveTvNaming('Program')).toEqual(true);
|
||||
expect(isUsingLiveTvNaming('Timer')).toEqual(true);
|
||||
expect(isUsingLiveTvNaming('Recording')).toEqual(true);
|
||||
});
|
||||
|
||||
describe('isResizable', () => {
|
||||
test('is resizable if difference between screen width and window width is greater than 20px', () => {
|
||||
Object.defineProperty(window, 'screen', {
|
||||
value: {
|
||||
availWidth: 2048
|
||||
}
|
||||
});
|
||||
expect(isResizable(1024)).toEqual(true);
|
||||
});
|
||||
|
||||
test('is not resizable if difference between screen width and window width is less than or equal to 20px', () => {
|
||||
Object.defineProperty(window, 'screen', {
|
||||
value: {
|
||||
availWidth: 1044
|
||||
}
|
||||
});
|
||||
expect(isResizable(1024)).toEqual(false);
|
||||
});
|
||||
|
||||
test('is not resizable if screen width is not provided', () => {
|
||||
Object.defineProperty(window, 'screen', {
|
||||
value: undefined
|
||||
});
|
||||
expect(isResizable(1024)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveAction', () => {
|
||||
test('default action', () => expect(resolveAction({ defaultAction: 'link', isFolder: false, isPhoto: false })).toEqual('link'));
|
||||
|
||||
test('photo', () => expect(resolveAction({ defaultAction: 'link', isFolder: false, isPhoto: true })).toEqual('play'));
|
||||
|
||||
test('default action is "play" and is folder', () => expect(resolveAction({ defaultAction: 'play', isFolder: true, isPhoto: true })).toEqual('link'));
|
||||
});
|
||||
|
||||
describe('resolveMixedShapeByAspectRatio', () => {
|
||||
test('primary aspect ratio is >= 1.33', () => {
|
||||
expect(resolveMixedShapeByAspectRatio(1.33)).toEqual('mixedBackdrop');
|
||||
expect(resolveMixedShapeByAspectRatio(1.34)).toEqual('mixedBackdrop');
|
||||
});
|
||||
|
||||
test('primary aspect ratio is > 0.71', () => {
|
||||
expect(resolveMixedShapeByAspectRatio(0.72)).toEqual('mixedSquare');
|
||||
expect(resolveMixedShapeByAspectRatio(0.73)).toEqual('mixedSquare');
|
||||
expect(resolveMixedShapeByAspectRatio(1.32)).toEqual('mixedSquare');
|
||||
});
|
||||
|
||||
test('primary aspect ratio is <= 0.71', () => {
|
||||
expect(resolveMixedShapeByAspectRatio(0.71)).toEqual('mixedPortrait');
|
||||
expect(resolveMixedShapeByAspectRatio(0.70)).toEqual('mixedPortrait');
|
||||
expect(resolveMixedShapeByAspectRatio(0.01)).toEqual('mixedPortrait');
|
||||
});
|
||||
|
||||
test('primary aspect ratio is not provided', () => {
|
||||
expect(resolveMixedShapeByAspectRatio(undefined)).toEqual('mixedSquare');
|
||||
expect(resolveMixedShapeByAspectRatio(null)).toEqual('mixedSquare');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveCardCssClasses', () => {
|
||||
test('card CSS classes', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
cardCssClass: 'custom-class',
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card custom-class card-withuserdata');
|
||||
});
|
||||
|
||||
test('card classes', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
cardClass: 'custom-card',
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card custom-card card-withuserdata');
|
||||
});
|
||||
|
||||
test('shape', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
shape: 'portrait',
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card portraitCard card-withuserdata');
|
||||
});
|
||||
|
||||
test('desktop', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: true
|
||||
})
|
||||
).toEqual('card card-hoverable card-withuserdata');
|
||||
});
|
||||
|
||||
test('tv', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: true,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card show-focus card-withuserdata');
|
||||
});
|
||||
|
||||
test('tv with focus transform', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: true,
|
||||
enableFocusTransform: true,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card show-focus show-animation card-withuserdata');
|
||||
});
|
||||
|
||||
test('non-music item type', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card card-withuserdata');
|
||||
});
|
||||
|
||||
test('music item type', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'MusicAlbum',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card');
|
||||
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'MusicArtist',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card');
|
||||
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'Audio',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card');
|
||||
});
|
||||
|
||||
test('child count indicator', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: true,
|
||||
childCount: 5,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card groupedCard card-withuserdata');
|
||||
});
|
||||
|
||||
test('button tag name', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
tagName: 'button',
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: false,
|
||||
isTV: false,
|
||||
enableFocusTransform: false,
|
||||
isDesktop: false
|
||||
})
|
||||
).toEqual('card card-withuserdata itemAction');
|
||||
});
|
||||
|
||||
test('all', () => {
|
||||
expect(resolveCardCssClasses({
|
||||
shape: 'portrait',
|
||||
cardCssClass: 'card-css',
|
||||
cardClass: 'card',
|
||||
itemType: 'non-music',
|
||||
showChildCountIndicator: true,
|
||||
childCount: 5,
|
||||
tagName: 'button',
|
||||
isTV: true,
|
||||
enableFocusTransform: true,
|
||||
isDesktop: true
|
||||
})
|
||||
).toEqual('card portraitCard card-css card-hoverable show-focus show-animation groupedCard card-withuserdata itemAction');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveCardImageContainerCssClasses', () => {
|
||||
test('with image URL, no cover image', () => {
|
||||
expect(resolveCardImageContainerCssClasses({
|
||||
itemType: '',
|
||||
itemName: 'Movie Name',
|
||||
imgUrl: 'https://jellyfin.org/some-image',
|
||||
hasCoverImage: false
|
||||
})).toEqual('cardImageContainer');
|
||||
});
|
||||
|
||||
test('no cover image, no image URL', () => {
|
||||
expect(resolveCardImageContainerCssClasses({
|
||||
itemType: '',
|
||||
itemName: 'Movie Name',
|
||||
hasCoverImage: false
|
||||
})).toEqual('cardImageContainer defaultCardBackground defaultCardBackground1');
|
||||
});
|
||||
|
||||
test('with cover image, no image URL', () => {
|
||||
expect(resolveCardImageContainerCssClasses({
|
||||
itemType: '',
|
||||
itemName: 'Movie Name',
|
||||
hasCoverImage: true
|
||||
})).toEqual('cardImageContainer coveredImage defaultCardBackground defaultCardBackground1');
|
||||
});
|
||||
|
||||
test('with cover image, item type is TV channel, no image URL', () => {
|
||||
expect(resolveCardImageContainerCssClasses({
|
||||
itemType: 'TvChannel',
|
||||
itemName: 'Movie Name',
|
||||
hasCoverImage: true
|
||||
})).toEqual('cardImageContainer coveredImage coveredImage-contain defaultCardBackground defaultCardBackground1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveCardBoxCssClasses', () => {
|
||||
test('non-card layout', () => expect(resolveCardBoxCssClasses({ cardLayout: false, hasOuterCardFooter: false })).toEqual('cardBox'));
|
||||
|
||||
test('card layout', () => expect(resolveCardBoxCssClasses({ cardLayout: true, hasOuterCardFooter: false })).toEqual('cardBox visualCardBox'));
|
||||
|
||||
test('has outer card footer', () => expect(resolveCardBoxCssClasses({ cardLayout: false, hasOuterCardFooter: true })).toEqual('cardBox cardBox-bottompadded'));
|
||||
});
|
||||
|
||||
describe('getDefaultBackgroundClass', () => {
|
||||
test('no randomization string provided', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const bgClass = getDefaultBackgroundClass();
|
||||
const colorIndex = parseInt(bgClass.slice(bgClass.length - 1), 10);
|
||||
expect(colorIndex).toBeGreaterThanOrEqual(1);
|
||||
expect(colorIndex).toBeLessThanOrEqual(5);
|
||||
expect(bgClass).toEqual(`defaultCardBackground defaultCardBackground${colorIndex}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('randomization string provided', () => {
|
||||
const generateRandomString = (stringLength: number): string => (Math.random() + 1).toString(36).substring(stringLength);
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const randomString = generateRandomString(6);
|
||||
const bgClass = getDefaultBackgroundClass(randomString);
|
||||
const colorIndex = getDefaultColorIndex(randomString);
|
||||
expect(bgClass).toEqual(`defaultCardBackground defaultCardBackground${colorIndex}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultColorIndex', () => {
|
||||
test('no randomization string provided', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const colorIndex = getDefaultColorIndex();
|
||||
expect(colorIndex).toBeGreaterThanOrEqual(1);
|
||||
expect(colorIndex).toBeLessThanOrEqual(5);
|
||||
}
|
||||
});
|
||||
|
||||
test('randomization string provided', () => {
|
||||
expect(getDefaultColorIndex('Movie name')).toEqual(1);
|
||||
expect(getDefaultColorIndex('Mo')).toEqual(4);
|
||||
expect(getDefaultColorIndex('Mov')).toEqual(4);
|
||||
expect(getDefaultColorIndex('Movi')).toEqual(1);
|
||||
expect(getDefaultColorIndex('Movie')).toEqual(1);
|
||||
expect(getDefaultColorIndex('Movie ')).toEqual(2);
|
||||
expect(getDefaultColorIndex('Movie n')).toEqual(2);
|
||||
expect(getDefaultColorIndex('Movie na')).toEqual(3);
|
||||
expect(getDefaultColorIndex('Movie nam')).toEqual(3);
|
||||
expect(getDefaultColorIndex('Movie name')).toEqual(1);
|
||||
expect(getDefaultColorIndex('TV show')).toEqual(3);
|
||||
expect(getDefaultColorIndex('Music album')).toEqual(1);
|
||||
expect(getDefaultColorIndex('Song')).toEqual(3);
|
||||
expect(getDefaultColorIndex('Musical artist')).toEqual(1);
|
||||
});
|
||||
});
|
316
src/components/cardbuilder/cardBuilderUtils.ts
Normal file
316
src/components/cardbuilder/cardBuilderUtils.ts
Normal file
|
@ -0,0 +1,316 @@
|
|||
import { randomInt } from '../../utils/number';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const ASPECT_RATIOS = {
|
||||
portrait: (2 / 3),
|
||||
backdrop: (16 / 9),
|
||||
square: 1,
|
||||
banner: (1000 / 185)
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the item is live TV.
|
||||
* @param {string} itemType - Item type to use for the check.
|
||||
* @returns {boolean} Flag showing if the item is live TV.
|
||||
*/
|
||||
export const isUsingLiveTvNaming = (itemType: string): boolean => itemType === 'Program' || itemType === 'Timer' || itemType === 'Recording';
|
||||
|
||||
/**
|
||||
* Resolves Card action to display
|
||||
* @param opts options to determine the action to return
|
||||
*/
|
||||
export const resolveAction = (opts: { defaultAction: string, isFolder: boolean, isPhoto: boolean }): string => {
|
||||
if (opts.defaultAction === 'play' && opts.isFolder) {
|
||||
// If this hard-coding is ever removed make sure to test nested photo albums
|
||||
return 'link';
|
||||
} else if (opts.isPhoto) {
|
||||
return 'play';
|
||||
} else {
|
||||
return opts.defaultAction;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the window is resizable.
|
||||
* @param {number} windowWidth - Width of the device's screen.
|
||||
* @returns {boolean} - Result of the check.
|
||||
*/
|
||||
export const isResizable = (windowWidth: number): boolean => {
|
||||
const screen = window.screen;
|
||||
if (screen) {
|
||||
const screenWidth = screen.availWidth;
|
||||
|
||||
if ((screenWidth - windowWidth) > 20) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves mixed shape based on aspect ratio
|
||||
* @param primaryImageAspectRatio image aspect ratio that determines mixed shape
|
||||
*/
|
||||
export const resolveMixedShapeByAspectRatio = (primaryImageAspectRatio: number | null | undefined) => {
|
||||
if (primaryImageAspectRatio === undefined || primaryImageAspectRatio === null) {
|
||||
return 'mixedSquare';
|
||||
}
|
||||
|
||||
if (primaryImageAspectRatio >= 1.33) {
|
||||
return 'mixedBackdrop';
|
||||
} else if (primaryImageAspectRatio > 0.71) {
|
||||
return 'mixedSquare';
|
||||
} else {
|
||||
return 'mixedPortrait';
|
||||
}
|
||||
};
|
||||
|
||||
type CardCssClassOpts = {
|
||||
shape?: string,
|
||||
cardCssClass?: string,
|
||||
cardClass?: string,
|
||||
tagName?: string,
|
||||
itemType: string,
|
||||
childCount?: number,
|
||||
showChildCountIndicator: boolean,
|
||||
isTV: boolean,
|
||||
enableFocusTransform: boolean,
|
||||
isDesktop: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves applicable Card CSS classes
|
||||
* @param opts options for determining which CSS classes are applicable
|
||||
*/
|
||||
export const resolveCardCssClasses = (opts: CardCssClassOpts): string => {
|
||||
return classNames({
|
||||
'card': true,
|
||||
[`${opts.shape}Card`]: opts.shape,
|
||||
[`${opts.cardCssClass}`]: opts.cardCssClass,
|
||||
[`${opts.cardClass}`]: opts.cardClass,
|
||||
'card-hoverable': opts.isDesktop,
|
||||
'show-focus': opts.isTV,
|
||||
'show-animation': opts.isTV && opts.enableFocusTransform,
|
||||
'groupedCard': opts.showChildCountIndicator && opts.childCount,
|
||||
'card-withuserdata': !['MusicAlbum', 'MusicArtist', 'Audio'].includes(opts.itemType),
|
||||
'itemAction': opts.tagName === 'button'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves applicable Card Image container CSS classes
|
||||
* @param opts options for determining which CSS classes are applicable
|
||||
*/
|
||||
export const resolveCardImageContainerCssClasses = (opts: { itemType: string, hasCoverImage: boolean, itemName?: string, imgUrl?: string}): string => {
|
||||
return classNames({
|
||||
'cardImageContainer': true,
|
||||
'coveredImage': opts.hasCoverImage,
|
||||
'coveredImage-contain': opts.hasCoverImage && opts.itemType === 'TvChannel',
|
||||
[getDefaultBackgroundClass(opts.itemName)]: !opts.imgUrl
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves applicable Card Box CSS classes
|
||||
* @param opts options for determining which CSS classes are applicable
|
||||
*/
|
||||
export const resolveCardBoxCssClasses = (opts: { cardLayout: boolean, hasOuterCardFooter: boolean }): string => {
|
||||
return classNames({
|
||||
'cardBox': true,
|
||||
'visualCardBox': opts.cardLayout,
|
||||
'cardBox-bottompadded': opts.hasOuterCardFooter && !opts.cardLayout
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the default background class for a card based on a string.
|
||||
* @param {?string} [str] - Text used to generate the background class.
|
||||
* @returns {string} CSS classes for default card backgrounds.
|
||||
*/
|
||||
export const getDefaultBackgroundClass = (str?: string | null): string => `defaultCardBackground defaultCardBackground${getDefaultColorIndex(str)}`;
|
||||
|
||||
/**
|
||||
* Generates an index used to select the default color of a card based on a string.
|
||||
* @param {?string} [str] - String to use for generating the index.
|
||||
* @returns {number} Index of the color.
|
||||
*/
|
||||
export const getDefaultColorIndex = (str?: string | null): number => {
|
||||
const numRandomColors = 5;
|
||||
|
||||
if (str) {
|
||||
const charIndex = Math.floor(str.length / 2);
|
||||
const character = String(str.slice(charIndex, charIndex + 1).charCodeAt(0));
|
||||
let sum = 0;
|
||||
for (let i = 0; i < character.length; i++) {
|
||||
sum += parseInt(character.charAt(i), 10);
|
||||
}
|
||||
const index = parseInt(String(sum).slice(-1), 10);
|
||||
|
||||
return (index % numRandomColors) + 1;
|
||||
} else {
|
||||
return randomInt(1, numRandomColors);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the aspect ratio for a card given its shape.
|
||||
* @param {string} shape - Shape for which to get the aspect ratio.
|
||||
* @returns {null|number} Ratio of the shape.
|
||||
*/
|
||||
export const getDesiredAspect = (shape: string | null | undefined): null | number => {
|
||||
if (!shape) {
|
||||
return null;
|
||||
}
|
||||
|
||||
shape = shape.toLowerCase();
|
||||
if (shape.indexOf('portrait') !== -1) {
|
||||
return ASPECT_RATIOS.portrait;
|
||||
}
|
||||
if (shape.indexOf('backdrop') !== -1) {
|
||||
return ASPECT_RATIOS.backdrop;
|
||||
}
|
||||
if (shape.indexOf('square') !== -1) {
|
||||
return ASPECT_RATIOS.square;
|
||||
}
|
||||
if (shape.indexOf('banner') !== -1) {
|
||||
return ASPECT_RATIOS.banner;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the number of posters per row.
|
||||
* @param {string} shape - Shape of the cards.
|
||||
* @param {number} screenWidth - Width of the screen.
|
||||
* @param {boolean} isOrientationLandscape - Flag for the orientation of the screen.
|
||||
* @param {boolean} isTV - Flag to denote if posters are rendered on a television screen.
|
||||
* @returns {number} Number of cards per row for an itemsContainer.
|
||||
*/
|
||||
export const getPostersPerRow = (shape: string, screenWidth: number, isOrientationLandscape: boolean, isTV: boolean): number => {
|
||||
switch (shape) {
|
||||
case 'portrait': return postersPerRowPortrait(screenWidth, isTV);
|
||||
case 'square': return postersPerRowSquare(screenWidth, isTV);
|
||||
case 'banner': return postersPerRowBanner(screenWidth);
|
||||
case 'backdrop': return postersPerRowBackdrop(screenWidth, isTV);
|
||||
case 'smallBackdrop': return postersPerRowSmallBackdrop(screenWidth);
|
||||
case 'overflowSmallBackdrop': return postersPerRowOverflowSmallBackdrop(screenWidth, isOrientationLandscape, isTV);
|
||||
case 'overflowPortrait': return postersPerRowOverflowPortrait(screenWidth, isOrientationLandscape, isTV);
|
||||
case 'overflowSquare': return postersPerRowOverflowSquare(screenWidth, isOrientationLandscape, isTV);
|
||||
case 'overflowBackdrop': return postersPerRowOverflowBackdrop(screenWidth, isOrientationLandscape, isTV);
|
||||
default: return 4;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowPortrait = (screenWidth: number, isTV: boolean) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 16.66666667;
|
||||
case screenWidth >= 2200: return 10;
|
||||
case screenWidth >= 1920: return 100 / 11.1111111111;
|
||||
case screenWidth >= 1600: return 8;
|
||||
case screenWidth >= 1400: return 100 / 14.28571428571;
|
||||
case screenWidth >= 1200: return 100 / 16.66666667;
|
||||
case screenWidth >= 800: return 5;
|
||||
case screenWidth >= 700: return 4;
|
||||
case screenWidth >= 500: return 100 / 33.33333333;
|
||||
default: return 100 / 33.33333333;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowSquare = (screenWidth: number, isTV: boolean) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 16.66666667;
|
||||
case screenWidth >= 2200: return 10;
|
||||
case screenWidth >= 1920: return 100 / 11.1111111111;
|
||||
case screenWidth >= 1600: return 8;
|
||||
case screenWidth >= 1400: return 100 / 14.28571428571;
|
||||
case screenWidth >= 1200: return 100 / 16.66666667;
|
||||
case screenWidth >= 800: return 5;
|
||||
case screenWidth >= 700: return 4;
|
||||
case screenWidth >= 500: return 100 / 33.33333333;
|
||||
default: return 2;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowBanner = (screenWidth: number) => {
|
||||
switch (true) {
|
||||
case screenWidth >= 2200: return 4;
|
||||
case screenWidth >= 1200: return 100 / 33.33333333;
|
||||
case screenWidth >= 800: return 2;
|
||||
default: return 1;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowBackdrop = (screenWidth: number, isTV: boolean) => {
|
||||
switch (true) {
|
||||
case isTV: return 4;
|
||||
case screenWidth >= 2500: return 6;
|
||||
case screenWidth >= 1600: return 5;
|
||||
case screenWidth >= 1200: return 4;
|
||||
case screenWidth >= 770: return 3;
|
||||
case screenWidth >= 420: return 2;
|
||||
default: return 1;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowSmallBackdrop = (screenWidth: number) => {
|
||||
switch (true) {
|
||||
case screenWidth >= 1600: return 8;
|
||||
case screenWidth >= 1400: return 100 / 14.2857142857;
|
||||
case screenWidth >= 1200: return 100 / 16.66666667;
|
||||
case screenWidth >= 1000: return 5;
|
||||
case screenWidth >= 800: return 4;
|
||||
case screenWidth >= 500: return 100 / 33.33333333;
|
||||
default: return 2;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowOverflowSmallBackdrop = (screenWidth: number, isLandscape: boolean, isTV: boolean) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 18.9;
|
||||
case isLandscape && screenWidth >= 800: return 100 / 15.5;
|
||||
case isLandscape: return 100 / 23.3;
|
||||
case screenWidth >= 540: return 100 / 30;
|
||||
default: return 100 / 72;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowOverflowPortrait = (screenWidth: number, isLandscape: boolean, isTV: boolean) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 15.5;
|
||||
case isLandscape && screenWidth >= 1700: return 100 / 11.6;
|
||||
case isLandscape: return 100 / 15.5;
|
||||
case screenWidth >= 1400: return 100 / 15;
|
||||
case screenWidth >= 1200: return 100 / 18;
|
||||
case screenWidth >= 760: return 100 / 23;
|
||||
case screenWidth >= 400: return 100 / 31.5;
|
||||
default: return 100 / 42;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowOverflowSquare = (screenWidth: number, isLandscape: boolean, isTV: boolean) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 15.5;
|
||||
case isLandscape && screenWidth >= 1700: return 100 / 11.6;
|
||||
case isLandscape: return 100 / 15.5;
|
||||
case screenWidth >= 1400: return 100 / 15;
|
||||
case screenWidth >= 1200: return 100 / 18;
|
||||
case screenWidth >= 760: return 100 / 23;
|
||||
case screenWidth >= 540: return 100 / 31.5;
|
||||
default: return 100 / 42;
|
||||
}
|
||||
};
|
||||
|
||||
const postersPerRowOverflowBackdrop = (screenWidth: number, isLandscape: boolean, isTV: boolean) => {
|
||||
switch (true) {
|
||||
case isTV: return 100 / 23.3;
|
||||
case isLandscape && screenWidth >= 1700: return 100 / 18.5;
|
||||
case isLandscape: return 100 / 23.3;
|
||||
case screenWidth >= 1800: return 100 / 23.5;
|
||||
case screenWidth >= 1400: return 100 / 30;
|
||||
case screenWidth >= 760: return 100 / 40;
|
||||
case screenWidth >= 640: return 100 / 56;
|
||||
default: return 100 / 72;
|
||||
}
|
||||
};
|
|
@ -3,9 +3,9 @@ import React, { FunctionComponent } from 'react';
|
|||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { getLocaleWithSuffix } from '../../../utils/dateFnsLocale';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import cardBuilder from '../../cardbuilder/cardBuilder';
|
||||
import IconButtonElement from '../../../elements/IconButtonElement';
|
||||
import escapeHTML from 'escape-html';
|
||||
import { getDefaultBackgroundClass } from '../../cardbuilder/cardBuilderUtils';
|
||||
|
||||
const createLinkElement = ({ user, renderImgUrl }: { user: UserDto, renderImgUrl: string }) => ({
|
||||
__html: `<a
|
||||
|
@ -56,7 +56,7 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
|
|||
|
||||
const renderImgUrl = imgUrl ?
|
||||
`<div class='${imageClass}' style='background-image:url(${imgUrl})'></div>` :
|
||||
`<div class='${imageClass} ${cardBuilder.getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center'>
|
||||
`<div class='${imageClass} ${getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center'>
|
||||
<span class='material-icons cardImageIcon person' aria-hidden='true'></span>
|
||||
</div>`;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import './listview.scss';
|
|||
import '../../elements/emby-ratingbutton/emby-ratingbutton';
|
||||
import '../../elements/emby-playstatebutton/emby-playstatebutton';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import { getDefaultBackgroundClass } from '../cardbuilder/cardBuilderUtils';
|
||||
|
||||
function getIndex(item, options) {
|
||||
if (options.index === 'disc') {
|
||||
|
@ -279,7 +280,7 @@ export function getListViewHtml(options) {
|
|||
if (imgUrl) {
|
||||
html += '<div data-action="' + imageAction + '" class="' + imageClass + ' lazy" data-src="' + imgUrl + '" item-icon>';
|
||||
} else {
|
||||
html += '<div class="' + imageClass + ' cardImageContainer ' + cardBuilder.getDefaultBackgroundClass(item.Name) + '">' + cardBuilder.getDefaultText(item, options);
|
||||
html += '<div class="' + imageClass + ' cardImageContainer ' + getDefaultBackgroundClass(item.Name) + '">' + cardBuilder.getDefaultText(item, options);
|
||||
}
|
||||
|
||||
const mediaSourceCount = item.MediaSourceCount || 1;
|
||||
|
|
|
@ -10,7 +10,6 @@ import { appHost } from '../apphost';
|
|||
import globalize from '../../scripts/globalize';
|
||||
import layoutManager from '../layoutManager';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import cardBuilder from '../cardbuilder/cardBuilder';
|
||||
import itemContextMenu from '../itemContextMenu';
|
||||
import '../cardbuilder/card.scss';
|
||||
import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
|
@ -19,6 +18,7 @@ import '../../elements/emby-ratingbutton/emby-ratingbutton';
|
|||
import ServerConnections from '../ServerConnections';
|
||||
import toast from '../toast/toast';
|
||||
import { appRouter } from '../router/appRouter';
|
||||
import { getDefaultBackgroundClass } from '../cardbuilder/cardBuilderUtils';
|
||||
|
||||
let showMuteButton = true;
|
||||
let showVolumeSlider = true;
|
||||
|
@ -248,7 +248,7 @@ function setImageUrl(context, state, url) {
|
|||
context.querySelector('.nowPlayingPageImage').classList.toggle('nowPlayingPageImageAudio', item.Type === 'Audio');
|
||||
context.querySelector('.nowPlayingPageImage').classList.toggle('nowPlayingPageImagePoster', item.Type !== 'Audio');
|
||||
} else {
|
||||
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardImageContainer coveredImage ' + cardBuilder.getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album" aria-hidden="true"></span></button></div>';
|
||||
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardImageContainer coveredImage ' + getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album" aria-hidden="true"></span></button></div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import Dashboard from '../../utils/dashboard';
|
|||
import ServerConnections from '../../components/ServerConnections';
|
||||
import alert from '../../components/alert';
|
||||
import confirm from '../../components/confirm/confirm';
|
||||
import { getDefaultBackgroundClass } from '../../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
function showPlaybackInfo(btn, session) {
|
||||
let title;
|
||||
|
@ -259,7 +260,7 @@ function renderActiveConnections(view, sessions) {
|
|||
html += '<div class="cardBox visualCardBox">';
|
||||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||
html += '<div class="cardPadder cardPadder-backdrop"></div>';
|
||||
html += `<div class="cardContent ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
html += `<div class="cardContent ${getDefaultBackgroundClass()}">`;
|
||||
|
||||
if (imgUrl) {
|
||||
html += '<div class="sessionNowPlayingContent sessionNowPlayingContent-withbackground"';
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import escapeHtml from 'escape-html';
|
||||
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||
import loading from '../../../components/loading/loading';
|
||||
import dom from '../../../scripts/dom';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
@ -11,6 +10,7 @@ import '../../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
import '../../../components/cardbuilder/card.scss';
|
||||
import Dashboard from '../../../utils/dashboard';
|
||||
import confirm from '../../../components/confirm/confirm';
|
||||
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
// Local cache of loaded
|
||||
let deviceIds = [];
|
||||
|
@ -94,7 +94,7 @@ function load(page, devices) {
|
|||
deviceHtml += '<div class="cardBox visualCardBox">';
|
||||
deviceHtml += '<div class="cardScalable">';
|
||||
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
|
||||
deviceHtml += `<a is="emby-linkbutton" href="#/dashboard/devices/edit?id=${escapeHtml(device.Id)}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
deviceHtml += `<a is="emby-linkbutton" href="#/dashboard/devices/edit?id=${escapeHtml(device.Id)}" class="cardContent cardImageContainer ${getDefaultBackgroundClass()}">`;
|
||||
// audit note: getDeviceIcon returns static text
|
||||
const iconUrl = imageHelper.getDeviceIcon(device);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import '../../components/cardbuilder/card.scss';
|
|||
import '../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator';
|
||||
import Dashboard, { pageClassOn, pageIdOn } from '../../utils/dashboard';
|
||||
import confirm from '../../components/confirm/confirm';
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import { getDefaultBackgroundClass } from '../../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
function addVirtualFolder(page) {
|
||||
import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: MediaLibraryCreator }) => {
|
||||
|
@ -275,11 +275,11 @@ function getVirtualFolderHtml(page, virtualFolder, index) {
|
|||
let hasCardImageContainer;
|
||||
|
||||
if (imgUrl) {
|
||||
html += `<div class="cardImageContainer editLibrary ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer">`;
|
||||
html += `<div class="cardImageContainer editLibrary ${imgUrl ? '' : getDefaultBackgroundClass()}" style="cursor:pointer">`;
|
||||
html += `<img src="${imgUrl}" style="width:100%" />`;
|
||||
hasCardImageContainer = true;
|
||||
} else if (!virtualFolder.showNameWithIcon) {
|
||||
html += `<div class="cardImageContainer editLibrary ${cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer;">`;
|
||||
html += `<div class="cardImageContainer editLibrary ${getDefaultBackgroundClass()}" style="cursor:pointer;">`;
|
||||
html += '<span class="cardImageIcon material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '" aria-hidden="true"></span>';
|
||||
hasCardImageContainer = true;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ import escapeHTML from 'escape-html';
|
|||
import loading from '../../../../components/loading/loading';
|
||||
import libraryMenu from '../../../../scripts/libraryMenu';
|
||||
import globalize from '../../../../scripts/globalize';
|
||||
import * as cardBuilder from '../../../../components/cardbuilder/cardBuilder.js';
|
||||
import '../../../../components/cardbuilder/card.scss';
|
||||
import '../../../../elements/emby-button/emby-button';
|
||||
import '../../../../elements/emby-checkbox/emby-checkbox';
|
||||
import '../../../../elements/emby-select/emby-select';
|
||||
import { getDefaultBackgroundClass } from '../../../../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
function reloadList(page) {
|
||||
loading.show();
|
||||
|
@ -137,7 +137,7 @@ function getPluginHtml(plugin, options, installedPlugins) {
|
|||
if (plugin.imageUrl) {
|
||||
html += `<img src="${escapeHTML(plugin.imageUrl)}" style="width:100%" />`;
|
||||
} else {
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${getDefaultBackgroundClass()}">`;
|
||||
html += '<span class="cardImageIcon material-icons extension" aria-hidden="true"></span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ import loading from '../../../../components/loading/loading';
|
|||
import libraryMenu from '../../../../scripts/libraryMenu';
|
||||
import dom from '../../../../scripts/dom';
|
||||
import globalize from '../../../../scripts/globalize';
|
||||
import * as cardBuilder from '../../../../components/cardbuilder/cardBuilder.js';
|
||||
import '../../../../components/cardbuilder/card.scss';
|
||||
import '../../../../elements/emby-button/emby-button';
|
||||
import Dashboard, { pageIdOn } from '../../../../utils/dashboard';
|
||||
import confirm from '../../../../components/confirm/confirm';
|
||||
import { getDefaultBackgroundClass } from '../../../../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
function deletePlugin(page, uniqueid, version, name) {
|
||||
const msg = globalize.translate('UninstallPluginConfirmation', name);
|
||||
|
@ -73,7 +73,7 @@ function getPluginCardHtml(plugin, pluginConfigurationPages) {
|
|||
const imageUrl = ApiClient.getUrl(`/Plugins/${plugin.Id}/${plugin.Version}/Image`);
|
||||
html += `<img src="${imageUrl}" style="width:100%" />`;
|
||||
} else {
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${getDefaultBackgroundClass()}">`;
|
||||
html += '<span class="cardImageIcon material-icons extension" aria-hidden="true"></span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'jquery';
|
|||
import globalize from '../scripts/globalize';
|
||||
import taskButton from '../scripts/taskbutton';
|
||||
import dom from '../scripts/dom';
|
||||
import cardBuilder from '../components/cardbuilder/cardBuilder';
|
||||
import layoutManager from '../components/layoutManager';
|
||||
import loading from '../components/loading/loading';
|
||||
import browser from '../scripts/browser';
|
||||
|
@ -14,6 +13,7 @@ import 'material-design-icons-iconfont';
|
|||
import '../elements/emby-button/emby-button';
|
||||
import Dashboard from '../utils/dashboard';
|
||||
import confirm from '../components/confirm/confirm';
|
||||
import { getDefaultBackgroundClass } from '../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
const enableFocusTransform = !browser.slow && !browser.edge;
|
||||
|
||||
|
@ -38,7 +38,7 @@ function getDeviceHtml(device) {
|
|||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||
html += '<div class="' + padderClass + '"></div>';
|
||||
html += '<div class="cardContent searchImage">';
|
||||
html += `<div class="cardImageContainer coveredImage ${cardBuilder.getDefaultBackgroundClass()}"><span class="cardImageIcon material-icons dvr" aria-hidden="true"></span></div>`;
|
||||
html += `<div class="cardImageContainer coveredImage ${getDefaultBackgroundClass()}"><span class="cardImageIcon material-icons dvr" aria-hidden="true"></span></div>`;
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="cardFooter visualCardBox-cardFooter">';
|
||||
|
|
|
@ -15,8 +15,8 @@ import ServerConnections from '../../../components/ServerConnections';
|
|||
import toast from '../../../components/toast/toast';
|
||||
import dialogHelper from '../../../components/dialogHelper/dialogHelper';
|
||||
import baseAlert from '../../../components/alert';
|
||||
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||
import './login.scss';
|
||||
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
const enableFocusTransform = !browser.slow && !browser.edge;
|
||||
|
||||
|
@ -164,7 +164,7 @@ function loadUserList(context, apiClient, users) {
|
|||
|
||||
html += '<div class="cardImageContainer coveredImage" style="background-image:url(\'' + imgUrl + "');\"></div>";
|
||||
} else {
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
html += `<div class="cardImage flex align-items-center justify-content-center ${getDefaultBackgroundClass()}">`;
|
||||
html += '<span class="material-icons cardImageIcon person" aria-hidden="true"></span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ import '../../../elements/emby-button/emby-button';
|
|||
import Dashboard from '../../../utils/dashboard';
|
||||
import ServerConnections from '../../../components/ServerConnections';
|
||||
import alert from '../../../components/alert';
|
||||
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||
import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionState.ts';
|
||||
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
|
||||
|
||||
const enableFocusTransform = !browser.slow && !browser.edge;
|
||||
|
||||
|
@ -56,7 +56,7 @@ function renderSelectServerItems(view, servers) {
|
|||
cardContainer += '<div class="cardPadder cardPadder-square">';
|
||||
cardContainer += '</div>';
|
||||
cardContainer += '<div class="cardContent">';
|
||||
cardContainer += `<div class="cardImageContainer coveredImage ${cardBuilder.getDefaultBackgroundClass()}">`;
|
||||
cardContainer += `<div class="cardImageContainer coveredImage ${getDefaultBackgroundClass()}">`;
|
||||
cardContainer += cardImageContainer;
|
||||
cardContainer += '</div>';
|
||||
cardContainer += '</div>';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue