.improved recent fetches
This commit is contained in:
@@ -935,6 +935,34 @@ func (a *App) ClearFetchHistoryByType(itemType string) error {
|
|||||||
return backend.ClearFetchHistoryByType(itemType, "SpotiFLAC")
|
return backend.ClearFetchHistoryByType(itemType, "SpotiFLAC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetRecentFetches() (string, error) {
|
||||||
|
items, err := backend.LoadRecentFetches()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(items)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SaveRecentFetches(payload string) error {
|
||||||
|
payload = strings.TrimSpace(payload)
|
||||||
|
if payload == "" {
|
||||||
|
payload = "[]"
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []backend.RecentFetchItem
|
||||||
|
if err := json.Unmarshal([]byte(payload), &items); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return backend.SaveRecentFetches(items)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) SaveSpectrumImage(audioFilePath string, base64Data string) (string, error) {
|
func (a *App) SaveSpectrumImage(audioFilePath string, base64Data string) (string, error) {
|
||||||
if audioFilePath == "" || base64Data == "" {
|
if audioFilePath == "" || base64Data == "" {
|
||||||
return "", fmt.Errorf("file path and image data are required")
|
return "", fmt.Errorf("file path and image data are required")
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const recentFetchesFileName = "recent_fetches.json"
|
||||||
|
|
||||||
|
type RecentFetchItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Artist string `json:"artist"`
|
||||||
|
Image string `json:"image"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
recentFetchesMu sync.Mutex
|
||||||
|
recentFetchesDirResolver = GetFFmpegDir
|
||||||
|
)
|
||||||
|
|
||||||
|
func recentFetchesFilePath() (string, error) {
|
||||||
|
baseDir, err := recentFetchesDirResolver()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(baseDir, 0o755); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(baseDir, recentFetchesFileName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadRecentFetches() ([]RecentFetchItem, error) {
|
||||||
|
recentFetchesMu.Lock()
|
||||||
|
defer recentFetchesMu.Unlock()
|
||||||
|
|
||||||
|
filePath, err := recentFetchesFilePath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return []RecentFetchItem{}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(string(data)) == "" {
|
||||||
|
return []RecentFetchItem{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []RecentFetchItem
|
||||||
|
if err := json.Unmarshal(data, &items); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if items == nil {
|
||||||
|
return []RecentFetchItem{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveRecentFetches(items []RecentFetchItem) error {
|
||||||
|
recentFetchesMu.Lock()
|
||||||
|
defer recentFetchesMu.Unlock()
|
||||||
|
|
||||||
|
filePath, err := recentFetchesFilePath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if items == nil {
|
||||||
|
items = []RecentFetchItem{}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(items, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(filePath, data, 0o644)
|
||||||
|
}
|
||||||
+43
-20
@@ -5,7 +5,7 @@ import { Search, X, ArrowUp } from "lucide-react";
|
|||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { getSettings, getSettingsWithDefaults, loadSettings, saveSettings, applyThemeMode, applyFont, updateSettings } from "@/lib/settings";
|
import { getSettings, getSettingsWithDefaults, loadSettings, saveSettings, applyThemeMode, applyFont, updateSettings } from "@/lib/settings";
|
||||||
import { applyTheme } from "@/lib/themes";
|
import { applyTheme } from "@/lib/themes";
|
||||||
import { OpenFolder, CheckFFmpegInstalled, DownloadFFmpeg, GetBrewPath, InstallFFmpegWithBrew } from "../wailsjs/go/main/App";
|
import { OpenFolder, CheckFFmpegInstalled, DownloadFFmpeg, GetBrewPath, GetRecentFetches, InstallFFmpegWithBrew, SaveRecentFetches } from "../wailsjs/go/main/App";
|
||||||
import { EventsOn, EventsOff, Quit } from "../wailsjs/runtime/runtime";
|
import { EventsOn, EventsOff, Quit } from "../wailsjs/runtime/runtime";
|
||||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||||
import { TitleBar } from "@/components/TitleBar";
|
import { TitleBar } from "@/components/TitleBar";
|
||||||
@@ -104,6 +104,25 @@ function dedupeHistoryItems(items: HistoryItem[]): HistoryItem[] {
|
|||||||
}
|
}
|
||||||
return deduped;
|
return deduped;
|
||||||
}
|
}
|
||||||
|
function sortHistoryItems(items: HistoryItem[]): HistoryItem[] {
|
||||||
|
return [...items].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
||||||
|
}
|
||||||
|
function normalizeHistoryItems(items: HistoryItem[]): HistoryItem[] {
|
||||||
|
return dedupeHistoryItems(sortHistoryItems(items)).slice(0, MAX_HISTORY);
|
||||||
|
}
|
||||||
|
function parseStoredHistory(value: string | null): HistoryItem[] {
|
||||||
|
if (!value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error("Failed to parse stored history:", err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
function App() {
|
function App() {
|
||||||
const [currentPage, setCurrentPage] = useState<PageType>("main");
|
const [currentPage, setCurrentPage] = useState<PageType>("main");
|
||||||
const [spotifyUrl, setSpotifyUrl] = useState("");
|
const [spotifyUrl, setSpotifyUrl] = useState("");
|
||||||
@@ -182,7 +201,7 @@ function App() {
|
|||||||
mediaQuery.addEventListener("change", handleChange);
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
ensureApiStatusCheckStarted();
|
ensureApiStatusCheckStarted();
|
||||||
loadHistory();
|
void loadHistory();
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
setShowScrollTop(window.scrollY > 300);
|
setShowScrollTop(window.scrollY > 300);
|
||||||
};
|
};
|
||||||
@@ -232,19 +251,29 @@ function App() {
|
|||||||
console.error("Failed to check for updates:", err);
|
console.error("Failed to check for updates:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const loadHistory = () => {
|
const persistRecentHistory = useCallback(async (history: HistoryItem[]) => {
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem(HISTORY_KEY);
|
await SaveRecentFetches(JSON.stringify(history));
|
||||||
if (saved) {
|
|
||||||
const deduped = dedupeHistoryItems(JSON.parse(saved));
|
|
||||||
setFetchHistory(deduped);
|
|
||||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(deduped));
|
|
||||||
}
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error("Failed to save recent fetches:", err);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
const loadHistory = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const saved = parseStoredHistory(localStorage.getItem(HISTORY_KEY));
|
||||||
|
const persisted = parseStoredHistory(await GetRecentFetches());
|
||||||
|
const normalized = normalizeHistoryItems([...persisted, ...saved]);
|
||||||
|
setFetchHistory(normalized);
|
||||||
|
await persistRecentHistory(normalized);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error("Failed to load history:", err);
|
console.error("Failed to load history:", err);
|
||||||
}
|
}
|
||||||
};
|
finally {
|
||||||
|
localStorage.removeItem(HISTORY_KEY);
|
||||||
|
}
|
||||||
|
}, [persistRecentHistory]);
|
||||||
const handleInstallFFmpeg = async (useBrew: boolean = false) => {
|
const handleInstallFFmpeg = async (useBrew: boolean = false) => {
|
||||||
setIsInstallingFFmpeg(true);
|
setIsInstallingFFmpeg(true);
|
||||||
setFfmpegInstallProgress(0);
|
setFfmpegInstallProgress(0);
|
||||||
@@ -283,14 +312,6 @@ function App() {
|
|||||||
setFfmpegInstallStatus("");
|
setFfmpegInstallStatus("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const saveHistory = (history: HistoryItem[]) => {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error("Failed to save history:", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const addToHistory = (item: Omit<HistoryItem, "id" | "timestamp">) => {
|
const addToHistory = (item: Omit<HistoryItem, "id" | "timestamp">) => {
|
||||||
setFetchHistory((prev) => {
|
setFetchHistory((prev) => {
|
||||||
const normalizedUrl = normalizeHistoryURL(item.url);
|
const normalizedUrl = normalizeHistoryURL(item.url);
|
||||||
@@ -302,15 +323,17 @@ function App() {
|
|||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
const updated = [newItem, ...filtered].slice(0, MAX_HISTORY);
|
const updated = normalizeHistoryItems([newItem, ...filtered]);
|
||||||
saveHistory(updated);
|
void persistRecentHistory(updated);
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const removeFromHistory = (id: string) => {
|
const removeFromHistory = (id: string) => {
|
||||||
setFetchHistory((prev) => {
|
setFetchHistory((prev) => {
|
||||||
|
if (!prev.some((h) => h.id === id))
|
||||||
|
return prev;
|
||||||
const updated = prev.filter((h) => h.id !== id);
|
const updated = prev.filter((h) => h.id !== id);
|
||||||
saveHistory(updated);
|
void persistRecentHistory(updated);
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user