feat: make asset downloads opt-in via "Include assets" toggle

Project/selected downloads now include only videos by default. Add an
"Include assets" checkbox toggle to both download dropdowns (default off)
that adds b-rolls and other attached assets to the download when enabled.

- buildProjectDownloadManifest gains an includeAssets option (default false).
- Download route reads ?assets=1 and passes it through.
This commit is contained in:
yusufipk
2026-07-10 21:03:34 +07:00
parent 57c5a127d1
commit 8d7d064647
3 changed files with 65 additions and 15 deletions
@@ -27,8 +27,10 @@ import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
@@ -105,6 +107,7 @@ export function ProjectContentClient({
const [selectedVideoIds, setSelectedVideoIds] = useState<string[]>([]);
const [selectionMode, setSelectionMode] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
const [includeAssetsInDownload, setIncludeAssetsInDownload] = useState(false);
const [isDeletingSelected, setIsDeletingSelected] = useState(false);
const [showDeleteSelectedDialog, setShowDeleteSelectedDialog] = useState(false);
const [showMoveSelectedDialog, setShowMoveSelectedDialog] = useState(false);
@@ -184,7 +187,7 @@ export function ProjectContentClient({
}, []);
const startProjectDownload = useCallback(
async (videoIds?: string[], options?: { allVersions?: boolean }) => {
async (videoIds?: string[], options?: { allVersions?: boolean; includeAssets?: boolean }) => {
if (!canDownloadProject || isDownloading) return;
const searchParams = new URLSearchParams();
@@ -194,6 +197,9 @@ export function ProjectContentClient({
if (options?.allVersions) {
searchParams.set('versions', 'all');
}
if (options?.includeAssets) {
searchParams.set('assets', '1');
}
const query = searchParams.toString() ? `?${searchParams.toString()}` : '';
setIsDownloading(true);
@@ -359,11 +365,28 @@ export function ProjectContentClient({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => startProjectDownload()}>
<DropdownMenuCheckboxItem
checked={includeAssetsInDownload}
onCheckedChange={(checked) => setIncludeAssetsInDownload(checked === true)}
onSelect={(event) => event.preventDefault()}
>
Include assets
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() =>
startProjectDownload(undefined, { includeAssets: includeAssetsInDownload })
}
>
Latest version only
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => startProjectDownload(undefined, { allVersions: true })}
onClick={() =>
startProjectDownload(undefined, {
allVersions: true,
includeAssets: includeAssetsInDownload,
})
}
>
All versions
</DropdownMenuItem>
@@ -446,11 +469,30 @@ export function ProjectContentClient({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => startProjectDownload(selectedVideoIds)}>
<DropdownMenuCheckboxItem
checked={includeAssetsInDownload}
onCheckedChange={(checked) => setIncludeAssetsInDownload(checked === true)}
onSelect={(event) => event.preventDefault()}
>
Include assets
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() =>
startProjectDownload(selectedVideoIds, {
includeAssets: includeAssetsInDownload,
})
}
>
Latest version only
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => startProjectDownload(selectedVideoIds, { allVersions: true })}
onClick={() =>
startProjectDownload(selectedVideoIds, {
allVersions: true,
includeAssets: includeAssetsInDownload,
})
}
>
All versions
</DropdownMenuItem>
@@ -23,6 +23,7 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
const { projectId } = await params;
const requestedVideoIds = parseRequestedVideoIds(request.nextUrl.searchParams.get('videoIds'));
const includeAllVersions = request.nextUrl.searchParams.get('versions') === 'all';
const includeAssets = request.nextUrl.searchParams.get('assets') === '1';
if (requestedVideoIds && requestedVideoIds.length === 0) {
return apiErrors.badRequest('At least one video must be selected for download');
@@ -93,7 +94,10 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
}
}
const manifest = buildProjectDownloadManifest(project.name, videos, { includeAllVersions });
const manifest = buildProjectDownloadManifest(project.name, videos, {
includeAllVersions,
includeAssets,
});
const validationError = validateProjectDownloadManifest(manifest);
if (validationError) {
return apiErrors.badRequest(validationError);