cover-grid album dropdown behavior fixes

This commit is contained in:
2026-02-18 11:34:10 -05:00
parent 026ab1c333
commit c9e78c491c
17 changed files with 934 additions and 424 deletions
@@ -49,6 +49,18 @@ export class AlbumDropdown extends LitElement {
@property({ type: Number, attribute: 'phantom-rows' })
phantomRows = 1;
/** Grid item height in pixels (passed from parent). */
@property({ type: Number })
gridItemHeight = 230;
/** Grid gap in pixels (passed from parent). */
@property({ type: Number })
gridGap = 16;
/** Width of the grid container in pixels (passed from parent). */
@property({ type: Number })
containerWidth = 800;
static override styles = css`
:host {
display: block;
@@ -60,7 +72,6 @@ export class AlbumDropdown extends LitElement {
border-radius: 4px;
padding: 12px 16px;
box-sizing: border-box;
min-height: 230px;
}
.dropdown-loading {
@@ -73,7 +84,6 @@ export class AlbumDropdown extends LitElement {
}
.dropdown-tracks {
column-count: 3;
column-fill: auto;
column-gap: 24px;
}
@@ -87,6 +97,7 @@ export class AlbumDropdown extends LitElement {
cursor: default;
user-select: none;
font-size: 12px;
line-height: 16px;
break-inside: avoid;
}
@@ -150,6 +161,24 @@ export class AlbumDropdown extends LitElement {
}
`;
/* ================================================================
* Layout helpers
* ================================================================ */
/**
* Derive the number of track-list columns from the
* grid container width.
*/
get columnCount(): number {
const w = this.containerWidth;
if (w < 500) return 1;
if (w < 800) return 2;
if (w < 1200) return 3;
return 4;
}
/* ================================================================
* Rendering helpers
* ================================================================ */
@@ -165,21 +194,28 @@ export class AlbumDropdown extends LitElement {
}
/**
* Compute the track container height from the number
* of phantom grid rows allocated by the parent.
* Total height of the outer .album-dropdown box,
* matching the phantom grid space exactly.
*/
private get dropdownHeight(): number {
return (
this.phantomRows * this.gridItemHeight +
(this.phantomRows - 1) * this.gridGap
);
}
/**
* Height of the inner .dropdown-tracks container.
* Uses the full inner space so that column-fill:auto
* fills each column completely before moving to the
* next.
*
* Grid constants: itemHeight=230, gap=16.
* Dropdown chrome: 12+12 padding + 2+2 border = 28px.
*/
private get tracksHeight(): number {
const gridItemHeight = 230;
const gridGap = 16;
const chrome = 28;
const total =
this.phantomRows * gridItemHeight +
(this.phantomRows - 1) * gridGap;
return total - chrome;
return this.dropdownHeight - chrome;
}
/* ================================================================
@@ -323,10 +359,13 @@ export class AlbumDropdown extends LitElement {
}
return html`
<div class="album-dropdown">
<div
class="album-dropdown"
style="height:${this.dropdownHeight}px"
>
<div
class="dropdown-tracks"
style="height:${this.tracksHeight}px"
style="height:${this.tracksHeight}px;column-count:${this.columnCount}"
>
${this.tracks.map(
(track, i) =>
+437 -117
View File
@@ -39,25 +39,26 @@ type ContextMenuTarget =
*/
type GridEntry =
| {
kind: 'album';
album: library.Album;
albumIndex: number;
}
kind: 'album';
album: library.Album;
albumIndex: number;
}
| { kind: 'phantom'; phantomIndex: number };
/** Milliseconds to debounce visibility-changed saves. */
const SCROLL_DEBOUNCE_MS = 100;
/** Pixels to change card width per scroll tick. */
const ZOOM_STEP = 16;
@customElement('cover-grid')
export class CoverGrid extends LitElement {
private libraryCtrl = new LibraryController(this);
// Grid layout constants — must match the virtualizer
// grid config and the CSS card dimensions.
private static readonly GRID_ITEM_WIDTH = 176;
private static readonly GRID_ITEM_HEIGHT = 230;
private static readonly GRID_GAP = 16;
private static readonly GRID_PADDING = 16;
// Fixed grid spacing constants.
private static readonly GRID_GAP = 8;
private static readonly GRID_PADDING = 8;
private static readonly CARD_PADDING = 5;
private lastSelectedAlbumIndex: number | null = null;
private lastSelectedTrackIndex: number | null = null;
@@ -68,22 +69,72 @@ export class CoverGrid extends LitElement {
private closeHandler = () => this.closeContextMenu();
/**
* Tracks per phantom row. Each track row is ~28px,
* the base content height is 202px, each column fits
* ~7 tracks, and 3 columns = 21 tracks per row.
* Exact height of a single track row in pixels.
* line-height 16 + padding 4+4 = 24.
*/
private static readonly TRACKS_PER_PHANTOM_ROW = 21;
private static readonly TRACK_ROW_HEIGHT = 24;
// Virtualizer grid layout instance.
private gridLayout = grid({
itemSize: {
width: `${CoverGrid.GRID_ITEM_WIDTH}px`,
height: `${CoverGrid.GRID_ITEM_HEIGHT}px`,
},
gap: `${CoverGrid.GRID_GAP}px`,
padding: `${CoverGrid.GRID_PADDING}px`,
justify: 'center',
});
/** Dropdown chrome: 12+12 padding + 2+2 border. */
private static readonly DROPDOWN_CHROME = 28;
/** Current card width — driven by the store. */
private get cardWidth(): number {
return this.libraryCtrl.coverSize;
}
/**
* Height of the text area below the cover image.
* Two lines: album name (+ year) and artist.
*/
private get cardTextHeight(): number {
const w = this.cardWidth;
if (w < 160) return 36;
return w > 250 ? 46 : 40;
}
/** Derived card height from card width. */
private get cardHeight(): number {
return this.cardWidth + this.cardTextHeight;
}
/** Image size inside the card (card minus padding). */
private get imageSize(): number {
return this.cardWidth - CoverGrid.CARD_PADDING * 2;
}
// Virtualizer grid layout instance — recreated when
// the card size changes.
private gridLayout = this.createGridLayout();
private gridLayoutWidth = 0;
private createGridLayout() {
const w = this.libraryCtrl?.coverSize ?? 176;
this.gridLayoutWidth = w;
const h = w + this.cardTextHeight;
const gap = CoverGrid.GRID_GAP;
const pad = CoverGrid.GRID_PADDING;
return grid({
itemSize: {
width: `${w}px`,
height: `${h}px`,
},
gap: `${gap}px`,
padding: `${pad}px`,
justify: 'center',
});
}
/** Wheel event handler ref for manual add/remove. */
private wheelHandler = (e: WheelEvent) => {
this.onWheel(e);
};
private wheelListenerAttached = false;
// buildVirtualizerItems() memoization cache.
private itemsCacheAlbums: library.Album[] = [];
@@ -114,10 +165,10 @@ export class CoverGrid extends LitElement {
flex-direction: column;
cursor: pointer;
border-radius: 8px;
padding: 8px;
padding: 5px;
transition: background-color 0.2s ease;
box-sizing: border-box;
width: 176px;
width: var(--card-width, 176px);
}
.album-card:hover {
@@ -166,17 +217,18 @@ export class CoverGrid extends LitElement {
#282828 100%
);
color: #b3b3b3;
font-size: 48px;
font-size: var(--placeholder-font, 48px);
}
.album-info {
margin-top: 8px;
margin-top: 4px;
min-width: 0;
text-align: center;
}
.album-name {
font-size: 14px;
font-weight: 600;
font-size: var(--album-name-font, 14px);
font-weight: 400;
color: #fff;
white-space: nowrap;
overflow: hidden;
@@ -184,12 +236,16 @@ export class CoverGrid extends LitElement {
}
.artist-name {
font-size: 12px;
font-size: var(--artist-name-font, 12px);
color: #b3b3b3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 4px;
margin-top: 2px;
}
.album-year {
color: #888;
}
/* ========================================
@@ -334,7 +390,7 @@ export class CoverGrid extends LitElement {
/** Number of phantom rows reserved for the dropdown overlay. */
@state()
private phantomRowCount = 1;
private phantomRowCount = 0;
/** Pixel offset of the dropdown overlay from the top of the scroll content. */
@state()
@@ -387,6 +443,7 @@ export class CoverGrid extends LitElement {
this.onGridImageError,
true,
);
}
override disconnectedCallback() {
@@ -404,6 +461,11 @@ export class CoverGrid extends LitElement {
this.onGridImageError,
true,
);
this.scrollContainer?.removeEventListener(
'wheel',
this.wheelHandler,
);
this.wheelListenerAttached = false;
if (this.scrollDebounceTimer !== null) {
clearTimeout(this.scrollDebounceTimer);
@@ -422,40 +484,145 @@ export class CoverGrid extends LitElement {
) {
super.updated(changed);
// When the dropdown opens or tracks change,
// recompute the phantom row count and overlay
// position.
// Ctrl+Scroll zoom — lazily attach to the
// scroll container once it exists in the DOM.
if (
changed.has('expandedAlbumId') ||
changed.has('expandedTracks')
!this.wheelListenerAttached &&
this.scrollContainer
) {
this.scrollContainer.addEventListener(
'wheel',
this.wheelHandler,
{ passive: false },
);
this.wheelListenerAttached = true;
}
// Recreate the virtualizer grid layout when
// the card size changes.
const cardSizeChanged =
this.gridLayoutWidth !== this.cardWidth;
if (cardSizeChanged) {
this.gridLayout = this.createGridLayout();
// Invalidate the items cache so the
// virtualizer picks up the new layout.
this.itemsCacheColumns = 0;
}
// Apply CSS custom properties for dynamic sizing.
this.updateSizeProperties();
// When the dropdown opens, tracks change, or
// card size changes (zoom), recompute the
// phantom row count and overlay position.
// Phantom rows are only injected once tracks
// have loaded so the dropdown renders at the
// correct size immediately.
const dropdownNeedsUpdate =
changed.has('expandedAlbumId') ||
changed.has('expandedTracks') ||
(cardSizeChanged &&
this.expandedAlbumId !== null &&
this.expandedTracks.length > 0);
if (dropdownNeedsUpdate) {
this.phantomRowCount =
this.expandedAlbumId !== null
? Math.max(
1,
Math.ceil(
this.expandedTracks
.length /
CoverGrid.TRACKS_PER_PHANTOM_ROW,
),
)
: 1;
? this.computePhantomRowCount(
this.expandedTracks.length,
)
: 0;
this.updateDropdownPosition();
}
// When a new album is expanded, scroll its
// row to the top of the viewport after the
// DOM reflects the new phantom rows.
if (
changed.has('expandedAlbumId') &&
this.expandedAlbumId !== null
) {
// Scroll to show the dropdown once tracks
// have loaded, or re-focus the expanded album
// after a zoom.
const shouldScroll =
(changed.has('expandedTracks') &&
this.expandedAlbumId !== null &&
this.expandedTracks.length > 0) ||
(cardSizeChanged &&
this.expandedAlbumId !== null &&
this.expandedTracks.length > 0);
if (shouldScroll) {
void this.updateComplete.then(() => {
this.scrollToExpandedAlbum();
this.scrollToShowDropdown();
});
}
}
/* ====================================================================
* Dynamic size properties
* ==================================================================== */
private updateSizeProperties() {
const w = this.cardWidth;
this.style.setProperty(
'--card-width',
`${w}px`,
);
// Scale placeholder initial font.
const placeholderFont =
Math.max(16, Math.round(w * 0.3));
this.style.setProperty(
'--placeholder-font',
`${placeholderFont}px`,
);
// Text sizing tiers.
if (w < 160) {
this.classList.add('size-small');
this.style.setProperty(
'--album-name-font',
'11px',
);
this.style.setProperty(
'--artist-name-font',
'10px',
);
} else if (w > 250) {
this.classList.remove('size-small');
this.style.setProperty(
'--album-name-font',
'16px',
);
this.style.setProperty(
'--artist-name-font',
'13px',
);
} else {
this.classList.remove('size-small');
this.style.setProperty(
'--album-name-font',
'14px',
);
this.style.setProperty(
'--artist-name-font',
'12px',
);
}
}
/* ====================================================================
* Ctrl+Scroll zoom
* ==================================================================== */
private onWheel(e: WheelEvent) {
if (!e.ctrlKey) return;
e.preventDefault();
const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
this.libraryCtrl.coverSize =
this.cardWidth + delta;
}
/* ====================================================================
* Data loading
* ==================================================================== */
@@ -561,13 +728,6 @@ export class CoverGrid extends LitElement {
// repeated calls (e.g. library re-scan).
this.resizeObserver?.disconnect();
const {
GRID_ITEM_HEIGHT,
GRID_GAP,
GRID_PADDING,
} = CoverGrid;
const rowStep = GRID_ITEM_HEIGHT + GRID_GAP;
this.currentColumnCount =
this.getColumnCount();
@@ -585,15 +745,24 @@ export class CoverGrid extends LitElement {
this.currentColumnCount = newColumns;
// If a dropdown is open, snap the
// expanded album's row to the top.
// If a dropdown is open, reposition and
// re-evaluate scroll with smart logic.
if (this.expandedAlbumId !== null) {
this.phantomRowCount =
this.computePhantomRowCount(
this.expandedTracks.length,
);
this.updateDropdownPosition();
this.scrollToExpandedAlbum();
this.scrollToShowDropdown();
return;
}
const gap = CoverGrid.GRID_GAP;
const pad = CoverGrid.GRID_PADDING;
const rowStep =
this.cardHeight + gap;
// Derive the album's row under the new
// column count. Both albumIndex and
// newColumns are integers, so newRow is
@@ -602,7 +771,7 @@ export class CoverGrid extends LitElement {
pending.albumIndex / newColumns,
);
const newY =
GRID_PADDING + newRow * rowStep;
pad + newRow * rowStep;
container.scrollTop =
newY - pending.viewportOffset;
@@ -610,6 +779,9 @@ export class CoverGrid extends LitElement {
this.resizeObserver = new ResizeObserver(
() => {
const rowStep =
this.cardHeight + CoverGrid.GRID_GAP;
// Capture on the first event using
// the pre-resize column count.
if (this.pendingFocus === null) {
@@ -681,7 +853,7 @@ export class CoverGrid extends LitElement {
container: HTMLElement,
rowStep: number,
) {
const { GRID_PADDING } = CoverGrid;
const pad = CoverGrid.GRID_PADDING;
const cols = this.currentColumnCount;
// Prefer the expanded album as focus.
@@ -695,7 +867,7 @@ export class CoverGrid extends LitElement {
idx / cols,
);
const albumY =
GRID_PADDING + albumRow * rowStep;
pad + albumRow * rowStep;
this.pendingFocus = {
albumIndex: idx,
@@ -713,8 +885,8 @@ export class CoverGrid extends LitElement {
container.scrollTop +
container.clientHeight / 2;
const centerRow = Math.floor(
Math.max(0, centerY - GRID_PADDING) /
rowStep,
Math.max(0, centerY - pad) /
rowStep,
);
const albumIndex = Math.min(
centerRow * cols,
@@ -725,7 +897,7 @@ export class CoverGrid extends LitElement {
// to the viewport top — used exactly once in
// restoreScroll, never fed back.
const albumY =
GRID_PADDING + centerRow * rowStep;
pad + centerRow * rowStep;
this.pendingFocus = {
albumIndex,
@@ -744,16 +916,72 @@ export class CoverGrid extends LitElement {
if (!el) return 1;
const { GRID_ITEM_WIDTH, GRID_GAP, GRID_PADDING } =
CoverGrid;
const gap = CoverGrid.GRID_GAP;
const pad = CoverGrid.GRID_PADDING;
const availableWidth =
el.clientWidth - GRID_PADDING * 2;
el.clientWidth - pad * 2;
return Math.max(
1,
Math.floor(
(availableWidth + GRID_GAP) /
(GRID_ITEM_WIDTH + GRID_GAP),
(availableWidth + gap) /
(this.cardWidth + gap),
),
);
}
/** Container width in pixels for the dropdown. */
private getContainerWidth(): number {
const el =
this.scrollContainer ?? this.virtualizer;
return el?.clientWidth ?? 800;
}
/**
* Derive the number of track-list columns from
* the grid container width. Mirrors the logic
* in AlbumDropdown.columnCount.
*/
private getDropdownColumnCount(): number {
const w = this.getContainerWidth();
if (w < 500) return 1;
if (w < 800) return 2;
if (w < 1200) return 3;
return 4;
}
/**
* Compute how many phantom grid rows are needed
* to fit all tracks given the current dropdown
* column count and card height.
*
* Derives the required height from the actual
* track content rather than per-column capacity,
* keeping the phantom space tight regardless of
* the number of columns.
*/
private computePhantomRowCount(
trackCount: number,
): number {
if (trackCount === 0) return 0;
const cols = this.getDropdownColumnCount();
const rowsPerCol = Math.ceil(
trackCount / cols,
);
const contentHeight =
rowsPerCol * CoverGrid.TRACK_ROW_HEIGHT +
CoverGrid.DROPDOWN_CHROME;
const gap = CoverGrid.GRID_GAP;
const rowStep = this.cardHeight + gap;
return Math.max(
1,
Math.ceil(
(contentHeight + gap) / rowStep,
),
);
}
@@ -778,7 +1006,7 @@ export class CoverGrid extends LitElement {
if (
this.itemsCacheAlbums === this.albums &&
this.itemsCacheExpandedId ===
this.expandedAlbumId &&
this.expandedAlbumId &&
this.itemsCacheColumns === columns &&
this.itemsCachePhantomRows === phantomRows
) {
@@ -804,13 +1032,13 @@ export class CoverGrid extends LitElement {
const insertAfter =
expandedIndex >= 0
? Math.min(
(Math.floor(
expandedIndex / columns,
) +
1) *
columns,
this.albums.length,
)
(Math.floor(
expandedIndex / columns,
) +
1) *
columns,
this.albums.length,
)
: this.albums.length;
const phantomCount = columns * phantomRows;
@@ -871,10 +1099,22 @@ export class CoverGrid extends LitElement {
* ==================================================================== */
/**
* Smooth-scroll the container so the expanded
* album's row is positioned at the top of the view.
* Scroll the container so the expanded album card
* and its dropdown are visible, using minimal
* movement:
*
* 1. If both fit in the viewport already, don't
* scroll.
* 2. If the album card top is slightly above the
* viewport, scroll up to reveal it.
* 3. If the dropdown bottom overflows below the
* viewport, scroll down to align it with the
* viewport bottom.
* 4. If showing the dropdown bottom would push
* the album card above the viewport, pin the
* album card top to the viewport top instead.
*/
private scrollToExpandedAlbum() {
private scrollToShowDropdown() {
const container = this.scrollContainer;
if (
@@ -890,24 +1130,60 @@ export class CoverGrid extends LitElement {
if (expandedIndex < 0) return;
const {
GRID_ITEM_HEIGHT,
GRID_GAP,
GRID_PADDING,
} = CoverGrid;
const gap = CoverGrid.GRID_GAP;
const pad = CoverGrid.GRID_PADDING;
const columns = this.getColumnCount();
const rowStep = GRID_ITEM_HEIGHT + GRID_GAP;
const rowStep = this.cardHeight + gap;
const albumRow = Math.floor(
expandedIndex / columns,
);
container.scrollTo({
top:
GRID_PADDING +
albumRow * rowStep -
GRID_GAP / 2,
behavior: 'instant',
});
// Top of the album card (at the midpoint of
// the gap above the row) in scroll-content
// coordinates.
const albumTop =
pad + albumRow * rowStep - gap / 2;
// Bottom of the dropdown overlay.
const dropdownHeight =
this.phantomRowCount * this.cardHeight +
(this.phantomRowCount - 1) * gap;
const dropdownBottom =
this.dropdownTopPx + dropdownHeight;
const viewTop = container.scrollTop;
const viewHeight = container.clientHeight;
// The valid scroll range where both the album
// top and dropdown bottom are in view:
// scrollTop <= albumTop (card visible)
// scrollTop >= dropdownBottom - viewHeight
const minScroll =
dropdownBottom - viewHeight;
const maxScroll = albumTop;
let newScrollTop: number;
if (minScroll <= maxScroll) {
// Both can fit — clamp to the valid
// range, only scrolling if needed.
newScrollTop = Math.max(
minScroll,
Math.min(viewTop, maxScroll),
);
} else {
// Combined height exceeds the viewport.
// Pin the album card top to the viewport
// top so it stays visible.
newScrollTop = albumTop;
}
if (newScrollTop !== viewTop) {
container.scrollTo({
top: newScrollTop,
behavior: 'instant',
});
}
}
private updateDropdownPosition() {
@@ -920,17 +1196,14 @@ export class CoverGrid extends LitElement {
if (expandedIndex < 0) return;
const {
GRID_ITEM_HEIGHT,
GRID_GAP,
GRID_PADDING,
} = CoverGrid;
const rowStep = GRID_ITEM_HEIGHT + GRID_GAP;
const gap = CoverGrid.GRID_GAP;
const pad = CoverGrid.GRID_PADDING;
const rowStep = this.cardHeight + gap;
const phantomStartRow =
Math.floor(expandedIndex / columns) + 1;
this.dropdownTopPx =
GRID_PADDING + phantomStartRow * rowStep;
pad + phantomStartRow * rowStep;
}
/* ====================================================================
@@ -1035,7 +1308,7 @@ export class CoverGrid extends LitElement {
this.expandedTracks = [];
this.selectedTracks = new Set();
this.lastSelectedTrackIndex = null;
this.phantomRowCount = 1;
this.phantomRowCount = 0;
return;
}
@@ -1045,7 +1318,7 @@ export class CoverGrid extends LitElement {
this.expandedTracks = [];
this.selectedTracks = new Set();
this.lastSelectedTrackIndex = null;
this.phantomRowCount = 1;
this.phantomRowCount = 0;
this.loadingTracks = true;
try {
@@ -1476,6 +1749,40 @@ export class CoverGrid extends LitElement {
return name.charAt(0).toUpperCase();
}
/**
* Pick the appropriate cover art URL based on the
* current card size and device pixel ratio.
*/
private getCoverUrl(album: library.Album): string {
const needed =
this.imageSize * window.devicePixelRatio;
if (needed <= 100) {
return (
album.CoverArtSmall ||
album.CoverArtMedium ||
album.CoverArtPath
);
}
if (needed <= 200) {
return (
album.CoverArtMedium ||
album.CoverArtLarge ||
album.CoverArtPath
);
}
if (needed <= 400) {
return (
album.CoverArtLarge ||
album.CoverArtPath
);
}
return album.CoverArtPath;
}
/* ====================================================================
* Render: grid entry (virtualizer renderItem)
* ==================================================================== */
@@ -1520,6 +1827,8 @@ export class CoverGrid extends LitElement {
.filter(Boolean)
.join(' ');
const imgSize = this.imageSize;
return html`
<div
class=${classes}
@@ -1532,10 +1841,10 @@ export class CoverGrid extends LitElement {
${album.CoverArtPath
? html`<img
class="cover-image"
src="${album.CoverArtThumbnailPath || album.CoverArtPath}"
src="${this.getCoverUrl(album)}"
alt="${album.Name} cover"
width="160"
height="160"
width="${imgSize}"
height="${imgSize}"
loading="lazy"
decoding="async"
/>`
@@ -1550,15 +1859,18 @@ export class CoverGrid extends LitElement {
class="album-name"
title="${album.Name}"
>
${album.Name}
${album.Name}${album.Year
? html`
<span class="album-year">
(${album.Year})</span
>`
: nothing}
</div>
<div
class="artist-name"
title="${album.ArtistName}"
>
${album.ArtistName}${album.Year
? ` - ${album.Year}`
: ''}
${album.ArtistName}
</div>
</div>
</div>
@@ -1605,7 +1917,8 @@ export class CoverGrid extends LitElement {
@visibilityChanged=${this.onVisibilityChanged}
></lit-virtualizer>
${this.expandedAlbumId !== null
${this.expandedAlbumId !== null &&
this.expandedTracks.length > 0
? html`
<album-dropdown
class="dropdown-overlay"
@@ -1614,6 +1927,9 @@ export class CoverGrid extends LitElement {
?loading-tracks=${this.loadingTracks}
.selectedTracks=${this.selectedTracks}
.phantomRows=${this.phantomRowCount}
.gridItemHeight=${this.cardHeight}
.gridGap=${CoverGrid.GRID_GAP}
.containerWidth=${this.getContainerWidth()}
@track-click=${this.onTrackClick}
@track-dblclick=${this.onTrackDblClick}
@track-contextmenu=${this.onTrackContextMenu}
@@ -1625,6 +1941,8 @@ export class CoverGrid extends LitElement {
<wa-popup
id="context-menu"
placement="bottom-start"
flip
shift
.active=${this.contextMenuOpen}
>
${this.contextMenuOpen
@@ -1693,6 +2011,8 @@ export class CoverGrid extends LitElement {
<wa-popup
id="playlist-submenu"
placement="right-start"
flip
shift
.active=${this.playlistSubmenuOpen}
>
${this.playlistSubmenuOpen
@@ -135,7 +135,7 @@ export class NowPlaying extends LitElement {
<div class="cover-art">
${track.coverArt
? html`<img
src="${track.coverArtThumbnail || track.coverArt}"
src="${track.coverArtSmall || track.coverArt}"
alt="Album cover"
@error=${(e: Event) => {
const img = e.target as HTMLImageElement;
@@ -849,6 +849,8 @@ export class PlaylistView
<wa-popup
id="context-menu"
placement="bottom-start"
flip
shift
.active=${this.contextMenuOpen}
>
${this.contextMenuOpen
@@ -930,6 +932,8 @@ export class PlaylistView
<wa-popup
id="playlist-submenu"
placement="right-start"
flip
shift
.active=${this.playlistSubmenuOpen}
>
${this.playlistSubmenuOpen &&
@@ -777,6 +777,8 @@ export class QueuePanel
<wa-popup
id="context-menu"
placement="bottom-start"
flip
shift
.active=${this.contextMenuOpen}
>
${this.contextMenuOpen
@@ -834,6 +836,8 @@ export class QueuePanel
<wa-popup
id="playlist-submenu"
placement="right-start"
flip
shift
.active=${this.playlistSubmenuOpen}
>
${this.playlistSubmenuOpen &&
@@ -24,7 +24,7 @@ import { formatMilliseconds } from '@utils/time';
* trackTitle="Song Name"
* artist="Artist Name"
* coverArt="/covers/abc.jpg"
* coverArtThumbnail="/covers/abc_thumb.jpg"
* coverArtSmall="/covers/abc_sm.jpg"
* ></track-info>
* ```
*/
@@ -34,7 +34,7 @@ export class TrackInfo extends LitElement {
@property() artist?: string;
@property() album?: string;
@property() coverArt?: string;
@property() coverArtThumbnail?: string;
@property() coverArtSmall?: string;
@property() duration?: string;
@property() filePath?: string;
@@ -109,7 +109,7 @@ export class TrackInfo extends LitElement {
override render() {
const showCover =
this.coverArt !== undefined || this.coverArtThumbnail !== undefined;
this.coverArt !== undefined || this.coverArtSmall !== undefined;
const displayTitle = this.getDisplayTitle();
const secondaryParts = this.getSecondaryText();
@@ -134,7 +134,7 @@ export class TrackInfo extends LitElement {
}
private renderCoverArt() {
const src = this.coverArtThumbnail ?? this.coverArt;
const src = this.coverArtSmall ?? this.coverArt;
if (!src) {
return html`
@@ -713,6 +713,8 @@ export class TrackList extends LitElement implements SelectionHost {
<wa-popup
id="context-menu"
placement="bottom-start"
flip
shift
.active=${this.contextMenuOpen}
>
${this.contextMenuOpen
@@ -756,6 +758,8 @@ export class TrackList extends LitElement implements SelectionHost {
<wa-popup
id="playlist-submenu"
placement="right-start"
flip
shift
.active=${this.playlistSubmenuOpen}
>
${this.playlistSubmenuOpen && this.selection.hasSelection