diff --git a/app.go b/app.go index d8a22b8..4b0b338 100644 --- a/app.go +++ b/app.go @@ -6,10 +6,10 @@ import ( "encoding/json" "fmt" "os" - "os/exec" + "path/filepath" "regexp" - goRuntime "runtime" + "spotiflac/backend" "strings" "time" @@ -1181,57 +1181,5 @@ func (a *App) CheckFFmpegInstalled() (bool, error) { } func (a *App) GetOSInfo() (string, error) { - osType := goRuntime.GOOS - arch := goRuntime.GOARCH - - switch osType { - case "windows": - out, err := exec.Command("wmic", "os", "get", "Caption,Version", "/value").Output() - if err != nil { - outVer, errVer := exec.Command("cmd", "/c", "ver").Output() - if errVer != nil { - return fmt.Sprintf("Windows %s", arch), nil - } - return strings.TrimSpace(string(outVer)), nil - } - - lines := strings.Split(string(out), "\n") - var caption, version string - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "Caption=") { - caption = strings.TrimPrefix(line, "Caption=") - } else if strings.HasPrefix(line, "Version=") { - version = strings.TrimPrefix(line, "Version=") - } - } - if caption != "" && version != "" { - return fmt.Sprintf("%s (%s, %s)", caption, version, arch), nil - } - return strings.TrimSpace(string(out)), nil - - case "darwin": - out, err := exec.Command("sw_vers", "-productVersion").Output() - if err != nil { - return fmt.Sprintf("macOS %s", arch), nil - } - version := strings.TrimSpace(string(out)) - return fmt.Sprintf("macOS %s (%s)", version, arch), nil - - case "linux": - out, err := exec.Command("cat", "/etc/os-release").Output() - if err == nil { - lines := strings.Split(string(out), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "PRETTY_NAME=") { - name := strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"") - return fmt.Sprintf("%s (%s)", name, arch), nil - } - } - } - return fmt.Sprintf("Linux %s", arch), nil - - default: - return fmt.Sprintf("%s %s", osType, arch), nil - } + return backend.GetOSInfo() } diff --git a/backend/system_unix.go b/backend/system_unix.go new file mode 100644 index 0000000..8783175 --- /dev/null +++ b/backend/system_unix.go @@ -0,0 +1,41 @@ +//go:build !windows + +package backend + +import ( + "fmt" + "os/exec" + "runtime" + "strings" +) + +func GetOSInfo() (string, error) { + osType := runtime.GOOS + arch := runtime.GOARCH + + switch osType { + case "darwin": + out, err := exec.Command("sw_vers", "-productVersion").Output() + if err != nil { + return fmt.Sprintf("macOS %s", arch), nil + } + version := strings.TrimSpace(string(out)) + return fmt.Sprintf("macOS %s (%s)", version, arch), nil + + case "linux": + out, err := exec.Command("cat", "/etc/os-release").Output() + if err == nil { + lines := strings.Split(string(out), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "PRETTY_NAME=") { + name := strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"") + return fmt.Sprintf("%s (%s)", name, arch), nil + } + } + } + return fmt.Sprintf("Linux %s", arch), nil + + default: + return fmt.Sprintf("%s %s", osType, arch), nil + } +} diff --git a/backend/system_windows.go b/backend/system_windows.go new file mode 100644 index 0000000..3330a44 --- /dev/null +++ b/backend/system_windows.go @@ -0,0 +1,41 @@ +package backend + +import ( + "fmt" + "os/exec" + "runtime" + "strings" + "syscall" +) + +func GetOSInfo() (string, error) { + arch := runtime.GOARCH + + cmd := exec.Command("wmic", "os", "get", "Caption,Version", "/value") + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + out, err := cmd.Output() + if err != nil { + cmdVer := exec.Command("cmd", "/c", "ver") + cmdVer.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + outVer, errVer := cmdVer.Output() + if errVer != nil { + return fmt.Sprintf("Windows %s", arch), nil + } + return strings.TrimSpace(string(outVer)), nil + } + + lines := strings.Split(string(out), "\n") + var caption, version string + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "Caption=") { + caption = strings.TrimPrefix(line, "Caption=") + } else if strings.HasPrefix(line, "Version=") { + version = strings.TrimPrefix(line, "Version=") + } + } + if caption != "" && version != "" { + return fmt.Sprintf("%s (%s, %s)", caption, version, arch), nil + } + return strings.TrimSpace(string(out)), nil +} diff --git a/frontend/src/components/HistoryPage.tsx b/frontend/src/components/HistoryPage.tsx index 1433847..a7f5e5f 100644 --- a/frontend/src/components/HistoryPage.tsx +++ b/frontend/src/components/HistoryPage.tsx @@ -1,12 +1,13 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { Button } from "@/components/ui/button"; -import { Trash2, ExternalLink, Search, ArrowUpDown, History } from "lucide-react"; +import { Trash2, ExternalLink, Search, ArrowUpDown, History, Play, Pause } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; -import { GetDownloadHistory, ClearDownloadHistory } from "../../wailsjs/go/main/App"; +import { GetDownloadHistory, ClearDownloadHistory, GetPreviewURL } from "../../wailsjs/go/main/App"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { openExternal } from "@/lib/utils"; const formatDate = (timestamp: number) => { const date = new Date(timestamp * 1000); @@ -38,6 +39,8 @@ export function HistoryPage() { const [searchQuery, setSearchQuery] = useState(""); const [sortBy, setSortBy] = useState("default"); const [currentPage, setCurrentPage] = useState(1); + const [playingPreviewId, setPlayingPreviewId] = useState(null); + const audioRef = useRef(null); const ITEMS_PER_PAGE = 50; const fetchHistory = async () => { try { @@ -51,8 +54,39 @@ export function HistoryPage() { useEffect(() => { fetchHistory(); const interval = setInterval(fetchHistory, 5000); - return () => clearInterval(interval); + return () => { + clearInterval(interval); + if (audioRef.current) { + audioRef.current.pause(); + } + }; }, []); + + const handlePreview = async (id: string, spotifyId: string) => { + if (playingPreviewId === id) { + audioRef.current?.pause(); + setPlayingPreviewId(null); + return; + } + + if (audioRef.current) { + audioRef.current.pause(); + } + + try { + const url = await GetPreviewURL(spotifyId); + if (url) { + const audio = new Audio(url); + audioRef.current = audio; + audio.volume = 0.5; + audio.onended = () => setPlayingPreviewId(null); + audio.play(); + setPlayingPreviewId(id); + } + } catch (e) { + console.error("Failed to play preview:", e); + } + }; useEffect(() => { let result = [...history]; if (searchQuery) { @@ -187,7 +221,7 @@ export function HistoryPage() { Format Dur Downloaded At - Link + Actions @@ -219,12 +253,39 @@ export function HistoryPage() { {item.duration_str} - {formatDate(item.timestamp)} +
+ {formatDate(item.timestamp).split(' ')[0]} + {formatDate(item.timestamp).split(' ')[1]} +
- +
+ + + + + + +

{playingPreviewId === item.id ? "Pause Preview" : "Play Preview"}

+
+
+
+ + + + + + + +

Open in Spotify

+
+
+
+
))}