Format imports, partial implementation of import upload entries

This commit is contained in:
2026-01-06 02:37:03 +05:00
parent 16d284fe68
commit c29c12feec
17 changed files with 607 additions and 174 deletions

View File

@ -1,9 +1,9 @@
<template>
<Frame margin="none" class="px-3 py-4 flex items-center gap-2">
<Frame margin="none" class="px-3 py-4 flex items-center gap-2 cursor-pointer" @click="openDialog">
<div>
<Disc3 :size="40" v-if="hasLoaded" />
<AudioWaveform :size="40" v-if="hasProgress" />
<FileQuestionMark :size="40" v-if="hasError" />
<AudioWaveform :size="40" v-else-if="hasProgress" />
<FileQuestionMark :size="40" v-else-if="hasError" />
</div>
<div class="w-full">
<div class="flex flex-row items-center gap-1">
@ -28,7 +28,7 @@
<p class="text-sm text-muted-foreground">
{{ progress }}%
</p>
<UiProgress :modelValue="progress" />
<Progress :modelValue="progress" />
</div>
<div class="flex flex-row" v-if="hasError">
<p class="text-sm text-destructive-foreground">
@ -39,44 +39,156 @@
<div>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<UiButton variant="ghost">
<Button variant="ghost">
<EllipsisVertical :size="40" />
</UiButton>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-56" align="start">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
</DropdownMenuItem>
<DropdownMenuItem>
Billing
</DropdownMenuItem>
<DropdownMenuItem>
Settings
</DropdownMenuItem>
<DropdownMenuItem>
Keyboard shortcuts
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuItem @click="openDialog">
<Eye class="mr-2 h-4 w-4" />
View Details
</DropdownMenuItem>
<DropdownMenuItem>
<Download class="mr-2 h-4 w-4" />
Download
</DropdownMenuItem>
<DropdownMenuItem class="text-destructive">
<Trash2 class="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Dialog v-model:show="isDialogOpen">
<DialogContent class="max-w-3xl">
<DialogHeader>
<DialogTitle>Track Upload Details</DialogTitle>
<DialogDescription>
Detailed information about this track upload
</DialogDescription>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="space-y-4">
<h3 class="text-lg font-semibold">Track Information</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label>Title</Label>
<p class="text-sm">{{ title }}</p>
</div>
<div class="space-y-2">
<Label>Format</Label>
<p class="text-sm">{{ format }}</p>
</div>
<div class="space-y-2">
<Label>Size</Label>
<p class="text-sm">{{ size }}</p>
</div>
<div class="space-y-2">
<Label>Type</Label>
<p class="text-sm">{{ type }}</p>
</div>
</div>
</div>
<div v-if="trackProgressData" class="space-y-4">
<h3 class="text-lg font-semibold">Upload Details</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label>Progress</Label>
<div class="flex items-center gap-2">
<p class="text-sm">{{ progress }}%</p>
<Progress :modelValue="progress" class="w-24" />
</div>
</div>
<div class="space-y-2">
<Label>Timestamp</Label>
<p class="text-sm">{{ formatTimestamp(trackProgressData.timestamp) }}</p>
</div>
<div class="space-y-2">
<Label>User ID</Label>
<p class="text-sm">{{ trackProgressData.userId }}</p>
</div>
<div class="space-y-2">
<Label>Track Source ID</Label>
<p class="text-sm">{{ trackProgressData.trackSourceId }}</p>
</div>
</div>
<div v-if="trackProgressData.title || trackProgressData.format" class="space-y-2">
<Label>Additional Information</Label>
<div class="bg-muted rounded-md p-3 space-y-1">
<div v-if="trackProgressData.title" class="flex justify-between">
<span class="text-sm font-medium">Original Title:</span>
<span class="text-sm">{{ trackProgressData.title }}</span>
</div>
<div v-if="trackProgressData.format" class="flex justify-between">
<span class="text-sm font-medium">Source Format:</span>
<span class="text-sm">{{ trackProgressData.format }}</span>
</div>
</div>
</div>
</div>
<div class="space-y-4">
<h3 class="text-lg font-semibold">Status</h3>
<div class="flex items-center gap-3 p-4 rounded-lg border">
<div :class="`h-3 w-3 rounded-full ${getStatusColor()}`" />
<div>
<p class="font-medium">{{ getStatusText() }}</p>
<p v-if="progress" class="text-sm text-muted-foreground">
Upload progress: {{ progress }}%
</p>
<p v-if="trackProgressData?.timestamp" class="text-sm text-muted-foreground">
Last updated: {{ formatTimestamp(trackProgressData.timestamp) }}
</p>
</div>
</div>
</div>
<div v-if="hasError" class="space-y-4">
<h3 class="text-lg font-semibold text-destructive">Error Details</h3>
<div class="bg-destructive/10 rounded-md p-4">
<p class="text-sm text-destructive">{{ error }}</p>
<Button v-if="hasError" variant="outline" size="sm" class="mt-2" @click="handleRetry">
<RefreshCw class="mr-2 h-4 w-4" />
Retry Upload
</Button>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="isDialogOpen = false">Close</Button>
<Button variant="secondary" @click="downloadTrack">
<Download class="mr-2 h-4 w-4" />
Download Track
</Button>
<Button @click="openInPlayer">
<Play class="mr-2 h-4 w-4" />
Open in Player
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Frame>
</template>
<script setup lang="ts">
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuShortcut,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
import Frame from '@/components/ui/frame/Frame.vue';
import { AudioWaveform, Disc3, Dot, EllipsisVertical, FileQuestionMark } from 'lucide-vue-next';
AudioWaveform,
Disc3,
Dot,
Download,
EllipsisVertical,
Eye,
FileQuestionMark,
Play,
RefreshCw,
Trash2
} from 'lucide-vue-next';
import { ref } from 'vue';
interface Props {
title: string
@ -85,13 +197,67 @@ interface Props {
type?: string
progress?: number
error?: string
trackProgressData?: {
playlistId?: number
trackSourceId: number
userId: number
timestamp: number
title: string
format: string
}
}
const props = withDefaults(defineProps<Props>(), {
progress: 0
});
const emit = defineEmits<{
retry: []
download: []
play: []
}>();
const isDialogOpen = ref(false);
const hasLoaded = props.size && props.format && props.type;
const hasProgress = props.progress;
const hasProgress = props.progress !== undefined && props.progress > 0;
const hasError = props.error;
const openDialog = () => {
isDialogOpen.value = true;
};
const getStatusColor = () => {
if (hasError) return 'bg-destructive';
if (hasLoaded && props.progress === 100) return 'bg-green-500';
if (hasProgress) return 'bg-blue-500';
return 'bg-gray-500';
};
const getStatusText = () => {
if (hasError) return 'Error';
if (hasLoaded && props.progress === 100) return 'Upload Complete';
if (hasProgress) return 'Uploading';
return 'Pending';
};
const formatTimestamp = (timestamp?: number) => {
if (!timestamp) return 'N/A';
return new Date(timestamp).toLocaleString();
};
const handleRetry = () => {
emit('retry');
isDialogOpen.value = false;
};
const downloadTrack = () => {
emit('download');
isDialogOpen.value = false;
};
const openInPlayer = () => {
emit('play');
isDialogOpen.value = false;
};
</script>