This commit is contained in:
afkarxyz
2026-04-14 07:28:39 +07:00
parent ce1e6cc65a
commit a9c52e7b6d
10 changed files with 35 additions and 58 deletions
+6 -6
View File
@@ -211,10 +211,10 @@ export function AlbumInfo({ albumInfo, trackList, searchQuery, sortBy, selectedT
<span className="font-medium">
{clickableAlbumArtists.length > 0 ? clickableAlbumArtists.map((artist, index) => (<span key={`${artist.id || artist.name}-${index}`}>
{onArtistClick && artist.external_urls ? (<span className="cursor-pointer hover:underline" onClick={() => onArtistClick({
id: artist.id,
name: artist.name,
external_urls: artist.external_urls,
})}>
id: artist.id,
name: artist.name,
external_urls: artist.external_urls,
})}>
{artist.name}
</span>) : (artist.name)}
{index < clickableAlbumArtists.length - 1 && artistSeparator}
@@ -225,8 +225,8 @@ export function AlbumInfo({ albumInfo, trackList, searchQuery, sortBy, selectedT
<span></span>
<span>
{showStreamingProgress
? `${fetchedTrackCount.toLocaleString()} / ${totalTrackCount.toLocaleString()} tracks`
: `${Math.max(totalTrackCount, fetchedTrackCount).toLocaleString()} ${Math.max(totalTrackCount, fetchedTrackCount) === 1 ? "track" : "tracks"}`}
? `${fetchedTrackCount.toLocaleString()} / ${totalTrackCount.toLocaleString()} tracks`
: `${Math.max(totalTrackCount, fetchedTrackCount).toLocaleString()} ${Math.max(totalTrackCount, fetchedTrackCount) === 1 ? "track" : "tracks"}`}
</span>
</div>
</div>
+5 -26
View File
@@ -2,19 +2,16 @@ import type { ReactNode } from "react";
import type { TrackAvailability } from "@/types/api";
import { openExternal } from "@/lib/utils";
import { AmazonAvailabilityIcon, QobuzAvailabilityIcon, TidalAvailabilityIcon } from "./PlatformIcons";
interface AvailabilityLinkEntry {
id: string;
found: boolean;
url?: string;
icon: ReactNode;
}
function getAvailabilityLinkEntries(availability: TrackAvailability): AvailabilityLinkEntry[] {
const tidalUrl = availability.tidal_url?.trim() || "";
const qobuzUrl = availability.qobuz_url?.trim() || "";
const amazonUrl = availability.amazon_url?.trim() || "";
return [
{
id: "tidal",
@@ -36,48 +33,30 @@ function getAvailabilityLinkEntries(availability: TrackAvailability): Availabili
},
];
}
export function hasAvailabilityLinks(availability?: TrackAvailability): boolean {
if (!availability) {
return false;
}
return getAvailabilityLinkEntries(availability).some((entry) => entry.found);
}
export function AvailabilityLinks({ availability }: {
availability?: TrackAvailability;
}) {
if (!availability) {
return <p>Check Availability</p>;
}
const entries = getAvailabilityLinkEntries(availability);
return (
<div className="flex flex-col gap-1.5 w-[260px] max-w-[260px] pointer-events-auto">
{entries.map((entry) => entry.found ? (
<button
key={entry.id}
type="button"
onClick={() => entry.url && openExternal(entry.url)}
className="flex items-center gap-2 text-left text-xs hover:underline min-w-0 cursor-pointer"
title={entry.url}
>
return (<div className="flex flex-col gap-1.5 w-[260px] max-w-[260px] pointer-events-auto">
{entries.map((entry) => entry.found ? (<button key={entry.id} type="button" onClick={() => entry.url && openExternal(entry.url)} className="flex items-center gap-2 text-left text-xs hover:underline min-w-0 cursor-pointer" title={entry.url}>
{entry.icon}
<span className="truncate whitespace-nowrap leading-5 min-w-0">
{entry.url}
</span>
</button>
) : (
<div
key={entry.id}
className="flex items-center gap-2 text-left text-xs min-w-0"
>
</button>) : (<div key={entry.id} className="flex items-center gap-2 text-left text-xs min-w-0">
{entry.icon}
<span className="truncate whitespace-nowrap leading-5 min-w-0 text-red-500">
Not Found
</span>
</div>
))}
</div>
);
</div>))}
</div>);
}
+2 -2
View File
@@ -188,8 +188,8 @@ export function PlaylistInfo({ playlistInfo, trackList, searchQuery, sortBy, sel
<span></span>
<span>
{showStreamingProgress
? `${fetchedTrackCount.toLocaleString()} / ${totalTrackCount.toLocaleString()} tracks`
: `${Math.max(totalTrackCount, fetchedTrackCount).toLocaleString()} ${Math.max(totalTrackCount, fetchedTrackCount) === 1 ? "track" : "tracks"}`}
? `${fetchedTrackCount.toLocaleString()} / ${totalTrackCount.toLocaleString()} tracks`
: `${Math.max(totalTrackCount, fetchedTrackCount).toLocaleString()} ${Math.max(totalTrackCount, fetchedTrackCount) === 1 ? "track" : "tracks"}`}
</span>
<span></span>
<span>{playlistInfo.followers.total.toLocaleString()} {playlistInfo.followers.total === 1 ? "follower" : "followers"}</span>
+6 -8
View File
@@ -5,7 +5,6 @@ import { fetchCurrentIPInfo } from "@/lib/api";
import type { CurrentIPInfo } from "@/types/api";
import { openExternal } from "@/lib/utils";
import { useEffect, useRef, useState } from "react";
const IP_INFO_REFRESH_INTERVAL_MS = 30000;
const SPOTIFY_BLOCKED_COUNTRY_CODES = new Set([
"AF",
@@ -25,7 +24,6 @@ const SPOTIFY_BLOCKED_COUNTRY_CODES = new Set([
"TM",
"YE",
]);
export function TitleBar() {
const [currentIPInfo, setCurrentIPInfo] = useState<CurrentIPInfo | null>(null);
const [isLoadingCurrentIPInfo, setIsLoadingCurrentIPInfo] = useState(false);
@@ -117,12 +115,12 @@ export function TitleBar() {
{detectedFlagPath ? (<img src={detectedFlagPath} alt={detectedCountryCode} className="h-3.5 w-[18px] rounded-[2px] border object-cover bg-muted"/>) : (<Globe className="w-4 h-4 opacity-70"/>)}
<span className="font-mono text-xs truncate">
{isLoadingCurrentIPInfo
? "Detecting..."
: currentIPInfo
? showIPAddress
? `${currentIPInfo.ip} - ${currentIPInfo.country}${detectedCountryCode ? ` (${detectedCountryCode})` : ""}`
: `${currentIPInfo.country}${detectedCountryCode ? ` (${detectedCountryCode})` : ""}`
: "Unavailable"}
? "Detecting..."
: currentIPInfo
? showIPAddress
? `${currentIPInfo.ip} - ${currentIPInfo.country}${detectedCountryCode ? ` (${detectedCountryCode})` : ""}`
: `${currentIPInfo.country}${detectedCountryCode ? ` (${detectedCountryCode})` : ""}`
: "Unavailable"}
</span>
</div>
{currentIPInfo && !isLoadingCurrentIPInfo && (<button type="button" onClick={() => setShowIPAddress((prev) => !prev)} className="inline-flex h-6 w-6 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted hover:text-foreground transition-colors" aria-label={showIPAddress ? "Hide IP" : "Show IP"}>
+4 -4
View File
@@ -85,10 +85,10 @@ export function TrackInfo({ track, isDownloading, downloadingTrack, isDownloaded
<p className="text-lg text-muted-foreground">
{clickableArtists.length > 0 ? clickableArtists.map((artist, index) => (<span key={`${artist.id || artist.name}-${index}`}>
{onArtistClick ? (<span className="cursor-pointer hover:underline" onClick={() => onArtistClick({
id: artist.id,
name: artist.name,
external_urls: artist.external_urls,
})}>
id: artist.id,
name: artist.name,
external_urls: artist.external_urls,
})}>
{artist.name}
</span>) : (artist.name)}
{index < clickableArtists.length - 1 && ", "}
+4 -4
View File
@@ -257,10 +257,10 @@ export function TrackList({ tracks, searchQuery, sortBy, selectedTracks, downloa
}
return clickableArtists.map((artist, i) => (<span key={`${artist.id || artist.name}-${i}`}>
{onArtistClick ? (<span className="cursor-pointer hover:underline" onClick={() => onArtistClick({
id: artist.id,
name: artist.name,
external_urls: artist.external_urls,
})}>
id: artist.id,
name: artist.name,
external_urls: artist.external_urls,
})}>
{artist.name}
</span>) : (artist.name)}
{i < clickableArtists.length - 1 && ", "}
+4 -2
View File
@@ -33,8 +33,10 @@ const CheckFilesExistence = (outputDir: string, rootDir: string, tracks: CheckFi
const SkipDownloadItem = (itemID: string, filePath: string): Promise<void> => (window as any)["go"]["main"]["App"]["SkipDownloadItem"](itemID, filePath);
const CreateM3U8File = (playlistName: string, outputDir: string, filePaths: string[]): Promise<void> => (window as any)["go"]["main"]["App"]["CreateM3U8File"](playlistName, outputDir, filePaths);
const GetTrackISRC = (spotifyId: string): Promise<string> => (window as any)["go"]["main"]["App"]["GetTrackISRC"](spotifyId);
async function resolveTemplateISRC(settings: { folderTemplate?: string; filenameTemplate?: string }, spotifyId?: string): Promise<string> {
async function resolveTemplateISRC(settings: {
folderTemplate?: string;
filenameTemplate?: string;
}, spotifyId?: string): Promise<string> {
if (!spotifyId) {
return "";
}
+4 -2
View File
@@ -6,8 +6,10 @@ import { joinPath, sanitizePath, getFirstArtist } from "@/lib/utils";
import { logger } from "@/lib/logger";
import type { TrackMetadata } from "@/types/api";
const GetTrackISRC = (spotifyId: string): Promise<string> => (window as any)["go"]["main"]["App"]["GetTrackISRC"](spotifyId);
async function resolveTemplateISRC(settings: { folderTemplate?: string; filenameTemplate?: string }, spotifyId?: string): Promise<string> {
async function resolveTemplateISRC(settings: {
folderTemplate?: string;
filenameTemplate?: string;
}, spotifyId?: string): Promise<string> {
if (!spotifyId) {
return "";
}
-1
View File
@@ -32,7 +32,6 @@ let apiStatusState: ApiStatusState = {
};
let activeCheckAll: Promise<void> | null = null;
const listeners = new Set<() => void>();
type SpotiFLACUnifiedStatusResponse = {
tidal?: string;
qobuz_a?: string;
-3
View File
@@ -1,11 +1,9 @@
import type { ArtistSimple } from "@/types/api";
export interface ClickableArtist {
id: string;
name: string;
external_urls: string;
}
export function splitArtistNames(value: string): string[] {
const trimmed = value.trim();
if (!trimmed) {
@@ -14,7 +12,6 @@ export function splitArtistNames(value: string): string[] {
const parts = trimmed.split(/\s*[;,]\s*/).map((part) => part.trim()).filter(Boolean);
return parts.length > 0 ? parts : [trimmed];
}
export function buildClickableArtists(artists: string, artistsData?: ArtistSimple[], fallbackArtistId?: string, fallbackArtistUrl?: string): ClickableArtist[] {
const names = splitArtistNames(artists);
if (names.length === 0) {