├── src ├── features │ └── editor │ │ ├── index.ts │ │ ├── scene │ │ ├── index.ts │ │ ├── scene.types.ts │ │ └── board.tsx │ │ ├── timeline │ │ ├── controls │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── items │ │ │ ├── helper.ts │ │ │ ├── preview-drag-item.ts │ │ │ ├── index.ts │ │ │ ├── hill-audio-bars.ts │ │ │ ├── wave-audio-bars.ts │ │ │ ├── lineal-audio-bars.ts │ │ │ └── radial-audio-bars.ts │ │ └── types.ts │ │ ├── menu-item │ │ ├── index.tsx │ │ ├── texts.tsx │ │ ├── menu-item.tsx │ │ └── transitions.tsx │ │ ├── player │ │ ├── index.ts │ │ ├── animated │ │ │ ├── index.ts │ │ │ ├── text-animated-types │ │ │ │ ├── animations-loop │ │ │ │ │ ├── spin.tsx │ │ │ │ │ ├── billboard.tsx │ │ │ │ │ ├── wave.tsx │ │ │ │ │ ├── heartbeat.tsx │ │ │ │ │ ├── shake-text.tsx │ │ │ │ │ ├── vogue.tsx │ │ │ │ │ ├── shaky-letters-text.tsx │ │ │ │ │ ├── dragonfly.tsx │ │ │ │ │ ├── glitch.tsx │ │ │ │ │ ├── font-change.tsx │ │ │ │ │ ├── rotate-3d.tsx │ │ │ │ │ ├── pulse.tsx │ │ │ │ │ └── vintage.tsx │ │ │ │ ├── animations-in │ │ │ │ │ ├── drop-in.tsx │ │ │ │ │ ├── descompress-in.tsx │ │ │ │ │ ├── great-thinkers-in.tsx │ │ │ │ │ ├── domino-dreams-in.tsx │ │ │ │ │ ├── beatiful-question-in.tsx │ │ │ │ │ ├── sunny-mornings-in.tsx │ │ │ │ │ ├── made-with-love-in.tsx │ │ │ │ │ ├── rising-strong-in.tsx │ │ │ │ │ ├── text-animated-in.tsx │ │ │ │ │ ├── type-writer-in.tsx │ │ │ │ │ ├── reality-is-broken-in.tsx │ │ │ │ │ └── background-in.tsx │ │ │ │ └── animations-out │ │ │ │ │ ├── drop-out.tsx │ │ │ │ │ ├── descompress-out.tsx │ │ │ │ │ ├── great-thinkers-out.tsx │ │ │ │ │ ├── domino-dreams-out.tsx │ │ │ │ │ ├── sunny-mornings-out.tsx │ │ │ │ │ ├── beatiful-question-out.tsx │ │ │ │ │ ├── made-with-love-out.tsx │ │ │ │ │ ├── text-animated-out.tsx │ │ │ │ │ ├── reality-is-broken-out.tsx │ │ │ │ │ └── type-writer-out.tsx │ │ │ └── types.ts │ │ ├── transitions │ │ │ ├── validate.ts │ │ │ ├── presentations │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── flatten-children.ts │ │ │ ├── use-transition-progress.ts │ │ │ ├── timings │ │ │ │ ├── spring-timing.ts │ │ │ │ └── linear-timing.ts │ │ │ ├── types.ts │ │ │ └── context.tsx │ │ ├── media-background.tsx │ │ ├── items │ │ │ ├── hill-audio-bars.tsx │ │ │ ├── wave-audio-bars.tsx │ │ │ ├── lineal-audio-bars.tsx │ │ │ ├── radial-audio-bars.tsx │ │ │ ├── index.ts │ │ │ ├── audio.tsx │ │ │ ├── audio-bars │ │ │ │ ├── radial-audio-bars.tsx │ │ │ │ ├── audio-utils.ts │ │ │ │ ├── lineal-audio-bars.tsx │ │ │ │ └── wave-audio-bars.tsx │ │ │ ├── progress-bar.tsx │ │ │ ├── illustration.tsx │ │ │ ├── shape.tsx │ │ │ └── image.tsx │ │ ├── player.tsx │ │ └── sequence-item.tsx │ │ ├── control-item │ │ ├── index.tsx │ │ ├── smart.tsx │ │ ├── presets.tsx │ │ ├── common │ │ │ ├── aspect-ratio.tsx │ │ │ ├── animation-caption.tsx │ │ │ ├── playback-rate.tsx │ │ │ ├── flip.tsx │ │ │ ├── volume.tsx │ │ │ ├── radius.tsx │ │ │ ├── blur.tsx │ │ │ ├── opacity.tsx │ │ │ └── brightness.tsx │ │ └── floating-controls │ │ │ └── floating-control.tsx │ │ ├── utils │ │ ├── math.ts │ │ ├── scene.ts │ │ ├── frames.ts │ │ ├── search.ts │ │ ├── file.ts │ │ ├── thumbnail-cache.ts │ │ ├── fonts.ts │ │ └── filmstrip.ts │ │ ├── interfaces │ │ ├── captions.ts │ │ ├── layout.ts │ │ └── editor.ts │ │ ├── store │ │ ├── use-data-state.ts │ │ ├── use-folder.ts │ │ └── use-layout-store.ts │ │ ├── hooks │ │ ├── use-timeline-offset.ts │ │ ├── useClickOutside.ts │ │ ├── is-dragging-over-timeline.tsx │ │ ├── use-current-frame.tsx │ │ └── use-zoom.tsx │ │ ├── constants │ │ ├── events.ts │ │ ├── font.ts │ │ ├── payload.ts │ │ └── constants.ts │ │ └── data │ │ ├── uploads.ts │ │ ├── audio.ts │ │ └── images.ts ├── app │ ├── favicon.ico │ ├── edit │ │ ├── page.tsx │ │ └── [...id] │ │ │ └── page.tsx │ ├── page.tsx │ ├── api │ │ ├── transcribe │ │ │ ├── route.ts │ │ │ └── [id] │ │ │ │ └── route.ts │ │ ├── render │ │ │ └── [id] │ │ │ │ └── route.ts │ │ └── voices │ │ │ └── route.ts │ └── layout.tsx ├── components │ ├── color-picker │ │ ├── utils │ │ │ ├── isValidRgba.ts │ │ │ ├── isValidHex.ts │ │ │ ├── rgbaToHex.ts │ │ │ ├── useDebounce.ts │ │ │ ├── getHexAlpha.ts │ │ │ ├── index.ts │ │ │ ├── checkFormat.ts │ │ │ ├── hexToRgba.ts │ │ │ ├── rgbaToArray.ts │ │ │ └── getGradient.ts │ │ ├── helpers.ts │ │ ├── gradient-panel │ │ │ └── types.ts │ │ ├── color-panel │ │ │ └── types.ts │ │ ├── input.tsx │ │ ├── types.ts │ │ ├── popover.tsx │ │ ├── constants.ts │ │ ├── button.tsx │ │ └── tabs.tsx │ ├── store-initializer.tsx │ ├── back-nav.tsx │ ├── ui │ │ ├── image-loading.tsx │ │ ├── visually-hidden.tsx │ │ ├── sonner.tsx │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── separator.tsx │ │ ├── input.tsx │ │ ├── checkbox.tsx │ │ ├── copy-button.tsx │ │ ├── badge.tsx │ │ ├── switch.tsx │ │ ├── avatar.tsx │ │ ├── interrupt-prompt.tsx │ │ ├── border-beam.tsx │ │ ├── toggle.tsx │ │ ├── popover.tsx │ │ ├── scroll-area.tsx │ │ ├── tooltip.tsx │ │ ├── tabs.tsx │ │ └── toggle-group.tsx │ └── query-provider.tsx ├── lib │ ├── utils.ts │ └── types.ts ├── utils │ ├── id.ts │ ├── download.ts │ └── metadata.ts ├── store │ └── use-scene-store.ts ├── constants │ └── api.ts └── hooks │ ├── use-media-query.ts │ ├── use-copy-to-clipboard.ts │ └── use-autosize-textarea.ts ├── images ├── combo.png ├── preview.png └── editor-preview.png ├── public ├── banner.png ├── vercel.svg ├── window.svg ├── file.svg ├── icon.svg ├── globe.svg └── next.svg ├── postcss.config.mjs ├── next.config.ts ├── components.json ├── .gitignore ├── tsconfig.json ├── biome.json └── README.md /src/features/editor/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./editor"; 2 | -------------------------------------------------------------------------------- /src/features/editor/scene/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./scene"; 2 | -------------------------------------------------------------------------------- /src/features/editor/timeline/controls/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./controls"; 2 | -------------------------------------------------------------------------------- /src/features/editor/menu-item/index.tsx: -------------------------------------------------------------------------------- 1 | export { MenuItem } from "./menu-item"; 2 | -------------------------------------------------------------------------------- /src/features/editor/timeline/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./timeline"; 2 | -------------------------------------------------------------------------------- /src/features/editor/player/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Player } from "./player"; 2 | -------------------------------------------------------------------------------- /src/features/editor/control-item/index.tsx: -------------------------------------------------------------------------------- 1 | export { ControlItem } from "./control-item"; 2 | -------------------------------------------------------------------------------- /images/combo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designcombo/react-video-editor/HEAD/images/combo.png -------------------------------------------------------------------------------- /images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designcombo/react-video-editor/HEAD/images/preview.png -------------------------------------------------------------------------------- /public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designcombo/react-video-editor/HEAD/public/banner.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designcombo/react-video-editor/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/features/editor/scene/scene.types.ts: -------------------------------------------------------------------------------- 1 | export interface SceneRef { 2 | recalculateZoom: () => void; 3 | } 4 | -------------------------------------------------------------------------------- /images/editor-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designcombo/react-video-editor/HEAD/images/editor-preview.png -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /src/features/editor/player/animated/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | 3 | export { presets } from "./presets"; 4 | -------------------------------------------------------------------------------- /src/app/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import Editor from "@/features/editor"; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Editor from "@/features/editor"; 3 | 4 | export default function Home() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /src/features/editor/utils/math.ts: -------------------------------------------------------------------------------- 1 | export function clamp(value: number, min: number, max: number) { 2 | return Math.max(min, Math.min(value, max)); 3 | } 4 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/isValidRgba.ts: -------------------------------------------------------------------------------- 1 | import rgbaToHex from "./rgbaToHex"; 2 | 3 | export default (rgba: Array) => { 4 | return !!rgbaToHex(rgba); 5 | }; 6 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | reactStrictMode: false, 6 | }; 7 | 8 | export default nextConfig; 9 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/isValidHex.ts: -------------------------------------------------------------------------------- 1 | export default (hex: string) => { 2 | const validHex = new RegExp( 3 | /^#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i 4 | ); 5 | 6 | return validHex.test(hex); 7 | }; 8 | -------------------------------------------------------------------------------- /src/features/editor/utils/scene.ts: -------------------------------------------------------------------------------- 1 | export const getIdFromClassName = (input: string): string => { 2 | const regex = /designcombo-scene-item id-([^ ]+)/; 3 | const match = input.match(regex); 4 | return match ? match[1] : (null as unknown as string); 5 | }; 6 | -------------------------------------------------------------------------------- /src/features/editor/player/transitions/validate.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-destructuring */ 2 | import { NoReactInternals } from "remotion/no-react"; 3 | 4 | export const validateDurationInFrames: typeof NoReactInternals.validateDurationInFrames = 5 | NoReactInternals.validateDurationInFrames; 6 | -------------------------------------------------------------------------------- /src/features/editor/utils/frames.ts: -------------------------------------------------------------------------------- 1 | export const calculateFrames = ( 2 | display: { from: number; to: number }, 3 | fps: number 4 | ) => { 5 | const from = (display.from / 1000) * fps; 6 | const durationInFrames = (display.to / 1000) * fps - from; 7 | return { from, durationInFrames }; 8 | }; 9 | -------------------------------------------------------------------------------- /src/features/editor/control-item/smart.tsx: -------------------------------------------------------------------------------- 1 | const Smart = () => { 2 | return ( 3 |
4 |
5 | Ai things 6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Smart; 12 | -------------------------------------------------------------------------------- /src/app/edit/[...id]/page.tsx: -------------------------------------------------------------------------------- 1 | import Editor from "@/features/editor"; 2 | 3 | export default async function Page({ 4 | params 5 | }: { 6 | params: Promise<{ id: string[] }>; 7 | }) { 8 | const { id } = await params; 9 | const [sceneId] = id; // Get the first ID from the array 10 | 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /src/features/editor/control-item/presets.tsx: -------------------------------------------------------------------------------- 1 | const Presets = () => { 2 | return ( 3 |
4 |
5 | Presets 6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Presets; 12 | -------------------------------------------------------------------------------- /src/utils/id.ts: -------------------------------------------------------------------------------- 1 | export function generateId(length: number): string { 2 | const chars = 3 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 4 | let result = ""; 5 | for (let i = 0; i < length; i++) { 6 | result += chars.charAt(Math.floor(Math.random() * chars.length)); 7 | } 8 | return result; 9 | } 10 | -------------------------------------------------------------------------------- /src/features/editor/interfaces/captions.ts: -------------------------------------------------------------------------------- 1 | export interface Word { 2 | end: number; 3 | start: number; 4 | word: string; 5 | } 6 | export interface CaptionsSegment { 7 | start: number; 8 | end: number; 9 | text: string; 10 | words: Word[]; 11 | } 12 | export interface CaptionsData { 13 | segments: CaptionsSegment[]; 14 | } 15 | -------------------------------------------------------------------------------- /src/features/editor/timeline/items/helper.ts: -------------------------------------------------------------------------------- 1 | import { Helper as HelperBase, HelperProps } from "@designcombo/timeline"; 2 | 3 | class Helper extends HelperBase { 4 | static type = "Helper"; 5 | 6 | constructor(props: HelperProps) { 7 | props.activeGuideFill = "#ffffff"; 8 | super(props); 9 | } 10 | } 11 | 12 | export default Helper; 13 | -------------------------------------------------------------------------------- /src/store/use-scene-store.ts: -------------------------------------------------------------------------------- 1 | import { IDesign } from "@designcombo/types"; 2 | import { create } from "zustand"; 3 | 4 | interface ISceneStore { 5 | scene: IDesign | null; 6 | setScene: (scene: IDesign) => void; 7 | } 8 | 9 | export const useSceneStore = create((set) => ({ 10 | scene: null, 11 | setScene: (scene) => set({ scene }) 12 | })); 13 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/editor/store/use-data-state.ts: -------------------------------------------------------------------------------- 1 | import { IDataState } from "../interfaces/editor"; 2 | import { create } from "zustand"; 3 | 4 | const useDataState = create((set) => ({ 5 | fonts: [], 6 | compactFonts: [], 7 | setFonts: (fonts) => set({ fonts }), 8 | setCompactFonts: (compactFonts) => set({ compactFonts }) 9 | })); 10 | 11 | export default useDataState; 12 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/constants/api.ts: -------------------------------------------------------------------------------- 1 | export const API_ENDPOINTS = { 2 | CHAT: "/api/chat", 3 | GENERATE_IMAGE: "/api/generate-image", 4 | GENERATE_AUDIO: "/api/generate-audio", 5 | SCHEMA: "/api/schema", 6 | SCHEME: { 7 | BASE: "https://scheme.combo.sh", 8 | CREATE: "https://scheme.combo.sh/schemes", 9 | RUN: (id: string) => `https://scheme.combo.sh/run/${id}` 10 | } 11 | } as const; 12 | -------------------------------------------------------------------------------- /src/components/store-initializer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useEffect } from "react"; 3 | import type { Upload } from "@/lib/types"; 4 | 5 | interface InitialData { 6 | uploads?: Upload[]; 7 | } 8 | 9 | export function StoreInitializer() { 10 | // No-op, removed user store logic 11 | return null; 12 | } 13 | 14 | export function BackgroundUploadRunner() { 15 | return null; 16 | } 17 | -------------------------------------------------------------------------------- /src/features/editor/timeline/items/preview-drag-item.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PreviewTrackItem as PreviewTrackItemBase, 3 | PreviewTrackItemProps 4 | } from "@designcombo/timeline"; 5 | 6 | class PreviewTrackItem extends PreviewTrackItemBase { 7 | static type = "PreviewTrackItem"; 8 | constructor(props: PreviewTrackItemProps) { 9 | super(props); 10 | } 11 | } 12 | 13 | export default PreviewTrackItem; 14 | -------------------------------------------------------------------------------- /src/features/editor/hooks/use-timeline-offset.ts: -------------------------------------------------------------------------------- 1 | import { useIsSmallScreen } from "../../../hooks/use-media-query"; 2 | import { 3 | TIMELINE_OFFSET_X_SMALL, 4 | TIMELINE_OFFSET_X_LARGE 5 | } from "../constants/constants"; 6 | 7 | export function useTimelineOffsetX(): number { 8 | const isSmallScreen = useIsSmallScreen(); 9 | return isSmallScreen ? TIMELINE_OFFSET_X_SMALL : TIMELINE_OFFSET_X_LARGE; 10 | } 11 | -------------------------------------------------------------------------------- /src/features/editor/constants/events.ts: -------------------------------------------------------------------------------- 1 | export const PLAYER_PREFIX = "player"; 2 | export const PLAYER_PLAY = `${PLAYER_PREFIX}:play`; 3 | export const PLAYER_PAUSE = `${PLAYER_PREFIX}:pause`; 4 | export const PLAYER_SEEK = `${PLAYER_PREFIX}:seek`; 5 | export const PLAYER_SEEK_TO = `${PLAYER_PREFIX}:seekTo`; 6 | export const PLAYER_SEEK_BY = `${PLAYER_PREFIX}:seekBy`; 7 | export const PLAYER_TOGGLE_PLAY = `${PLAYER_PREFIX}:togglePlay`; 8 | -------------------------------------------------------------------------------- /src/features/editor/data/uploads.ts: -------------------------------------------------------------------------------- 1 | export const UPLOADS = [ 2 | { 3 | id: "1", 4 | src: "https://ik.imagekit.io/snapmotion/upload-video-1.mp4", 5 | type: "video" 6 | }, 7 | { 8 | id: "2", 9 | src: "https://ik.imagekit.io/snapmotion/upload-video-2.mp4", 10 | type: "video" 11 | }, 12 | { 13 | id: "3", 14 | src: "https://ik.imagekit.io/snapmotion/upload-video-3.mp4", 15 | type: "video" 16 | } 17 | ]; 18 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/rgbaToHex.ts: -------------------------------------------------------------------------------- 1 | export default (params: Array) => { 2 | if (!Array.isArray(params)) return ""; 3 | 4 | if (params.length < 3 || params.length > 4) return ""; 5 | 6 | const parts = params.map(function (e: string | number) { 7 | let r = (+e).toString(16); 8 | r.length === 1 && (r = "0" + r); 9 | return r; 10 | }, []); 11 | 12 | return !~parts.indexOf("NaN") ? "#" + parts.join("") : ""; 13 | }; 14 | -------------------------------------------------------------------------------- /src/features/editor/player/media-background.tsx: -------------------------------------------------------------------------------- 1 | const MediaBackground = ({ background }: { background: string }) => { 2 | return ( 3 |
14 | ); 15 | }; 16 | 17 | export default MediaBackground; 18 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default (value: T, delay?: number): T => { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | return () => { 11 | clearTimeout(handler); 12 | }; 13 | }, [value, delay]); 14 | 15 | return debouncedValue; 16 | }; 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /src/features/editor/player/animated/text-animated-types/animations-loop/spin.tsx: -------------------------------------------------------------------------------- 1 | const Spin = ({ 2 | text, 3 | frame, 4 | fps 5 | }: { 6 | text: string; 7 | frame: number; 8 | fps: number; 9 | }) => { 10 | const t = frame / fps; 11 | const rotateZ = t * 360; 12 | 13 | return ( 14 | 20 | {text} 21 | 22 | ); 23 | }; 24 | export default Spin; 25 | -------------------------------------------------------------------------------- /src/components/back-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | import { ArrowLeftIcon } from "lucide-react"; 4 | import { useRouter } from "next/navigation"; 5 | 6 | export default function BackNav() { 7 | const router = useRouter(); 8 | 9 | return ( 10 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/ui/image-loading.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2 } from "lucide-react"; 2 | 3 | interface ImageLoadingProps { 4 | message?: string; 5 | } 6 | 7 | export function ImageLoading({ 8 | message = "Loading images..." 9 | }: ImageLoadingProps) { 10 | return ( 11 |
12 | 13 |

{message}

14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | export interface Upload { 2 | id: string; 3 | fileName: string; 4 | filePath: string; 5 | fileSize: number; 6 | contentType: string; 7 | metadata: any | null; 8 | folder: string | null; 9 | type: "VIDEO" | "IMAGE" | "AUDIO" | "DOCUMENT" | "OTHER"; 10 | method: "USER" | "API" | "SYSTEM" | "OTHER"; 11 | origin: "USER_CREATED" | "AI_GENERATED" | "UNKNOWN"; 12 | status: "PENDING" | "COMPLETED" | "FAILED"; 13 | userId: string; 14 | createdAt: Date; 15 | updatedAt: Date; 16 | isPreview: boolean; 17 | } 18 | -------------------------------------------------------------------------------- /src/features/editor/constants/font.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_FONT = { 2 | id: "font_UwdNKSyVq2iiMiuHSRRsUIOu", 3 | family: "Roboto", 4 | fullName: "Roboto Bold", 5 | postScriptName: "Roboto-Bold", 6 | preview: "https://ik.imagekit.io/lh/fonts/v2/5zQgS86djScKA0ri67BBCqW7.png", 7 | style: "Roboto-Bold", 8 | url: "https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlvAx05IsDqlA.ttf", 9 | category: "sans-serif", 10 | createdAt: "2023-06-20T04:42:55.909Z", 11 | updatedAt: "2023-06-20T04:42:55.909Z", 12 | userId: null 13 | }; 14 | -------------------------------------------------------------------------------- /src/features/editor/player/items/hill-audio-bars.tsx: -------------------------------------------------------------------------------- 1 | import { IHillAudioBars } from "@designcombo/types"; 2 | import { BaseSequence, SequenceItemOptions } from "../base-sequence"; 3 | import { HillBars } from "./audio-bars/hill-audio-bars"; 4 | 5 | export default function HillAudioBars({ 6 | item, 7 | options 8 | }: { 9 | item: IHillAudioBars; 10 | options: SequenceItemOptions; 11 | }) { 12 | const children = ( 13 | <> 14 | 15 | 16 | ); 17 | return BaseSequence({ item, options, children }); 18 | } 19 | -------------------------------------------------------------------------------- /src/features/editor/player/items/wave-audio-bars.tsx: -------------------------------------------------------------------------------- 1 | import { IWaveAudioBars } from "@designcombo/types"; 2 | import { BaseSequence, SequenceItemOptions } from "../base-sequence"; 3 | import { WaveBars } from "./audio-bars/wave-audio-bars"; 4 | 5 | export default function WaveAudioBars({ 6 | item, 7 | options 8 | }: { 9 | item: IWaveAudioBars; 10 | options: SequenceItemOptions; 11 | }) { 12 | const children = ( 13 | <> 14 | 15 | 16 | ); 17 | return BaseSequence({ item, options, children }); 18 | } 19 | -------------------------------------------------------------------------------- /src/features/editor/player/animated/text-animated-types/animations-loop/billboard.tsx: -------------------------------------------------------------------------------- 1 | const BillboardText = ({ 2 | frame, 3 | fps, 4 | char 5 | }: { 6 | char: string; 7 | frame: number; 8 | fps: number; 9 | }) => { 10 | const scale = 1 + 0.2 * Math.sin((2 * Math.PI * frame) / (fps * 1)); // 1 ciclo por segundo 11 | 12 | return ( 13 | 19 | {char} 20 | 21 | ); 22 | }; 23 | 24 | export default BillboardText; 25 | -------------------------------------------------------------------------------- /src/features/editor/player/transitions/presentations/index.ts: -------------------------------------------------------------------------------- 1 | export { circle } from "./circle"; 2 | export { rectangle } from "./rectangle"; 3 | export { star } from "./star"; 4 | export { slidingDoors } from "./sliding-doors"; 5 | 6 | export { linearTiming } from "../"; 7 | export { fade } from "@remotion/transitions/fade"; 8 | export { slide, type SlideDirection } from "@remotion/transitions/slide"; 9 | export { wipe } from "@remotion/transitions/wipe"; 10 | export { flip } from "@remotion/transitions/flip"; 11 | export { clockWipe } from "@remotion/transitions/clock-wipe"; 12 | -------------------------------------------------------------------------------- /src/features/editor/player/transitions/index.ts: -------------------------------------------------------------------------------- 1 | // Timings 2 | export { linearTiming } from "./timings/linear-timing"; 3 | export { springTiming } from "./timings/spring-timing"; 4 | // Component 5 | export { TransitionSeries } from "./transition-series"; 6 | export type { 7 | TransitionPresentation, 8 | TransitionPresentationComponentProps, 9 | TransitionTiming 10 | } from "./types"; 11 | // Hooks 12 | export { useTransitionProgress } from "./use-transition-progress"; 13 | export type { TransitionState } from "./use-transition-progress"; 14 | export * from "./presentations"; 15 | -------------------------------------------------------------------------------- /src/features/editor/player/items/lineal-audio-bars.tsx: -------------------------------------------------------------------------------- 1 | import { ILinealAudioBars } from "@designcombo/types"; 2 | import { BaseSequence, SequenceItemOptions } from "../base-sequence"; 3 | import { LinealBars } from "./audio-bars/lineal-audio-bars"; 4 | 5 | export default function LinealAudioBars({ 6 | item, 7 | options 8 | }: { 9 | item: ILinealAudioBars; 10 | options: SequenceItemOptions; 11 | }) { 12 | const children = ( 13 | <> 14 | 15 | 16 | ); 17 | return BaseSequence({ item, options, children }); 18 | } 19 | -------------------------------------------------------------------------------- /src/features/editor/player/items/radial-audio-bars.tsx: -------------------------------------------------------------------------------- 1 | import { IRadialAudioBars } from "@designcombo/types"; 2 | import { BaseSequence, SequenceItemOptions } from "../base-sequence"; 3 | import { RadialBars } from "./audio-bars/radial-audio-bars"; 4 | 5 | export default function RadialAudioBars({ 6 | item, 7 | options 8 | }: { 9 | item: IRadialAudioBars; 10 | options: SequenceItemOptions; 11 | }) { 12 | const children = ( 13 | <> 14 | 15 | 16 | ); 17 | 18 | return BaseSequence({ item, options, children }); 19 | } 20 | -------------------------------------------------------------------------------- /src/features/editor/player/animated/text-animated-types/animations-loop/wave.tsx: -------------------------------------------------------------------------------- 1 | const Wave = ({ 2 | char, 3 | frame, 4 | fps, 5 | index 6 | }: { 7 | char: string; 8 | frame: number; 9 | fps: number; 10 | index: number; 11 | }) => { 12 | const offset = index * 20; 13 | const translateY = Math.sin((frame * 8 - offset) / fps) * 20; 14 | 15 | return ( 16 | 22 | {char} 23 | 24 | ); 25 | }; 26 | export default Wave; 27 | -------------------------------------------------------------------------------- /src/components/ui/visually-hidden.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cn } from "@/lib/utils"; 3 | 4 | const VisuallyHidden = React.forwardRef< 5 | HTMLSpanElement, 6 | React.HTMLAttributes 7 | >(({ className, ...props }, ref) => { 8 | return ( 9 | 18 | ); 19 | }); 20 | VisuallyHidden.displayName = "VisuallyHidden"; 21 | 22 | export { VisuallyHidden }; 23 | -------------------------------------------------------------------------------- /src/features/editor/timeline/items/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Text } from "./text"; 2 | export { default as Image } from "./image"; 3 | export { default as Audio } from "./audio"; 4 | export { default as Video } from "./video"; 5 | export { default as Caption } from "./caption"; 6 | export { default as Helper } from "./helper"; 7 | export { default as Track } from "./track"; 8 | export { default as LinealAudioBars } from "./lineal-audio-bars"; 9 | export { default as RadialAudioBars } from "./radial-audio-bars"; 10 | export { default as WaveAudioBars } from "./wave-audio-bars"; 11 | export { default as HillAudioBars } from "./hill-audio-bars"; 12 | -------------------------------------------------------------------------------- /src/utils/download.ts: -------------------------------------------------------------------------------- 1 | export const download = (url: string, filename: string) => { 2 | fetch(url) 3 | .then((response) => response.blob()) 4 | .then((blob) => { 5 | const url = window.URL.createObjectURL(blob); 6 | const link = document.createElement("a"); 7 | link.href = url; 8 | link.setAttribute("download", `${filename}.mp4`); // Specify the filename for the downloaded video 9 | document.body.appendChild(link); 10 | link.click(); 11 | link.parentNode?.removeChild(link); 12 | window.URL.revokeObjectURL(url); 13 | }) 14 | .catch((error) => console.error("Download error:", error)); 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/getHexAlpha.ts: -------------------------------------------------------------------------------- 1 | import tinycolor from "tinycolor2"; 2 | 3 | export default (value: string) => { 4 | const defaultObject = { 5 | hex: "#ffffff", 6 | alpha: 100 7 | }; 8 | const tinyColor = tinycolor(value); 9 | 10 | if (value) { 11 | if ( 12 | tinyColor.isValid() && 13 | !value.trim().startsWith("radial-gradient") && 14 | !value.trim().startsWith("linear-gradient") 15 | ) { 16 | defaultObject.hex = tinyColor.toHexString(); 17 | defaultObject.alpha = Math.round(tinyColor.getAlpha() * 100); 18 | } else { 19 | return defaultObject; 20 | } 21 | } 22 | 23 | return defaultObject; 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { Toaster as Sonner, ToasterProps } from "sonner"; 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme(); 8 | 9 | return ( 10 | 22 | ); 23 | }; 24 | 25 | export { Toaster }; 26 | -------------------------------------------------------------------------------- /src/features/editor/utils/search.ts: -------------------------------------------------------------------------------- 1 | export type BinarySearchPredicate = ( 2 | value: T, 3 | index: number, 4 | arr: T[] 5 | ) => boolean; 6 | 7 | /** 8 | * Searches for a value by predicate function. 9 | * @param arr The list of any values. 10 | * @param predicate Predicate function. 11 | * @returns Found index or -1. 12 | */ 13 | export function findIndex( 14 | arr: T[], 15 | predicate: BinarySearchPredicate 16 | ): number { 17 | let l = -1; 18 | let r = arr.length - 1; 19 | 20 | while (1 + l < r) { 21 | const mid = l + ((r - l) >> 1); 22 | const cmp = predicate(arr[mid], mid, arr); 23 | 24 | cmp ? (r = mid) : (l = mid); 25 | } 26 | 27 | return r; 28 | } 29 | -------------------------------------------------------------------------------- /src/features/editor/store/use-folder.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | interface IFolderStore { 4 | valueFolder: string; 5 | setValueFolder: (valueFolder: string) => void; 6 | videos: any[]; 7 | setVideos: (videos: any[] | ((prev: any[]) => any[])) => void; 8 | } 9 | 10 | const useFolderStore = create((set) => ({ 11 | valueFolder: "", 12 | setValueFolder: (valueFolder) => set({ valueFolder }), 13 | videos: [], 14 | setVideos: (videosOrUpdater) => 15 | set((state) => ({ 16 | videos: 17 | typeof videosOrUpdater === "function" 18 | ? videosOrUpdater(state.videos) 19 | : videosOrUpdater 20 | })) 21 | })); 22 | 23 | export default useFolderStore; 24 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as hexToRgba } from "./hexToRgba"; 2 | export { default as getHexAlpha } from "./getHexAlpha"; 3 | export { default as useDebounce } from "./useDebounce"; 4 | export { default as parseGradient } from "./parseGradient"; 5 | export { default as getGradient } from "./getGradient"; 6 | export { default as rgbaToArray } from "./rgbaToArray"; 7 | export { default as rgbaToHex } from "./rgbaToHex"; 8 | export { default as isValidHex } from "./isValidHex"; 9 | export { default as isValidRgba } from "./isValidRgba"; 10 | export { default as checkFormat } from "./checkFormat"; 11 | export { default as validGradient } from "./validGradient"; 12 | export { default as TinyColor } from "./color"; 13 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ); 22 | } 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /src/features/editor/constants/payload.ts: -------------------------------------------------------------------------------- 1 | import { generateId } from "@designcombo/timeline"; 2 | import { DEFAULT_FONT } from "./font"; 3 | 4 | export const TEXT_ADD_PAYLOAD = { 5 | id: generateId(), 6 | display: { 7 | from: 0, 8 | to: 5000 9 | }, 10 | type: "text", 11 | details: { 12 | text: "Heading and some body", 13 | fontSize: 120, 14 | width: 600, 15 | fontUrl: DEFAULT_FONT.url, 16 | fontFamily: DEFAULT_FONT.postScriptName, 17 | color: "#ffffff", 18 | wordWrap: "break-word", 19 | textAlign: "center", 20 | borderWidth: 0, 21 | borderColor: "#000000", 22 | boxShadow: { 23 | color: "#ffffff", 24 | x: 0, 25 | y: 0, 26 | blur: 0 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/color-picker/helpers.ts: -------------------------------------------------------------------------------- 1 | export const getAlphaValue = (value: string) => { 2 | value = value.replace(/%/i, ""); // Ensure to assign the result back 3 | if (value[0] === "0" && value.length > 1) { 4 | return value.substring(1); // Replaced substr with substring 5 | } else if (Number(value) >= 100) { 6 | return 100; 7 | } else if (!isNaN(Number(value))) { 8 | return value || 0; 9 | } 10 | return parseInt(value); 11 | }; 12 | 13 | export const onlyDigits = (string: string) => { 14 | return string ? string.substring(0, 3).replace(/[^\d]/g, "") : ""; // Replaced substr with substring 15 | }; 16 | 17 | export const onlyLatins = (string: string) => { 18 | return string ? string.substring(0, 7) : string; 19 | }; 20 | -------------------------------------------------------------------------------- /src/features/editor/player/animated/text-animated-types/animations-loop/heartbeat.tsx: -------------------------------------------------------------------------------- 1 | const Heartbeat = ({ 2 | char, 3 | frame, 4 | fps 5 | }: { 6 | char: string; 7 | frame: number; 8 | fps: number; 9 | }) => { 10 | const time = frame / fps; 11 | const cycleDuration = 1; 12 | const cycleTime = time % cycleDuration; 13 | 14 | let scale = 1; 15 | if (cycleTime < 0.2 || (cycleTime >= 0.3 && cycleTime < 0.5)) { 16 | scale = 1 + Math.sin((cycleTime % 0.2) * Math.PI * 5) * 0.8; 17 | } 18 | 19 | return ( 20 | 26 | {char} 27 | 28 | ); 29 | }; 30 | export default Heartbeat; 31 | -------------------------------------------------------------------------------- /src/features/editor/player/animated/text-animated-types/animations-loop/shake-text.tsx: -------------------------------------------------------------------------------- 1 | function random(seed: number) { 2 | const x = Math.sin(seed) * 10000; 3 | return x - Math.floor(x); 4 | } 5 | 6 | const ShakeText = ({ text, frame }: { text: string; frame: number }) => { 7 | const offsetX = (random(frame) - 0.5) * 8; // entre -4 y 4 px 8 | const offsetY = (random(frame + 999) - 0.5) * 8; 9 | const rotate = (random(frame + 500) - 0.5) * 6; // entre -3 y 3 grados 10 | 11 | return ( 12 | 18 | {text} 19 | 20 | ); 21 | }; 22 | 23 | export default ShakeText; 24 | -------------------------------------------------------------------------------- /src/features/editor/player/items/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Audio } from "./audio"; 2 | export { default as Caption } from "./caption"; 3 | export { default as Illustration } from "./illustration"; 4 | export { default as Image } from "./image"; 5 | export { default as Shape } from "./shape"; 6 | export { default as Text } from "./text"; 7 | export { default as Video } from "./video"; 8 | export { default as ProgressBar } from "./progress-bar"; 9 | export { default as LinealAudioBars } from "./lineal-audio-bars"; 10 | export { default as ProgressFrame } from "./progress-frame"; 11 | export { default as RadialAudioBars } from "./radial-audio-bars"; 12 | export { default as HillAudioBars } from "./hill-audio-bars"; 13 | export { default as WaveAudioBars } from "./wave-audio-bars"; 14 | -------------------------------------------------------------------------------- /src/features/editor/player/animated/text-animated-types/animations-loop/vogue.tsx: -------------------------------------------------------------------------------- 1 | const VogueLetterByLetter = ({ 2 | char, 3 | frame, 4 | fps, 5 | index 6 | }: { 7 | char: string; 8 | frame: number; 9 | fps: number; 10 | index: number; 11 | }) => { 12 | const delay = index * 4; // desfase por letra 13 | const t = (frame - delay) / fps; 14 | 15 | // Loop suave con rotación más notoria 16 | const scale = 1 + 0.25 * Math.sin(t * 2 * Math.PI); 17 | const rotateY = 40 * Math.sin(t * 2 * Math.PI); 18 | 19 | return ( 20 | 26 | {char} 27 | 28 | ); 29 | }; 30 | export default VogueLetterByLetter; 31 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/checkFormat.ts: -------------------------------------------------------------------------------- 1 | import tinycolor from "tinycolor2"; 2 | 3 | export default (color: string, format: string, stateColorAlpha?: number) => { 4 | const tinyColor = tinycolor(color); 5 | let value: string; 6 | const alphaValue = stateColorAlpha || tinyColor.getAlpha() * 100; 7 | 8 | switch (format) { 9 | case "rgb": 10 | value = tinyColor.toRgbString(); 11 | break; 12 | case "hsl": 13 | value = tinyColor.toHslString(); 14 | break; 15 | case "hex": 16 | if (alphaValue !== 100) { 17 | value = tinyColor.toHex8String(); 18 | } else { 19 | value = tinyColor.toHexString(); 20 | } 21 | break; 22 | 23 | default: 24 | value = ""; 25 | break; 26 | } 27 | 28 | return value; 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/color-picker/utils/hexToRgba.ts: -------------------------------------------------------------------------------- 1 | export default (hexVal: string, opacityVal: number) => { 2 | const opacity = isNaN(opacityVal) ? 100 : opacityVal; 3 | const hex = hexVal.replace("#", ""); 4 | let r; 5 | let g; 6 | let b; 7 | 8 | if (hex.length === 6) { 9 | r = parseInt(hex.substring(0, 2), 16); 10 | g = parseInt(hex.substring(2, 4), 16); 11 | b = parseInt(hex.substring(4, 6), 16); 12 | } else { 13 | const rd = hex.substring(0, 1) + hex.substring(0, 1); 14 | const gd = hex.substring(1, 2) + hex.substring(1, 2); 15 | const bd = hex.substring(2, 3) + hex.substring(2, 3); 16 | r = parseInt(rd, 16); 17 | g = parseInt(gd, 16); 18 | b = parseInt(bd, 16); 19 | } 20 | 21 | return "rgba(" + r + ", " + g + ", " + b + ", " + opacity / 100 + ")"; 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |