Update
This commit is contained in:
@@ -3,7 +3,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Clock10Icon, Download, Droplets } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const VideoDownloadModal = ({
|
||||
nonWatermarkVideosLeft = 0,
|
||||
@@ -17,14 +17,24 @@ const VideoDownloadModal = ({
|
||||
}) => {
|
||||
const [showDebug, setShowDebug] = useState(false);
|
||||
const [exportType, setExportType] = useState(null);
|
||||
const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState(null);
|
||||
const [status, setStatus] = useState('start'); // 'start', 'processing', 'complete'
|
||||
|
||||
const exportStartTime = useRef(null);
|
||||
const lastProgressTime = useRef(null);
|
||||
const lastProgress = useRef(0);
|
||||
|
||||
const handleExportWithoutWatermark = () => {
|
||||
setExportType('without');
|
||||
setEstimatedTimeRemaining(null);
|
||||
setStatus('processing');
|
||||
handleDownloadButton();
|
||||
};
|
||||
|
||||
const handleExportWithWatermark = () => {
|
||||
setExportType('with');
|
||||
setEstimatedTimeRemaining(null);
|
||||
setStatus('processing');
|
||||
handleDownloadButton();
|
||||
};
|
||||
|
||||
@@ -32,10 +42,86 @@ const VideoDownloadModal = ({
|
||||
onClose();
|
||||
setTimeout(() => {
|
||||
setExportType(null);
|
||||
setEstimatedTimeRemaining(null);
|
||||
setStatus('start');
|
||||
exportStartTime.current = null;
|
||||
lastProgressTime.current = null;
|
||||
lastProgress.current = 0;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const isComplete = exportProgress >= 100 && !isExporting;
|
||||
// Update status based on export progress - transition to complete immediately when 100%
|
||||
useEffect(() => {
|
||||
if (status === 'processing' && exportProgress >= 100) {
|
||||
setStatus('complete');
|
||||
}
|
||||
}, [exportProgress, status]);
|
||||
|
||||
// Calculate estimated time remaining based on progress speed
|
||||
useEffect(() => {
|
||||
if (status !== 'processing' || exportProgress === 0) {
|
||||
exportStartTime.current = null;
|
||||
lastProgressTime.current = null;
|
||||
lastProgress.current = 0;
|
||||
setEstimatedTimeRemaining(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear estimate when complete
|
||||
if (exportProgress >= 100) {
|
||||
setEstimatedTimeRemaining(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// Initialize timing on first progress
|
||||
if (!exportStartTime.current) {
|
||||
exportStartTime.current = now;
|
||||
lastProgress.current = exportProgress;
|
||||
console.log('Initialized timing at', exportProgress + '%');
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate every time progress changes after initial 3 seconds
|
||||
const timeSinceStart = now - exportStartTime.current;
|
||||
const progressDelta = exportProgress - lastProgress.current;
|
||||
|
||||
console.log(
|
||||
'Progress:',
|
||||
exportProgress + '%',
|
||||
'Time since start:',
|
||||
Math.round(timeSinceStart / 1000) + 's',
|
||||
'Progress delta:',
|
||||
progressDelta + '%',
|
||||
);
|
||||
|
||||
if (timeSinceStart > 2000 && progressDelta > 0) {
|
||||
const progressRate = progressDelta / timeSinceStart; // progress per ms
|
||||
const remainingProgress = 100 - exportProgress;
|
||||
const estimatedMs = remainingProgress / progressRate;
|
||||
const estimatedSeconds = Math.round(estimatedMs / 1000);
|
||||
|
||||
console.log('Progress rate:', progressRate, 'Estimated seconds:', estimatedSeconds);
|
||||
|
||||
if (estimatedSeconds >= 1 && estimatedSeconds <= 600) {
|
||||
setEstimatedTimeRemaining(estimatedSeconds);
|
||||
console.log('Set estimated time:', estimatedSeconds + 's');
|
||||
} else {
|
||||
console.log('Time estimate out of range:', estimatedSeconds + 's');
|
||||
}
|
||||
}
|
||||
}, [status, exportProgress]);
|
||||
|
||||
const formatTimeRemaining = (seconds) => {
|
||||
if (seconds < 60) {
|
||||
return `~${seconds}s remaining`;
|
||||
} else {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `~${minutes}m ${remainingSeconds}s remaining`;
|
||||
}
|
||||
};
|
||||
|
||||
const exportTimes = [
|
||||
{
|
||||
@@ -56,18 +142,18 @@ const VideoDownloadModal = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={!isExporting ? onClose : undefined}>
|
||||
<Dialog open={isOpen} onOpenChange={status !== 'processing' ? onClose : undefined}>
|
||||
<DialogContent
|
||||
className="border-2 shadow-2xl sm:max-w-lg [&>button]:hidden"
|
||||
onInteractOutside={(e) => isExporting && e.preventDefault()}
|
||||
onEscapeKeyDown={(e) => isExporting && e.preventDefault()}
|
||||
onInteractOutside={(e) => status === 'processing' && e.preventDefault()}
|
||||
onEscapeKeyDown={(e) => status === 'processing' && e.preventDefault()}
|
||||
>
|
||||
<DialogHeader className="space-y-4">
|
||||
<DialogTitle className="text-2xl font-semibold tracking-tight">Export Video</DialogTitle>
|
||||
<DialogDescription className="hidden text-base leading-relaxed"></DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{!isExporting && !isComplete && (
|
||||
{status === 'start' && (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-muted/30 rounded-xl border p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
@@ -77,7 +163,7 @@ const VideoDownloadModal = ({
|
||||
</p>
|
||||
<div className="text-muted-foreground space-y-2 text-sm">
|
||||
{exportTimes.map((exportTime, index) => (
|
||||
<div className="grid items-center justify-between sm:flex sm:gap-3">
|
||||
<div key={index} className="grid items-center justify-between sm:flex sm:gap-3">
|
||||
<div className="grid items-center text-base sm:flex sm:gap-1">
|
||||
<span className="text-2xl sm:text-base">{exportTime.icon}</span>
|
||||
<span>{exportTime.label}</span>
|
||||
@@ -117,7 +203,7 @@ const VideoDownloadModal = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isExporting && (
|
||||
{status === 'processing' && (
|
||||
<div className="space-y-8 py-4">
|
||||
<div className="space-y-4 text-center">
|
||||
<div className="bg-muted mx-auto flex h-16 w-16 items-center justify-center rounded-full">
|
||||
@@ -128,7 +214,11 @@ const VideoDownloadModal = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-semibold">Exporting {exportType === 'without' ? 'without watermark' : 'with'}</h3>
|
||||
<h3 className="text-xl font-semibold">Exporting {exportType === 'without' ? 'without watermark' : ''}</h3>
|
||||
|
||||
<p className="text-muted-foreground text-sm text-wrap">
|
||||
Please do not close this window while the export is in progress.
|
||||
</p>
|
||||
{exportStatus && <p className="text-muted-foreground text-sm text-wrap">{exportStatus}</p>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,9 +229,35 @@ const VideoDownloadModal = ({
|
||||
<span className="font-mono text-sm font-semibold">{Math.round(exportProgress)}%</span>
|
||||
</div>
|
||||
<Progress value={exportProgress} className="h-3" />
|
||||
{estimatedTimeRemaining != null ? (
|
||||
<div className="text-center">
|
||||
<span className="text-muted-foreground text-xs">{formatTimeRemaining(estimatedTimeRemaining)}</span>
|
||||
</div>
|
||||
) : (
|
||||
exportProgress > 5 && (
|
||||
<div className="text-center">
|
||||
<span className="text-muted-foreground text-xs">Calculating time remaining...</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'complete' && (
|
||||
<div className="space-y-6 py-8 text-center">
|
||||
<div className="bg-muted mx-auto flex h-16 w-16 items-center justify-center rounded-full">
|
||||
<Download className="h-8 w-8" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-semibold">Export Complete!</h3>
|
||||
<p className="text-muted-foreground text-sm">Your video has been successfully exported.</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={handleClose} className="w-full" size="lg">
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ export default defineConfig({
|
||||
],
|
||||
esbuild: {
|
||||
jsx: 'automatic',
|
||||
drop: ["console", "debugger"],
|
||||
//drop: ["console", "debugger"],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
Reference in New Issue
Block a user