From 6c4015f8c448acffd875212542bc24ee4d55a153 Mon Sep 17 00:00:00 2001 From: "Mr.Dat" Date: Mon, 26 Jan 2026 16:26:58 +0700 Subject: [PATCH] fix color --- components.d.ts | 22 ++ src/components/DashboardLayout.vue | 20 +- src/components/GlobalUploadIndicator.vue | 103 ++++++++ src/components/NotificationDrawer.vue | 56 ++--- src/components/dashboard/StatsCard.vue | 43 ++-- src/components/icons/AlertTriangleIcon.vue | 16 ++ src/components/icons/ArrowRightIcon.vue | 15 ++ src/components/icons/CheckCircleIcon.vue | 16 ++ src/components/icons/CheckMarkIcon.vue | 14 ++ src/components/icons/CreditCardIcon.vue | 16 ++ src/components/icons/InfoIcon.vue | 16 ++ src/components/icons/SettingsIcon.vue | 18 ++ src/components/icons/TrashIcon.vue | 17 ++ src/components/icons/VideoIcon.vue | 16 ++ src/components/icons/XCircleIcon.vue | 17 ++ src/composables/useUploadQueue.ts | 164 +++++++++++++ src/main.ts | 2 +- .../components/NotificationItem.vue | 110 +++++---- src/routes/overview/Overview.vue | 8 +- .../overview/components/QuickActions.vue | 2 +- src/routes/overview/components/Referral.vue | 2 +- .../overview/components/StatsOverview.vue | 8 +- src/routes/upload/Upload.vue | 18 +- .../upload/components/RemoteUrlForm.vue | 2 +- .../upload/components/UploadModeToggle.vue | 2 +- src/routes/upload/components/UploadQueue.vue | 22 +- .../upload/components/UploadQueueItem.vue | 74 ++++-- src/routes/video/Videos.vue | 225 +++++++++--------- uno.config.ts | 129 ++++++++-- 29 files changed, 867 insertions(+), 306 deletions(-) create mode 100644 src/components/GlobalUploadIndicator.vue create mode 100644 src/components/icons/AlertTriangleIcon.vue create mode 100644 src/components/icons/ArrowRightIcon.vue create mode 100644 src/components/icons/CheckCircleIcon.vue create mode 100644 src/components/icons/CheckMarkIcon.vue create mode 100644 src/components/icons/CreditCardIcon.vue create mode 100644 src/components/icons/InfoIcon.vue create mode 100644 src/components/icons/SettingsIcon.vue create mode 100644 src/components/icons/TrashIcon.vue create mode 100644 src/components/icons/VideoIcon.vue create mode 100644 src/components/icons/XCircleIcon.vue create mode 100644 src/composables/useUploadQueue.ts diff --git a/components.d.ts b/components.d.ts index 8cb46a0..8b97abb 100644 --- a/components.d.ts +++ b/components.d.ts @@ -13,19 +13,26 @@ export {} declare module 'vue' { export interface GlobalComponents { Add: typeof import('./src/components/icons/Add.vue')['default'] + AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default'] + ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default'] Bell: typeof import('./src/components/icons/Bell.vue')['default'] Button: typeof import('primevue/button')['default'] Chart: typeof import('./src/components/icons/Chart.vue')['default'] Checkbox: typeof import('primevue/checkbox')['default'] + CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default'] CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default'] + CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default'] ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default'] Credit: typeof import('./src/components/icons/Credit.vue')['default'] + CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default'] DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default'] EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default'] FloatLabel: typeof import('primevue/floatlabel')['default'] + GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default'] HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default'] Home: typeof import('./src/components/icons/Home.vue')['default'] IconField: typeof import('primevue/iconfield')['default'] + InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default'] InputIcon: typeof import('primevue/inputicon')['default'] InputText: typeof import('primevue/inputtext')['default'] Layout: typeof import('./src/components/icons/Layout.vue')['default'] @@ -38,30 +45,41 @@ declare module 'vue' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] Select: typeof import('primevue/select')['default'] + SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default'] StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default'] TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default'] + TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default'] Upload: typeof import('./src/components/icons/Upload.vue')['default'] Video: typeof import('./src/components/icons/Video.vue')['default'] + VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default'] VueHead: typeof import('./src/components/VueHead.tsx')['default'] + XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default'] } } // For TSX support declare global { const Add: typeof import('./src/components/icons/Add.vue')['default'] + const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default'] + const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default'] const Bell: typeof import('./src/components/icons/Bell.vue')['default'] const Button: typeof import('primevue/button')['default'] const Chart: typeof import('./src/components/icons/Chart.vue')['default'] const Checkbox: typeof import('primevue/checkbox')['default'] + const CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default'] const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default'] + const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default'] const ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default'] const Credit: typeof import('./src/components/icons/Credit.vue')['default'] + const CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default'] const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default'] const EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default'] const FloatLabel: typeof import('primevue/floatlabel')['default'] + const GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default'] const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default'] const Home: typeof import('./src/components/icons/Home.vue')['default'] const IconField: typeof import('primevue/iconfield')['default'] + const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default'] const InputIcon: typeof import('primevue/inputicon')['default'] const InputText: typeof import('primevue/inputtext')['default'] const Layout: typeof import('./src/components/icons/Layout.vue')['default'] @@ -74,9 +92,13 @@ declare global { const RouterLink: typeof import('vue-router')['RouterLink'] const RouterView: typeof import('vue-router')['RouterView'] const Select: typeof import('primevue/select')['default'] + const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default'] const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default'] const TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default'] + const TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default'] const Upload: typeof import('./src/components/icons/Upload.vue')['default'] const Video: typeof import('./src/components/icons/Video.vue')['default'] + const VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default'] const VueHead: typeof import('./src/components/VueHead.tsx')['default'] + const XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default'] } \ No newline at end of file diff --git a/src/components/DashboardLayout.vue b/src/components/DashboardLayout.vue index 11f4427..c1cc9e3 100644 --- a/src/components/DashboardLayout.vue +++ b/src/components/DashboardLayout.vue @@ -6,6 +6,7 @@ import Video from "@/components/icons/Video.vue"; import Credit from "@/components/icons/Credit.vue"; import Upload from "./icons/Upload.vue"; import NotificationDrawer from "./NotificationDrawer.vue"; +import GlobalUploadIndicator from "./GlobalUploadIndicator.vue"; import { cn } from "@/lib/utils"; import { createStaticVNode, ref } from "vue"; @@ -38,14 +39,9 @@ const links = [
@@ -53,11 +49,11 @@ const links = [
- - + +
-
+
+
- diff --git a/src/components/GlobalUploadIndicator.vue b/src/components/GlobalUploadIndicator.vue new file mode 100644 index 0000000..0fb2ddd --- /dev/null +++ b/src/components/GlobalUploadIndicator.vue @@ -0,0 +1,103 @@ + + + diff --git a/src/components/NotificationDrawer.vue b/src/components/NotificationDrawer.vue index fb288be..54bbbb6 100644 --- a/src/components/NotificationDrawer.vue +++ b/src/components/NotificationDrawer.vue @@ -1,7 +1,7 @@ diff --git a/src/components/icons/ArrowRightIcon.vue b/src/components/icons/ArrowRightIcon.vue new file mode 100644 index 0000000..5219d46 --- /dev/null +++ b/src/components/icons/ArrowRightIcon.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/icons/CheckCircleIcon.vue b/src/components/icons/CheckCircleIcon.vue new file mode 100644 index 0000000..60f2266 --- /dev/null +++ b/src/components/icons/CheckCircleIcon.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/icons/CheckMarkIcon.vue b/src/components/icons/CheckMarkIcon.vue new file mode 100644 index 0000000..cd541f1 --- /dev/null +++ b/src/components/icons/CheckMarkIcon.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/icons/CreditCardIcon.vue b/src/components/icons/CreditCardIcon.vue new file mode 100644 index 0000000..e45780c --- /dev/null +++ b/src/components/icons/CreditCardIcon.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/icons/InfoIcon.vue b/src/components/icons/InfoIcon.vue new file mode 100644 index 0000000..5e26ce6 --- /dev/null +++ b/src/components/icons/InfoIcon.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/icons/SettingsIcon.vue b/src/components/icons/SettingsIcon.vue new file mode 100644 index 0000000..ea1491f --- /dev/null +++ b/src/components/icons/SettingsIcon.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/components/icons/TrashIcon.vue b/src/components/icons/TrashIcon.vue new file mode 100644 index 0000000..efff4b7 --- /dev/null +++ b/src/components/icons/TrashIcon.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/icons/VideoIcon.vue b/src/components/icons/VideoIcon.vue new file mode 100644 index 0000000..8fc1485 --- /dev/null +++ b/src/components/icons/VideoIcon.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/icons/XCircleIcon.vue b/src/components/icons/XCircleIcon.vue new file mode 100644 index 0000000..9f762fd --- /dev/null +++ b/src/components/icons/XCircleIcon.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/composables/useUploadQueue.ts b/src/composables/useUploadQueue.ts new file mode 100644 index 0000000..a13a9be --- /dev/null +++ b/src/composables/useUploadQueue.ts @@ -0,0 +1,164 @@ +import { ref, computed } from 'vue'; + +export interface QueueItem { + id: string; + name: string; + type: 'local' | 'remote'; + status: 'uploading' | 'processing' | 'fetching' | 'complete' | 'error' | 'pending'; + progress?: number; + uploaded?: string; + total?: string; + speed?: string; + thumbnail?: string; + file?: File; // Keep reference to file for local uploads + url?: string; // Keep reference to url for remote uploads +} + +const items = ref([]); + +export function useUploadQueue() { + + const addFiles = (files: FileList) => { + const newItems: QueueItem[] = Array.from(files).map((file) => ({ + id: Math.random().toString(36).substring(2, 9), + name: file.name, + type: 'local', + status: 'pending', // Start as pending + progress: 0, + uploaded: '0 MB', + total: formatSize(file.size), + speed: '0 MB/s', + file: file, + thumbnail: undefined // We could generate a thumbnail here if needed + })); + + items.value.push(...newItems); + }; + + const addRemoteUrls = (urls: string[]) => { + const newItems: QueueItem[] = urls.map((url) => ({ + id: Math.random().toString(36).substring(2, 9), + name: url.split('/').pop() || 'Remote File', + type: 'remote', + status: 'fetching', // Remote URLs start fetching immediately or pending? User said "khi nao nhan upload". Let's use pending. + progress: 0, + uploaded: '0 MB', + total: 'Unknown', + speed: '0 MB/s', + url: url + })); + + // Override status to pending for consistency with user request + newItems.forEach(i => i.status = 'pending'); + + items.value.push(...newItems); + }; + + const removeItem = (id: string) => { + const index = items.value.findIndex(item => item.id === id); + if (index !== -1) { + items.value.splice(index, 1); + } + }; + + const startQueue = () => { + items.value.forEach(item => { + if (item.status === 'pending') { + if (item.type === 'local') { + startMockUpload(item.id); + } else { + startMockRemoteFetch(item.id); + } + } + }); + }; + + // Mock Upload Logic + const startMockUpload = (id: string) => { + const item = items.value.find(i => i.id === id); + if (!item) return; + + item.status = 'uploading'; + let progress = 0; + const totalSize = item.file ? item.file.size : 1024 * 1024 * 50; // Default 50MB if unknown + + // Random speed between 1MB/s and 5MB/s + const speedBytesPerStep = (1024 * 1024) + Math.random() * (1024 * 1024 * 4); + + const interval = setInterval(() => { + if (progress >= 100) { + clearInterval(interval); + item.status = 'complete'; + item.progress = 100; + item.uploaded = item.total; + return; + } + + // Increment progress randomly + const increment = Math.random() * 5 + 1; // 1-6% increment + progress = Math.min(progress + increment, 100); + + item.progress = Math.floor(progress); + + // Calculate uploaded size string + const currentBytes = (progress / 100) * totalSize; + item.uploaded = formatSize(currentBytes); + + // Re-randomize speed for realism + const currentSpeed = (1024 * 1024) + Math.random() * (1024 * 1024 * 2); + item.speed = formatSize(currentSpeed) + '/s'; + + }, 500); + }; + + // Mock Remote Fetch Logic + const startMockRemoteFetch = (id: string) => { + const item = items.value.find(i => i.id === id); + if (!item) return; + + item.status = 'fetching'; // Update status to fetching + + // Remote fetch takes some time then completes + setTimeout(() => { + // Switch to uploading/processing phase if we wanted, or just finish + item.status = 'complete'; + item.progress = 100; + }, 3000 + Math.random() * 3000); + }; + + + const formatSize = (bytes: number): string => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }; + + const totalSize = computed(() => { + let total = 0; + items.value.forEach(item => { + if (item.file) total += item.file.size; + }); + return formatSize(total); + }); + + const completeCount = computed(() => { + return items.value.filter(i => i.status === 'complete').length; + }); + + const pendingCount = computed(() => { + return items.value.filter(i => i.status === 'pending').length; + }); + + return { + items, + addFiles, + addRemoteUrls, + removeItem, + startQueue, + totalSize, + completeCount, + pendingCount + }; +} diff --git a/src/main.ts b/src/main.ts index 3bd0796..77d04c8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,7 @@ import { createPinia } from "pinia"; import { useAuthStore } from './stores/auth'; import ToastService from 'primevue/toastservice'; import Tooltip from 'primevue/tooltip'; -const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen" +const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen bg-[#FAF8F8]" export function createApp() { const pinia = createPinia(); const app = createSSRApp(withErrorBoundary(RouterView)); diff --git a/src/routes/notification/components/NotificationItem.vue b/src/routes/notification/components/NotificationItem.vue index 40da859..0874cbd 100644 --- a/src/routes/notification/components/NotificationItem.vue +++ b/src/routes/notification/components/NotificationItem.vue @@ -1,5 +1,15 @@ - - diff --git a/src/routes/overview/Overview.vue b/src/routes/overview/Overview.vue index 85b3a2d..cc635c3 100644 --- a/src/routes/overview/Overview.vue +++ b/src/routes/overview/Overview.vue @@ -1,13 +1,13 @@ @@ -44,6 +50,8 @@ const handleRemoteUrls = (urls: string[]) => { - + \ No newline at end of file diff --git a/src/routes/upload/components/RemoteUrlForm.vue b/src/routes/upload/components/RemoteUrlForm.vue index 7a2d068..a2a08d8 100644 --- a/src/routes/upload/components/RemoteUrlForm.vue +++ b/src/routes/upload/components/RemoteUrlForm.vue @@ -19,7 +19,7 @@ const handleSubmit = () => {