v7.0.7
This commit is contained in:
@@ -19,6 +19,7 @@ interface CheckFileExistenceRequest {
|
||||
filename_format?: string;
|
||||
include_track_number?: boolean;
|
||||
audio_format?: string;
|
||||
relative_path?: string;
|
||||
}
|
||||
interface FileExistenceResult {
|
||||
spotify_id: string;
|
||||
@@ -29,7 +30,7 @@ interface FileExistenceResult {
|
||||
}
|
||||
const CheckFilesExistence = (outputDir: string, tracks: CheckFileExistenceRequest[]): Promise<FileExistenceResult[]> => (window as any)["go"]["main"]["App"]["CheckFilesExistence"](outputDir, tracks);
|
||||
const SkipDownloadItem = (itemID: string, filePath: string): Promise<void> => (window as any)["go"]["main"]["App"]["SkipDownloadItem"](itemID, filePath);
|
||||
export function useDownload() {
|
||||
export function useDownload(region: string) {
|
||||
const [downloadProgress, setDownloadProgress] = useState<number>(0);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [downloadingTrack, setDownloadingTrack] = useState<string | null>(null);
|
||||
@@ -141,7 +142,7 @@ export function useDownload() {
|
||||
if (spotifyId) {
|
||||
try {
|
||||
const { GetStreamingURLs } = await import("../../wailsjs/go/main/App");
|
||||
const urlsJson = await GetStreamingURLs(spotifyId);
|
||||
const urlsJson = await GetStreamingURLs(spotifyId, region);
|
||||
streamingURLs = JSON.parse(urlsJson);
|
||||
}
|
||||
catch (err) {
|
||||
@@ -372,8 +373,9 @@ export function useDownload() {
|
||||
year: yearValue,
|
||||
playlist: folderName?.replace(/\//g, placeholder),
|
||||
};
|
||||
const useAlbumTag = settings.folderTemplate?.includes("{album}");
|
||||
if (folderName && (!isAlbum || !useAlbumTag)) {
|
||||
const folderTemplate = settings.folderTemplate || "";
|
||||
const useAlbumSubfolder = folderTemplate.includes("{album}") || folderTemplate.includes("{album_artist}") || folderTemplate.includes("{playlist}");
|
||||
if (folderName && (!isAlbum || !useAlbumSubfolder)) {
|
||||
outputDir = joinPath(os, outputDir, sanitizePath(folderName.replace(/\//g, " "), os));
|
||||
}
|
||||
if (settings.folderTemplate) {
|
||||
@@ -391,7 +393,7 @@ export function useDownload() {
|
||||
if (spotifyId) {
|
||||
try {
|
||||
const { GetStreamingURLs } = await import("../../wailsjs/go/main/App");
|
||||
const urlsJson = await GetStreamingURLs(spotifyId);
|
||||
const urlsJson = await GetStreamingURLs(spotifyId, region);
|
||||
streamingURLs = JSON.parse(urlsJson);
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -2,13 +2,11 @@ import { useState } from "react";
|
||||
import { fetchSpotifyMetadata } from "@/lib/api";
|
||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { AddFetchHistory } from "../../wailsjs/go/main/App";
|
||||
import type { SpotifyMetadataResponse } from "@/types/api";
|
||||
export function useMetadata() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [metadata, setMetadata] = useState<SpotifyMetadataResponse | null>(null);
|
||||
const [showTimeoutDialog, setShowTimeoutDialog] = useState(false);
|
||||
const [timeoutValue, setTimeoutValue] = useState(60);
|
||||
const [pendingUrl, setPendingUrl] = useState("");
|
||||
const [showAlbumDialog, setShowAlbumDialog] = useState(false);
|
||||
const [selectedAlbum, setSelectedAlbum] = useState<{
|
||||
id: string;
|
||||
@@ -27,6 +25,57 @@ export function useMetadata() {
|
||||
return "artist";
|
||||
return "unknown";
|
||||
};
|
||||
const saveToHistory = async (url: string, data: SpotifyMetadataResponse) => {
|
||||
try {
|
||||
let name = "";
|
||||
let info = "";
|
||||
let image = "";
|
||||
let type = "unknown";
|
||||
if ("track" in data) {
|
||||
type = "track";
|
||||
name = data.track.name;
|
||||
info = data.track.artists;
|
||||
image = (data.track.images && data.track.images.length > 0) ? data.track.images : "";
|
||||
}
|
||||
else if ("album_info" in data) {
|
||||
type = "album";
|
||||
name = data.album_info.name;
|
||||
info = `${data.track_list.length} tracks`;
|
||||
image = data.album_info.images;
|
||||
}
|
||||
else if ("playlist_info" in data) {
|
||||
type = "playlist";
|
||||
if (data.playlist_info.name) {
|
||||
name = data.playlist_info.name;
|
||||
}
|
||||
else if (data.playlist_info.owner.name) {
|
||||
name = data.playlist_info.owner.name;
|
||||
}
|
||||
info = `${data.playlist_info.tracks.total} tracks`;
|
||||
image = data.playlist_info.cover || "";
|
||||
}
|
||||
else if ("artist_info" in data) {
|
||||
type = "artist";
|
||||
name = data.artist_info.name;
|
||||
info = `${data.artist_info.total_albums || data.album_list.length} albums`;
|
||||
image = data.artist_info.images;
|
||||
}
|
||||
const jsonStr = JSON.stringify(data);
|
||||
await AddFetchHistory({
|
||||
id: crypto.randomUUID(),
|
||||
url: url,
|
||||
type: type,
|
||||
name: name,
|
||||
info: info,
|
||||
image: image,
|
||||
data: jsonStr,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Failed to save fetch history:", err);
|
||||
}
|
||||
};
|
||||
const fetchMetadataDirectly = async (url: string) => {
|
||||
const urlType = getUrlType(url);
|
||||
logger.info(`fetching ${urlType} metadata...`);
|
||||
@@ -35,7 +84,8 @@ export function useMetadata() {
|
||||
setMetadata(null);
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const data = await fetchSpotifyMetadata(url);
|
||||
const timeout = urlType === "artist" ? 60 : 300;
|
||||
const data = await fetchSpotifyMetadata(url, true, 1.0, timeout);
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
if ("playlist_info" in data) {
|
||||
const playlistInfo = data.playlist_info;
|
||||
@@ -56,6 +106,7 @@ export function useMetadata() {
|
||||
}
|
||||
}
|
||||
setMetadata(data);
|
||||
saveToHistory(url, data);
|
||||
if ("track" in data) {
|
||||
logger.success(`fetched track: ${data.track.name} - ${data.track.artists}`);
|
||||
logger.debug(`isrc: ${data.track.isrc}, duration: ${data.track.duration_ms}ms`);
|
||||
@@ -84,6 +135,17 @@ export function useMetadata() {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const loadFromCache = (cachedData: string) => {
|
||||
try {
|
||||
const data = JSON.parse(cachedData);
|
||||
setMetadata(data);
|
||||
toast.success("Loaded from cache");
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Failed to load from cache:", err);
|
||||
toast.error("Failed to load from cache");
|
||||
}
|
||||
};
|
||||
const handleFetchMetadata = async (url: string) => {
|
||||
if (!url.trim()) {
|
||||
logger.warning("empty url provided");
|
||||
@@ -97,43 +159,15 @@ export function useMetadata() {
|
||||
logger.debug("converted to discography url");
|
||||
}
|
||||
if (isArtistUrl) {
|
||||
logger.info("artist url detected, showing timeout dialog");
|
||||
setPendingUrl(urlToFetch);
|
||||
logger.info("artist url detected");
|
||||
setPendingArtistName(null);
|
||||
setShowTimeoutDialog(true);
|
||||
await fetchMetadataDirectly(urlToFetch);
|
||||
}
|
||||
else {
|
||||
await fetchMetadataDirectly(urlToFetch);
|
||||
}
|
||||
return urlToFetch;
|
||||
};
|
||||
const handleConfirmFetch = async () => {
|
||||
setShowTimeoutDialog(false);
|
||||
logger.info(`fetching artist discography (timeout: ${timeoutValue}s)...`);
|
||||
logger.debug(`url: ${pendingUrl}`);
|
||||
setLoading(true);
|
||||
setMetadata(null);
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const data = await fetchSpotifyMetadata(pendingUrl, true, 1.0, timeoutValue);
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
setMetadata(data);
|
||||
if ("artist_info" in data) {
|
||||
logger.success(`fetched artist: ${data.artist_info.name}`);
|
||||
logger.debug(`${data.album_list.length} albums, ${data.track_list.length} tracks`);
|
||||
}
|
||||
logger.info(`fetch completed in ${elapsed}s`);
|
||||
toast.success("Metadata fetched successfully");
|
||||
}
|
||||
catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : "Failed to fetch metadata";
|
||||
logger.error(`fetch failed: ${errorMsg}`);
|
||||
toast.error(errorMsg);
|
||||
}
|
||||
finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const handleAlbumClick = (album: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -150,9 +184,8 @@ export function useMetadata() {
|
||||
}) => {
|
||||
logger.debug(`artist clicked: ${artist.name}`);
|
||||
const artistUrl = artist.external_urls.replace(/\/$/, "") + "/discography/all";
|
||||
setPendingUrl(artistUrl);
|
||||
setPendingArtistName(artist.name);
|
||||
setShowTimeoutDialog(true);
|
||||
await fetchMetadataDirectly(artistUrl);
|
||||
return artistUrl;
|
||||
};
|
||||
const handleConfirmAlbumFetch = async () => {
|
||||
@@ -179,6 +212,7 @@ export function useMetadata() {
|
||||
}
|
||||
}
|
||||
setMetadata(data);
|
||||
saveToHistory(albumUrl, data);
|
||||
if ("album_info" in data) {
|
||||
logger.success(`fetched album: ${data.album_info.name}`);
|
||||
logger.debug(`${data.track_list.length} tracks, released: ${data.album_info.release_date}`);
|
||||
@@ -200,18 +234,15 @@ export function useMetadata() {
|
||||
return {
|
||||
loading,
|
||||
metadata,
|
||||
showTimeoutDialog,
|
||||
setShowTimeoutDialog,
|
||||
timeoutValue,
|
||||
setTimeoutValue,
|
||||
showAlbumDialog,
|
||||
setShowAlbumDialog,
|
||||
selectedAlbum,
|
||||
pendingArtistName,
|
||||
handleFetchMetadata,
|
||||
handleConfirmFetch,
|
||||
handleAlbumClick,
|
||||
handleConfirmAlbumFetch,
|
||||
handleArtistClick,
|
||||
loadFromCache,
|
||||
resetMetadata: () => setMetadata(null),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { GetPreviewURL } from "@/../wailsjs/go/main/App";
|
||||
import { toast } from "sonner";
|
||||
export function usePreview() {
|
||||
const [loadingPreview, setLoadingPreview] = useState<string | null>(null);
|
||||
const [currentAudio, setCurrentAudio] = useState<HTMLAudioElement | null>(null);
|
||||
const [playingTrack, setPlayingTrack] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (currentAudio) {
|
||||
currentAudio.pause();
|
||||
currentAudio.currentTime = 0;
|
||||
}
|
||||
};
|
||||
}, [currentAudio]);
|
||||
const playPreview = async (trackId: string, trackName: string) => {
|
||||
try {
|
||||
if (playingTrack === trackId && currentAudio) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
export function useTypingEffect(texts: string[], typingSpeed: number = 50, deletingSpeed: number = 50, pauseDuration: number = 1500) {
|
||||
const [displayedText, setDisplayedText] = useState('');
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [textIndex, setTextIndex] = useState(0);
|
||||
useEffect(() => {
|
||||
setDisplayedText("");
|
||||
setIsDeleting(false);
|
||||
setTextIndex(0);
|
||||
}, [texts]);
|
||||
useEffect(() => {
|
||||
const currentText = texts[textIndex % texts.length];
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
if (isDeleting) {
|
||||
timer = setTimeout(() => {
|
||||
setDisplayedText((prev) => prev.substring(0, prev.length - 1));
|
||||
}, deletingSpeed);
|
||||
}
|
||||
else {
|
||||
timer = setTimeout(() => {
|
||||
setDisplayedText((prev) => currentText.substring(0, prev.length + 1));
|
||||
}, typingSpeed);
|
||||
}
|
||||
if (!isDeleting && displayedText === currentText) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => setIsDeleting(true), pauseDuration);
|
||||
}
|
||||
else if (isDeleting && displayedText === '') {
|
||||
setIsDeleting(false);
|
||||
setTextIndex((prev) => (prev + 1) % texts.length);
|
||||
}
|
||||
return () => clearTimeout(timer);
|
||||
}, [displayedText, isDeleting, textIndex, texts, typingSpeed, deletingSpeed, pauseDuration]);
|
||||
return displayedText;
|
||||
}
|
||||
Reference in New Issue
Block a user