fix: Improve folder icon rendering (#2467)

This commit is contained in:
Dermot Duffy
2026-06-30 17:45:12 -07:00
committed by dermotduffy
parent d8ad347dfa
commit 056a038e81
4 changed files with 48 additions and 4 deletions
@@ -76,7 +76,15 @@ export class ThumbnailFeatureController {
if (thumbnail) {
this._thumbnail = thumbnail;
this._icon = null;
this._thumbnailClass = isBrandUrl(thumbnail) ? 'placeholder' : null;
// Treat as a placeholder (centered, contain-fit) when either the URL
// looks brand-like, or the item is a folder. The folder check is
// necessary because HA's media browser often returns folder thumbnails
// as local/proxy URLs that don't match isBrandUrl, even though they
// visually represent integration logos.
this._thumbnailClass =
isBrandUrl(thumbnail) || ViewItemClassifier.isFolder(item)
? 'placeholder'
: null;
} else {
this._thumbnail = null;
this._thumbnailClass = null;
@@ -31,6 +31,12 @@ img {
object-fit: cover;
}
// Placeholder images (brand logos, folder icons) are not previews of content,
// so they should be shown in full rather than cropped to fill a square.
:host(.placeholder) img {
object-fit: contain;
}
advanced-camera-card-icon {
display: flex;
width: 50%;
+20 -3
View File
@@ -45,13 +45,30 @@ advanced-camera-card-thumbnail-feature-thumbnail:not(.placeholder) {
left: 0;
}
// Placeholder icons/thumbnails: centered, 90% of container height
// Placeholder icons/thumbnails: nearly fill the .media container so wide logos
// can use the full width. Per-image fit (contain vs cover) is handled inside
// the inner thumbnail/icon.
//
// The two size rules look inverted (95% with text, 60% without) because the
// percentages are relative to .media, which itself differs by case:
// .has-text → .media is 100% × 50% of tile, so 95% ≈ 47% of tile height
// :not(.has-text) → .media is 100% × 100% of tile, so 60% = 60% of tile height
// Both cases land at roughly half-tile visible; without the no-text shrink the
// placeholder would fill the entire tile and stop reading as an icon.
advanced-camera-card-thumbnail-feature-thumbnail.placeholder,
advanced-camera-card-icon.placeholder {
height: 90%;
aspect-ratio: 1 / 1;
--mdc-icon-size: 100%;
}
.media.has-text > advanced-camera-card-thumbnail-feature-thumbnail.placeholder,
.media.has-text > advanced-camera-card-icon.placeholder {
width: 95%;
height: 95%;
}
.media:not(.has-text) > advanced-camera-card-thumbnail-feature-thumbnail.placeholder,
.media:not(.has-text) > advanced-camera-card-icon.placeholder {
width: 60%;
height: 60%;
}
advanced-camera-card-icon.background {
display: block;
@@ -177,5 +177,18 @@ describe('ThumbnailFeatureController', () => {
expect(controller.getThumbnail()).toBe('https://card.camera/thumbnail.jpg');
expect(controller.getThumbnailClass()).toBeNull();
});
it('should set placeholder thumbnail for non-brand folder thumbnail', () => {
const controller = new ThumbnailFeatureController();
const folder = new ViewFolder(createFolder(), [], {
title: 'Test Folder',
thumbnail: 'https://card.camera/thumbnail.jpg',
});
controller.calculate(null, folder, false);
expect(controller.getThumbnail()).toBe('https://card.camera/thumbnail.jpg');
expect(controller.getThumbnailClass()).toBe('placeholder');
});
});
});