From 93d1e3383ce47e45157d70f9ba6251239dae6bc2 Mon Sep 17 00:00:00 2001 From: Maze <167211895+mazeincoding@users.noreply.github.com> Date: Mon, 23 Feb 2026 03:24:02 +0100 Subject: feat: major editor overhaul (assets, properties, timeline, fonts) (#709) * feat: major editor overhaul (assets, properties, timeline, fonts) Refactor editor core systems to standardize UI architecture and improve performance. Assets & Properties: - Replace monolithic property items with composable `Section` architecture. - Add specialized sections for Transform, Blending, and Text. - Implement `NumberField` with scrubbing and math evaluation. - Add new ColorPicker with EyeDropper and multiple format support. - Standardize asset panels using new `PanelView` layout. Fonts & Stickers: - Implement custom font atlas/sprite system for high-performance previews. - Add virtualized FontPicker with search and favorites. - Refactor stickers to use a provider-based architecture (icons, emoji, flags, shapes). - Standardize sticker IDs to `provider:value` format. Timeline & Interaction: - Convert bookmarks to rich objects with notes, colors, and duration. - Refactor drag-and-drop to use Command pattern (enabling proper undo/redo). - Add Shift modifier to disable snapping during moves/resizes. - Add new overlays for layout guides and text editing. Renderer: - Add support for multi-line text, custom line-height, and letter-spacing. - Implement global composite operation (blend modes). - Update sticker node to resolve dynamic provider IDs. Infrastructure: - Add storage migrations (v3->v6) for text weights, sticker IDs, and bookmarks. - Update global styles and core UI components (Button, Input, Popover). * add ts-nocheck directive to settings-legacy.tsx to suppress TypeScript errors * fix: correct global composite operation assignment in TextNode to ensure proper blend mode handling * deleted shadcn components with errors * formatting * fix linter issues * migrate from next middleware to proxy * add missing component back * add breadcrumb back * chore: add @radix-ui/react-primitive deps * chore: more deps * chore: add missing env vars to bun-ci * next env --- apps/web/src/constants/editor-constants.ts | 6 -- apps/web/src/constants/font-constants.ts | 88 ++++----------------------- apps/web/src/constants/site-constants.ts | 2 +- apps/web/src/constants/sticker-constants.ts | 8 +++ apps/web/src/constants/stickers-constants.ts | 16 ----- apps/web/src/constants/text-constants.ts | 78 ++++++++++++------------ apps/web/src/constants/timeline-constants.tsx | 15 ++++- 7 files changed, 74 insertions(+), 139 deletions(-) create mode 100644 apps/web/src/constants/sticker-constants.ts delete mode 100644 apps/web/src/constants/stickers-constants.ts (limited to 'apps/web/src/constants') diff --git a/apps/web/src/constants/editor-constants.ts b/apps/web/src/constants/editor-constants.ts index 213ea875..00e8f19b 100644 --- a/apps/web/src/constants/editor-constants.ts +++ b/apps/web/src/constants/editor-constants.ts @@ -1,9 +1,3 @@ -import type { TPlatformLayout } from "@/types/editor"; - -export const PLATFORM_LAYOUTS: Record = { - tiktok: "TikTok", -}; - export const PANEL_CONFIG = { panels: { tools: 25, diff --git a/apps/web/src/constants/font-constants.ts b/apps/web/src/constants/font-constants.ts index 46f84848..d115d09b 100644 --- a/apps/web/src/constants/font-constants.ts +++ b/apps/web/src/constants/font-constants.ts @@ -1,79 +1,13 @@ -export interface FontOption { - value: string; - label: string; - category: "system" | "google" | "custom"; - weights?: number[]; - hasClassName?: boolean; -} - -export const FONT_OPTIONS: FontOption[] = [ - // System fonts (always available) - { value: "Arial", label: "Arial", category: "system", hasClassName: false }, - { - value: "Helvetica", - label: "Helvetica", - category: "system", - hasClassName: false, - }, - { - value: "Times New Roman", - label: "Times New Roman", - category: "system", - hasClassName: false, - }, - { - value: "Georgia", - label: "Georgia", - category: "system", - hasClassName: false, - }, - - // Google Fonts (loaded in layout.tsx) - { - value: "Inter", - label: "Inter", - category: "google", - weights: [400, 700], - hasClassName: true, - }, - { - value: "Roboto", - label: "Roboto", - category: "google", - weights: [400, 700], - hasClassName: true, - }, - { - value: "Open Sans", - label: "Open Sans", - category: "google", - hasClassName: true, - }, - { - value: "Playfair Display", - label: "Playfair Display", - category: "google", - hasClassName: true, - }, - { - value: "Comic Neue", - label: "Comic Neue", - category: "google", - hasClassName: false, - }, -] as const; - export const DEFAULT_FONT = "Arial"; -// Type-safe font family union -export type FontFamily = (typeof FONT_OPTIONS)[number]["value"]; - -// Helper functions -export const getFontByValue = (value: string): FontOption | undefined => - FONT_OPTIONS.find((font) => font.value === value); - -export const getGoogleFonts = (): FontOption[] => - FONT_OPTIONS.filter((font) => font.category === "google"); - -export const getSystemFonts = (): FontOption[] => - FONT_OPTIONS.filter((font) => font.category === "system"); +export const SYSTEM_FONTS = new Set([ + "Arial", + "Helvetica", + "Times New Roman", + "Courier New", + "Verdana", + "Georgia", + "monospace", + "sans-serif", + "serif", +]); diff --git a/apps/web/src/constants/site-constants.ts b/apps/web/src/constants/site-constants.ts index ac6cd2cd..10981727 100644 --- a/apps/web/src/constants/site-constants.ts +++ b/apps/web/src/constants/site-constants.ts @@ -1,4 +1,4 @@ -import { OcDataBuddyIcon, OcMarbleIcon, } from "@opencut/ui/icons"; +import { OcDataBuddyIcon, OcMarbleIcon } from "@opencut/ui/icons"; export const SITE_URL = "https://opencut.app"; diff --git a/apps/web/src/constants/sticker-constants.ts b/apps/web/src/constants/sticker-constants.ts new file mode 100644 index 00000000..506e552d --- /dev/null +++ b/apps/web/src/constants/sticker-constants.ts @@ -0,0 +1,8 @@ +export const STICKER_CATEGORIES = { + all: "All", + logos: "Logos", + icons: "Icons", + emoji: "Emoji", + flags: "Flags", + shapes: "Shapes", +}; diff --git a/apps/web/src/constants/stickers-constants.ts b/apps/web/src/constants/stickers-constants.ts deleted file mode 100644 index 3c98d4f7..00000000 --- a/apps/web/src/constants/stickers-constants.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const STICKER_CATEGORIES = [ - "all", - "general", - "brands", - "emoji", -] as const; - -export const STICKER_CATEGORY_CONFIG: Record< - (typeof STICKER_CATEGORIES)[number], - string | undefined -> = { - all: undefined, - general: "General", - brands: "Brands / Social", - emoji: "Emoji", -}; diff --git a/apps/web/src/constants/text-constants.ts b/apps/web/src/constants/text-constants.ts index 83dd251f..f0b2143d 100644 --- a/apps/web/src/constants/text-constants.ts +++ b/apps/web/src/constants/text-constants.ts @@ -1,38 +1,40 @@ -import type { TextElement } from "@/types/timeline"; -import { TIMELINE_CONSTANTS } from "./timeline-constants"; - -export const MIN_FONT_SIZE = 5; -export const MAX_FONT_SIZE = 300; - -/** - * higher value: smaller font size - * lower value: larger font size - */ -export const FONT_SIZE_SCALE_REFERENCE = 90; - -export const DEFAULT_TEXT_ELEMENT: Omit = { - type: "text", - name: "Text", - content: "Default text", - fontSize: 15, - fontFamily: "Arial", - color: "#ffffff", - backgroundColor: "transparent", - textAlign: "center", - fontWeight: "normal", - fontStyle: "normal", - textDecoration: "none", - duration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION, - startTime: 0, - trimStart: 0, - trimEnd: 0, - transform: { - scale: 1, - position: { - x: 0, - y: 0, - }, - rotate: 0, - }, - opacity: 1, -}; +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; + +/** + * higher value: smaller font size + * lower value: larger font size + */ +export const FONT_SIZE_SCALE_REFERENCE = 90; + +export const DEFAULT_LETTER_SPACING = 0; +export const DEFAULT_LINE_HEIGHT = 1.2; + +export const DEFAULT_TEXT_ELEMENT: Omit = { + type: "text", + name: "Text", + content: "Default text", + fontSize: 15, + fontFamily: "Arial", + color: "#ffffff", + backgroundColor: "#000000", + 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, +}; diff --git a/apps/web/src/constants/timeline-constants.tsx b/apps/web/src/constants/timeline-constants.tsx index d6af3c3a..5ac0913a 100644 --- a/apps/web/src/constants/timeline-constants.tsx +++ b/apps/web/src/constants/timeline-constants.tsx @@ -1,5 +1,6 @@ import type { TTimelineViewState } from "@/types/project"; -import type { TrackType } from "@/types/timeline"; +import type { BlendMode } from "@/types/rendering"; +import type { TrackType, Transform } from "@/types/timeline"; import { Happy01Icon, MusicNote03Icon, @@ -8,6 +9,16 @@ import { 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_COLORS: Record = { video: { background: "transparent", @@ -32,6 +43,8 @@ export const TRACK_HEIGHTS: Record = { export const TRACK_GAP = 4; +export const DRAG_THRESHOLD_PX = 5; + export const TIMELINE_CONSTANTS = { PIXELS_PER_SECOND: 50, DEFAULT_ELEMENT_DURATION: 5, -- cgit v1.3.1