Format imports, partial implementation of import upload entries
This commit is contained in:
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user