') : ('
');
cardImageContainerClose = '
';
} 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 ? ('
') : ('');
+ cardImageContainerOpen = imgUrl ? ('') : ('');
cardImageContainerClose = '';
}
@@ -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 '
';
}
- const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item);
+ const defaultName = isUsingLiveTvNaming(item.Type) ? item.Name : itemHelper.getDisplayName(item);
return '
' + escapeHtml(defaultName) + '
';
}
@@ -1515,7 +1415,6 @@ export function onSeriesTimerCancelled(cancelledTimerId, itemsContainer) {
export default {
getCardsHtml: getCardsHtml,
- getDefaultBackgroundClass: getDefaultBackgroundClass,
getDefaultText: getDefaultText,
buildCards: buildCards,
onUserDataChanged: onUserDataChanged,
diff --git a/src/components/cardbuilder/cardBuilderUtils.js b/src/components/cardbuilder/cardBuilderUtils.js
deleted file mode 100644
index 494dcaf64..000000000
--- a/src/components/cardbuilder/cardBuilderUtils.js
+++ /dev/null
@@ -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
-};
diff --git a/src/components/cardbuilder/cardBuilderUtils.test.js b/src/components/cardbuilder/cardBuilderUtils.test.ts
similarity index 52%
rename from src/components/cardbuilder/cardBuilderUtils.test.js
rename to src/components/cardbuilder/cardBuilderUtils.test.ts
index 46599135d..501a395f9 100644
--- a/src/components/cardbuilder/cardBuilderUtils.test.js
+++ b/src/components/cardbuilder/cardBuilderUtils.test.ts
@@ -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);
+ });
+});
diff --git a/src/components/cardbuilder/cardBuilderUtils.ts b/src/components/cardbuilder/cardBuilderUtils.ts
new file mode 100644
index 000000000..d7215b190
--- /dev/null
+++ b/src/components/cardbuilder/cardBuilderUtils.ts
@@ -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;
+ }
+};
diff --git a/src/components/dashboard/users/UserCardBox.tsx b/src/components/dashboard/users/UserCardBox.tsx
index f0fbdf96a..e4bc40d2b 100644
--- a/src/components/dashboard/users/UserCardBox.tsx
+++ b/src/components/dashboard/users/UserCardBox.tsx
@@ -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: `
= ({ user = {} }: IProps) => {
const renderImgUrl = imgUrl ?
`` :
- `
+ `
`;
diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js
index 4c05be198..018a8a7b6 100644
--- a/src/components/listview/listview.js
+++ b/src/components/listview/listview.js
@@ -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 += '
';
} else {
- html += '
' + cardBuilder.getDefaultText(item, options);
+ html += '
' + cardBuilder.getDefaultText(item, options);
}
const mediaSourceCount = item.MediaSourceCount || 1;
diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js
index 0c6fe4982..a786f6fdd 100644
--- a/src/components/remotecontrol/remotecontrol.js
+++ b/src/components/remotecontrol/remotecontrol.js
@@ -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 = '
';
+ imgContainer.innerHTML = '
';
}
}
diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js
index 30f37764b..393e4c603 100644
--- a/src/controllers/dashboard/dashboard.js
+++ b/src/controllers/dashboard/dashboard.js
@@ -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 += '
';
html += '
';
html += '
';
- html += `
`;
+ html += `
`;
if (imgUrl) {
html += '
';
deviceHtml += '
';
deviceHtml += '
';
- deviceHtml += `
`;
+ deviceHtml += ``;
// audit note: getDeviceIcon returns static text
const iconUrl = imageHelper.getDeviceIcon(device);
diff --git a/src/controllers/dashboard/library.js b/src/controllers/dashboard/library.js
index 987169ecd..897ad3ecb 100644
--- a/src/controllers/dashboard/library.js
+++ b/src/controllers/dashboard/library.js
@@ -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 += ``;
+ html += `
`;
html += `

`;
hasCardImageContainer = true;
} else if (!virtualFolder.showNameWithIcon) {
- html += `
`;
+ html += `
`;
html += '
';
hasCardImageContainer = true;
}
diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js
index b3445b5cb..8ce093229 100644
--- a/src/controllers/dashboard/plugins/available/index.js
+++ b/src/controllers/dashboard/plugins/available/index.js
@@ -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 += `
})
`;
} else {
- html += `
`;
+ html += `
`;
html += '';
html += '
';
}
diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js
index 91ebcfdff..9600eb0a3 100644
--- a/src/controllers/dashboard/plugins/installed/index.js
+++ b/src/controllers/dashboard/plugins/installed/index.js
@@ -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 += `

`;
} else {
- html += `
`;
+ html += `
`;
html += '';
html += '
';
}
diff --git a/src/controllers/livetvstatus.js b/src/controllers/livetvstatus.js
index 8532e8ae2..35c219b93 100644
--- a/src/controllers/livetvstatus.js
+++ b/src/controllers/livetvstatus.js
@@ -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 += '
';
html += '
';
html += '
';
- html += `
`;
+ html += `
`;
html += '
';
html += '
';
html += '