) {
36 | return (
37 |
50 |
54 |
55 | );
56 | }
57 |
58 | export { ScrollArea, ScrollBar };
59 |
--------------------------------------------------------------------------------
/src/features/editor/control-item/common/volume.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/ui/input";
2 |
3 | import { Slider } from "@/components/ui/slider";
4 | import { useEffect, useState } from "react";
5 |
6 | const Volume = ({
7 | value,
8 | onChange
9 | }: {
10 | value: number;
11 | onChange: (v: number) => void;
12 | }) => {
13 | // Create local state to manage opacity
14 | const [localValue, setLocalValue] = useState(value);
15 |
16 | // Update local state when prop value changes
17 | useEffect(() => {
18 | setLocalValue(value);
19 | }, [value]);
20 |
21 | return (
22 |
23 |
24 | Volume
25 |
26 |
33 | {
37 | const newValue = Number(e.target.value);
38 | if (newValue >= 0 && newValue <= 100) {
39 | setLocalValue(newValue); // Update local state
40 | onChange(newValue); // Optionally propagate immediately, or adjust as needed
41 | }
42 | }}
43 | value={localValue} // Use local state for input value
44 | />
45 | {
49 | setLocalValue(e[0]); // Update local state
50 | }}
51 | onValueCommit={() => {
52 | onChange(localValue); // Propagate value to parent when user commits change
53 | }}
54 | max={100}
55 | step={1}
56 | aria-label="Temperature"
57 | />
58 |
59 |
60 | );
61 | };
62 |
63 | export default Volume;
64 |
--------------------------------------------------------------------------------
/src/features/editor/control-item/common/radius.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/ui/input";
2 |
3 | import { Slider } from "@/components/ui/slider";
4 | import { useEffect, useState } from "react";
5 |
6 | const Rounded = ({
7 | value,
8 | onChange
9 | }: {
10 | value: number;
11 | onChange: (v: number) => void;
12 | }) => {
13 | // Create local state to manage opacity
14 | const [localValue, setLocalValue] = useState(value);
15 |
16 | // Update local state when prop value changes
17 | useEffect(() => {
18 | setLocalValue(value);
19 | }, [value]);
20 |
21 | return (
22 |
23 |
24 | Round
25 |
26 |
33 | {
37 | const newValue = Number(e.target.value);
38 | if (newValue >= 0 && newValue <= 100) {
39 | setLocalValue(newValue); // Update local state
40 | onChange(newValue); // Optionally propagate immediately, or adjust as needed
41 | }
42 | }}
43 | value={localValue} // Use local state for input value
44 | />
45 | {
49 | setLocalValue(e[0]); // Update local state
50 | }}
51 | onValueCommit={() => {
52 | onChange(localValue); // Propagate value to parent when user commits change
53 | }}
54 | min={0}
55 | max={50}
56 | step={1}
57 | aria-label="rounded"
58 | />
59 |
60 |
61 | );
62 | };
63 |
64 | export default Rounded;
65 |
--------------------------------------------------------------------------------
/src/features/editor/control-item/common/blur.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/ui/input";
2 | import { Slider } from "@/components/ui/slider";
3 | import { useState, useEffect } from "react";
4 |
5 | const Blur = ({
6 | value,
7 | onChange
8 | }: {
9 | value: number;
10 | onChange: (v: number) => void;
11 | }) => {
12 | // Create local state to manage opacity
13 | const [localValue, setLocalValue] = useState(value);
14 |
15 | // Update local state when prop value changes
16 | useEffect(() => {
17 | setLocalValue(value);
18 | }, [value]);
19 |
20 | return (
21 |
22 |
23 | Blur
24 |
25 |
32 | {
37 | const newValue = Number(e.target.value);
38 | if (newValue >= 0 && newValue <= 100) {
39 | setLocalValue(newValue); // Update local state
40 | onChange(newValue); // Optionally propagate immediately, or adjust as needed
41 | }
42 | }}
43 | value={localValue} // Use local state for input value
44 | />
45 | {
49 | setLocalValue(e[0]); // Update local state
50 | }}
51 | onValueCommit={() => {
52 | onChange(localValue); // Propagate value to parent when user commits change
53 | }}
54 | min={0}
55 | max={100}
56 | step={1}
57 | aria-label="Blur"
58 | />
59 |
60 |
61 | );
62 | };
63 |
64 | export default Blur;
65 |
--------------------------------------------------------------------------------
/src/features/editor/player/sequence-item.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | IAudio,
4 | ICaption,
5 | IHillAudioBars,
6 | IIllustration,
7 | IImage,
8 | ITrackItem,
9 | ILinealAudioBars,
10 | IProgressBar,
11 | IProgressFrame,
12 | IRadialAudioBars,
13 | IShape,
14 | IText,
15 | IVideo,
16 | IWaveAudioBars
17 | } from "@designcombo/types";
18 | import {
19 | Audio,
20 | Caption,
21 | HillAudioBars,
22 | Illustration,
23 | Image,
24 | LinealAudioBars,
25 | ProgressBar,
26 | ProgressFrame,
27 | RadialAudioBars,
28 | Shape,
29 | Text,
30 | Video,
31 | WaveAudioBars
32 | } from "./items";
33 | import { SequenceItemOptions } from "./base-sequence";
34 |
35 | export const SequenceItem: Record<
36 | string,
37 | (item: ITrackItem, options: SequenceItemOptions) => React.JSX.Element
38 | > = {
39 | text: (item, options) => Text({ item: item as IText, options }),
40 | caption: (item, options) => Caption({ item: item as ICaption, options }),
41 | shape: (item, options) => Shape({ item: item as IShape, options }),
42 | video: (item, options) => Video({ item: item as IVideo, options }),
43 | audio: (item, options) => Audio({ item: item as IAudio, options }),
44 | image: (item, options) => Image({ item: item as IImage, options }),
45 | illustration: (item, options) =>
46 | Illustration({ item: item as IIllustration, options }),
47 | progressBar: (item, options) =>
48 | ProgressBar({ item: item as IProgressBar, options }),
49 | linealAudioBars: (item, options) =>
50 | LinealAudioBars({ item: item as ILinealAudioBars, options }),
51 | waveAudioBars: (item, options) =>
52 | WaveAudioBars({ item: item as IWaveAudioBars, options }),
53 | hillAudioBars: (item, options) =>
54 | HillAudioBars({ item: item as IHillAudioBars, options }),
55 | progressFrame: (item, options) =>
56 | ProgressFrame({ item: item as IProgressFrame, options }),
57 | radialAudioBars: (item, options) =>
58 | RadialAudioBars({ item: item as IRadialAudioBars, options })
59 | };
60 |
--------------------------------------------------------------------------------
/src/components/color-picker/constants.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_COLORS = [
2 | "#FF6900",
3 | "#FCB900",
4 | "#7BDCB5",
5 | "#00D084",
6 | "#8ED1FC",
7 | "#0693E3",
8 | "#ABB8C3",
9 | "#607d8b",
10 | "#EB144C",
11 | "#F78DA7",
12 | "#ba68c8",
13 | "#9900EF",
14 | "linear-gradient(0deg, rgb(255, 177, 153) 0%, rgb(255, 8, 68) 100%)",
15 | "linear-gradient(270deg, rgb(251, 171, 126) 8.00%, rgb(247, 206, 104) 92.00%)",
16 | "linear-gradient(315deg, rgb(150, 230, 161) 8.00%, rgb(212, 252, 121) 92.00%)",
17 | "linear-gradient(to left, rgb(249, 240, 71) 0%, rgb(15, 216, 80) 100%)",
18 | "linear-gradient(315deg, rgb(194, 233, 251) 8.00%, rgb(161, 196, 253) 92.00%)",
19 | "linear-gradient(0deg, rgb(0, 198, 251) 0%, rgb(0, 91, 234) 100%)",
20 | "linear-gradient(0deg, rgb(167, 166, 203) 0%, rgb(137, 137, 186) 51.00%, rgb(137, 137, 186) 100%)",
21 | "linear-gradient(0deg, rgb(80, 82, 133) 0%, rgb(88, 94, 146) 15.0%, rgb(101, 104, 159) 28.00%, rgb(116, 116, 176) 43.00%, rgb(126, 126, 187) 57.00%, rgb(131, 137, 199) 71.00%, rgb(151, 149, 212) 82.00%, rgb(162, 161, 220) 92.00%, rgb(181, 174, 228) 100%)",
22 | "linear-gradient(270deg, rgb(255, 126, 179) 0%, rgb(255, 117, 140) 100%)",
23 | "linear-gradient(90deg, rgb(120, 115, 245) 0%, rgb(236, 119, 171) 100%)",
24 | "linear-gradient(45deg, #2e266f 0.00%, #9664dd38 100.00%)",
25 | "radial-gradient(circle at center, yellow 0%, #009966 50%, purple 100%)"
26 | ];
27 |
28 | export const RADIALS_POS = [
29 | { pos: "tl", css: "circle at left top", active: false },
30 | { pos: "tm", css: "circle at center top", active: false },
31 | { pos: "tr", css: "circle at right top", active: false },
32 |
33 | { pos: "l", css: "circle at left", active: false },
34 | { pos: "m", css: "circle at center", active: true },
35 | { pos: "r", css: "circle at right", active: false },
36 |
37 | { pos: "bl", css: "circle at left bottom", active: false },
38 | { pos: "bm", css: "circle at center bottom", active: false },
39 | { pos: "br", css: "circle at right bottom", active: false }
40 | ];
41 |
--------------------------------------------------------------------------------
/src/features/editor/control-item/common/opacity.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/ui/input";
2 | import { Slider } from "@/components/ui/slider";
3 | import { useState, useEffect } from "react";
4 |
5 | const Opacity = ({
6 | value,
7 | onChange
8 | }: {
9 | value: number;
10 | onChange: (v: number) => void;
11 | }) => {
12 | // Create local state to manage opacity
13 | const [localValue, setLocalValue] = useState(value);
14 |
15 | // Update local state when prop value changes
16 | useEffect(() => {
17 | setLocalValue(value);
18 | }, [value]);
19 |
20 | return (
21 |
22 |
23 | Opacity
24 |
25 |
32 | {
37 | const newValue = Number(e.target.value);
38 | if (newValue >= 0 && newValue <= 100) {
39 | setLocalValue(newValue); // Update local state
40 | onChange(newValue); // Optionally propagate immediately, or adjust as needed
41 | }
42 | }}
43 | value={localValue} // Use local state for input value
44 | />
45 | {
49 | setLocalValue(e[0]); // Update local state
50 | }}
51 | onValueCommit={() => {
52 | onChange(localValue); // Propagate value to parent when user commits change
53 | }}
54 | min={0}
55 | max={100}
56 | step={1}
57 | aria-label="Opacity"
58 | />
59 |
60 |
61 | );
62 | };
63 |
64 | export default Opacity;
65 |
--------------------------------------------------------------------------------
/src/features/editor/timeline/types.ts:
--------------------------------------------------------------------------------
1 | export interface Filmstrip {
2 | segmentIndex?: number;
3 | offset: number;
4 | thumbnailsCount: number;
5 | startTime: number;
6 | widthOnScreen: number;
7 | }
8 |
9 | export interface FilmstripBacklogOptions {
10 | thumbnailsPerSegment: number; // Number of thumbnails preloaded for smooth scrolling
11 | segmentSize: number; // Total width required to display thumbnails side by side
12 | }
13 |
14 | export const calculateThumbnailSegmentLayout = (
15 | thumbnailHeight: number
16 | ): FilmstripBacklogOptions => {
17 | // Calculate the maximum number of thumbnails based on the thumbnail width
18 | let maxThumbnails = Math.floor(1200 / thumbnailHeight);
19 |
20 | // Calculate the total width required for the thumbnails
21 | let segmentSize = maxThumbnails * thumbnailHeight;
22 |
23 | return {
24 | thumbnailsPerSegment: maxThumbnails,
25 | segmentSize
26 | };
27 | };
28 |
29 | // it calculates the number of segments that are offscreen
30 | export const calculateOffscreenSegments = (
31 | offscreenHeight: number,
32 | trimFromSize: number,
33 | segmentSize: number
34 | ) => {
35 | const offscreenSegments = Math.floor(
36 | (offscreenHeight + trimFromSize) / segmentSize
37 | );
38 | return offscreenSegments;
39 | };
40 |
41 | interface Thumbnail {
42 | ts: number;
43 | url: string;
44 | }
45 |
46 | interface Result {
47 | ts: number;
48 | url: string;
49 | }
50 |
51 | export function matchTimestampsToNearestThumbnails(
52 | timestamps: number[],
53 | thumbnailsList: Thumbnail[]
54 | ): Result[] {
55 | const results: Result[] = [];
56 |
57 | timestamps.forEach((ts) => {
58 | // Find the closest thumbnail
59 | const closestThumbnail = thumbnailsList.reduce((prev, curr) => {
60 | return Math.abs(curr.ts - ts) < Math.abs(prev.ts - ts) ? curr : prev;
61 | });
62 |
63 | // Push the result into the results array
64 | results.push({
65 | ts,
66 | url: closestThumbnail.url
67 | });
68 | });
69 |
70 | return results;
71 | }
72 |
--------------------------------------------------------------------------------
/src/features/editor/control-item/common/brightness.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/ui/input";
2 | import { Slider } from "@/components/ui/slider";
3 | import { useState, useEffect } from "react";
4 |
5 | const Brightness = ({
6 | value,
7 | onChange
8 | }: {
9 | value: number;
10 | onChange: (v: number) => void;
11 | }) => {
12 | // Create local state to manage opacity
13 | const [localValue, setLocalValue] = useState(value);
14 |
15 | // Update local state when prop value changes
16 | useEffect(() => {
17 | setLocalValue(value);
18 | }, [value]);
19 |
20 | return (
21 |
22 |
23 | Brightness
24 |
25 |
32 | {
37 | const newValue = Number(e.target.value);
38 | if (newValue >= 0 && newValue <= 100) {
39 | setLocalValue(newValue); // Update local state
40 | onChange(newValue); // Optionally propagate immediately, or adjust as needed
41 | }
42 | }}
43 | value={localValue} // Use local state for input value
44 | />
45 | {
49 | setLocalValue(e[0]); // Update local state
50 | }}
51 | onValueCommit={() => {
52 | onChange(localValue); // Propagate value to parent when user commits change
53 | }}
54 | min={0}
55 | max={100}
56 | step={1}
57 | aria-label="Brightness"
58 | />
59 |
60 |
61 | );
62 | };
63 |
64 | export default Brightness;
65 |
--------------------------------------------------------------------------------
/src/components/color-picker/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline"
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10"
27 | }
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default"
32 | }
33 | }
34 | );
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean;
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button";
45 | return (
46 |
51 | );
52 | }
53 | );
54 | Button.displayName = "Button";
55 |
56 | export { Button, buttonVariants };
57 |
--------------------------------------------------------------------------------
/src/components/color-picker/tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as TabsPrimitive from "@radix-ui/react-tabs";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const Tabs = TabsPrimitive.Root;
7 |
8 | const TabsList = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ));
21 | TabsList.displayName = TabsPrimitive.List.displayName;
22 |
23 | const TabsTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
35 | ));
36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
37 |
38 | const TabsContent = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
50 | ));
51 | TabsContent.displayName = TabsPrimitive.Content.displayName;
52 |
53 | export { Tabs, TabsList, TabsTrigger, TabsContent };
54 |
--------------------------------------------------------------------------------
/src/features/editor/player/items/shape.tsx:
--------------------------------------------------------------------------------
1 | import { IShape } from "@designcombo/types";
2 | import { BaseSequence, SequenceItemOptions } from "../base-sequence";
3 | import { BoxAnim, ContentAnim, MaskAnim } from "@designcombo/animations";
4 | import { calculateContainerStyles } from "../styles";
5 | import { getAnimations } from "../../utils/get-animations";
6 | import { calculateFrames } from "../../utils/frames";
7 |
8 | export const Shape = ({
9 | item,
10 | options
11 | }: {
12 | item: IShape;
13 | options: SequenceItemOptions;
14 | }) => {
15 | const { fps, frame } = options;
16 | const { details, animations } = item;
17 | const { animationIn, animationOut, animationTimed } = getAnimations(
18 | animations!,
19 | item,
20 | frame,
21 | fps
22 | );
23 | const { durationInFrames } = calculateFrames(item.display, fps);
24 | const currentFrame = (frame || 0) - (item.display.from * fps) / 1000;
25 | const children = (
26 |
33 |
39 |
44 |
55 |
56 |
57 |
58 | );
59 | return BaseSequence({ item, options, children });
60 | };
61 |
62 | export default Shape;
63 |
--------------------------------------------------------------------------------
/src/features/editor/player/animated/text-animated-types/animations-in/background-in.tsx:
--------------------------------------------------------------------------------
1 | import { ITextDetails } from "@designcombo/types";
2 | import { interpolate } from "remotion";
3 |
4 | const BackgroundIn = ({
5 | text,
6 | frame,
7 | details,
8 | animationTextInFrames
9 | }: {
10 | text: string;
11 | frame: number;
12 | details: ITextDetails;
13 | animationTextInFrames: number;
14 | }) => {
15 | const progress = interpolate(frame, [0, animationTextInFrames], [0, 1], {
16 | extrapolateRight: "clamp"
17 | });
18 | const fullWidth = details.width;
19 | const fullHeight = details.height;
20 |
21 | const revealWidth = interpolate(progress, [0, 1], [0, fullWidth]);
22 | const textTranslateX = interpolate(progress, [0, 1], [fullWidth / 2, 0]);
23 |
24 | return (
25 |
36 |
48 |
64 | {text}
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | export default BackgroundIn;
72 |
--------------------------------------------------------------------------------
/src/features/editor/player/items/image.tsx:
--------------------------------------------------------------------------------
1 | import { IImage } from "@designcombo/types";
2 | import { BaseSequence, SequenceItemOptions } from "../base-sequence";
3 | import { BoxAnim, ContentAnim, MaskAnim } from "@designcombo/animations";
4 | import { calculateContainerStyles, calculateMediaStyles } from "../styles";
5 | import { getAnimations } from "../../utils/get-animations";
6 | import { calculateFrames } from "../../utils/frames";
7 | import { Img } from "remotion";
8 |
9 | export default function Image({
10 | item,
11 | options
12 | }: {
13 | item: IImage;
14 | options: SequenceItemOptions;
15 | }) {
16 | const { fps, frame } = options;
17 | const { details, animations } = item;
18 | const { animationIn, animationOut, animationTimed } = getAnimations(
19 | animations!,
20 | item,
21 | frame,
22 | fps
23 | );
24 | const crop = details?.crop || {
25 | x: 0,
26 | y: 0,
27 | width: details.width,
28 | height: details.height
29 | };
30 | const { durationInFrames } = calculateFrames(item.display, fps);
31 | const currentFrame = (frame || 0) - (item.display.from * fps) / 1000;
32 |
33 | const children = (
34 |
43 |
48 |
53 |
57 | {/* image layer */}
58 |

59 |
60 |
61 |
62 |
63 | );
64 |
65 | return BaseSequence({ item, options, children });
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | function TooltipProvider({
9 | delayDuration = 0,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | );
19 | }
20 |
21 | function Tooltip({
22 | ...props
23 | }: React.ComponentProps) {
24 | return (
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | function TooltipTrigger({
32 | ...props
33 | }: React.ComponentProps) {
34 | return ;
35 | }
36 |
37 | function TooltipContent({
38 | className,
39 | sideOffset = 0,
40 | children,
41 | ...props
42 | }: React.ComponentProps) {
43 | return (
44 |
45 |
54 | {children}
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
62 |
--------------------------------------------------------------------------------
/src/features/editor/menu-item/transitions.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Draggable from "@/components/shared/draggable";
3 | import { ScrollArea } from "@/components/ui/scroll-area";
4 | import { TRANSITIONS } from "../data/transitions";
5 | import { useIsDraggingOverTimeline } from "../hooks/is-dragging-over-timeline";
6 |
7 | export const Transitions = () => {
8 | const isDraggingOverTimeline = useIsDraggingOverTimeline();
9 |
10 | return (
11 |
12 |
13 | Transitions
14 |
15 |
16 |
17 | {TRANSITIONS.map((transition, index) => (
18 |
23 | ))}
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | const TransitionsMenuItem = ({
31 | transition,
32 | shouldDisplayPreview
33 | }: {
34 | transition: Partial;
35 | shouldDisplayPreview: boolean;
36 | }) => {
37 | const style = React.useMemo(
38 | () => ({
39 | backgroundImage: `url(${transition.preview})`,
40 | backgroundSize: "cover",
41 | width: "70px",
42 | height: "70px"
43 | }),
44 | [transition.preview]
45 | );
46 |
47 | return (
48 | }
51 | shouldDisplayPreview={shouldDisplayPreview}
52 | >
53 |
54 |
57 |
58 | {transition.name || transition.type}
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export default TransitionsMenuItem;
66 |
--------------------------------------------------------------------------------
/src/features/editor/hooks/use-zoom.tsx:
--------------------------------------------------------------------------------
1 | import { ISize } from "@designcombo/types";
2 | import { useCallback, useEffect, useRef, useState } from "react";
3 |
4 | function useZoom(containerRef: React.RefObject, size: ISize) {
5 | const [zoom, setZoom] = useState(0.01);
6 | const currentZoomRef = useRef(0.01);
7 |
8 | const calculateZoom = useCallback(() => {
9 | const container = containerRef.current;
10 | if (!container) return;
11 |
12 | const PADDING = 30;
13 | const containerHeight = container.clientHeight - PADDING;
14 | const containerWidth = container.clientWidth - PADDING;
15 | const { width, height } = size;
16 |
17 | const desiredZoom = Math.min(
18 | containerWidth / width,
19 | containerHeight / height
20 | );
21 | currentZoomRef.current = desiredZoom;
22 | setZoom(desiredZoom);
23 | }, [containerRef, size]);
24 |
25 | useEffect(() => {
26 | calculateZoom();
27 | }, [calculateZoom]);
28 |
29 | useEffect(() => {
30 | const container = containerRef.current;
31 | if (!container) return;
32 |
33 | // Use ResizeObserver to watch for container size changes
34 | const resizeObserver = new ResizeObserver(() => {
35 | calculateZoom();
36 | });
37 |
38 | resizeObserver.observe(container);
39 |
40 | // Also listen for window resize events
41 | const handleWindowResize = () => {
42 | calculateZoom();
43 | };
44 |
45 | window.addEventListener("resize", handleWindowResize);
46 |
47 | return () => {
48 | resizeObserver.disconnect();
49 | window.removeEventListener("resize", handleWindowResize);
50 | };
51 | }, [calculateZoom]);
52 |
53 | const handlePinch = useCallback((e: any) => {
54 | const deltaY = (e as any).inputEvent.deltaY;
55 | const changer = deltaY > 0 ? 0.0085 : -0.0085;
56 | const currentZoom = currentZoomRef.current;
57 | const newZoom = currentZoom + changer;
58 | if (newZoom >= 0.001 && newZoom <= 10) {
59 | currentZoomRef.current = newZoom;
60 | setZoom(newZoom);
61 | }
62 | }, []);
63 |
64 | return { zoom, handlePinch, recalculateZoom: calculateZoom };
65 | }
66 |
67 | export default useZoom;
68 |
--------------------------------------------------------------------------------
/src/features/editor/interfaces/editor.ts:
--------------------------------------------------------------------------------
1 | export interface IUpload {
2 | id: string;
3 | name: string;
4 | originalName: string;
5 | fileId: string;
6 | userId?: string;
7 | previewUrl: string;
8 | url: string;
9 | previewData?: string;
10 | }
11 | export interface User {
12 | id: string;
13 | email: string;
14 | avatar: string;
15 | username: string;
16 | provider: "github";
17 | }
18 | export interface IFont {
19 | id: string;
20 | family: string;
21 | fullName: string;
22 | postScriptName: string;
23 | preview: string;
24 | style: string;
25 | url: string;
26 | category: string;
27 | createdAt: string;
28 | updatedAt: string;
29 | userId: string | null;
30 | }
31 |
32 | export interface ICompactFont {
33 | family: string;
34 | styles: IFont[];
35 | default: IFont;
36 | name?: string;
37 | }
38 |
39 | export interface IDataState {
40 | fonts: IFont[];
41 | compactFonts: ICompactFont[];
42 | setFonts: (fonts: IFont[]) => void;
43 | setCompactFonts: (compactFonts: ICompactFont[]) => void;
44 | }
45 |
46 | export type IPropertyType = "textContent" | "fontSize" | "color";
47 |
48 | /**
49 | * Width / height
50 | */
51 | export type Ratio = number;
52 |
53 | export type Area = [x: number, y: number, width: number, height: number];
54 |
55 | export interface Voice {
56 | id: string;
57 | name: string;
58 | accent: string;
59 | gender: string;
60 | age: string;
61 | descriptive: string;
62 | useCase: string;
63 | category: string;
64 | language: string;
65 | locale: string | null;
66 | description: string;
67 | previewUrl: string;
68 | }
69 |
70 | // Avatar interface
71 | export interface Avatar {
72 | id: string;
73 | name: string;
74 | gender: string;
75 | previewImageUrl: string;
76 | previewVideoUrl: string;
77 | }
78 |
79 | // Tab configuration interface
80 | export interface TabConfig {
81 | name: string;
82 | value: string;
83 | icon: React.ComponentType<{ className?: string }>;
84 | content: React.ReactNode;
85 | }
86 |
87 | // Filter state interfaces
88 | export interface VoiceFilters {
89 | language: string;
90 | gender: string;
91 | }
92 |
93 | export interface AvatarFilters {
94 | gender: string;
95 | }
96 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TabsPrimitive from "@radix-ui/react-tabs";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | function Tabs({
9 | className,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | );
19 | }
20 |
21 | function TabsList({
22 | className,
23 | ...props
24 | }: React.ComponentProps) {
25 | return (
26 |
34 | );
35 | }
36 |
37 | function TabsTrigger({
38 | className,
39 | ...props
40 | }: React.ComponentProps) {
41 | return (
42 |
50 | );
51 | }
52 |
53 | function TabsContent({
54 | className,
55 | ...props
56 | }: React.ComponentProps) {
57 | return (
58 |
63 | );
64 | }
65 |
66 | export { Tabs, TabsList, TabsTrigger, TabsContent };
67 |
--------------------------------------------------------------------------------
/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
5 | import { type VariantProps } from "class-variance-authority";
6 |
7 | import { cn } from "@/lib/utils";
8 | import { toggleVariants } from "@/components/ui/toggle";
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default"
15 | });
16 |
17 | function ToggleGroup({
18 | className,
19 | variant,
20 | size,
21 | children,
22 | ...props
23 | }: React.ComponentProps &
24 | VariantProps) {
25 | return (
26 |
36 |
37 | {children}
38 |
39 |
40 | );
41 | }
42 |
43 | function ToggleGroupItem({
44 | className,
45 | children,
46 | variant,
47 | size,
48 | ...props
49 | }: React.ComponentProps &
50 | VariantProps) {
51 | const context = React.useContext(ToggleGroupContext);
52 |
53 | return (
54 |
68 | {children}
69 |
70 | );
71 | }
72 |
73 | export { ToggleGroup, ToggleGroupItem };
74 |
--------------------------------------------------------------------------------