mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
06140a490b
* feat(queue): add Now-Playing Info tab with artist bio, song credits and Bandsintown tour dates A third tab in the right-side queue panel surfaces context for the currently playing track: - Artist card: image + biography from Subsonic getArtistInfo (Last.fm "Read more on Last.fm" anchor stripped), with read-more toggle that only appears when the bio actually overflows the 4-line clamp. - Song info: contributor credits from OpenSubsonic contributors[] rendered stacked (name prominent, role muted). Section is hidden entirely on servers without contributor support, and rows that just re-state the main artist under role "artist" are filtered out. - On tour: optional Bandsintown integration (opt-in, off by default). HTTP fetch + JSON parsing happen entirely on the Rust side; the frontend wrapper deduplicates concurrent calls and caches results in RAM for the session. Limited to 5 events with a "Show N more" toggle. When the toggle is off, the section becomes an in-place opt-in card with a privacy info-tooltip explaining what data is sent — same tooltip is also exposed on the matching toggle in Settings. Caching: artist info and song detail are memoised by stable IDs across component remounts, so jumping between tracks of the same album/artist does not refire the network calls. Implementation notes: - Bandsintown app_id "js_app_id" — arbitrary strings (e.g. "psysonic") now return HTTP 403 from rest.bandsintown.com; js_app_id is the ID Bandsintown's own embeddable widget uses and is broadly accepted. - Tour items use negative left/right margins so the date badge stays visually aligned with the section title while the hover background extends slightly past the section edges. - New i18n namespace nowPlayingInfo across all 8 locales (en, de, fr, nl, zh, nb, ru, es) including pluralised "Show N more" forms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(now-playing-info): switch nested flex-columns to block layout to stop content-dependent drift Repeated reports of the Tour and Song-info sections rendering at inconsistent x-positions (sometimes left of the section title, sometimes centred, sometimes correctly aligned) — varying by artist, sidebar width, and even whether "Show more" was clicked. Root cause: nested flex-direction: column containers (.np-info → .np-info-section → .np-info-tour / .np-info-credits) where the cross-axis (horizontal) sizing on WebKitGTK occasionally inherits the intrinsic min-content of the longest child. With one "Naherholungsgebiet/Freizeitgelände Lago Alfredo"-style venue name that overflows the sidebar, the parent ul gets pushed past 100% width and the entire layout drifts. min-width: 0 on every level didn't help because cross-axis stretch in flex-column ignores it for content-driven overflow. Fix: collapse all the section/list containers to display: block. Block layout is content-agnostic — children always render at 100% parent width, left-aligned, deterministically. Spacing between siblings now uses the lobotomy selector (`> * + * { margin-top }`). Only the tour item itself stays flex (badge ↔ meta horizontal layout), and that one still has overflow: hidden + flex: 1 1 0 + min-width: 0 on the meta column to truncate venue/place text with ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Psychotoxical <dev@psysonic.app> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
1.9 KiB
TypeScript
74 lines
1.9 KiB
TypeScript
import { invoke } from '@tauri-apps/api/core';
|
|
|
|
/** Snake_case payload from the Rust `fetch_bandsintown_events` command. */
|
|
interface RawBandsintownEvent {
|
|
datetime: string;
|
|
venue_name: string;
|
|
venue_city: string;
|
|
venue_region: string;
|
|
venue_country: string;
|
|
url: string;
|
|
on_sale_datetime: string;
|
|
lineup: string[];
|
|
}
|
|
|
|
export interface BandsintownEvent {
|
|
datetime: string;
|
|
venueName: string;
|
|
venueCity: string;
|
|
venueRegion: string;
|
|
venueCountry: string;
|
|
url: string;
|
|
onSaleDatetime: string;
|
|
lineup: string[];
|
|
}
|
|
|
|
const cache = new Map<string, BandsintownEvent[]>();
|
|
const inflight = new Map<string, Promise<BandsintownEvent[]>>();
|
|
|
|
function cacheKey(name: string): string {
|
|
return name.trim().toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* Fetch upcoming events for an artist. Results are cached in RAM for the session
|
|
* (no TTL — restart drops them). Concurrent calls for the same artist share one
|
|
* inflight promise. Failures resolve to an empty array — never throws.
|
|
*/
|
|
export async function fetchBandsintownEvents(artistName: string): Promise<BandsintownEvent[]> {
|
|
const key = cacheKey(artistName);
|
|
if (!key) return [];
|
|
const hit = cache.get(key);
|
|
if (hit) return hit;
|
|
const pending = inflight.get(key);
|
|
if (pending) return pending;
|
|
|
|
const promise = (async () => {
|
|
try {
|
|
const raw = await invoke<RawBandsintownEvent[]>('fetch_bandsintown_events', {
|
|
artistName,
|
|
});
|
|
const events: BandsintownEvent[] = (raw ?? []).map(r => ({
|
|
datetime: r.datetime,
|
|
venueName: r.venue_name,
|
|
venueCity: r.venue_city,
|
|
venueRegion: r.venue_region,
|
|
venueCountry: r.venue_country,
|
|
url: r.url,
|
|
onSaleDatetime: r.on_sale_datetime,
|
|
lineup: r.lineup ?? [],
|
|
}));
|
|
cache.set(key, events);
|
|
return events;
|
|
} catch {
|
|
cache.set(key, []);
|
|
return [];
|
|
} finally {
|
|
inflight.delete(key);
|
|
}
|
|
})();
|
|
|
|
inflight.set(key, promise);
|
|
return promise;
|
|
}
|