fix(frontend): reposition search indicator into toolbar and fix album cover art lookup
Move search indicator from absolute-positioned overlay into sort toolbar (or dedicated search-bar-row for artists/genres views). Shows indicator on empty-state screens. Fix cover art not displaying for expanded album tracks by checking expandedAlbumId before the albumName guard. Add ScanWarning model bindings.
This commit is contained in:
@@ -327,12 +327,22 @@ export class ArtistsView
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.search-bar-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 30px;
|
||||
border-bottom: 1px solid
|
||||
var(--yj-border-subtle, #333);
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.search-indicator {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
background: var(
|
||||
--yj-bg-overlay,
|
||||
@@ -343,7 +353,7 @@ export class ArtistsView
|
||||
#b3b3b3
|
||||
);
|
||||
font-size: 12px;
|
||||
padding: 4px 14px;
|
||||
padding: 2px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid
|
||||
var(--yj-border-subtle, #555);
|
||||
@@ -1186,9 +1196,19 @@ export class ArtistsView
|
||||
}
|
||||
|
||||
const entries = this.cachedGridEntries;
|
||||
const searchBar = this.searchCtrl.term
|
||||
? html`<div class="search-bar-row">
|
||||
<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl
|
||||
.term}”
|
||||
</div>
|
||||
</div>`
|
||||
: nothing;
|
||||
|
||||
if (entries.length === 0) {
|
||||
return html`
|
||||
${searchBar}
|
||||
<div class="empty-message">
|
||||
${this.searchCtrl.term
|
||||
? 'No artists match your search.'
|
||||
@@ -1198,15 +1218,7 @@ export class ArtistsView
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.searchCtrl.term
|
||||
? html`<div
|
||||
class="search-indicator"
|
||||
>
|
||||
Showing results for
|
||||
“${this.searchCtrl
|
||||
.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
${searchBar}
|
||||
<div
|
||||
class="grid-scroll-container"
|
||||
style=${this.restoringScroll
|
||||
|
||||
@@ -313,10 +313,12 @@ export class AlbumSelectionManager {
|
||||
albumName: string,
|
||||
expandedAlbumId: number | null,
|
||||
): CoverArtUrls | null {
|
||||
if (!albumName) return null;
|
||||
|
||||
// Prefer the expanded album (we know the track
|
||||
// belongs to it) for an O(1) lookup.
|
||||
// belongs to it) for an O(1) lookup. This must
|
||||
// run before the albumName guard because
|
||||
// GetAlbumTracks returns tracks without an Album
|
||||
// field, so albumName may be empty even when the
|
||||
// album ID is known.
|
||||
if (expandedAlbumId !== null) {
|
||||
const album = this.albumById.get(
|
||||
expandedAlbumId,
|
||||
@@ -333,6 +335,8 @@ export class AlbumSelectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (!albumName) return null;
|
||||
|
||||
// Fallback: name-based search across all albums.
|
||||
for (const album of this.albumById.values()) {
|
||||
if (
|
||||
|
||||
@@ -243,17 +243,19 @@ const gridStyles = css`
|
||||
color: var(--yj-text-secondary, #b3b3b3);
|
||||
}
|
||||
|
||||
.sort-toolbar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-indicator {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
background: var(--yj-bg-overlay, #495057);
|
||||
color: var(--yj-text-secondary, #b3b3b3);
|
||||
font-size: var(--yj-text-sm);
|
||||
padding: 4px 14px;
|
||||
padding: 2px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid
|
||||
var(--yj-border-subtle, #555);
|
||||
|
||||
@@ -1613,6 +1613,12 @@ export class CoverGrid
|
||||
name=${dirIcon}
|
||||
></wa-icon>
|
||||
</button>
|
||||
${this.searchCtrl.term
|
||||
? html`<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this.renderSortDropdownPopup()}
|
||||
`;
|
||||
@@ -1812,6 +1818,7 @@ export class CoverGrid
|
||||
|
||||
if (this.cachedFilteredAlbums.length === 0) {
|
||||
return html`
|
||||
${this.renderSortToolbar()}
|
||||
<div class="empty-state">
|
||||
<p>No albums match your search.</p>
|
||||
</div>
|
||||
@@ -1824,12 +1831,6 @@ export class CoverGrid
|
||||
|
||||
return html`
|
||||
${this.renderSortToolbar()}
|
||||
${this.searchCtrl.term
|
||||
? html`<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
<div
|
||||
class="grid-scroll-container"
|
||||
@click=${this.onGridClick}
|
||||
|
||||
@@ -326,13 +326,23 @@ export class GenresView
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.search-indicator {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
.search-bar-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 30px;
|
||||
border-bottom: 1px solid
|
||||
var(--yj-border-subtle, #333);
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.search-indicator {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: none;
|
||||
background: var(
|
||||
--yj-bg-overlay,
|
||||
#495057
|
||||
@@ -342,7 +352,7 @@ export class GenresView
|
||||
#b3b3b3
|
||||
);
|
||||
font-size: 12px;
|
||||
padding: 4px 14px;
|
||||
padding: 2px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid
|
||||
var(--yj-border-subtle, #555);
|
||||
@@ -1138,9 +1148,19 @@ export class GenresView
|
||||
}
|
||||
|
||||
const entries = this.cachedGridEntries;
|
||||
const searchBar = this.searchCtrl.term
|
||||
? html`<div class="search-bar-row">
|
||||
<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl
|
||||
.term}”
|
||||
</div>
|
||||
</div>`
|
||||
: nothing;
|
||||
|
||||
if (entries.length === 0) {
|
||||
return html`
|
||||
${searchBar}
|
||||
<div class="empty-message">
|
||||
${this.searchCtrl.term
|
||||
? 'No genres match your search.'
|
||||
@@ -1150,15 +1170,7 @@ export class GenresView
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.searchCtrl.term
|
||||
? html`<div
|
||||
class="search-indicator"
|
||||
>
|
||||
Showing results for
|
||||
“${this.searchCtrl
|
||||
.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
${searchBar}
|
||||
<div
|
||||
class="grid-scroll-container"
|
||||
style=${this.restoringScroll
|
||||
|
||||
@@ -745,12 +745,14 @@ export class PlaylistView
|
||||
color: var(--yj-text-secondary, #b3b3b3);
|
||||
}
|
||||
|
||||
.sort-toolbar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-indicator {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
background: var(--yj-bg-overlay, #495057);
|
||||
color: var(
|
||||
@@ -758,7 +760,7 @@ export class PlaylistView
|
||||
#b3b3b3
|
||||
);
|
||||
font-size: 12px;
|
||||
padding: 4px 14px;
|
||||
padding: 2px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid
|
||||
var(--yj-border-subtle, #555);
|
||||
@@ -2442,6 +2444,12 @@ export class PlaylistView
|
||||
name=${dirIcon}
|
||||
></wa-icon>
|
||||
</button>
|
||||
${this.searchCtrl.term
|
||||
? html`<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this.renderSortDropdownPopup()}
|
||||
`;
|
||||
@@ -2532,15 +2540,6 @@ export class PlaylistView
|
||||
|
||||
${this.renderSortToolbar()}
|
||||
|
||||
${this.searchCtrl.term &&
|
||||
this.filteredEntries.length > 0
|
||||
? html`<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl
|
||||
.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
|
||||
${this.creating
|
||||
? this.renderCreateForm()
|
||||
: nothing}
|
||||
|
||||
@@ -897,17 +897,19 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
|
||||
background-color: var(--yj-text-tertiary, #6c757d);
|
||||
}
|
||||
|
||||
.sort-toolbar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-indicator {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
background: var(--yj-bg-overlay, #495057);
|
||||
color: var(--yj-text-secondary, #b3b3b3);
|
||||
font-size: var(--yj-text-sm);
|
||||
padding: 4px 14px;
|
||||
padding: 2px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--yj-border-subtle, #555);
|
||||
white-space: nowrap;
|
||||
@@ -1640,6 +1642,12 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
${this.searchCtrl.term
|
||||
? html`<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this.renderSortDropdownPopup()}
|
||||
`;
|
||||
@@ -1743,13 +1751,6 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
|
||||
></lit-virtualizer>
|
||||
`}
|
||||
|
||||
${this.searchCtrl.term && visibleTracks.length > 0
|
||||
? html`<div class="search-indicator">
|
||||
Showing results for
|
||||
“${this.searchCtrl.term}”
|
||||
</div>`
|
||||
: nothing}
|
||||
|
||||
<div class="resize-overlay">
|
||||
${this.colBoundaryPositions.map(
|
||||
(pos, i) => html`
|
||||
|
||||
@@ -66,6 +66,22 @@ export namespace library {
|
||||
|
||||
}
|
||||
}
|
||||
export class ScanWarning {
|
||||
filePath: string;
|
||||
phase: string;
|
||||
err: any;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ScanWarning(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.filePath = source["filePath"];
|
||||
this.phase = source["phase"];
|
||||
this.err = source["err"];
|
||||
}
|
||||
}
|
||||
export class ScanMetrics {
|
||||
total: number;
|
||||
loadExisting: number;
|
||||
@@ -92,6 +108,7 @@ export namespace library {
|
||||
updated: number;
|
||||
skipped: number;
|
||||
removed: number;
|
||||
warnings: ScanWarning[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ScanMetrics(source);
|
||||
@@ -124,8 +141,28 @@ export namespace library {
|
||||
this.updated = source["updated"];
|
||||
this.skipped = source["skipped"];
|
||||
this.removed = source["removed"];
|
||||
this.warnings = this.convertValues(source["warnings"], ScanWarning);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
export class Track {
|
||||
TrackName: string;
|
||||
ArtistName: string;
|
||||
|
||||
Reference in New Issue
Block a user