mirror of
https://github.com/rishikanthc/Scriberr.git
synced 2026-06-29 15:26:02 +00:00
feat: enable global audio upload from any page and add dialog blur
- Created GlobalUploadContext for global upload functionality - Header now uses context when props not provided - Upload from Settings/AudioDetail shows toast notifications - Upload from Dashboard shows progress bar - All add button options (Quick Transcribe, YouTube, Record, etc.) work everywhere - Added backdrop-blur-sm to Dialog and AlertDialog overlays - Fixed dialog flash bug by setting modal default to true
This commit is contained in:
@@ -15,6 +15,7 @@ import { YouTubeDownloadDialog } from "@/features/transcription/components/YouTu
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "@/features/auth/hooks/useAuth";
|
||||
import { isVideoFile, isAudioFile } from "../utils/fileProcessor";
|
||||
import { useGlobalUpload } from "@/contexts/GlobalUploadContext";
|
||||
|
||||
interface FileWithType {
|
||||
file: File;
|
||||
@@ -22,7 +23,7 @@ interface FileWithType {
|
||||
}
|
||||
|
||||
interface HeaderProps {
|
||||
onFileSelect: (files: File | File[] | FileWithType | FileWithType[]) => void;
|
||||
onFileSelect?: (files: File | File[] | FileWithType | FileWithType[]) => void;
|
||||
onMultiTrackClick?: () => void;
|
||||
onDownloadComplete?: () => void;
|
||||
}
|
||||
@@ -36,6 +37,14 @@ export function Header({ onFileSelect, onMultiTrackClick, onDownloadComplete }:
|
||||
const [isQuickTranscriptionOpen, setIsQuickTranscriptionOpen] = useState(false);
|
||||
const [isYouTubeDialogOpen, setIsYouTubeDialogOpen] = useState(false);
|
||||
|
||||
// Use global upload context as fallback when props are not provided
|
||||
const globalUpload = useGlobalUpload();
|
||||
|
||||
// Determine which handlers to use (prop or global context)
|
||||
const effectiveFileSelect = onFileSelect ?? globalUpload.handleFileSelect;
|
||||
const effectiveMultiTrackClick = onMultiTrackClick ?? globalUpload.openMultiTrackDialog;
|
||||
const effectiveRecordingComplete = globalUpload.handleRecordingComplete;
|
||||
|
||||
const handleUploadClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
@@ -57,7 +66,7 @@ export function Header({ onFileSelect, onMultiTrackClick, onDownloadComplete }:
|
||||
};
|
||||
|
||||
const handleMultiTrackClick = () => {
|
||||
onMultiTrackClick?.();
|
||||
effectiveMultiTrackClick();
|
||||
};
|
||||
|
||||
const handleSettingsClick = () => {
|
||||
@@ -88,7 +97,7 @@ export function Header({ onFileSelect, onMultiTrackClick, onDownloadComplete }:
|
||||
// Filter to only audio files
|
||||
const audioFiles = fileArray.filter(file => isAudioFile(file));
|
||||
if (audioFiles.length > 0) {
|
||||
onFileSelect(audioFiles.length === 1 ? audioFiles[0] : audioFiles);
|
||||
effectiveFileSelect(audioFiles.length === 1 ? audioFiles[0] : audioFiles);
|
||||
// Reset the input so the same files can be selected again
|
||||
event.target.value = "";
|
||||
} else {
|
||||
@@ -107,7 +116,7 @@ export function Header({ onFileSelect, onMultiTrackClick, onDownloadComplete }:
|
||||
if (videoFiles.length > 0) {
|
||||
// Pass video files with type marker
|
||||
const filesWithType: FileWithType[] = videoFiles.map(file => ({ file, isVideo: true }));
|
||||
onFileSelect(filesWithType.length === 1 ? filesWithType[0] : filesWithType);
|
||||
effectiveFileSelect(filesWithType.length === 1 ? filesWithType[0] : filesWithType);
|
||||
// Reset the input so the same files can be selected again
|
||||
event.target.value = "";
|
||||
}
|
||||
@@ -115,11 +124,11 @@ export function Header({ onFileSelect, onMultiTrackClick, onDownloadComplete }:
|
||||
};
|
||||
|
||||
const handleRecordingComplete = async (blob: Blob, title: string) => {
|
||||
// Convert blob to file and use existing upload logic
|
||||
const file = new File([blob], `${title}.webm`, { type: blob.type });
|
||||
onFileSelect(file);
|
||||
// Use global recording complete handler
|
||||
await effectiveRecordingComplete(blob, title);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<header className="sticky top-4 sm:top-6 z-50 glass rounded-[var(--radius-card)] px-4 py-3 sm:px-6 sm:py-4 transition-all duration-500 shadow-[var(--shadow-float)] border border-[var(--border-subtle)]">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -34,7 +34,7 @@ function AlertDialogOverlay({
|
||||
<AlertDialogPrimitive.Overlay
|
||||
data-slot="alert-dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-carbon-950/50",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-carbon-950/50 backdrop-blur-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { XIcon } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Dialog({
|
||||
modal = false,
|
||||
modal = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />
|
||||
@@ -37,7 +37,7 @@ function DialogOverlay({
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-carbon-950/60",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-carbon-950/60 backdrop-blur-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
286
web/frontend/src/contexts/GlobalUploadContext.tsx
Normal file
286
web/frontend/src/contexts/GlobalUploadContext.tsx
Normal file
@@ -0,0 +1,286 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useCallback,
|
||||
type PropsWithChildren,
|
||||
} from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useAudioUpload, useMultiTrackUpload } from "@/features/transcription/hooks/useAudioFiles";
|
||||
import { useToast } from "@/components/ui/toast";
|
||||
import { MultiTrackUploadDialog } from "@/features/transcription/components/MultiTrackUploadDialog";
|
||||
|
||||
// Types
|
||||
interface FileWithType {
|
||||
file: File;
|
||||
isVideo: boolean;
|
||||
}
|
||||
|
||||
interface UploadProgress {
|
||||
fileName: string;
|
||||
status: "uploading" | "success" | "error";
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface GlobalUploadContextValue {
|
||||
// File upload
|
||||
handleFileSelect: (
|
||||
files: File | File[] | FileWithType | FileWithType[]
|
||||
) => Promise<void>;
|
||||
// Multi-track
|
||||
handleMultiTrackUpload: (
|
||||
files: File[],
|
||||
aupFile: File,
|
||||
title: string
|
||||
) => Promise<void>;
|
||||
openMultiTrackDialog: () => void;
|
||||
// Recording completion
|
||||
handleRecordingComplete: (blob: Blob, title: string) => Promise<void>;
|
||||
// State
|
||||
isUploading: boolean;
|
||||
uploadProgress: UploadProgress[];
|
||||
// For Dashboard to render its own progress bar
|
||||
isOnDashboard: boolean;
|
||||
}
|
||||
|
||||
const GlobalUploadContext = createContext<GlobalUploadContextValue | null>(
|
||||
null
|
||||
);
|
||||
|
||||
export function GlobalUploadProvider({ children }: PropsWithChildren) {
|
||||
const { mutateAsync: uploadFile } = useAudioUpload();
|
||||
const { mutateAsync: uploadMultiTrack } = useMultiTrackUpload();
|
||||
const { toast } = useToast();
|
||||
const location = useLocation();
|
||||
|
||||
// Check if we're on the dashboard (home page)
|
||||
const isOnDashboard = location.pathname === "/" || location.pathname === "";
|
||||
|
||||
// Upload state
|
||||
const [uploadProgress, setUploadProgress] = useState<UploadProgress[]>([]);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
// Multi-track dialog state
|
||||
const [isMultiTrackDialogOpen, setIsMultiTrackDialogOpen] = useState(false);
|
||||
const [multiTrackPreview, setMultiTrackPreview] = useState<{
|
||||
audioFiles: File[];
|
||||
aupFile: File;
|
||||
title: string;
|
||||
} | null>(null);
|
||||
|
||||
const handleFileSelect = useCallback(
|
||||
async (files: File | File[] | FileWithType | FileWithType[]) => {
|
||||
// Normalize input to an array of FileWithType objects
|
||||
const fileArray = Array.isArray(files) ? files : [files];
|
||||
const processedFiles = fileArray.map((item) => {
|
||||
if ("file" in item && "isVideo" in item) {
|
||||
return item;
|
||||
} else {
|
||||
return { file: item as File, isVideo: false };
|
||||
}
|
||||
});
|
||||
|
||||
if (processedFiles.length === 0) return;
|
||||
|
||||
setIsUploading(true);
|
||||
|
||||
// If on dashboard, use progress bar; otherwise use toasts
|
||||
if (isOnDashboard) {
|
||||
setUploadProgress(
|
||||
processedFiles.map((item) => ({
|
||||
fileName: item.file.name,
|
||||
status: "uploading",
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
toast({
|
||||
title: "Uploading...",
|
||||
description: `Uploading ${processedFiles.length} file(s)`,
|
||||
});
|
||||
}
|
||||
|
||||
let successCount = 0;
|
||||
|
||||
// Upload files sequentially
|
||||
for (let i = 0; i < processedFiles.length; i++) {
|
||||
const fileItem = processedFiles[i];
|
||||
const file = fileItem.file;
|
||||
const isVideo = fileItem.isVideo;
|
||||
|
||||
try {
|
||||
await uploadFile({ file, isVideo });
|
||||
|
||||
if (isOnDashboard) {
|
||||
setUploadProgress((prev) =>
|
||||
prev.map((item, index) =>
|
||||
index === i
|
||||
? { ...item, status: "success", error: undefined }
|
||||
: item
|
||||
)
|
||||
);
|
||||
}
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
if (isOnDashboard) {
|
||||
setUploadProgress((prev) =>
|
||||
prev.map((item, index) =>
|
||||
index === i
|
||||
? {
|
||||
...item,
|
||||
status: "error",
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Upload failed",
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
} else {
|
||||
toast({
|
||||
title: "Upload Failed",
|
||||
description: `Failed to upload ${file.name}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setIsUploading(false);
|
||||
|
||||
// Show success toast if not on dashboard
|
||||
if (!isOnDashboard && successCount > 0) {
|
||||
toast({
|
||||
title: "Upload Complete",
|
||||
description: `Successfully uploaded ${successCount} file(s)`,
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-hide progress after 3 seconds if all succeeded (for dashboard)
|
||||
if (isOnDashboard && successCount === fileArray.length) {
|
||||
setTimeout(() => setUploadProgress([]), 3000);
|
||||
}
|
||||
},
|
||||
[isOnDashboard, uploadFile, toast]
|
||||
);
|
||||
|
||||
const handleMultiTrackUpload = useCallback(
|
||||
async (files: File[], aupFile: File, title: string) => {
|
||||
setIsUploading(true);
|
||||
|
||||
if (isOnDashboard) {
|
||||
setUploadProgress([
|
||||
{
|
||||
fileName: `${title} (${files.length} tracks)`,
|
||||
status: "uploading",
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
toast({
|
||||
title: "Uploading Multi-Track...",
|
||||
description: `Uploading ${title} with ${files.length} tracks`,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await uploadMultiTrack({ files, aupFile, title });
|
||||
|
||||
if (isOnDashboard) {
|
||||
setUploadProgress([
|
||||
{
|
||||
fileName: `${title} (${files.length} tracks)`,
|
||||
status: "success",
|
||||
},
|
||||
]);
|
||||
setTimeout(() => setUploadProgress([]), 3000);
|
||||
} else {
|
||||
toast({
|
||||
title: "Upload Complete",
|
||||
description: `Successfully uploaded ${title}`,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (isOnDashboard) {
|
||||
setUploadProgress([
|
||||
{
|
||||
fileName: `${title} (${files.length} tracks)`,
|
||||
status: "error",
|
||||
error:
|
||||
error instanceof Error ? error.message : "Upload failed",
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
toast({
|
||||
title: "Upload Failed",
|
||||
description: `Failed to upload ${title}`,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
},
|
||||
[isOnDashboard, uploadMultiTrack, toast]
|
||||
);
|
||||
|
||||
const openMultiTrackDialog = useCallback(() => {
|
||||
setMultiTrackPreview(null);
|
||||
setIsMultiTrackDialogOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleRecordingComplete = useCallback(
|
||||
async (blob: Blob, title: string) => {
|
||||
const file = new File([blob], `${title}.webm`, { type: blob.type });
|
||||
await handleFileSelect(file);
|
||||
},
|
||||
[handleFileSelect]
|
||||
);
|
||||
|
||||
const handleMultiTrackDialogClose = useCallback(() => {
|
||||
setIsMultiTrackDialogOpen(false);
|
||||
setMultiTrackPreview(null);
|
||||
}, []);
|
||||
|
||||
const handleMultiTrackConfirm = useCallback(
|
||||
async (files: File[], aupFile: File, title: string) => {
|
||||
await handleMultiTrackUpload(files, aupFile, title);
|
||||
handleMultiTrackDialogClose();
|
||||
},
|
||||
[handleMultiTrackUpload, handleMultiTrackDialogClose]
|
||||
);
|
||||
|
||||
|
||||
const value: GlobalUploadContextValue = {
|
||||
handleFileSelect,
|
||||
handleMultiTrackUpload,
|
||||
openMultiTrackDialog,
|
||||
handleRecordingComplete,
|
||||
isUploading,
|
||||
uploadProgress,
|
||||
isOnDashboard,
|
||||
};
|
||||
|
||||
return (
|
||||
<GlobalUploadContext.Provider value={value}>
|
||||
{children}
|
||||
|
||||
{/* Multi-track Upload Dialog (global) */}
|
||||
<MultiTrackUploadDialog
|
||||
open={isMultiTrackDialogOpen}
|
||||
onOpenChange={handleMultiTrackDialogClose}
|
||||
onMultiTrackUpload={handleMultiTrackConfirm}
|
||||
prePopulatedFiles={multiTrackPreview?.audioFiles}
|
||||
prePopulatedAupFile={multiTrackPreview?.aupFile}
|
||||
prePopulatedTitle={multiTrackPreview?.title}
|
||||
/>
|
||||
</GlobalUploadContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useGlobalUpload() {
|
||||
const ctx = useContext(GlobalUploadContext);
|
||||
if (!ctx) {
|
||||
throw new Error(
|
||||
"useGlobalUpload must be used within GlobalUploadProvider"
|
||||
);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -47,15 +47,9 @@ export function Settings() {
|
||||
fetchLLM();
|
||||
}, [activeTab, getAuthHeaders]);
|
||||
|
||||
|
||||
// Dummy function for file select (Settings page doesn't upload files)
|
||||
const handleFileSelect = () => {
|
||||
// No file upload in settings
|
||||
};
|
||||
|
||||
return (
|
||||
<MainLayout
|
||||
header={<Header onFileSelect={handleFileSelect} />}
|
||||
header={<Header />}
|
||||
>
|
||||
{/* Main Content Container with same styling as Homepage */}
|
||||
<div className="bg-[var(--bg-card)] border border-[var(--border-subtle)] shadow-[var(--shadow-card)] rounded-[var(--radius-card)] p-2 sm:p-6 mt-8">
|
||||
|
||||
@@ -186,7 +186,7 @@ export const AudioDetailView = function AudioDetailView({ audioId: propAudioId }
|
||||
<div className="flex-1 overflow-y-auto scrollbar-thin">
|
||||
<div className="mx-auto w-full max-w-[960px] px-4 sm:px-6 py-6 pb-32">
|
||||
<div className="mb-6 pb-6">
|
||||
<Header onFileSelect={() => { }} />
|
||||
<Header />
|
||||
</div>
|
||||
<div className="space-y-6 sm:space-y-8">
|
||||
{/* Title & Metadata */}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useState, useCallback, useRef, useEffect } from "react";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { Header } from "@/components/Header";
|
||||
import { MainLayout } from "@/components/layout/MainLayout";
|
||||
import { AudioFilesTable } from "./AudioFilesTable";
|
||||
import { DragDropOverlay } from "@/components/DragDropOverlay";
|
||||
import { MultiTrackUploadDialog } from "./MultiTrackUploadDialog";
|
||||
import { useAudioUpload, useMultiTrackUpload } from "@/features/transcription/hooks/useAudioFiles";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { X, CheckCircle, AlertCircle } from "lucide-react";
|
||||
@@ -16,134 +14,30 @@ import {
|
||||
getFileDescription,
|
||||
validateMultiTrackFiles
|
||||
} from "@/utils/fileProcessor";
|
||||
|
||||
interface FileWithType {
|
||||
file: File;
|
||||
isVideo: boolean;
|
||||
}
|
||||
|
||||
interface UploadProgress {
|
||||
fileName: string;
|
||||
status: 'uploading' | 'success' | 'error';
|
||||
error?: string;
|
||||
}
|
||||
import { useGlobalUpload } from "@/contexts/GlobalUploadContext";
|
||||
|
||||
export function Dashboard() {
|
||||
const { mutateAsync: uploadFile } = useAudioUpload();
|
||||
const { mutateAsync: uploadMultiTrack } = useMultiTrackUpload();
|
||||
// Get upload functionality from global context
|
||||
const {
|
||||
handleFileSelect,
|
||||
openMultiTrackDialog,
|
||||
isUploading,
|
||||
uploadProgress,
|
||||
} = useGlobalUpload();
|
||||
|
||||
const [uploadProgress, setUploadProgress] = useState<UploadProgress[]>([]);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
// Drag and drop state
|
||||
// Drag and drop state (dashboard-specific UI)
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [dragCount, setDragCount] = useState(0);
|
||||
const [draggedFileGroup, setDraggedFileGroup] = useState<ReturnType<typeof groupFiles> | null>(null);
|
||||
const [isMultiTrackDialogOpen, setIsMultiTrackDialogOpen] = useState(false);
|
||||
const [multiTrackPreview, setMultiTrackPreview] = useState<{ audioFiles: File[], aupFile: File, title: string } | null>(null);
|
||||
const dragCounter = useRef(0);
|
||||
|
||||
|
||||
|
||||
const handleFileSelect = async (files: File | File[] | FileWithType | FileWithType[]) => {
|
||||
// Normalize input to an array of FileWithType objects
|
||||
const fileArray = Array.isArray(files) ? files : [files];
|
||||
const processedFiles = fileArray.map(item => {
|
||||
if ('file' in item && 'isVideo' in item) {
|
||||
// It's already a FileWithType
|
||||
return item;
|
||||
} else {
|
||||
// It's a regular File, treat as audio
|
||||
return { file: item as File, isVideo: false };
|
||||
}
|
||||
});
|
||||
|
||||
if (processedFiles.length === 0) return;
|
||||
|
||||
setIsUploading(true);
|
||||
setUploadProgress(processedFiles.map(item => ({
|
||||
fileName: item.file.name,
|
||||
status: 'uploading'
|
||||
})));
|
||||
|
||||
let successCount = 0;
|
||||
|
||||
// Upload files sequentially to avoid overwhelming the server
|
||||
for (let i = 0; i < processedFiles.length; i++) {
|
||||
const fileItem = processedFiles[i];
|
||||
const file = fileItem.file;
|
||||
const isVideo = fileItem.isVideo;
|
||||
|
||||
try {
|
||||
await uploadFile({ file, isVideo });
|
||||
|
||||
setUploadProgress(prev => prev.map((item, index) =>
|
||||
index === i ? {
|
||||
...item,
|
||||
status: 'success',
|
||||
error: undefined
|
||||
} : item
|
||||
));
|
||||
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
setUploadProgress(prev => prev.map((item, index) =>
|
||||
index === i ? {
|
||||
...item,
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error.message : 'Upload failed'
|
||||
} : item
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
setIsUploading(false);
|
||||
|
||||
// Auto-hide progress after 3 seconds if all succeeded
|
||||
if (successCount === fileArray.length) {
|
||||
setTimeout(() => setUploadProgress([]), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTranscribe = () => {
|
||||
// Table auto-refreshes when transcription starts via query invalidation
|
||||
};
|
||||
|
||||
const dismissProgress = () => {
|
||||
setUploadProgress([]);
|
||||
};
|
||||
|
||||
const handleMultiTrackUpload = async (files: File[], aupFile: File, title: string) => {
|
||||
setIsUploading(true);
|
||||
|
||||
// Create progress entry for multi-track upload
|
||||
const multiTrackProgress = {
|
||||
fileName: `${title} (${files.length} tracks)`,
|
||||
status: 'uploading' as const
|
||||
};
|
||||
|
||||
setUploadProgress([multiTrackProgress]);
|
||||
|
||||
try {
|
||||
await uploadMultiTrack({ files, aupFile, title });
|
||||
|
||||
setUploadProgress([{
|
||||
...multiTrackProgress,
|
||||
status: 'success'
|
||||
}]);
|
||||
|
||||
// Auto-hide progress after 3 seconds
|
||||
setTimeout(() => setUploadProgress([]), 3000);
|
||||
|
||||
} catch (error) {
|
||||
setUploadProgress([{
|
||||
...multiTrackProgress,
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error.message : 'Upload failed'
|
||||
}]);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
// Progress is managed by global context, but we can trigger a refresh
|
||||
// by updating dependencies. For now, progress auto-dismisses.
|
||||
};
|
||||
|
||||
// Global drag and drop handlers
|
||||
@@ -213,8 +107,8 @@ export function Dashboard() {
|
||||
if (fileGroup.type === 'multitrack') {
|
||||
const multiTrackFiles = prepareMultiTrackFiles(fileGroup);
|
||||
if (multiTrackFiles) {
|
||||
setMultiTrackPreview(multiTrackFiles);
|
||||
setIsMultiTrackDialogOpen(true);
|
||||
// Open the global multi-track dialog
|
||||
openMultiTrackDialog();
|
||||
}
|
||||
} else if (fileGroup.type === 'video') {
|
||||
const filesWithType = convertToFileWithType(fileGroup.files, true);
|
||||
@@ -236,30 +130,12 @@ export function Dashboard() {
|
||||
window.removeEventListener('dragover', handleWindowDragOver);
|
||||
window.removeEventListener('drop', handleWindowDrop);
|
||||
};
|
||||
}, []); // Empty dependency array as handlers use refs or stable functions
|
||||
|
||||
const handleMultiTrackDialogClose = useCallback(() => {
|
||||
setIsMultiTrackDialogOpen(false);
|
||||
setMultiTrackPreview(null);
|
||||
}, []);
|
||||
|
||||
const handleMultiTrackConfirm = useCallback(async (files: File[], aupFile: File, title: string) => {
|
||||
await handleMultiTrackUpload(files, aupFile, title);
|
||||
handleMultiTrackDialogClose();
|
||||
}, []);
|
||||
}, [handleFileSelect, openMultiTrackDialog]);
|
||||
|
||||
return (
|
||||
<MainLayout
|
||||
className="min-h-screen bg-[var(--bg-main)]"
|
||||
header={
|
||||
<Header
|
||||
onFileSelect={handleFileSelect}
|
||||
onMultiTrackClick={() => setIsMultiTrackDialogOpen(true)}
|
||||
onDownloadComplete={() => {
|
||||
// Table auto-refreshes due to query invalidation
|
||||
}}
|
||||
/>
|
||||
}
|
||||
header={<Header />}
|
||||
>
|
||||
{/* Upload Progress */}
|
||||
|
||||
@@ -344,16 +220,6 @@ export function Dashboard() {
|
||||
: "No supported files found")
|
||||
: undefined}
|
||||
/>
|
||||
|
||||
{/* Multi-track Upload Dialog with pre-populated data */}
|
||||
<MultiTrackUploadDialog
|
||||
open={isMultiTrackDialogOpen}
|
||||
onOpenChange={handleMultiTrackDialogClose}
|
||||
onMultiTrackUpload={handleMultiTrackConfirm}
|
||||
prePopulatedFiles={multiTrackPreview?.audioFiles}
|
||||
prePopulatedAupFile={multiTrackPreview?.aupFile}
|
||||
prePopulatedTitle={multiTrackPreview?.title}
|
||||
/>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ProtectedRoute } from './components/ProtectedRoute'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { ToastProvider } from '@/components/ui/toast'
|
||||
import { ChatEventsProvider } from './contexts/ChatEventsContext'
|
||||
import { GlobalUploadProvider } from './contexts/GlobalUploadContext'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
@@ -24,7 +25,9 @@ createRoot(document.getElementById('root')!).render(
|
||||
<ToastProvider>
|
||||
<ChatEventsProvider>
|
||||
<ProtectedRoute>
|
||||
<App />
|
||||
<GlobalUploadProvider>
|
||||
<App />
|
||||
</GlobalUploadProvider>
|
||||
</ProtectedRoute>
|
||||
</ChatEventsProvider>
|
||||
</ToastProvider>
|
||||
|
||||
Reference in New Issue
Block a user