feat: masks, properties refactor, shaders, storage migrations, and more

This commit is contained in:
Maze Winther
2026-03-29 15:48:22 +02:00
parent 39ea298a9c
commit 8db3bead13
690 changed files with 35618 additions and 7337 deletions
@@ -1,3 +1,17 @@
export type TPlatformLayout =
| "tiktok"
| "instagram"
| "youtube"
| "snapchat";
export const PREVIEW_ZOOM_PRESETS = [25, 50, 75, 100, 150, 200];
export const PREVIEW_ZOOM = {
min: 0.25,
max: 16,
step: 1.25,
};
export const PANEL_CONFIG = {
panels: {
tools: 25,
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ExportOptions } from "@/types/export";
import type { ExportOptions } from "@/lib/export";
export const DEFAULT_EXPORT_OPTIONS = {
format: "mp4",
-2
View File
@@ -1,5 +1,3 @@
export const DEFAULT_FONT = "Arial";
export const SYSTEM_FONTS = new Set([
"Arial",
"Helvetica",
@@ -0,0 +1,4 @@
export const GRID_MIN = 1;
export const GRID_MAX = 24;
export const DEFAULT_GRID_CONFIG = { rows: 3, cols: 3 } as const;
@@ -9,3 +9,6 @@ export const LANGUAGES = [
{ code: "ja", name: "Japanese" },
{ code: "zh", name: "Chinese" },
] as const;
export type Language = (typeof LANGUAGES)[number];
export type LanguageCode = Language["code"];
+4
View File
@@ -0,0 +1,4 @@
export const MAX_FEATHER = 1000;
export const FEATHER_HANDLE_SCALE = 0.11;
export const DEFAULT_SHAPE_MASK_SHORT_SIDE_RATIO = 0.6;
export const MIN_MASK_DIMENSION = 0.01;
+5 -5
View File
@@ -1,4 +1,4 @@
import type { TCanvasSize } from "@/types/project";
import type { TCanvasSize } from "@/lib/project/types";
export const DEFAULT_CANVAS_PRESETS: TCanvasSize[] = [
{ width: 1920, height: 1080 },
@@ -16,12 +16,12 @@ export const FPS_PRESETS = [
] as const;
export const BLUR_INTENSITY_PRESETS: { label: string; value: number }[] = [
{ label: "Light", value: 4 },
{ label: "Medium", value: 8 },
{ label: "Heavy", value: 18 },
{ label: "Light", value: 10 },
{ label: "Medium", value: 50 },
{ label: "Heavy", value: 100 },
] as const;
export const DEFAULT_CANVAS_SIZE: TCanvasSize = { width: 1920, height: 1080 };
export const DEFAULT_FPS = 30;
export const DEFAULT_BLUR_INTENSITY = 8;
export const DEFAULT_BLUR_INTENSITY = 10;
export const DEFAULT_COLOR = "#000000";
@@ -0,0 +1,30 @@
export const DEFAULT_RETIME_RATE = 1;
export const MIN_RETIME_RATE = 0.01;
export const MAX_RETIME_RATE = 5;
export const MAX_PITCH_PRESERVE_RATE = 10;
export function clampRetimeRate({ rate }: { rate: number }): number {
if (!Number.isFinite(rate) || rate <= 0) {
return DEFAULT_RETIME_RATE;
}
return Math.min(Math.max(rate, MIN_RETIME_RATE), MAX_RETIME_RATE);
}
export function canMaintainPitch({ rate }: { rate: number }): boolean {
if (!Number.isFinite(rate) || rate <= 0) {
return false;
}
return clampRetimeRate({ rate }) <= MAX_PITCH_PRESERVE_RATE;
}
export function shouldMaintainPitch({
rate,
maintainPitch,
}: {
rate: number;
maintainPitch?: boolean;
}): boolean {
return maintainPitch === true && canMaintainPitch({ rate });
}
+2 -2
View File
@@ -1,8 +1,8 @@
export const STICKER_INTRINSIC_SIZE_FALLBACK = 200;
export const STICKER_CATEGORIES = {
all: "All",
logos: "Logos",
icons: "Icons",
emoji: "Emoji",
flags: "Flags",
shapes: "Shapes",
};
-42
View File
@@ -1,10 +1,3 @@
import type { TextElement } from "@/types/timeline";
import {
DEFAULT_OPACITY,
DEFAULT_TRANSFORM,
TIMELINE_CONSTANTS,
} from "./timeline-constants";
export const MIN_FONT_SIZE = 5;
export const MAX_FONT_SIZE = 300;
@@ -14,40 +7,5 @@ export const MAX_FONT_SIZE = 300;
*/
export const FONT_SIZE_SCALE_REFERENCE = 90;
export const DEFAULT_LETTER_SPACING = 0;
export const DEFAULT_LINE_HEIGHT = 1.2;
export const CORNER_RADIUS_MIN = 0;
export const CORNER_RADIUS_MAX = 100;
export const DEFAULT_TEXT_BACKGROUND = {
enabled: false,
color: "#000000",
cornerRadius: 0,
paddingX: 30,
paddingY: 42,
offsetX: 0,
offsetY: 0,
};
export const DEFAULT_TEXT_ELEMENT: Omit<TextElement, "id"> = {
type: "text",
name: "Text",
content: "Default text",
fontSize: 15,
fontFamily: "Arial",
color: "#ffffff",
background: DEFAULT_TEXT_BACKGROUND,
textAlign: "center",
fontWeight: "normal",
fontStyle: "normal",
textDecoration: "none",
letterSpacing: DEFAULT_LETTER_SPACING,
lineHeight: DEFAULT_LINE_HEIGHT,
duration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,
startTime: 0,
trimStart: 0,
trimEnd: 0,
transform: DEFAULT_TRANSFORM,
opacity: DEFAULT_OPACITY,
};
@@ -0,0 +1,85 @@
import type { ElementType, TrackType } from "@/lib/timeline";
export const TRACK_CONFIG: Record<
TrackType,
{
height: number;
defaultName: string;
}
> = {
video: {
height: 65,
defaultName: "Video track",
},
text: {
height: 25,
defaultName: "Text track",
},
audio: {
height: 50,
defaultName: "Audio track",
},
graphic: {
height: 25,
defaultName: "Graphic track",
},
effect: {
height: 25,
defaultName: "Effect track",
},
} as const;
export const ELEMENT_TYPE_CONFIG: Record<
TrackType,
{
background: string;
waveformColor?: string;
}
> = {
video: { background: "transparent" },
text: { background: "bg-[#5DBAA0]" },
audio: {
background: "bg-[#8F5DBA]",
waveformColor: "rgba(255, 255, 255, 0.5)",
},
graphic: { background: "bg-[#BA5D7A]" },
effect: { background: "bg-[#5d93ba]" },
} as const;
export const ELEMENT_TRACK_MAP: Record<ElementType, TrackType> = {
audio: "audio",
text: "text",
sticker: "graphic",
graphic: "graphic",
effect: "effect",
video: "video",
image: "video",
};
export const TRACK_GAP = 6;
export const TRACK_LABELS_WIDTH_PX = 112;
export const TIMELINE_RULER_HEIGHT = 22;
export const TIMELINE_BOOKMARK_ROW_HEIGHT = 16;
export const DEFAULT_BOOKMARK_COLOR = "#009dff";
export const DRAG_THRESHOLD_PX = 5;
export const TIMELINE_SCROLLBAR_SIZE_PX = 12;
export const TIMELINE_LAYERS = {
trackContent: 10,
dragLine: 20,
playhead: 30,
snapIndicator: 40,
} as const;
export const TIMELINE_CONSTANTS = {
PIXELS_PER_SECOND: 50,
DEFAULT_ELEMENT_DURATION: 5,
PADDING_TOP_PX: 2,
HORIZONTAL_WHEEL_STEP_PX: 40,
ZOOM_MIN: 0.1,
ZOOM_MAX: 100,
ZOOM_BUTTON_FACTOR: 1.7,
ZOOM_ANCHOR_PLAYHEAD_THRESHOLD: 0.15,
TRACK_SELECTED_BG: "bg-accent/50",
} as const;
@@ -1,102 +0,0 @@
import type { TTimelineViewState } from "@/types/project";
import type { BlendMode } from "@/types/rendering";
import type { TrackType, Transform } from "@/types/timeline";
import {
Happy01Icon,
MagicWand05Icon,
MusicNote03Icon,
TextIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { OcVideoIcon } from "@opencut/ui/icons";
export const DEFAULT_TRANSFORM: Transform = {
scale: 1,
position: { x: 0, y: 0 },
rotate: 0,
};
export const DEFAULT_OPACITY = 1;
export const DEFAULT_BLEND_MODE: BlendMode = "normal";
export const DEFAULT_BOOKMARK_COLOR = "#009dff";
export const TRACK_CONFIG: Record<
TrackType,
{
background: string;
height: number;
defaultName: string;
icon: React.ReactNode;
}
> = {
video: {
background: "transparent",
height: 60,
defaultName: "Video track",
icon: <OcVideoIcon className="text-muted-foreground size-4 shrink-0" />,
},
text: {
background: "bg-[#5DBAA0]",
height: 25,
defaultName: "Text track",
icon: (
<HugeiconsIcon
icon={TextIcon}
className="text-muted-foreground size-4 shrink-0"
/>
),
},
audio: {
background: "bg-[#8F5DBA]",
height: 50,
defaultName: "Audio track",
icon: (
<HugeiconsIcon
icon={MusicNote03Icon}
className="text-muted-foreground size-4 shrink-0"
/>
),
},
sticker: {
background: "bg-[#BA5D7A]",
height: 50,
defaultName: "Sticker track",
icon: (
<HugeiconsIcon
icon={Happy01Icon}
className="text-muted-foreground size-4 shrink-0"
/>
),
},
effect: {
background: "bg-[#5d93ba]",
height: 25,
defaultName: "Effect track",
icon: (
<HugeiconsIcon
icon={MagicWand05Icon}
className="text-muted-foreground size-4 shrink-0"
/>
),
},
} as const;
export const TRACK_GAP = 4;
export const DRAG_THRESHOLD_PX = 5;
export const TIMELINE_CONSTANTS = {
PIXELS_PER_SECOND: 50,
DEFAULT_ELEMENT_DURATION: 5,
PADDING_TOP_PX: 0,
ZOOM_MIN: 0.1,
ZOOM_MAX: 100,
ZOOM_BUTTON_FACTOR: 1.7,
ZOOM_ANCHOR_PLAYHEAD_THRESHOLD: 0.15,
} as const;
export const DEFAULT_TIMELINE_VIEW_STATE: TTimelineViewState = {
zoomLevel: 1,
scrollLeft: 0,
playheadTime: 0,
};
@@ -2,8 +2,8 @@ import { LANGUAGES } from "@/constants/language-constants";
import type {
TranscriptionModel,
TranscriptionModelId,
} from "@/types/transcription";
import type { LanguageCode } from "@/types/language";
} from "@/lib/transcription/types";
import type { LanguageCode } from "@/constants/language-constants";
const SUPPORTED_TRANSCRIPTION_LANGS: ReadonlyArray<LanguageCode> = [
"en",