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/types/drag.ts | 2 +- apps/web/src/types/eyedropper.d.ts | 13 +++++++ apps/web/src/types/fonts.ts | 24 ++++++++++++ apps/web/src/types/rendering.ts | 18 +++++++++ apps/web/src/types/sounds.ts | 78 +++++++++++++++++++------------------- apps/web/src/types/stickers.ts | 55 ++++++++++++++++++++++++++- apps/web/src/types/timeline.ts | 22 ++++++++--- 7 files changed, 165 insertions(+), 47 deletions(-) create mode 100644 apps/web/src/types/eyedropper.d.ts create mode 100644 apps/web/src/types/fonts.ts create mode 100644 apps/web/src/types/rendering.ts (limited to 'apps/web/src/types') diff --git a/apps/web/src/types/drag.ts b/apps/web/src/types/drag.ts index e6e5daba..61aba9b5 100644 --- a/apps/web/src/types/drag.ts +++ b/apps/web/src/types/drag.ts @@ -15,7 +15,7 @@ export interface TextDragData extends BaseDragData { export interface StickerDragData extends BaseDragData { type: "sticker"; - iconName: string; + stickerId: string; } export type TimelineDragData = MediaDragData | TextDragData | StickerDragData; diff --git a/apps/web/src/types/eyedropper.d.ts b/apps/web/src/types/eyedropper.d.ts new file mode 100644 index 00000000..2d0e17e8 --- /dev/null +++ b/apps/web/src/types/eyedropper.d.ts @@ -0,0 +1,13 @@ +interface EyeDropperResult { + sRGBHex: string; +} + +interface EyeDropper { + open(options?: { signal?: AbortSignal }): Promise; +} + +declare const EyeDropper: + | { + new (): EyeDropper; + } + | undefined; diff --git a/apps/web/src/types/fonts.ts b/apps/web/src/types/fonts.ts new file mode 100644 index 00000000..a2db6f91 --- /dev/null +++ b/apps/web/src/types/fonts.ts @@ -0,0 +1,24 @@ +export interface FontOption { + value: string; + label: string; + category: "system" | "google" | "custom"; + weights?: number[]; + hasClassName?: boolean; +} + +export interface GoogleFontMeta { + family: string; + category: string; +} + +export interface FontAtlasEntry { + x: number; + y: number; + w: number; + ch: number; + s: string[]; +} + +export interface FontAtlas { + fonts: Record; +} diff --git a/apps/web/src/types/rendering.ts b/apps/web/src/types/rendering.ts new file mode 100644 index 00000000..f5e18502 --- /dev/null +++ b/apps/web/src/types/rendering.ts @@ -0,0 +1,18 @@ +export type BlendMode = + | "normal" + | "darken" + | "multiply" + | "color-burn" + | "lighten" + | "screen" + | "plus-lighter" + | "color-dodge" + | "overlay" + | "soft-light" + | "hard-light" + | "difference" + | "exclusion" + | "hue" + | "saturation" + | "color" + | "luminosity"; diff --git a/apps/web/src/types/sounds.ts b/apps/web/src/types/sounds.ts index f7526846..eecf7701 100644 --- a/apps/web/src/types/sounds.ts +++ b/apps/web/src/types/sounds.ts @@ -1,39 +1,39 @@ -export interface SoundEffect { - id: number; - name: string; - description: string; - url: string; - previewUrl?: string; - downloadUrl?: string; - duration: number; - filesize: number; - type: string; - channels: number; - bitrate: number; - bitdepth: number; - samplerate: number; - username: string; - tags: string[]; - license: string; - created: string; - downloads: number; - rating: number; - ratingCount: number; -} - -export interface SavedSound { - id: number; // freesound id - name: string; - username: string; - previewUrl?: string; - downloadUrl?: string; - duration: number; - tags: string[]; - license: string; - savedAt: string; // iso date string -} - -export interface SavedSoundsData { - sounds: SavedSound[]; - lastModified: string; -} +export interface SoundEffect { + id: number; + name: string; + description: string; + url: string; + previewUrl?: string; + downloadUrl?: string; + duration: number; + filesize: number; + type: string; + channels: number; + bitrate: number; + bitdepth: number; + samplerate: number; + username: string; + tags: string[]; + license: string; + created: string; + downloads: number; + rating: number; + ratingCount: number; +} + +export interface SavedSound { + id: number; // freesound id + name: string; + username: string; + previewUrl?: string; + downloadUrl?: string; + duration: number; + tags: string[]; + license: string; + savedAt: string; // iso date string +} + +export interface SavedSoundsData { + sounds: SavedSound[]; + lastModified: string; +} diff --git a/apps/web/src/types/stickers.ts b/apps/web/src/types/stickers.ts index c6adfd7a..65c9aeb2 100644 --- a/apps/web/src/types/stickers.ts +++ b/apps/web/src/types/stickers.ts @@ -1,3 +1,54 @@ -import type { STICKER_CATEGORIES } from "@/constants/stickers-constants"; +import type { STICKER_CATEGORIES } from "@/constants/sticker-constants"; -export type StickerCategory = (typeof STICKER_CATEGORIES)[number]; +export type StickerCategory = keyof typeof STICKER_CATEGORIES; + +export interface StickerItem { + id: string; + provider: string; + name: string; + previewUrl: string; + metadata: Record; +} + +export interface StickerSearchResult { + items: StickerItem[]; + total: number; + hasMore: boolean; +} + +export interface StickerProviderSearchOptions { + limit?: number; +} + +export interface StickerProviderBrowseOptions { + page?: number; + limit?: number; +} + +export interface StickerResolveOptions { + width?: number; + height?: number; +} + +export interface StickerProvider { + id: string; + search({ + query, + options, + }: { + query: string; + options?: StickerProviderSearchOptions; + }): Promise; + browse({ + options, + }: { + options?: StickerProviderBrowseOptions; + }): Promise; + resolveUrl({ + stickerId, + options, + }: { + stickerId: string; + options?: StickerResolveOptions; + }): string; +} diff --git a/apps/web/src/types/timeline.ts b/apps/web/src/types/timeline.ts index 69d40518..da96c7de 100644 --- a/apps/web/src/types/timeline.ts +++ b/apps/web/src/types/timeline.ts @@ -1,9 +1,18 @@ +import type { BlendMode } from "./rendering"; + +export interface Bookmark { + time: number; + note?: string; + color?: string; + duration?: number; +} + export interface TScene { id: string; name: string; isMain: boolean; tracks: TimelineTrack[]; - bookmarks: number[]; + bookmarks: Bookmark[]; createdAt: Date; updatedAt: Date; } @@ -87,6 +96,7 @@ export interface VideoElement extends BaseTimelineElement { hidden?: boolean; transform: Transform; opacity: number; + blendMode?: BlendMode; } export interface ImageElement extends BaseTimelineElement { @@ -95,6 +105,7 @@ export interface ImageElement extends BaseTimelineElement { hidden?: boolean; transform: Transform; opacity: number; + blendMode?: BlendMode; } export interface TextElement extends BaseTimelineElement { @@ -108,18 +119,21 @@ export interface TextElement extends BaseTimelineElement { fontWeight: "normal" | "bold"; fontStyle: "normal" | "italic"; textDecoration: "none" | "underline" | "line-through"; + letterSpacing?: number; + lineHeight?: number; hidden?: boolean; transform: Transform; opacity: number; + blendMode?: BlendMode; } export interface StickerElement extends BaseTimelineElement { type: "sticker"; - iconName: string; + stickerId: string; hidden?: boolean; transform: Transform; opacity: number; - color?: string; + blendMode?: BlendMode; } export type TimelineElement = @@ -147,8 +161,6 @@ export type CreateTimelineElement = | CreateTextElement | CreateStickerElement; -// ---- Drag State ---- - export interface ElementDragState { isDragging: boolean; elementId: string | null; -- cgit v1.3.1