diff options
Diffstat (limited to 'apps/web/src/components')
4 files changed, 177 insertions, 128 deletions
diff --git a/apps/web/src/components/editor/export-button.tsx b/apps/web/src/components/editor/export-button.tsx index 41a5f65c..246e8b6f 100644 --- a/apps/web/src/components/editor/export-button.tsx +++ b/apps/web/src/components/editor/export-button.tsx @@ -16,7 +16,7 @@ import { DEFAULT_EXPORT_OPTIONS, } from "@/lib/export"; import { useProjectStore } from "@/stores/project-store"; -import { Download, X } from "lucide-react"; +import { Check, Copy, Download, RotateCcw, X } from "lucide-react"; import { ExportFormat, ExportQuality, ExportResult } from "@/types/export"; import { PropertyGroup } from "./properties-panel/property-item"; @@ -134,135 +134,184 @@ function ExportPopover({ return ( <PopoverContent className="w-80 mr-4 flex flex-col gap-3 bg-background"> <> - <div className="flex items-center justify-between"> - <h3 className=" font-medium"> - {isExporting ? "Exporting project" : "Export project"} - </h3> - <Button variant="text" size="icon" onClick={handleClose}> - <X className="!size-5 text-foreground/85" /> - </Button> - </div> + {exportResult && !exportResult.success ? ( + <ExportError + error={exportResult.error || "Unknown error occurred"} + onRetry={handleExport} + /> + ) : ( + <> + <div className="flex items-center justify-between"> + <h3 className=" font-medium"> + {isExporting ? "Exporting project" : "Export project"} + </h3> + <Button variant="text" size="icon" onClick={handleClose}> + <X className="!size-5 text-foreground/85" /> + </Button> + </div> - <div className="flex flex-col gap-4"> - {!isExporting && ( - <> - <div className="flex flex-col gap-3"> - <PropertyGroup - title="Format" - titleClassName="text-sm" - defaultExpanded={false} - > - <RadioGroup - value={format} - onValueChange={(value) => setFormat(value as ExportFormat)} - > - <div className="flex items-center space-x-2"> - <RadioGroupItem value="mp4" id="mp4" /> - <Label htmlFor="mp4"> - MP4 (H.264) - Better compatibility - </Label> - </div> - <div className="flex items-center space-x-2"> - <RadioGroupItem value="webm" id="webm" /> - <Label htmlFor="webm"> - WebM (VP9) - Smaller file size - </Label> - </div> - </RadioGroup> - </PropertyGroup> + <div className="flex flex-col gap-4"> + {!isExporting && ( + <> + <div className="flex flex-col gap-3"> + <PropertyGroup + title="Format" + titleClassName="text-sm" + defaultExpanded={false} + > + <RadioGroup + value={format} + onValueChange={(value) => + setFormat(value as ExportFormat) + } + > + <div className="flex items-center space-x-2"> + <RadioGroupItem value="mp4" id="mp4" /> + <Label htmlFor="mp4"> + MP4 (H.264) - Better compatibility + </Label> + </div> + <div className="flex items-center space-x-2"> + <RadioGroupItem value="webm" id="webm" /> + <Label htmlFor="webm"> + WebM (VP9) - Smaller file size + </Label> + </div> + </RadioGroup> + </PropertyGroup> - <PropertyGroup - title="Quality" - titleClassName="text-sm" - defaultExpanded={false} - > - <RadioGroup - value={quality} - onValueChange={(value) => - setQuality(value as ExportQuality) - } - > - <div className="flex items-center space-x-2"> - <RadioGroupItem value="low" id="low" /> - <Label htmlFor="low">Low - Smallest file size</Label> - </div> - <div className="flex items-center space-x-2"> - <RadioGroupItem value="medium" id="medium" /> - <Label htmlFor="medium">Medium - Balanced</Label> - </div> - <div className="flex items-center space-x-2"> - <RadioGroupItem value="high" id="high" /> - <Label htmlFor="high">High - Recommended</Label> - </div> - <div className="flex items-center space-x-2"> - <RadioGroupItem value="very_high" id="very_high" /> - <Label htmlFor="very_high"> - Very High - Largest file size - </Label> - </div> - </RadioGroup> - </PropertyGroup> + <PropertyGroup + title="Quality" + titleClassName="text-sm" + defaultExpanded={false} + > + <RadioGroup + value={quality} + onValueChange={(value) => + setQuality(value as ExportQuality) + } + > + <div className="flex items-center space-x-2"> + <RadioGroupItem value="low" id="low" /> + <Label htmlFor="low">Low - Smallest file size</Label> + </div> + <div className="flex items-center space-x-2"> + <RadioGroupItem value="medium" id="medium" /> + <Label htmlFor="medium">Medium - Balanced</Label> + </div> + <div className="flex items-center space-x-2"> + <RadioGroupItem value="high" id="high" /> + <Label htmlFor="high">High - Recommended</Label> + </div> + <div className="flex items-center space-x-2"> + <RadioGroupItem value="very_high" id="very_high" /> + <Label htmlFor="very_high"> + Very High - Largest file size + </Label> + </div> + </RadioGroup> + </PropertyGroup> - <PropertyGroup - title="Audio" - titleClassName="text-sm" - defaultExpanded={false} - > - <div className="flex items-center space-x-2"> - <Checkbox - id="include-audio" - checked={includeAudio} - onCheckedChange={(checked) => setIncludeAudio(!!checked)} - /> - <Label htmlFor="include-audio"> - Include audio in export - </Label> + <PropertyGroup + title="Audio" + titleClassName="text-sm" + defaultExpanded={false} + > + <div className="flex items-center space-x-2"> + <Checkbox + id="include-audio" + checked={includeAudio} + onCheckedChange={(checked) => + setIncludeAudio(!!checked) + } + /> + <Label htmlFor="include-audio"> + Include audio in export + </Label> + </div> + </PropertyGroup> </div> - </PropertyGroup> - </div> - <Button onClick={handleExport} className="w-full gap-2"> - <Download className="w-4 h-4" /> - Export - </Button> - </> - )} - - {isExporting && ( - <div className="space-y-4"> - <div className="flex flex-col"> - <div className="text-center flex items-center justify-between"> - <p className="text-sm text-muted-foreground mb-2"> - {Math.round(progress * 100)}% - </p> - <p className="text-sm text-muted-foreground mb-2">100%</p> - </div> - <Progress value={progress * 100} className="w-full" /> - </div> + <Button onClick={handleExport} className="w-full gap-2"> + <Download className="w-4 h-4" /> + Export + </Button> + </> + )} - <Button - variant="outline" - className="rounded-md w-full" - onClick={() => {}} - > - Cancel - </Button> - </div> - )} + {isExporting && ( + <div className="space-y-4"> + <div className="flex flex-col"> + <div className="text-center flex items-center justify-between"> + <p className="text-sm text-muted-foreground mb-2"> + {Math.round(progress * 100)}% + </p> + <p className="text-sm text-muted-foreground mb-2">100%</p> + </div> + <Progress value={progress * 100} className="w-full" /> + </div> - {exportResult && !exportResult.success && ( - <div className="text-center space-y-3"> - <div className="text-red-600 font-medium">Export failed</div> - <p className="text-sm text-muted-foreground"> - {exportResult.error || "Unknown error occurred"} - </p> - <Button variant="outline" onClick={() => setExportResult(null)}> - Try Again - </Button> + <Button + variant="outline" + className="rounded-md w-full" + onClick={() => {}} + > + Cancel + </Button> + </div> + )} </div> - )} - </div> + </> + )} </> </PopoverContent> ); } + +function ExportError({ + error, + onRetry, +}: { + error: string; + onRetry: () => void; +}) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(error); + setCopied(true); + setTimeout(() => setCopied(false), 1000); + }; + + return ( + <div className="space-y-4"> + <div className="flex flex-col gap-1.5"> + <p className="text-sm font-medium text-red-400">Export failed</p> + <p className="text-xs text-muted-foreground"> + {error} + </p> + </div> + + <div className="flex gap-2"> + <Button + variant="outline" + size="sm" + className="flex-1 text-xs h-8" + onClick={handleCopy} + > + {copied ? <Check className="text-green-500" /> : <Copy />} + Copy + </Button> + <Button + variant="outline" + size="sm" + className="flex-1 text-xs h-8" + onClick={onRetry} + > + <RotateCcw /> + Retry + </Button> + </div> + </div> + ); +} diff --git a/apps/web/src/components/editor/media-panel/views/captions.tsx b/apps/web/src/components/editor/media-panel/views/captions.tsx index 59403a8d..fbe5b418 100644 --- a/apps/web/src/components/editor/media-panel/views/captions.tsx +++ b/apps/web/src/components/editor/media-panel/views/captions.tsx @@ -188,7 +188,7 @@ export function Captions() { }; return ( - <BaseView ref={containerRef} className="flex flex-col justify-between"> + <BaseView ref={containerRef} className="flex flex-col justify-between h-full"> <PropertyGroup title="Language"> <LanguageSelect selectedCountry={selectedCountry} diff --git a/apps/web/src/components/editor/panel-base-view.tsx b/apps/web/src/components/editor/panel-base-view.tsx index ec01028c..bef4e62c 100644 --- a/apps/web/src/components/editor/panel-base-view.tsx +++ b/apps/web/src/components/editor/panel-base-view.tsx @@ -1,6 +1,7 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
interface PanelBaseViewProps {
children?: React.ReactNode;
@@ -25,7 +26,7 @@ function ViewContent({ }) {
return (
<ScrollArea className="flex-1">
- <div className={`p-5 h-full ${className}`}>{children}</div>
+ <div className={cn("p-5", className)}>{children}</div>
</ScrollArea>
);
}
@@ -40,7 +41,7 @@ export function PanelBaseView({ ref,
}: PanelBaseViewProps) {
return (
- <div className={`h-full flex flex-col ${className}`} ref={ref}>
+ <div className={cn("h-full flex flex-col", className)} ref={ref}>
{!tabs || tabs.length === 0 ? (
<ViewContent className={className}>{children}</ViewContent>
) : (
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index b7cc9578..8b1abd81 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -260,6 +260,7 @@ export function PreviewPanel() { }, []); const hasAnyElements = tracks.some((track) => track.elements.length > 0); + const shouldRenderPreview = hasAnyElements || activeProject?.backgroundType; const getActiveElements = (): ActiveElement[] => { const activeElements: ActiveElement[] = []; @@ -701,7 +702,7 @@ export function PreviewPanel() { className="flex-1 flex flex-col items-center justify-center min-h-0 min-w-0" > <div className="flex-1" /> - {hasAnyElements ? ( + {shouldRenderPreview ? ( <div ref={previewRef} className="relative overflow-hidden border" @@ -727,9 +728,7 @@ export function PreviewPanel() { aria-label="Video preview canvas" /> {activeElements.length === 0 ? ( - <div className="absolute inset-0 flex items-center justify-center text-muted-foreground"> - No elements at current time - </div> + <></> ) : ( activeElements.map((elementData) => renderElement(elementData)) )} |
