feat: full studio build -- light theme, canvas thumbnails, i18n (fa/en)
This commit is contained in:
@@ -0,0 +1,432 @@
|
||||
export type VideoSidebarCategoryId =
|
||||
| "all"
|
||||
| "animation"
|
||||
| "intros"
|
||||
| "editing"
|
||||
| "invitation"
|
||||
| "holiday"
|
||||
| "slideshow"
|
||||
| "presentations"
|
||||
| "social"
|
||||
| "ads"
|
||||
| "sales"
|
||||
| "music";
|
||||
|
||||
export type AspectRatioFilter =
|
||||
| "all"
|
||||
| "widescreen"
|
||||
| "portrait"
|
||||
| "square"
|
||||
| "fourFive";
|
||||
|
||||
export type DurationFilter = "all" | "flexible" | "fixed";
|
||||
|
||||
export type RefineType = "templates" | "packs";
|
||||
|
||||
export interface VideoSidebarCategory {
|
||||
id: VideoSidebarCategoryId;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const VIDEO_SIDEBAR_CATEGORIES: VideoSidebarCategory[] = [
|
||||
{ id: "all", label: "All Templates" },
|
||||
{ id: "animation", label: "Animation Videos" },
|
||||
{ id: "intros", label: "Intros and Logos" },
|
||||
{ id: "editing", label: "Video Editing" },
|
||||
{ id: "invitation", label: "Invitation Videos" },
|
||||
{ id: "holiday", label: "Holiday Videos" },
|
||||
{ id: "slideshow", label: "Slideshow" },
|
||||
{ id: "presentations", label: "Presentations" },
|
||||
{ id: "social", label: "Social Media Videos" },
|
||||
{ id: "ads", label: "Video Ad Templates" },
|
||||
{ id: "sales", label: "Sales Videos" },
|
||||
{ id: "music", label: "Music Visualization" },
|
||||
];
|
||||
|
||||
export const ASPECT_RATIO_OPTIONS: {
|
||||
id: AspectRatioFilter;
|
||||
label: string;
|
||||
}[] = [
|
||||
{ id: "all", label: "All Sizes" },
|
||||
{ id: "widescreen", label: "16:9" },
|
||||
{ id: "portrait", label: "9:16" },
|
||||
{ id: "square", label: "1:1" },
|
||||
{ id: "fourFive", label: "4:5" },
|
||||
];
|
||||
|
||||
export type TemplateDetailAspectRatio = "16:9" | "9:16";
|
||||
|
||||
export const TEMPLATE_STYLE_COUNT = 4;
|
||||
|
||||
export interface VideoCatalogTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
videoCategory: Exclude<VideoSidebarCategoryId, "all">;
|
||||
aspectRatio: Exclude<AspectRatioFilter, "all">;
|
||||
aspectRatios?: readonly TemplateDetailAspectRatio[];
|
||||
durationType: "flexible" | "fixed";
|
||||
premium: boolean;
|
||||
sceneCount: number;
|
||||
supports4k: boolean;
|
||||
colorChange: boolean;
|
||||
scriptToVideo: boolean;
|
||||
description?: string;
|
||||
isNew?: boolean;
|
||||
}
|
||||
|
||||
export function getVideoTemplateCategoryLabel(
|
||||
category: Exclude<VideoSidebarCategoryId, "all">
|
||||
): string {
|
||||
const match = VIDEO_SIDEBAR_CATEGORIES.find((item) => item.id === category);
|
||||
return match?.label ?? category;
|
||||
}
|
||||
|
||||
export function getTemplateDetailAspectRatios(
|
||||
template: VideoCatalogTemplate
|
||||
): TemplateDetailAspectRatio[] {
|
||||
if (template.aspectRatios && template.aspectRatios.length > 0) {
|
||||
return [...template.aspectRatios];
|
||||
}
|
||||
return ["16:9", "9:16"];
|
||||
}
|
||||
|
||||
export function getVideoTemplateStyleImageSrc(
|
||||
templateId: string,
|
||||
styleIndex: number
|
||||
): string {
|
||||
return `https://picsum.photos/seed/${templateId}-style${styleIndex}/240/135`;
|
||||
}
|
||||
|
||||
export function getVideoTemplateExampleImageSrc(
|
||||
templateId: string,
|
||||
exampleIndex: number
|
||||
): string {
|
||||
return `https://picsum.photos/seed/${templateId}-example${exampleIndex}/520/325`;
|
||||
}
|
||||
|
||||
const templatesByCategory: Record<
|
||||
Exclude<VideoSidebarCategoryId, "all">,
|
||||
string[]
|
||||
> = {
|
||||
animation: [
|
||||
"Whiteboard Animation Toolkit",
|
||||
"3D Explainer Video Toolkit",
|
||||
"Trendy Explainer Toolkit",
|
||||
"Factory of 3D Animations",
|
||||
"Anime Stories Pack",
|
||||
"Healthcare Explainer Toolkit",
|
||||
],
|
||||
intros: [
|
||||
"Abstract Distortion Intro",
|
||||
"Glossy Bubbles Intro",
|
||||
"Neon Soundwaves Visualizer",
|
||||
"Minimal Logo Reveal",
|
||||
"Glitch Intro Pack",
|
||||
],
|
||||
editing: [
|
||||
"Cinematic Color Grade",
|
||||
"Quick Cut Montage",
|
||||
"Documentary Style Opener",
|
||||
],
|
||||
invitation: [
|
||||
"Wedding Invitation Slideshow",
|
||||
"Birthday Party Invite",
|
||||
"Corporate Event Opening",
|
||||
],
|
||||
holiday: [
|
||||
"Christmas Greeting Card",
|
||||
"New Year Countdown",
|
||||
"Seasonal Sale Promo",
|
||||
],
|
||||
slideshow: [
|
||||
"Polaroid Frames Slideshow",
|
||||
"Flipping Slideshow",
|
||||
"Fragmented Transitions Slideshow",
|
||||
"Parallax Circles",
|
||||
"Bokeh Effects Slideshow",
|
||||
],
|
||||
presentations: [
|
||||
"Business Presentation Pack",
|
||||
"Startup Pitch Deck",
|
||||
"Quarterly Report Intro",
|
||||
],
|
||||
social: [
|
||||
"Instagram Carousel",
|
||||
"TikTok Hook Pack",
|
||||
"Story Highlight Reel",
|
||||
"LinkedIn Promo",
|
||||
],
|
||||
ads: [
|
||||
"Product Launch Ad",
|
||||
"App Promo Vertical",
|
||||
"Flash Sale Countdown",
|
||||
],
|
||||
sales: [
|
||||
"SaaS Explainer",
|
||||
"Real Estate Walkthrough",
|
||||
"Restaurant Promo",
|
||||
],
|
||||
music: [
|
||||
"Audio Spectrum Visualizer",
|
||||
"Vinyl Record Spin",
|
||||
"Beat Sync Typography",
|
||||
],
|
||||
};
|
||||
|
||||
const aspectRatios: Exclude<AspectRatioFilter, "all">[] = [
|
||||
"widescreen",
|
||||
"portrait",
|
||||
"square",
|
||||
"fourFive",
|
||||
];
|
||||
|
||||
function buildVideoCatalog(): VideoCatalogTemplate[] {
|
||||
const items: VideoCatalogTemplate[] = [];
|
||||
let index = 0;
|
||||
|
||||
for (const [category, names] of Object.entries(templatesByCategory)) {
|
||||
const videoCategory = category as Exclude<VideoSidebarCategoryId, "all">;
|
||||
for (const baseName of names) {
|
||||
for (let variant = 0; variant < 2; variant += 1) {
|
||||
const name = variant > 0 ? `${baseName} ${variant + 1}` : baseName;
|
||||
const detailAspectRatios: TemplateDetailAspectRatio[] =
|
||||
index % 3 === 0 ? ["16:9"] : ["16:9", "9:16"];
|
||||
items.push({
|
||||
id: `vtpl-${category}-${index}`,
|
||||
name,
|
||||
videoCategory,
|
||||
aspectRatio: aspectRatios[index % aspectRatios.length],
|
||||
aspectRatios: detailAspectRatios,
|
||||
durationType: index % 3 === 0 ? "fixed" : "flexible",
|
||||
premium: index % 4 === 0,
|
||||
sceneCount: 5 + (index % 12) * 10,
|
||||
supports4k: index % 2 === 0,
|
||||
colorChange: index % 3 !== 0,
|
||||
scriptToVideo: index % 5 === 0,
|
||||
isNew: index < 8,
|
||||
});
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Featured presets on /studio/video/new — ids match TEMPLATE_GALLERY_ITEMS */
|
||||
const ONBOARDING_PRESET_TEMPLATES: VideoCatalogTemplate[] = [
|
||||
{
|
||||
id: "promo-reel",
|
||||
name: "Animated Inspirational Video",
|
||||
videoCategory: "animation",
|
||||
aspectRatio: "widescreen",
|
||||
aspectRatios: ["16:9", "9:16"],
|
||||
durationType: "flexible",
|
||||
premium: false,
|
||||
sceneCount: 12,
|
||||
supports4k: true,
|
||||
colorChange: true,
|
||||
scriptToVideo: false,
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
id: "product-launch",
|
||||
name: "Cybersecurity Company Promo",
|
||||
videoCategory: "ads",
|
||||
aspectRatio: "widescreen",
|
||||
aspectRatios: ["16:9", "9:16"],
|
||||
durationType: "flexible",
|
||||
premium: true,
|
||||
sceneCount: 8,
|
||||
supports4k: true,
|
||||
colorChange: true,
|
||||
scriptToVideo: false,
|
||||
},
|
||||
{
|
||||
id: "brand-story",
|
||||
name: "Get to Know Your Customers Day",
|
||||
videoCategory: "social",
|
||||
aspectRatio: "widescreen",
|
||||
aspectRatios: ["16:9", "9:16"],
|
||||
durationType: "flexible",
|
||||
premium: false,
|
||||
sceneCount: 10,
|
||||
supports4k: false,
|
||||
colorChange: true,
|
||||
scriptToVideo: true,
|
||||
},
|
||||
{
|
||||
id: "instagram-carousel",
|
||||
name: "SEO Agency Introduction",
|
||||
videoCategory: "social",
|
||||
aspectRatio: "square",
|
||||
aspectRatios: ["16:9", "9:16"],
|
||||
durationType: "flexible",
|
||||
premium: false,
|
||||
sceneCount: 6,
|
||||
supports4k: false,
|
||||
colorChange: true,
|
||||
scriptToVideo: false,
|
||||
},
|
||||
{
|
||||
id: "tiktok-hook",
|
||||
name: "Tech Startup Promo",
|
||||
videoCategory: "social",
|
||||
aspectRatio: "portrait",
|
||||
aspectRatios: ["9:16"],
|
||||
durationType: "flexible",
|
||||
premium: false,
|
||||
sceneCount: 5,
|
||||
supports4k: false,
|
||||
colorChange: true,
|
||||
scriptToVideo: false,
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
id: "pitch-deck",
|
||||
name: "Corporate Explainer",
|
||||
videoCategory: "presentations",
|
||||
aspectRatio: "widescreen",
|
||||
aspectRatios: ["16:9"],
|
||||
durationType: "fixed",
|
||||
premium: false,
|
||||
sceneCount: 15,
|
||||
supports4k: true,
|
||||
colorChange: true,
|
||||
scriptToVideo: false,
|
||||
},
|
||||
{
|
||||
id: "hero-promo",
|
||||
name: "Hero Product Launch",
|
||||
videoCategory: "ads",
|
||||
aspectRatio: "widescreen",
|
||||
aspectRatios: ["16:9", "9:16"],
|
||||
durationType: "flexible",
|
||||
premium: true,
|
||||
sceneCount: 9,
|
||||
supports4k: true,
|
||||
colorChange: true,
|
||||
scriptToVideo: false,
|
||||
},
|
||||
{
|
||||
id: "event-recap",
|
||||
name: "Event Recap Highlight",
|
||||
videoCategory: "slideshow",
|
||||
aspectRatio: "widescreen",
|
||||
aspectRatios: ["16:9", "9:16"],
|
||||
durationType: "flexible",
|
||||
premium: false,
|
||||
sceneCount: 11,
|
||||
supports4k: true,
|
||||
colorChange: true,
|
||||
scriptToVideo: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const VIDEO_TEMPLATES_CATALOG = [
|
||||
...ONBOARDING_PRESET_TEMPLATES,
|
||||
...buildVideoCatalog(),
|
||||
];
|
||||
|
||||
export interface VideoTemplateFilters {
|
||||
search: string;
|
||||
sidebarCategory: VideoSidebarCategoryId;
|
||||
aspectRatio: AspectRatioFilter;
|
||||
duration: DurationFilter;
|
||||
premiumOnly: boolean;
|
||||
supports4k: boolean;
|
||||
colorChange: boolean;
|
||||
scriptToVideo: boolean;
|
||||
}
|
||||
|
||||
export function filterVideoCatalog(
|
||||
templates: VideoCatalogTemplate[],
|
||||
filters: VideoTemplateFilters
|
||||
): VideoCatalogTemplate[] {
|
||||
const query = filters.search.trim().toLowerCase();
|
||||
|
||||
return templates.filter((template) => {
|
||||
if (
|
||||
filters.sidebarCategory !== "all" &&
|
||||
template.videoCategory !== filters.sidebarCategory
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
filters.aspectRatio !== "all" &&
|
||||
template.aspectRatio !== filters.aspectRatio
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
filters.duration !== "all" &&
|
||||
template.durationType !== filters.duration
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (filters.premiumOnly && !template.premium) return false;
|
||||
if (filters.supports4k && !template.supports4k) return false;
|
||||
if (filters.colorChange && !template.colorChange) return false;
|
||||
if (filters.scriptToVideo && !template.scriptToVideo) return false;
|
||||
if (query && !template.name.toLowerCase().includes(query)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function getVideoTemplateImageSrc(id: string): string {
|
||||
return `https://picsum.photos/seed/${id}/640/360`;
|
||||
}
|
||||
|
||||
export interface VideoTemplateSection {
|
||||
id: string;
|
||||
title: string;
|
||||
count: number;
|
||||
templates: VideoCatalogTemplate[];
|
||||
}
|
||||
|
||||
export function buildVideoTemplateSections(
|
||||
filtered: VideoCatalogTemplate[],
|
||||
sidebarCategory: VideoSidebarCategoryId
|
||||
): VideoTemplateSection[] {
|
||||
const newlyReleased = filtered.filter((t) => t.isNew).slice(0, 8);
|
||||
const sections: VideoTemplateSection[] = [];
|
||||
|
||||
if (newlyReleased.length > 0 && sidebarCategory === "all") {
|
||||
sections.push({
|
||||
id: "newly-released",
|
||||
title: "Newly released",
|
||||
count: newlyReleased.length,
|
||||
templates: newlyReleased,
|
||||
});
|
||||
}
|
||||
|
||||
const categories =
|
||||
sidebarCategory === "all"
|
||||
? VIDEO_SIDEBAR_CATEGORIES.filter((c) => c.id !== "all")
|
||||
: VIDEO_SIDEBAR_CATEGORIES.filter((c) => c.id === sidebarCategory);
|
||||
|
||||
for (const category of categories) {
|
||||
const templates = filtered
|
||||
.filter((t) => t.videoCategory === category.id)
|
||||
.slice(0, 12);
|
||||
if (templates.length === 0) continue;
|
||||
sections.push({
|
||||
id: category.id,
|
||||
title: category.label,
|
||||
count: filtered.filter((t) => t.videoCategory === category.id).length,
|
||||
templates,
|
||||
});
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
export function toProjectTemplate(
|
||||
template: VideoCatalogTemplate
|
||||
): { id: string; name: string; category: "Video" } {
|
||||
return {
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
category: "Video",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user