.status icons update
@@ -85,6 +85,42 @@ func containsStreamingURL(body []byte) bool {
|
||||
return isStreamingURL(trimmedBody)
|
||||
}
|
||||
|
||||
func containsLRCLIBResults(body []byte) bool {
|
||||
trimmedBody := strings.TrimSpace(string(body))
|
||||
if trimmedBody == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
var searchResults []map[string]interface{}
|
||||
if err := json.Unmarshal(body, &searchResults); err == nil {
|
||||
return len(searchResults) > 0
|
||||
}
|
||||
|
||||
var exactResult map[string]interface{}
|
||||
if err := json.Unmarshal(body, &exactResult); err == nil {
|
||||
return len(exactResult) > 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func containsMusicBrainzResults(body []byte) bool {
|
||||
trimmedBody := strings.TrimSpace(string(body))
|
||||
if trimmedBody == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Count int `json:"count"`
|
||||
Recordings []json.RawMessage `json:"recordings"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return payload.Count > 0 || len(payload.Recordings) > 0
|
||||
}
|
||||
|
||||
func isStreamingURL(raw string) bool {
|
||||
candidate := strings.TrimSpace(raw)
|
||||
if candidate == "" {
|
||||
@@ -948,6 +984,10 @@ func (a *App) CheckAPIStatus(apiType string, apiURL string) bool {
|
||||
checkURL = fmt.Sprintf("%s/api/track/360735657?quality=27", apiURL)
|
||||
} else if apiType == "amazon" {
|
||||
checkURL = fmt.Sprintf("%s/status", apiURL)
|
||||
} else if apiType == "lrclib" {
|
||||
checkURL = fmt.Sprintf("%s/api/search?artist_name=Adele&track_name=Hello", strings.TrimRight(apiURL, "/"))
|
||||
} else if apiType == "musicbrainz" {
|
||||
checkURL = fmt.Sprintf("%s/ws/2/recording?query=%s&fmt=json&limit=1", strings.TrimRight(apiURL, "/"), url.QueryEscape(`recording:"Hello" AND artist:"Adele"`))
|
||||
} else {
|
||||
checkURL = apiURL
|
||||
}
|
||||
@@ -958,6 +998,7 @@ func (a *App) CheckAPIStatus(apiType string, apiURL string) bool {
|
||||
return false, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
maxRetries := 3
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
@@ -981,7 +1022,15 @@ func (a *App) CheckAPIStatus(apiType string, apiURL string) bool {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if apiType != "amazon" && apiType != "qobuz" && apiType != "qbz" && statusCode == 200 {
|
||||
if apiType == "lrclib" && statusCode == 200 && containsLRCLIBResults(body) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if apiType == "musicbrainz" && statusCode == 200 && containsMusicBrainzResults(body) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if apiType != "amazon" && apiType != "qobuz" && apiType != "qbz" && apiType != "lrclib" && apiType != "musicbrainz" && statusCode == 200 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 345 B |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -1,6 +1,6 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RefreshCw, CheckCircle2, XCircle, Loader2 } from "lucide-react";
|
||||
import { TidalIcon, QobuzIcon, AmazonIcon } from "./PlatformIcons";
|
||||
import { TidalIcon, QobuzIcon, AmazonIcon, LrclibIcon, MusicBrainzIcon } from "./PlatformIcons";
|
||||
import { useApiStatus } from "@/hooks/useApiStatus";
|
||||
export function ApiStatusTab() {
|
||||
const { sources, statuses, isCheckingAll, refreshAll } = useApiStatus();
|
||||
@@ -12,12 +12,12 @@ export function ApiStatusTab() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{sources.map((source) => {
|
||||
const status = statuses[source.id] || "idle";
|
||||
return (<div key={source.id} className="flex items-center justify-between p-4 border rounded-lg bg-card text-card-foreground shadow-sm">
|
||||
<div className="flex items-center gap-3">
|
||||
{source.type === "tidal" ? <TidalIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "amazon" ? <AmazonIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : <QobuzIcon className="w-5 h-5 shrink-0 text-muted-foreground"/>}
|
||||
{source.type === "tidal" ? <TidalIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "amazon" ? <AmazonIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "lrclib" ? <LrclibIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "musicbrainz" ? <MusicBrainzIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : <QobuzIcon className="w-5 h-5 shrink-0 text-muted-foreground"/>}
|
||||
<p className="font-medium leading-none">{source.name}</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import amazonMusicIcon from "../assets/icons/amazon-music.png";
|
||||
import qobuzIcon from "../assets/icons/qobuz.png";
|
||||
import tidalIcon from "../assets/icons/tidal.png";
|
||||
const PLATFORM_ICON_URLS = {
|
||||
tidal: tidalIcon,
|
||||
qobuz: qobuzIcon,
|
||||
amazon: amazonMusicIcon,
|
||||
} as const;
|
||||
import amazonMusicIcon from "../assets/icons/amzn.png";
|
||||
import lrclibIcon from "../assets/icons/lrclib.png";
|
||||
import musicBrainzDarkIcon from "../assets/icons/musicbrainz_d.png";
|
||||
import musicBrainzLightIcon from "../assets/icons/musicbrainz_l.png";
|
||||
import qobuzIcon from "../assets/icons/qbz.png";
|
||||
import songlinkDarkIcon from "../assets/icons/songlink_d.png";
|
||||
import songlinkLightIcon from "../assets/icons/songlink_l.png";
|
||||
import songstatsIcon from "../assets/icons/songstats.png";
|
||||
import tidalDarkIcon from "../assets/icons/tidal_d.png";
|
||||
import tidalLightIcon from "../assets/icons/tidal_l.png";
|
||||
type PlatformIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
@@ -48,14 +50,48 @@ function PlatformIcon({ src, alt, className = "w-4 h-4", defaultClassName = "" }
|
||||
.join(" ");
|
||||
return <img src={src} alt={alt} className={imageClassName} loading="lazy" referrerPolicy="no-referrer"/>;
|
||||
}
|
||||
function ThemedPlatformIcon({ lightSrc, darkSrc, alt, className = "w-4 h-4", defaultClassName = "" }: {
|
||||
lightSrc: string;
|
||||
darkSrc: string;
|
||||
alt: string;
|
||||
className?: string;
|
||||
defaultClassName?: string;
|
||||
}) {
|
||||
const cleanedClassName = sanitizeClassName(className);
|
||||
const statusClasses = getStatusClasses(className);
|
||||
const wrapperClassName = [
|
||||
cleanedClassName || "w-4 h-4",
|
||||
"relative inline-flex shrink-0",
|
||||
!hasRoundedClass(cleanedClassName) ? defaultClassName : "",
|
||||
statusClasses,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
return <span role="img" aria-label={alt} className={wrapperClassName}>
|
||||
<img src={lightSrc} alt="" aria-hidden="true" className="h-full w-full object-contain dark:hidden" loading="lazy" referrerPolicy="no-referrer"/>
|
||||
<img src={darkSrc} alt="" aria-hidden="true" className="hidden h-full w-full object-contain dark:block" loading="lazy" referrerPolicy="no-referrer"/>
|
||||
</span>;
|
||||
}
|
||||
export function TidalIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <PlatformIcon src={PLATFORM_ICON_URLS.tidal} alt="Tidal" className={className} defaultClassName="rounded-[4px]"/>;
|
||||
return <ThemedPlatformIcon lightSrc={tidalLightIcon} darkSrc={tidalDarkIcon} alt="Tidal" className={className} defaultClassName="rounded-[4px]"/>;
|
||||
}
|
||||
export function QobuzIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <PlatformIcon src={PLATFORM_ICON_URLS.qobuz} alt="Qobuz" className={className}/>;
|
||||
return <PlatformIcon src={qobuzIcon} alt="Qobuz" className={className}/>;
|
||||
}
|
||||
export function AmazonIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <PlatformIcon src={PLATFORM_ICON_URLS.amazon} alt="Amazon Music" className={className} defaultClassName="rounded-[4px]"/>;
|
||||
return <PlatformIcon src={amazonMusicIcon} alt="Amazon Music" className={className} defaultClassName="rounded-[4px]"/>;
|
||||
}
|
||||
export function LrclibIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <PlatformIcon src={lrclibIcon} alt="LRCLIB" className={className}/>;
|
||||
}
|
||||
export function MusicBrainzIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <ThemedPlatformIcon lightSrc={musicBrainzLightIcon} darkSrc={musicBrainzDarkIcon} alt="MusicBrainz" className={className}/>;
|
||||
}
|
||||
export function SonglinkIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <ThemedPlatformIcon lightSrc={songlinkLightIcon} darkSrc={songlinkDarkIcon} alt="Songlink" className={className} defaultClassName="rounded-[3px]"/>;
|
||||
}
|
||||
export function SongstatsIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <PlatformIcon src={songstatsIcon} alt="Songstats" className={className} defaultClassName="rounded-[3px]"/>;
|
||||
}
|
||||
export function TidalAvailabilityIcon({ className = "w-4 h-4" }: PlatformIconProps) {
|
||||
return <svg viewBox="0 0 24 24" className={`${className} fill-current`}>
|
||||
|
||||
@@ -13,9 +13,7 @@ import { themes, applyTheme } from "@/lib/themes";
|
||||
import { SelectFolder, OpenConfigFolder } from "../../wailsjs/go/main/App";
|
||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||
import { ApiStatusTab } from "./ApiStatusTab";
|
||||
import { AmazonIcon, QobuzIcon, TidalIcon } from "./PlatformIcons";
|
||||
import songlinkIcon from "@/assets/icons/songlink.ico";
|
||||
import songstatsIcon from "@/assets/icons/songstats.png";
|
||||
import { AmazonIcon, QobuzIcon, SonglinkIcon, SongstatsIcon, TidalIcon } from "./PlatformIcons";
|
||||
interface SettingsPageProps {
|
||||
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
||||
onResetRequest?: (resetFn: () => void) => void;
|
||||
@@ -245,13 +243,13 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
|
||||
<SelectContent>
|
||||
<SelectItem value="songlink">
|
||||
<span className="flex items-center gap-2">
|
||||
<img src={songlinkIcon} alt="Songlink" className="h-4 w-4 shrink-0 rounded-[3px] object-contain" loading="lazy"/>
|
||||
<SonglinkIcon className="h-4 w-4 shrink-0"/>
|
||||
Songlink
|
||||
</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="songstats">
|
||||
<span className="flex items-center gap-2">
|
||||
<img src={songstatsIcon} alt="Songstats" className="h-4 w-4 shrink-0 rounded-[3px] object-contain" loading="lazy"/>
|
||||
<SongstatsIcon className="h-4 w-4 shrink-0"/>
|
||||
Songstats
|
||||
</span>
|
||||
</SelectItem>
|
||||
|
||||
@@ -19,6 +19,8 @@ export const API_SOURCES: ApiSource[] = [
|
||||
{ id: "qobuz2", type: "qobuz", name: "Qobuz B", url: "https://dabmusic.xyz" },
|
||||
{ id: "qobuz3", type: "qbz", name: "Qobuz C", url: "https://qbz.afkarxyz.qzz.io" },
|
||||
{ id: "amazon1", type: "amazon", name: "Amazon Music", url: "https://amzn.afkarxyz.qzz.io" },
|
||||
{ id: "lrclib", type: "lrclib", name: "LRCLIB", url: "https://lrclib.net" },
|
||||
{ id: "musicbrainz", type: "musicbrainz", name: "MusicBrainz", url: "https://musicbrainz.org" },
|
||||
];
|
||||
type ApiStatusState = {
|
||||
isCheckingAll: boolean;
|
||||
|
||||