From 3e89d2998588f8e35c524e523ba2ee853f0f6475 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 20 Apr 2026 11:31:17 +0200 Subject: refactor: restructure files to their domains, new preview overlay system, and dep graph --- apps/web/src/text/background.ts | 2 + apps/web/src/text/components/assets-view.tsx | 46 ++ apps/web/src/text/components/text-tab.tsx | 764 +++++++++++++++++++++++++++ apps/web/src/text/layout.ts | 226 ++++++++ apps/web/src/text/measure-element.ts | 133 +++++ apps/web/src/text/primitives.ts | 241 +++++++++ apps/web/src/text/typography.ts | 9 + 7 files changed, 1421 insertions(+) create mode 100644 apps/web/src/text/background.ts create mode 100644 apps/web/src/text/components/assets-view.tsx create mode 100644 apps/web/src/text/components/text-tab.tsx create mode 100644 apps/web/src/text/layout.ts create mode 100644 apps/web/src/text/measure-element.ts create mode 100644 apps/web/src/text/primitives.ts create mode 100644 apps/web/src/text/typography.ts (limited to 'apps/web/src/text') diff --git a/apps/web/src/text/background.ts b/apps/web/src/text/background.ts new file mode 100644 index 00000000..be5ab6f5 --- /dev/null +++ b/apps/web/src/text/background.ts @@ -0,0 +1,2 @@ +export const CORNER_RADIUS_MIN = 0; +export const CORNER_RADIUS_MAX = 100; diff --git a/apps/web/src/text/components/assets-view.tsx b/apps/web/src/text/components/assets-view.tsx new file mode 100644 index 00000000..25a49893 --- /dev/null +++ b/apps/web/src/text/components/assets-view.tsx @@ -0,0 +1,46 @@ +import { DraggableItem } from "@/components/editor/panels/assets/draggable-item"; +import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; +import { useEditor } from "@/editor/use-editor"; +import { DEFAULTS } from "@/timeline/defaults"; +import { buildTextElement } from "@/timeline/element-utils"; + +export function TextView() { + const editor = useEditor(); + + const handleAddToTimeline = ({ currentTime }: { currentTime: number }) => { + const activeScene = editor.scenes.getActiveScene(); + if (!activeScene) return; + + const element = buildTextElement({ + raw: DEFAULTS.text.element, + startTime: currentTime, + }); + + editor.timeline.insertElement({ + element, + placement: { mode: "auto" }, + }); + }; + + return ( + + + Default text + + } + dragData={{ + id: "temp-text-id", + type: DEFAULTS.text.element.type, + name: DEFAULTS.text.element.name, + content: DEFAULTS.text.element.content, + }} + aspectRatio={1} + onAddToTimeline={handleAddToTimeline} + shouldShowLabel={false} + /> + + ); +} diff --git a/apps/web/src/text/components/text-tab.tsx b/apps/web/src/text/components/text-tab.tsx new file mode 100644 index 00000000..bf14efba --- /dev/null +++ b/apps/web/src/text/components/text-tab.tsx @@ -0,0 +1,764 @@ +"use client"; + +import { Textarea } from "@/components/ui/textarea"; +import { FontPicker } from "@/components/ui/font-picker"; +import type { TextElement } from "@/timeline"; +import { NumberField } from "@/components/ui/number-field"; +import { useRef } from "react"; +import { + Section, + SectionContent, + SectionField, + SectionFields, + SectionHeader, + SectionTitle, +} from "@/components/section"; +import { ColorPicker } from "@/components/ui/color-picker"; +import { Button } from "@/components/ui/button"; +import { uppercase } from "@/utils/string"; +import { clamp, formatNumberForDisplay } from "@/utils/math"; +import { useEditor } from "@/editor/use-editor"; +import { CORNER_RADIUS_MAX, CORNER_RADIUS_MIN } from "@/text/background"; +import { + DEFAULT_TEXT_COLOR, + MAX_FONT_SIZE, + MIN_FONT_SIZE, +} from "@/text/typography"; +import { usePropertyDraft } from "@/components/editor/panels/properties/hooks/use-property-draft"; +import { useKeyframedColorProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-color-property"; +import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; +import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; +import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; +import { isPropertyAtDefault } from "@/rendering/components/transform-tab"; +import { resolveColorAtTime, resolveNumberAtTime } from "@/animation"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { + MinusSignIcon, + PlusSignIcon, + TextFontIcon, +} from "@hugeicons/core-free-icons"; +import { OcTextHeightIcon, OcTextWidthIcon } from "@/components/icons"; +import { DEFAULTS } from "@/timeline/defaults"; +import { cn } from "@/utils/ui"; + +export function TextTab({ + element, + trackId, +}: { + element: TextElement; + trackId: string; +}) { + return ( +
+ + + + +
+ ); +} + +function ContentSection({ + element, + trackId, +}: { + element: TextElement; + trackId: string; +}) { + const editor = useEditor(); + + const content = usePropertyDraft({ + displayValue: element.content, + parse: (input) => input, + onPreview: (value) => + editor.timeline.previewElements({ + updates: [ + { trackId, elementId: element.id, updates: { content: value } }, + ], + }), + onCommit: () => editor.timeline.commitPreview(), + }); + + return ( +
+ + Content + + +