This commit is contained in:
afkarxyz
2026-01-15 16:52:50 +07:00
parent 12729e2ca1
commit 9361c608ca
4 changed files with 155 additions and 64 deletions
+3 -55
View File
@@ -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()
}
+41
View File
@@ -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
}
}
+41
View File
@@ -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
}
+70 -9
View File
@@ -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<string | null>(null);
const audioRef = useRef<HTMLAudioElement | null>(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() {
<th className="h-10 px-4 text-left align-middle font-medium text-muted-foreground hidden lg:table-cell w-32 text-xs uppercase">Format</th>
<th className="h-10 px-4 text-left align-middle font-medium text-muted-foreground hidden xl:table-cell w-16 text-xs uppercase text-nowrap">Dur</th>
<th className="h-10 px-4 text-left align-middle font-medium text-muted-foreground hidden md:table-cell w-40 text-xs uppercase text-nowrap">Downloaded At</th>
<th className="h-10 px-4 text-center align-middle font-medium text-muted-foreground w-16 text-xs uppercase text-nowrap">Link</th>
<th className="h-10 px-4 text-center align-middle font-medium text-muted-foreground w-20 text-xs uppercase text-nowrap">Actions</th>
</tr>
</thead>
<tbody>
@@ -219,12 +253,39 @@ export function HistoryPage() {
{item.duration_str}
</td>
<td className="p-3 align-middle text-xs text-muted-foreground hidden md:table-cell whitespace-nowrap">
{formatDate(item.timestamp)}
<div className="flex flex-col">
<span>{formatDate(item.timestamp).split(' ')[0]}</span>
<span className="text-[10px] text-muted-foreground">{formatDate(item.timestamp).split(' ')[1]}</span>
</div>
</td>
<td className="p-3 align-middle text-center">
<Button variant="ghost" size="icon" className="h-8 w-8 cursor-pointer" onClick={() => openExternal(`https://open.spotify.com/track/${item.spotify_id}`)}>
<ExternalLink className="h-4 w-4" />
</Button>
<div className="flex items-center justify-center gap-1">
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 cursor-pointer" onClick={() => handlePreview(item.id, item.spotify_id)} disabled={!item.spotify_id}>
{playingPreviewId === item.id ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{playingPreviewId === item.id ? "Pause Preview" : "Play Preview"}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 cursor-pointer" onClick={() => openExternal(`https://open.spotify.com/track/${item.spotify_id}`)}>
<ExternalLink className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Open in Spotify</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</td>
</tr>))}
</tbody>