From eea6dcfddd8cbaca623fffe2ee47b886c55d120b Mon Sep 17 00:00:00 2001 From: Tad Date: Tue, 3 Jun 2025 14:40:21 -0700 Subject: [PATCH 1/8] Item Card component displays Critic and/or/neither Community from setting --- .../preferences/hooks/useDisplaySettings.ts | 2 + .../types/displaySettingsValues.ts | 1 + src/components/cardbuilder/card.scss | 49 +++++++++++++++++++ src/components/cardbuilder/cardBuilder.js | 27 ++++++++++ .../displaySettings/displaySettings.js | 2 + .../displaySettings.template.html | 14 ++++++ src/scripts/settings/userSettings.js | 20 ++++++-- src/strings/en-us.json | 3 ++ 8 files changed, 115 insertions(+), 3 deletions(-) diff --git a/src/apps/experimental/features/preferences/hooks/useDisplaySettings.ts b/src/apps/experimental/features/preferences/hooks/useDisplaySettings.ts index c6a544fb6cb9..165c0b65532c 100644 --- a/src/apps/experimental/features/preferences/hooks/useDisplaySettings.ts +++ b/src/apps/experimental/features/preferences/hooks/useDisplaySettings.ts @@ -79,6 +79,7 @@ async function loadDisplaySettings({ const displaySettings = { customCss: settings.customCss(), + cardRatings: settings.cardRatings() || 'none', dashboardTheme: settings.dashboardTheme() || 'auto', dateTimeLocale: settings.dateTimeLocale() || 'auto', disableCustomCss: Boolean(settings.disableCustomCss()), @@ -125,6 +126,7 @@ async function saveDisplaySettings({ userSettings.language(normalizeValue(newDisplaySettings.language)); } userSettings.customCss(normalizeValue(newDisplaySettings.customCss)); + userSettings.cardRatings(newDisplaySettings.cardRatings); userSettings.dashboardTheme(normalizeValue(newDisplaySettings.dashboardTheme)); userSettings.dateTimeLocale(normalizeValue(newDisplaySettings.dateTimeLocale)); userSettings.disableCustomCss(newDisplaySettings.disableCustomCss); diff --git a/src/apps/experimental/features/preferences/types/displaySettingsValues.ts b/src/apps/experimental/features/preferences/types/displaySettingsValues.ts index a5e08d2dd99a..ab3d569623ce 100644 --- a/src/apps/experimental/features/preferences/types/displaySettingsValues.ts +++ b/src/apps/experimental/features/preferences/types/displaySettingsValues.ts @@ -1,5 +1,6 @@ export interface DisplaySettingsValues { customCss: string; + cardRatings: string; dashboardTheme: string; dateTimeLocale: string; disableCustomCss: boolean; diff --git a/src/components/cardbuilder/card.scss b/src/components/cardbuilder/card.scss index 737850261b85..aca51fa43071 100644 --- a/src/components/cardbuilder/card.scss +++ b/src/components/cardbuilder/card.scss @@ -871,3 +871,52 @@ button::-moz-focus-inner { transform: scale(1.4, 1.4); transition: 0.2s; } + +.cardRatingContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + position: absolute; + top: 3px; + left: 3px; + padding: 5px; + font-weight: 550; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 15px; + z-index: 10; +} + +.card-hoverable:hover .cardRatingContainer { + background-color: rgba(0, 0, 0, 0.0); +} + +.cardCriticRating { + background-repeat: no-repeat; + background-size: auto 1.2em; + min-height: 1.2em; + background-position: 2px center; + padding-left: 24px; +} + +.cardRatingFresh { + background-image: url(../../assets/img/fresh.svg); +} + +.cardRatingRotten { + background-image: url(../../assets/img/rotten.svg); +} + +.cardCommunityRating { + min-height: 1.2em; + display: flex; + align-items: center; + margin-right: 3px; +} + +.cardStarIcon { + width: auto !important; + height: auto !important; + font-size: 1.4em; + margin-right: 3px; + color: #f2b01e; +} diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index b44b6e104c60..bf62374b6fcb 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -41,6 +41,8 @@ import { resolveMixedShapeByAspectRatio } from './cardBuilderUtils'; +import * as userSettings from 'scripts/settings/userSettings'; + const enableFocusTransform = !browser.slow && !browser.edge; /** @@ -1127,12 +1129,37 @@ function buildCard(index, item, apiClient, options) { let additionalCardContent = ''; if (layoutManager.desktop && !options.disableHoverMenu) { + additionalCardContent += getRatingHtml(item); additionalCardContent += getHoverMenuHtml(item, action); } return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + escapeHtml(prefix) + '" class="' + className + '"' + ariaLabelAttribute + '>' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + ''; } +/** + * Generates HTML markup for card rating. + * @param {object} item - Item used to generate the card rating. + * @returns {string} HTML markup of the card rating. + */ +function getRatingHtml(item) { + const ratingsSetting = userSettings.cardRatings(); + if (!ratingsSetting || ratingsSetting === "none") { + return ""; + } + + let cardRatingHtml = ""; + if (item.CriticRating && ["critic", "all"].includes(ratingsSetting)) { + const backgroundImageClass = item.CriticRating >= 60 ? "cardRatingFresh" : "cardRatingRotten"; + cardRatingHtml += `
${item.CriticRating}
`; + } + if (item.CommunityRating && ["community", "all"].includes(ratingsSetting)) { + const starIconHtml = '' + cardRatingHtml += `
${starIconHtml}${item.CommunityRating.toFixed(1)}
`; + } + + return cardRatingHtml ? `
${cardRatingHtml}
` : ""; +} + /** * Generates HTML markup for the card overlay. * @param {object} item - Item used to generate the card overlay. diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index 7ca0e11b6d99..8af758b6c12c 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -121,6 +121,7 @@ function loadForm(context, user, userSettings) { context.querySelector('#chkFadein').checked = userSettings.enableFastFadein(); context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash(); context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); + context.querySelector('#selectCardRatings').value = userSettings.cardRatings(); context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss(); @@ -168,6 +169,7 @@ function saveUser(context, user, userSettingsInstance, apiClient) { userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked); userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); + userSettingsInstance.cardRatings(context.querySelector('#selectCardRatings').value); userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked); diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index 1b5579b4c306..7daaa635609a 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -296,6 +296,20 @@

${UseEpisodeImagesInNextUpHelp}
+

+ ${ItemCard} +

+ +
+ +
${CardRatingsHelp}
+
+

${ItemDetails}

diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 30904ac51a66..4e28d3971196 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -309,9 +309,9 @@ export class UserSettings { } /** - * Get or set customCss. - * @param {string|undefined} [val] - Language. - * @return {string} Language. + * Get or set 'customCss'. + * @param {string|undefined} [val] - 'customCss' state. + * @return {string} 'customCss' state. */ customCss(val) { if (val !== undefined) { @@ -321,6 +321,19 @@ export class UserSettings { return this.get('customCss', false); } + /** + * Get or set 'cardRatings' state. + * @param {string|undefined} [val] - 'cardRatings' state. + * @return {string} 'cardRatings' state. + */ + cardRatings(val) { + if (val !== undefined) { + return this.set('cardRatings', val.toString(), false); + } + + return this.get('cardRatings', false); + } + /** * Get or set 'Details Banner' state. * @param {boolean|undefined} [val] - Flag to enable 'Details Banner' or undefined. @@ -694,6 +707,7 @@ export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentS export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings); export const enableBlurhash = currentSettings.enableBlurhash.bind(currentSettings); export const enableBackdrops = currentSettings.enableBackdrops.bind(currentSettings); +export const cardRatings = currentSettings.cardRatings.bind(currentSettings); export const detailsBanner = currentSettings.detailsBanner.bind(currentSettings); export const useEpisodeImagesInNextUpAndResume = currentSettings.useEpisodeImagesInNextUpAndResume.bind(currentSettings); export const language = currentSettings.language.bind(currentSettings); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 5402157b2ea4..aa02abb6ae92 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -147,6 +147,7 @@ "Bwdif": "Bob Weaver DeInterlacing Filter (BWDIF)", "CancelRecording": "Cancel recording", "CancelSeries": "Cancel series", + "CardRatingsHelp": "Allows title cards to display critic/community ratings, when available.", "Casual": "Casual", "Categories": "Categories", "ChangingMetadataImageSettingsNewContent": "Changes to metadata or artwork downloading settings will only apply to new content added to your library. To apply the changes to existing titles, you'll need to refresh their metadata manually.", @@ -572,6 +573,7 @@ "Inker": "Inker", "InstallingPackage": "Installing {0} (version {1})", "InstantMix": "Instant mix", + "ItemCard": "Item Card", "ItemCount": "{0} items", "ItemDetails": "Item Details", "Items": "Items", @@ -635,6 +637,7 @@ "LabelCachePath": "Cache path", "LabelCachePathHelp": "Specify a custom location for server cache files such as images. Leave blank to use the server default.", "LabelCancelled": "Cancelled", + "LabelCardRatings": "Item Card Ratings Display", "LabelCertificatePassword": "Certificate password", "LabelCertificatePasswordHelp": "If your certificate requires a password, please enter it here.", "LabelChannels": "Channels", From a22a8a29861e0f6dcfbda8136c01b72035964e7e Mon Sep 17 00:00:00 2001 From: Tad Date: Tue, 3 Jun 2025 15:47:56 -0700 Subject: [PATCH 2/8] cardBuilder#getRatingHtml use single quotes --- src/components/cardbuilder/cardBuilder.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index bf62374b6fcb..3d4edc08ab57 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -1143,21 +1143,21 @@ function buildCard(index, item, apiClient, options) { */ function getRatingHtml(item) { const ratingsSetting = userSettings.cardRatings(); - if (!ratingsSetting || ratingsSetting === "none") { - return ""; + if (!ratingsSetting || ratingsSetting === 'none') { + return ''; } - let cardRatingHtml = ""; - if (item.CriticRating && ["critic", "all"].includes(ratingsSetting)) { - const backgroundImageClass = item.CriticRating >= 60 ? "cardRatingFresh" : "cardRatingRotten"; + let cardRatingHtml = ''; + if (item.CriticRating && ['critic', 'all'].includes(ratingsSetting)) { + const backgroundImageClass = item.CriticRating >= 60 ? 'cardRatingFresh' : 'cardRatingRotten'; cardRatingHtml += `
${item.CriticRating}
`; } - if (item.CommunityRating && ["community", "all"].includes(ratingsSetting)) { + if (item.CommunityRating && ['community', 'all'].includes(ratingsSetting)) { const starIconHtml = '' cardRatingHtml += `
${starIconHtml}${item.CommunityRating.toFixed(1)}
`; } - return cardRatingHtml ? `
${cardRatingHtml}
` : ""; + return cardRatingHtml ? `
${cardRatingHtml}
` : ''; } /** From 6cab06cc0bb9b5b78eed3938832a74f2e88e74db Mon Sep 17 00:00:00 2001 From: Tad Date: Tue, 3 Jun 2025 17:01:42 -0700 Subject: [PATCH 3/8] fix lint issues --- src/components/cardbuilder/card.scss | 2 +- src/components/cardbuilder/cardBuilder.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/cardbuilder/card.scss b/src/components/cardbuilder/card.scss index aca51fa43071..c3c486952694 100644 --- a/src/components/cardbuilder/card.scss +++ b/src/components/cardbuilder/card.scss @@ -887,7 +887,7 @@ button::-moz-focus-inner { } .card-hoverable:hover .cardRatingContainer { - background-color: rgba(0, 0, 0, 0.0); + background-color: rgba(0, 0, 0, 0); } .cardCriticRating { diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 3d4edc08ab57..eb31036bcfdd 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -1153,7 +1153,7 @@ function getRatingHtml(item) { cardRatingHtml += `
${item.CriticRating}
`; } if (item.CommunityRating && ['community', 'all'].includes(ratingsSetting)) { - const starIconHtml = '' + const starIconHtml = ''; cardRatingHtml += `
${starIconHtml}${item.CommunityRating.toFixed(1)}
`; } From 8073994bd533e354bed76eac13873a318d8da146 Mon Sep 17 00:00:00 2001 From: Tad Date: Tue, 3 Jun 2025 18:27:57 -0700 Subject: [PATCH 4/8] style changes --- src/components/cardbuilder/card.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/cardbuilder/card.scss b/src/components/cardbuilder/card.scss index c3c486952694..1766799bbc6e 100644 --- a/src/components/cardbuilder/card.scss +++ b/src/components/cardbuilder/card.scss @@ -880,14 +880,20 @@ button::-moz-focus-inner { top: 3px; left: 3px; padding: 5px; - font-weight: 550; + font-weight: 600; background-color: rgba(0, 0, 0, 0.5); + transition: background-color 200ms ease-out; border-radius: 15px; z-index: 10; } .card-hoverable:hover .cardRatingContainer { background-color: rgba(0, 0, 0, 0); + transition: background-color 0; +} + +.cardRatingContainer div:first-of-type ~ div { + margin-top: 3px; } .cardCriticRating { From 446f9bc2da0530be905acbca3803973942018591 Mon Sep 17 00:00:00 2001 From: Tad Date: Tue, 3 Jun 2025 18:30:14 -0700 Subject: [PATCH 5/8] update CONTRIBUTORS --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 48b9b147b398..01520b992072 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -163,6 +163,7 @@ - [tikuf](https://github.com/tikuf/) - [Tim Hobbs](https://github.com/timhobbs) - [SvenVandenbrande](https://github.com/SvenVandenbrande) +- [shoecar](https://github.com/shoecar)