feat(explore): mark what is not owned, everywhere it can be shown
`explore-album-details` had the rule right for one tracklist and nothing else did: Explore's cards, `top-results-row` and the artist page's three card shapes all mixed owned and unowned with a small badge as the only difference, and drew a green tick on the *common* case — which is the treatment that tracklist's own green ticks were removed for. `utils/ownership.ts` is the rule written once, so eight call sites stop each holding their own version: - owned is plain, and draws no badge at all; - unowned is dimmed *and* says so in its accessible name, because dimming is a colour and cannot be the only signal; - a partly-held album says how partly. **Ownership is a file, and `localId` is the flag that says so.** The album page answers with `filePaths`, a real file per displayed track; a card grid cannot afford that and does not need to, because `local_*_id` is built by queries that all join `audio_files` and cleared by a prune whose existence test is a file test in every case. `inLibrary` is written by the same pass, so the two agree in a healthy database — but it is a one-way ratchet (`MAX(in_library, excluded)`) whose only clearing pass is gated on a non-null local id, so it cannot be un-set on its own. Where they already diverged was the client. Both `explore-view` and `explore-artist-details` kept a `libraryMBIDs` set that accumulated every MBID ever seen with `inLibrary` and cleared it never, in views that never unmount. Both are deleted. And one card answered the question twice and got two answers: `renderReleaseMenuItems` gates Play on `localId > 0` while the badge and `albumTarget.owned` used `inLibrary`, so an album with the flag and no local row drew a tick saying it was in your library, offered no Play, and — the request item being gated on *not* owned — offered no way to ask for it either. The count comes from `completenessStore`, shaped like `credit-store`: `request()` is per-card and coalesces a screenful into one `GetAlbumsCompleteness`, absence is cached as an answer, and the whole cache is dropped on a scan, a retag or a removal rather than aged. `aria-disabled` goes on rows that cannot be activated and deliberately not on cards: an unowned card still navigates to the catalog page for it, which is a perfectly good thing to do with something you do not own. Audited and unchanged: `home-view`, `downloads-view`, `cover-grid`, `artist-details` and `genre-details` cannot show catalog content, so everything on them is owned and "owned is plain" is already what they do. The album page's own header badge stays, because that page is about one entity and the badge is its answer rather than a mark on one of many. Closes #38
This commit is contained in:
@@ -3,6 +3,7 @@ import { customElement, property, state, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import { srOnly } from '../../styles/sr-only.css';
|
||||
import { unownedLabel, unownedStyles } from '@utils/ownership';
|
||||
import {
|
||||
LookupReleaseGroup,
|
||||
BrowseReleases,
|
||||
@@ -316,6 +317,7 @@ export class ExploreAlbumDetails extends LitElement implements ContextMenuHost {
|
||||
exploreLinkStyles,
|
||||
contextMenuStyles,
|
||||
srOnly,
|
||||
unownedStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
@@ -687,20 +689,14 @@ export class ExploreAlbumDetails extends LitElement implements ContextMenuHost {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* A track the library does not have, on the pattern a
|
||||
* streaming service uses for something it cannot play: the
|
||||
* row stays, dimmed, so the album reads as the album rather
|
||||
* than as the subset that happens to be here.
|
||||
*
|
||||
* The dimming is a colour, so it cannot be the only signal
|
||||
* — the row also carries aria-disabled, which is what
|
||||
* reaches anyone not seeing it. Secondary rather than
|
||||
* tertiary because the row's hover background is
|
||||
* bgOverlay, which tertiary does not clear. */
|
||||
.track-row.unowned .track-title {
|
||||
color: var(--yj-text-secondary, #b3b3b3);
|
||||
font-weight: 400;
|
||||
}
|
||||
/* The dimming itself is unownedStyles, from
|
||||
* utils/ownership.ts, imported above. It was written here
|
||||
* first — this tracklist is where the treatment came from —
|
||||
* and moved out when seven other surfaces had to draw the
|
||||
* same thing, because two of them would otherwise have
|
||||
* ended up drawing it slightly differently. (No backticks or
|
||||
* apostrophes-as-quotes here: this is inside a tagged
|
||||
* template literal.) */
|
||||
|
||||
/* The request control is offered on every row that has
|
||||
* something to request, and is not revealed on hover.
|
||||
@@ -3273,7 +3269,7 @@ export class ExploreAlbumDetails extends LitElement implements ContextMenuHost {
|
||||
aria-disabled=${owned ? 'false' : 'true'}
|
||||
aria-label=${owned
|
||||
? `Play “${track.title}”`
|
||||
: `${track.title} — not in your library`}
|
||||
: unownedLabel(track.title, 'track')}
|
||||
@dblclick=${() => this.onTrackRowDblClick(track)}
|
||||
@contextmenu=${(e: MouseEvent) => this.onTrackContextMenu(e, track)}
|
||||
@keydown=${(e: KeyboardEvent) => this.onTrackRowKeydown(e, track)}
|
||||
|
||||
Reference in New Issue
Block a user