From 9fb8406cdb7bf2281be5ee479eff9ee9938b62aa Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sun, 22 Feb 2026 17:36:02 +0100 Subject: [PATCH] formatting --- apps/web/src/app/globals.css | 446 +++++++++--------- .../editor/panels/assets/views/assets.tsx | 1 - .../editor/panels/preview/context-menu.tsx | 5 +- .../editor/panels/preview/index.tsx | 6 +- .../preview/preview-interaction-overlay.tsx | 5 +- .../panels/preview/transform-handles.tsx | 38 +- .../editor/panels/timeline/bookmarks.tsx | 32 +- apps/web/src/components/ui/color-picker.tsx | 81 ++-- apps/web/src/components/ui/input.tsx | 3 +- apps/web/src/components/ui/tooltip.tsx | 2 +- .../element/use-element-interaction.ts | 5 +- .../timeline/element/use-element-resize.ts | 3 +- .../src/hooks/timeline/use-bookmark-drag.ts | 21 +- .../hooks/timeline/use-timeline-drag-drop.ts | 3 +- .../hooks/timeline/use-timeline-playhead.ts | 86 ++-- .../hooks/timeline/use-timeline-snapping.ts | 3 +- apps/web/src/hooks/use-paste-media.ts | 2 +- apps/web/src/hooks/use-transform-handles.ts | 25 +- .../src/lib/commands/scene/update-bookmark.ts | 5 +- apps/web/src/lib/fonts/google-fonts.ts | 4 +- apps/web/src/lib/preview/element-bounds.ts | 6 +- apps/web/src/lib/preview/preview-snap.ts | 4 +- apps/web/src/lib/timeline/bookmarks.ts | 5 +- .../migrations/__tests__/v5-to-v6.test.ts | 28 +- apps/web/src/services/storage/service.ts | 6 +- apps/web/src/types/eyedropper.d.ts | 2 +- apps/web/src/utils/color.ts | 48 +- 27 files changed, 468 insertions(+), 407 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 2f1ab878..2d051c93 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -8,115 +8,115 @@ @plugin "tailwindcss-animate"; :root { - --background: hsl(0, 0%, 100%); - --foreground: hsl(0 0% 11%); - --card: hsl(0, 0%, 100%); - --card-foreground: hsl(0 0% 11%); - --popover: hsl(0, 0%, 100%); - --popover-hover: hsl(0, 0%, 96%); - --popover-foreground: hsl(0 0% 2%); - --primary: #009dff; - --primary-foreground: hsl(0, 0%, 100%); - --secondary: hsl(204, 100%, 97%); - --secondary-border: hsl(204, 100%, 94%); - --secondary-foreground: hsl(200, 98%, 39%); - --muted: hsl(0 0% 85.1%); - --muted-foreground: hsl(0 0% 50%); - --accent: hsl(0, 0%, 96%); - --accent-foreground: hsl(0 0% 2%); - --destructive: hsl(0, 83%, 50%); - --destructive-foreground: hsl(0, 0%, 100%); - --constructive: hsl(141, 71%, 48%); - --constructive-foreground: hsl(0, 0%, 100%); - --border: hsl(0 0% 91%); - --input: hsl(0, 0%, 100%); - --ring: hsl(0, 0%, 55%); - --chart-1: hsl(220 70% 50%); - --chart-2: hsl(160 60% 45%); - --chart-3: hsl(30 80% 55%); - --chart-4: hsl(280 65% 60%); - --chart-5: hsl(340 75% 55%); - --sidebar-background: hsl(0 0% 96.1%); - --sidebar-foreground: hsl(0 0% 2%); - --sidebar-primary: hsl(0 0% 2%); - --sidebar-primary-foreground: hsl(0 0% 91%); - --sidebar-accent: hsl(0 0% 85.1%); - --sidebar-accent-foreground: hsl(0 0% 2%); - --sidebar-border: hsl(0 0% 85.1%); - --sidebar-ring: hsl(0 0% 16.9%); - --sidebar: hsl(0 0% 98%); + --background: hsl(0, 0%, 100%); + --foreground: hsl(0 0% 11%); + --card: hsl(0, 0%, 100%); + --card-foreground: hsl(0 0% 11%); + --popover: hsl(0, 0%, 100%); + --popover-hover: hsl(0, 0%, 96%); + --popover-foreground: hsl(0 0% 2%); + --primary: #009dff; + --primary-foreground: hsl(0, 0%, 100%); + --secondary: hsl(204, 100%, 97%); + --secondary-border: hsl(204, 100%, 94%); + --secondary-foreground: hsl(200, 98%, 39%); + --muted: hsl(0 0% 85.1%); + --muted-foreground: hsl(0 0% 50%); + --accent: hsl(0, 0%, 96%); + --accent-foreground: hsl(0 0% 2%); + --destructive: hsl(0, 83%, 50%); + --destructive-foreground: hsl(0, 0%, 100%); + --constructive: hsl(141, 71%, 48%); + --constructive-foreground: hsl(0, 0%, 100%); + --border: hsl(0 0% 91%); + --input: hsl(0, 0%, 100%); + --ring: hsl(0, 0%, 55%); + --chart-1: hsl(220 70% 50%); + --chart-2: hsl(160 60% 45%); + --chart-3: hsl(30 80% 55%); + --chart-4: hsl(280 65% 60%); + --chart-5: hsl(340 75% 55%); + --sidebar-background: hsl(0 0% 96.1%); + --sidebar-foreground: hsl(0 0% 2%); + --sidebar-primary: hsl(0 0% 2%); + --sidebar-primary-foreground: hsl(0 0% 91%); + --sidebar-accent: hsl(0 0% 85.1%); + --sidebar-accent-foreground: hsl(0 0% 2%); + --sidebar-border: hsl(0 0% 85.1%); + --sidebar-ring: hsl(0 0% 16.9%); + --sidebar: hsl(0 0% 98%); } .panel { - --background: hsl(216 13% 98%); - --foreground: hsl(0 0% 13%); - --card: hsl(0, 0%, 98%); - --card-foreground: hsl(0 0% 13%); - --primary-foreground: hsl(0, 0%, 98%); - --secondary: hsl(204, 100%, 95%); - --secondary-border: hsl(204, 100%, 92%); - --secondary-foreground: hsl(200, 98%, 37%); - --muted: hsl(0 0% 83.1%); - --muted-foreground: hsl(0 0% 48%); - --accent: hsl(0, 0%, 93%); - --accent-foreground: hsl(0 0% 5%); - --destructive: hsl(0, 83%, 50%); - --destructive-foreground: hsl(0, 0%, 98%); - --constructive: hsl(141, 71%, 48%); - --constructive-foreground: hsl(0, 0%, 98%); - --border: hsl(0 0% 89%); - --input: hsl(0 0% 93%); - --ring: hsl(0, 0%, 53%); + --background: hsl(216 13% 98%); + --foreground: hsl(0 0% 13%); + --card: hsl(0, 0%, 98%); + --card-foreground: hsl(0 0% 13%); + --primary-foreground: hsl(0, 0%, 98%); + --secondary: hsl(204, 100%, 95%); + --secondary-border: hsl(204, 100%, 92%); + --secondary-foreground: hsl(200, 98%, 37%); + --muted: hsl(0 0% 83.1%); + --muted-foreground: hsl(0 0% 48%); + --accent: hsl(0, 0%, 93%); + --accent-foreground: hsl(0 0% 5%); + --destructive: hsl(0, 83%, 50%); + --destructive-foreground: hsl(0, 0%, 98%); + --constructive: hsl(141, 71%, 48%); + --constructive-foreground: hsl(0, 0%, 98%); + --border: hsl(0 0% 89%); + --input: hsl(0 0% 93%); + --ring: hsl(0, 0%, 53%); } .dark { - --background: hsl(0, 0%, 5%); - --foreground: hsl(0 0% 87%); - --card: hsl(0, 0%, 5%); - --card-foreground: hsl(0 0% 87%); - --popover: hsl(0, 0%, 5%); - --popover-hover: hsl(0, 0%, 22%); - --popover-foreground: hsl(0 0% 95%); - --secondary: hsl(204, 100%, 12%); - --secondary-border: hsl(204, 100%, 15%); - --secondary-foreground: hsl(200, 98%, 61%); - --muted: hsl(0 0% 20%); - --accent: hsl(0, 0%, 14%); - --accent-foreground: hsl(0 0% 95%); - --constructive: hsl(141, 71%, 52%); - --border: hsl(0 0% 16%); - --input: hsl(0 0% 5%); - --ring: hsl(0, 0%, 50%); - --sidebar-background: hsl(0 0% 8%); - --sidebar-foreground: hsl(0 0% 95%); - --sidebar-primary: hsl(0 0% 95%); - --sidebar-primary-foreground: hsl(0 0% 15%); - --sidebar-accent: hsl(0 0% 20%); - --sidebar-accent-foreground: hsl(0 0% 95%); - --sidebar-border: hsl(0 0% 20%); - --sidebar-ring: hsl(0 0% 83.1%); - --sidebar: hsl(0 0% 6%); + --background: hsl(0, 0%, 5%); + --foreground: hsl(0 0% 87%); + --card: hsl(0, 0%, 5%); + --card-foreground: hsl(0 0% 87%); + --popover: hsl(0, 0%, 5%); + --popover-hover: hsl(0, 0%, 22%); + --popover-foreground: hsl(0 0% 95%); + --secondary: hsl(204, 100%, 12%); + --secondary-border: hsl(204, 100%, 15%); + --secondary-foreground: hsl(200, 98%, 61%); + --muted: hsl(0 0% 20%); + --accent: hsl(0, 0%, 14%); + --accent-foreground: hsl(0 0% 95%); + --constructive: hsl(141, 71%, 52%); + --border: hsl(0 0% 16%); + --input: hsl(0 0% 5%); + --ring: hsl(0, 0%, 50%); + --sidebar-background: hsl(0 0% 8%); + --sidebar-foreground: hsl(0 0% 95%); + --sidebar-primary: hsl(0 0% 95%); + --sidebar-primary-foreground: hsl(0 0% 15%); + --sidebar-accent: hsl(0 0% 20%); + --sidebar-accent-foreground: hsl(0 0% 95%); + --sidebar-border: hsl(0 0% 20%); + --sidebar-ring: hsl(0 0% 83.1%); + --sidebar: hsl(0 0% 6%); } .dark .panel { - --background: hsl(0 0% 10%); - --foreground: hsl(0 0% 85%); - --card: hsl(0, 0%, 10%); - --card-foreground: hsl(0 0% 85%); - --secondary: hsl(204, 100%, 12%); - --secondary-border: hsl(204, 100%, 17%); - --secondary-foreground: hsl(200, 98%, 63%); - --muted: hsl(0 0% 22%); - --accent: hsl(0, 0%, 15%); - --accent-foreground: hsl(0 0% 93%); - --constructive: hsl(141, 71%, 52%); - --border: hsl(0 0% 18%); - --input: hsl(0 0% 22%); - --ring: hsl(0, 0%, 52%); + --background: hsl(0 0% 10%); + --foreground: hsl(0 0% 85%); + --card: hsl(0, 0%, 10%); + --card-foreground: hsl(0 0% 85%); + --secondary: hsl(204, 100%, 12%); + --secondary-border: hsl(204, 100%, 17%); + --secondary-foreground: hsl(200, 98%, 63%); + --muted: hsl(0 0% 22%); + --accent: hsl(0, 0%, 15%); + --accent-foreground: hsl(0 0% 93%); + --constructive: hsl(141, 71%, 52%); + --border: hsl(0 0% 18%); + --input: hsl(0 0% 22%); + --ring: hsl(0, 0%, 52%); } @layer base { - /* + /* The default border color has changed to `currentcolor` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -124,165 +124,165 @@ If we ever want to remove these styles, we need to add an explicit border color utility to any element that depends on these defaults. */ - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - /* Other default base styles */ - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - /* Prevent back/forward swipe */ - overscroll-behavior-x: contain; - } - ::selection { - @apply bg-primary/35 selection:text-primary-foreground; - } + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } + /* Other default base styles */ + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + /* Prevent back/forward swipe */ + overscroll-behavior-x: contain; + } + ::selection { + @apply bg-primary/35 selection:text-primary-foreground; + } } @theme inline { - /* Responsive breakpoints */ - --breakpoint-xs: 30rem; + /* Responsive breakpoints */ + --breakpoint-xs: 30rem; - /* Typography */ - --font-sans: var(--font-inter), sans-serif; + /* Typography */ + --font-sans: var(--font-inter), sans-serif; - /* Font sizes */ - --text-xs: 0.72rem; - --text-sm: 0.79rem; - --text-base: 0.92rem; - --text-base--line-height: calc(1.5 / 0.95); - --text-xs--line-height: calc(1 / 0.8); + /* Font sizes */ + --text-xs: 0.72rem; + --text-sm: 0.79rem; + --text-base: 0.92rem; + --text-base--line-height: calc(1.5 / 0.95); + --text-xs--line-height: calc(1 / 0.8); - /* Border radius */ - --radius-lg: 0.82rem; - --radius-md: 0.65rem; - --radius-sm: 0.35rem; + /* Border radius */ + --radius-lg: 0.82rem; + --radius-md: 0.65rem; + --radius-sm: 0.35rem; - /* Palette mapped to root design tokens */ - --color-background: var(--background); - --color-foreground: var(--foreground); + /* Palette mapped to root design tokens */ + --color-background: var(--background); + --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-hover: var(--popover-hover); - --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-popover-hover: var(--popover-hover); + --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-border: var(--secondary-border); - --color-secondary-foreground: var(--secondary-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-border: var(--secondary-border); + --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); - --color-constructive: var(--constructive); - --color-constructive-foreground: var(--constructive-foreground); + --color-constructive: var(--constructive); + --color-constructive-foreground: var(--constructive-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); - /* Chart colors */ - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); + /* Chart colors */ + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); - /* Sidebar */ - --color-sidebar: var(--sidebar-background); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); + /* Sidebar */ + --color-sidebar: var(--sidebar-background); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); - /* Animations */ - --animate-accordion-down: accordion-down 0.2s ease-out; - --animate-accordion-up: accordion-up 0.2s ease-out; + /* Animations */ + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; - @keyframes accordion-down { - from { - height: 0; - } - to { - height: var(--radix-accordion-content-height); - } - } + @keyframes accordion-down { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } + } - @keyframes accordion-up { - from { - height: var(--radix-accordion-content-height); - } - to { - height: 0; - } - } + @keyframes accordion-up { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } + } } @utility scrollbar-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar { - display: none; - } + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } } @utility scrollbar-x-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar:horizontal { - display: none; - } + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar:horizontal { + display: none; + } } @utility scrollbar-y-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar:vertical { - display: none; - } + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar:vertical { + display: none; + } } @utility scrollbar-thin { - &::-webkit-scrollbar { - width: 6px; - height: 8px; - } - &::-webkit-scrollbar-track { - background: transparent; - } - &::-webkit-scrollbar-thumb { - background: var(--border); - border-radius: 4px; - } - &::-webkit-scrollbar-thumb:hover { - background: var(--muted-foreground); - } + &::-webkit-scrollbar { + width: 6px; + height: 8px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; + } + &::-webkit-scrollbar-thumb:hover { + background: var(--muted-foreground); + } } @layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } } diff --git a/apps/web/src/components/editor/panels/assets/views/assets.tsx b/apps/web/src/components/editor/panels/assets/views/assets.tsx index d5a44c6e..808961f1 100644 --- a/apps/web/src/components/editor/panels/assets/views/assets.tsx +++ b/apps/web/src/components/editor/panels/assets/views/assets.tsx @@ -635,4 +635,3 @@ function SortMenuItem({ ); } - diff --git a/apps/web/src/components/editor/panels/preview/context-menu.tsx b/apps/web/src/components/editor/panels/preview/context-menu.tsx index 181f2f15..c2afca3f 100644 --- a/apps/web/src/components/editor/panels/preview/context-menu.tsx +++ b/apps/web/src/components/editor/panels/preview/context-menu.tsx @@ -20,10 +20,7 @@ export function PreviewContextMenu({ return ( - + Full screen + onToggleFullscreen={onToggleFullscreen} + containerRef={containerRef} + /> ); diff --git a/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx b/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx index ddabb330..4c33a1a1 100644 --- a/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx +++ b/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx @@ -42,10 +42,7 @@ export function PreviewInteractionOverlay({ onCancel={cancelTextEdit} /> ) : ( - + )} - canvasToOverlay({ canvasX, canvasY, canvasRect, containerRect, canvasSize }); + const toOverlay = ({ + canvasX, + canvasY, + }: { + canvasX: number; + canvasY: number; + }) => + canvasToOverlay({ + canvasX, + canvasY, + canvasRect, + containerRect, + canvasSize, + }); const center = toOverlay({ canvasX: bounds.cx, canvasY: bounds.cy }); const outlineWidth = bounds.width * displayScale.x; @@ -235,7 +248,6 @@ function CornerHandle({ ); } - function RotationHandle({ screen, onPointerDown, diff --git a/apps/web/src/components/editor/panels/timeline/bookmarks.tsx b/apps/web/src/components/editor/panels/timeline/bookmarks.tsx index 7c3064c0..64824f9a 100644 --- a/apps/web/src/components/editor/panels/timeline/bookmarks.tsx +++ b/apps/web/src/components/editor/panels/timeline/bookmarks.tsx @@ -395,22 +395,22 @@ function BookmarkPopoverContent({ /> - + ); } diff --git a/apps/web/src/components/ui/color-picker.tsx b/apps/web/src/components/ui/color-picker.tsx index 163c698d..d58b6144 100644 --- a/apps/web/src/components/ui/color-picker.tsx +++ b/apps/web/src/components/ui/color-picker.tsx @@ -1,7 +1,12 @@ import { forwardRef, useEffect, useRef, useState } from "react"; import { cn } from "@/utils/ui"; import { Input } from "./input"; -import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from "./popover"; +import { + Popover, + PopoverClose, + PopoverContent, + PopoverTrigger, +} from "./popover"; import { Select, SelectContent, @@ -64,8 +69,8 @@ const ColorPicker = forwardRef( } }; const hueDiff = Math.abs(h - internalHue); - const isSameHueWrapped = hueDiff < 1 || Math.abs(hueDiff - 360) < 1; - const displayHue = s === 0 || isSameHueWrapped ? internalHue : h; + const isSameHueWrapped = hueDiff < 1 || Math.abs(hueDiff - 360) < 1; + const displayHue = s === 0 || isSameHueWrapped ? internalHue : h; useEffect(() => { setInputValue(formatColorValue({ hex: value, format: colorFormat })); @@ -75,31 +80,37 @@ const ColorPicker = forwardRef( const handleMouseMove = (e: MouseEvent) => { if (!isDragging) return; - if (isDragging === "saturation" && saturationRef.current) { - const rect = saturationRef.current.getBoundingClientRect(); - const x = Math.max( - 0, - Math.min(1, (e.clientX - rect.left) / rect.width), - ); - const y = Math.max( - 0, - Math.min(1, (e.clientY - rect.top) / rect.height), - ); - const newHex = appendAlpha({ rgbHex: hsvToHex({ h: displayHue, s: x, v: 1 - y }), alpha }); - latestDragColorRef.current = newHex; - onChange?.(newHex); - } + if (isDragging === "saturation" && saturationRef.current) { + const rect = saturationRef.current.getBoundingClientRect(); + const x = Math.max( + 0, + Math.min(1, (e.clientX - rect.left) / rect.width), + ); + const y = Math.max( + 0, + Math.min(1, (e.clientY - rect.top) / rect.height), + ); + const newHex = appendAlpha({ + rgbHex: hsvToHex({ h: displayHue, s: x, v: 1 - y }), + alpha, + }); + latestDragColorRef.current = newHex; + onChange?.(newHex); + } - if (isDragging === "hue" && hueRef.current) { - const rect = hueRef.current.getBoundingClientRect(); - const x = Math.max( - 0, - Math.min(1, (e.clientX - rect.left) / rect.width), - ); - const newH = x * 360; - setInternalHue(newH); + if (isDragging === "hue" && hueRef.current) { + const rect = hueRef.current.getBoundingClientRect(); + const x = Math.max( + 0, + Math.min(1, (e.clientX - rect.left) / rect.width), + ); + const newH = x * 360; + setInternalHue(newH); if (s > 0) { - const newHex = appendAlpha({ rgbHex: hsvToHex({ h: newH, s, v }), alpha }); + const newHex = appendAlpha({ + rgbHex: hsvToHex({ h: newH, s, v }), + alpha, + }); latestDragColorRef.current = newHex; onChange?.(newHex); } @@ -143,7 +154,10 @@ const ColorPicker = forwardRef( const rect = saturationElement.getBoundingClientRect(); const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height)); - const newHex = appendAlpha({ rgbHex: hsvToHex({ h: displayHue, s: x, v: 1 - y }), alpha }); + const newHex = appendAlpha({ + rgbHex: hsvToHex({ h: displayHue, s: x, v: 1 - y }), + alpha, + }); latestDragColorRef.current = newHex; onChange?.(newHex); }; @@ -158,7 +172,10 @@ const ColorPicker = forwardRef( const newH = x * 360; setInternalHue(newH); if (s > 0) { - const newHex = appendAlpha({ rgbHex: hsvToHex({ h: newH, s, v }), alpha }); + const newHex = appendAlpha({ + rgbHex: hsvToHex({ h: newH, s, v }), + alpha, + }); latestDragColorRef.current = newHex; onChange?.(newHex); } @@ -322,10 +339,10 @@ const ColorPicker = forwardRef( )} - - + +
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx index e132ab6d..762cc675 100644 --- a/apps/web/src/components/ui/input.tsx +++ b/apps/web/src/components/ui/input.tsx @@ -12,8 +12,7 @@ const inputVariants = cva( { variants: { variant: { - default: - "selection:bg-primary selection:text-primary-foreground", + default: "selection:bg-primary selection:text-primary-foreground", destructive: "selection:bg-destructive selection:text-destructive-foreground focus-visible:border-destructive focus-visible:ring-destructive/10", }, diff --git a/apps/web/src/components/ui/tooltip.tsx b/apps/web/src/components/ui/tooltip.tsx index c38586fe..067042e6 100644 --- a/apps/web/src/components/ui/tooltip.tsx +++ b/apps/web/src/components/ui/tooltip.tsx @@ -60,7 +60,7 @@ const TooltipContent = React.forwardRef< fill="none" xmlns="http://www.w3.org/2000/svg" className="absolute top-1/2 left-[-6px] -translate-y-1/2" - aria-hidden="true" + aria-hidden="true" > { + ({ event, bookmark }: { event: React.MouseEvent; bookmark: Bookmark }) => { if (event.button !== 0) return; event.preventDefault(); diff --git a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts b/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts index d1824a6b..632d2c78 100644 --- a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts +++ b/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts @@ -315,8 +315,7 @@ export function useTimelineDragDrop({ zoomLevel, }); - const trackType: TrackType = - asset.type === "audio" ? "audio" : "video"; + const trackType: TrackType = asset.type === "audio" ? "audio" : "video"; const addMediaCmd = new AddMediaAssetCommand(projectId, asset); const assetId = addMediaCmd.getAssetId(); diff --git a/apps/web/src/hooks/timeline/use-timeline-playhead.ts b/apps/web/src/hooks/timeline/use-timeline-playhead.ts index 34b52a4b..44e12ab1 100644 --- a/apps/web/src/hooks/timeline/use-timeline-playhead.ts +++ b/apps/web/src/hooks/timeline/use-timeline-playhead.ts @@ -65,52 +65,52 @@ export function useTimelinePlayhead({ Math.min(timelineContentWidth, relativeMouseX), ); - const rawTime = Math.max( - 0, - Math.min( + const rawTime = Math.max( + 0, + Math.min( + duration, + clampedMouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel), + ), + ); + + const framesPerSecond = activeProject.settings.fps; + const frameTime = getSnappedSeekTime({ + rawTime, duration, - clampedMouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel), - ), - ); + fps: framesPerSecond, + }); - const framesPerSecond = activeProject.settings.fps; - const frameTime = getSnappedSeekTime({ - rawTime, + const bookmarks = editor.scenes.getActiveScene()?.bookmarks ?? []; + const bookmarkSnapPoints: SnapPoint[] = bookmarks.map((bookmark) => ({ + time: bookmark.time, + type: "bookmark", + })); + const shouldSnapToBookmark = + !isShiftHeldRef.current && bookmarkSnapPoints.length > 0; + const snapResult = shouldSnapToBookmark + ? snapToNearestPoint({ + targetTime: frameTime, + snapPoints: bookmarkSnapPoints, + zoomLevel, + }) + : null; + const time = snapResult?.snapPoint ? snapResult.snappedTime : frameTime; + + setScrubTime(time); + seek({ time }); + + lastMouseXRef.current = event.clientX; + }, + [ duration, - fps: framesPerSecond, - }); - - const bookmarks = editor.scenes.getActiveScene()?.bookmarks ?? []; - const bookmarkSnapPoints: SnapPoint[] = bookmarks.map((bookmark) => ({ - time: bookmark.time, - type: "bookmark", - })); - const shouldSnapToBookmark = - !isShiftHeldRef.current && bookmarkSnapPoints.length > 0; - const snapResult = shouldSnapToBookmark - ? snapToNearestPoint({ - targetTime: frameTime, - snapPoints: bookmarkSnapPoints, - zoomLevel, - }) - : null; - const time = snapResult?.snapPoint ? snapResult.snappedTime : frameTime; - - setScrubTime(time); - seek({ time }); - - lastMouseXRef.current = event.clientX; - }, - [ - duration, - zoomLevel, - seek, - rulerRef, - activeProject.settings.fps, - isShiftHeldRef, - editor.scenes, - snapToNearestPoint, - ], + zoomLevel, + seek, + rulerRef, + activeProject.settings.fps, + isShiftHeldRef, + editor.scenes, + snapToNearestPoint, + ], ); const handlePlayheadMouseDown = useCallback( diff --git a/apps/web/src/hooks/timeline/use-timeline-snapping.ts b/apps/web/src/hooks/timeline/use-timeline-snapping.ts index 89451980..5de75448 100644 --- a/apps/web/src/hooks/timeline/use-timeline-snapping.ts +++ b/apps/web/src/hooks/timeline/use-timeline-snapping.ts @@ -82,7 +82,8 @@ export function useTimelineSnapping({ for (const bookmark of bookmarks) { if ( excludeBookmarkTime != null && - Math.abs(bookmark.time - excludeBookmarkTime) < BOOKMARK_TIME_EPSILON + Math.abs(bookmark.time - excludeBookmarkTime) < + BOOKMARK_TIME_EPSILON ) { continue; } diff --git a/apps/web/src/hooks/use-paste-media.ts b/apps/web/src/hooks/use-paste-media.ts index 60d0af13..4533ab28 100644 --- a/apps/web/src/hooks/use-paste-media.ts +++ b/apps/web/src/hooks/use-paste-media.ts @@ -91,7 +91,7 @@ export function usePasteMedia() { console.error("Failed to paste media:", error); toast.error("Failed to paste media"); } - } + }; window.addEventListener("paste", handlePaste); return () => window.removeEventListener("paste", handlePaste); diff --git a/apps/web/src/hooks/use-transform-handles.ts b/apps/web/src/hooks/use-transform-handles.ts index ebc18f26..37ef6d5f 100644 --- a/apps/web/src/hooks/use-transform-handles.ts +++ b/apps/web/src/hooks/use-transform-handles.ts @@ -65,7 +65,13 @@ function getCornerDistance({ bounds, corner, }: { - bounds: { cx: number; cy: number; width: number; height: number; rotation: number }; + bounds: { + cx: number; + cy: number; + width: number; + height: number; + rotation: number; + }; corner: Corner; }): number { const halfW = bounds.width / 2; @@ -124,8 +130,7 @@ export function useTransformHandles({ : null; const hasVisualSelection = - selectedWithBounds !== null && - isVisualElement(selectedWithBounds.element); + selectedWithBounds !== null && isVisualElement(selectedWithBounds.element); const handleCornerPointerDown = useCallback( ({ event, corner }: { event: React.PointerEvent; corner: Corner }) => { @@ -135,11 +140,11 @@ export function useTransformHandles({ const { bounds, trackId, elementId, element } = selectedWithBounds; if (!isVisualElement(element)) return; - const initialDistance = getCornerDistance({ bounds, corner }); - const baseWidth = bounds.width / element.transform.scale; - const baseHeight = bounds.height / element.transform.scale; + const initialDistance = getCornerDistance({ bounds, corner }); + const baseWidth = bounds.width / element.transform.scale; + const baseHeight = bounds.height / element.transform.scale; - scaleStateRef.current = { + scaleStateRef.current = { trackId, elementId, initialTransform: element.transform, @@ -197,7 +202,11 @@ export function useTransformHandles({ canvas: canvasRef.current, }); - if (scaleStateRef.current && activeHandle && activeHandle !== "rotation") { + if ( + scaleStateRef.current && + activeHandle && + activeHandle !== "rotation" + ) { const { trackId, elementId, diff --git a/apps/web/src/lib/commands/scene/update-bookmark.ts b/apps/web/src/lib/commands/scene/update-bookmark.ts index e2c0575d..5ce91a36 100644 --- a/apps/web/src/lib/commands/scene/update-bookmark.ts +++ b/apps/web/src/lib/commands/scene/update-bookmark.ts @@ -2,10 +2,7 @@ import { Command } from "@/lib/commands/base-command"; import { EditorCore } from "@/core"; import type { Bookmark, TScene } from "@/types/timeline"; import { updateSceneInArray } from "@/lib/scenes"; -import { - getFrameTime, - updateBookmarkInArray, -} from "@/lib/timeline/bookmarks"; +import { getFrameTime, updateBookmarkInArray } from "@/lib/timeline/bookmarks"; export class UpdateBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; diff --git a/apps/web/src/lib/fonts/google-fonts.ts b/apps/web/src/lib/fonts/google-fonts.ts index 10a61076..7e55dffd 100644 --- a/apps/web/src/lib/fonts/google-fonts.ts +++ b/apps/web/src/lib/fonts/google-fonts.ts @@ -86,7 +86,5 @@ export async function loadFonts({ families: string[]; }): Promise { const googleFonts = families.filter((family) => !SYSTEM_FONTS.has(family)); - await Promise.all( - googleFonts.map((family) => loadFullFont({ family })), - ); + await Promise.all(googleFonts.map((family) => loadFullFont({ family }))); } diff --git a/apps/web/src/lib/preview/element-bounds.ts b/apps/web/src/lib/preview/element-bounds.ts index c0c37ad7..ec26c14d 100644 --- a/apps/web/src/lib/preview/element-bounds.ts +++ b/apps/web/src/lib/preview/element-bounds.ts @@ -32,7 +32,11 @@ function getVisualElementBounds({ canvasHeight: number; sourceWidth: number; sourceHeight: number; - transform: { scale: number; position: { x: number; y: number }; rotate: number }; + transform: { + scale: number; + position: { x: number; y: number }; + rotate: number; + }; }): ElementBounds { const containScale = Math.min( canvasWidth / sourceWidth, diff --git a/apps/web/src/lib/preview/preview-snap.ts b/apps/web/src/lib/preview/preview-snap.ts index 2e950647..bd53f0ca 100644 --- a/apps/web/src/lib/preview/preview-snap.ts +++ b/apps/web/src/lib/preview/preview-snap.ts @@ -282,7 +282,9 @@ export function snapRotation({ const nearestRotationSnap = Math.round(proposedRotation / ROTATION_SNAP_STEP_DEGREES) * ROTATION_SNAP_STEP_DEGREES; - const distanceToNearestSnap = Math.abs(proposedRotation - nearestRotationSnap); + const distanceToNearestSnap = Math.abs( + proposedRotation - nearestRotationSnap, + ); if (distanceToNearestSnap <= ROTATION_SNAP_THRESHOLD_DEGREES) { return { snappedRotation: nearestRotationSnap, isSnapped: true }; } diff --git a/apps/web/src/lib/timeline/bookmarks.ts b/apps/web/src/lib/timeline/bookmarks.ts index 450ac05a..efaa8f74 100644 --- a/apps/web/src/lib/timeline/bookmarks.ts +++ b/apps/web/src/lib/timeline/bookmarks.ts @@ -141,6 +141,9 @@ export function getBookmarksActiveAtTime({ bookmark.duration != null && bookmark.duration > 0 ? start + bookmark.duration : start; - return time >= start - BOOKMARK_TIME_EPSILON && time <= end + BOOKMARK_TIME_EPSILON; + return ( + time >= start - BOOKMARK_TIME_EPSILON && + time <= end + BOOKMARK_TIME_EPSILON + ); }); } diff --git a/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts b/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts index fdb7c25e..6bbd5634 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts @@ -5,20 +5,26 @@ import { v5Project } from "./fixtures"; describe("V5 to V6 Migration", () => { test("converts number bookmarks to Bookmark objects", async () => { const result = transformProjectV5ToV6({ - project: v5Project as Parameters[0]["project"], + project: v5Project as Parameters< + typeof transformProjectV5ToV6 + >[0]["project"], }); expect(result.skipped).toBe(false); expect(result.project.version).toBe(6); - const mainScene = (result.project.scenes as Array<{ bookmarks: unknown[] }>)[0]; + const mainScene = ( + result.project.scenes as Array<{ bookmarks: unknown[] }> + )[0]; expect(mainScene.bookmarks).toEqual([ { time: 2.0 }, { time: 5.5 }, { time: 12.0 }, ]); - const introScene = (result.project.scenes as Array<{ bookmarks: unknown[] }>)[1]; + const introScene = ( + result.project.scenes as Array<{ bookmarks: unknown[] }> + )[1]; expect(introScene.bookmarks).toEqual([]); }); @@ -29,7 +35,7 @@ describe("V5 to V6 Migration", () => { version: 6, scenes: [ { - ...((v5Project as { scenes: unknown[] }).scenes[0]), + ...(v5Project as { scenes: unknown[] }).scenes[0], bookmarks: [{ time: 2 }, { time: 5 }], }, ], @@ -58,7 +64,8 @@ describe("V5 to V6 Migration", () => { version: 5, scenes: [ { - ...(v5Project as { scenes: Array> }).scenes[0], + ...(v5Project as { scenes: Array> }) + .scenes[0], bookmarks: [ { time: 1, note: "Intro", color: "#ef4444" }, { time: 5.5, duration: 2 }, @@ -68,14 +75,15 @@ describe("V5 to V6 Migration", () => { }; const result = transformProjectV5ToV6({ - project: - projectWithRichBookmarks as Parameters< - typeof transformProjectV5ToV6 - >[0]["project"], + project: projectWithRichBookmarks as Parameters< + typeof transformProjectV5ToV6 + >[0]["project"], }); expect(result.skipped).toBe(false); - const mainScene = (result.project.scenes as Array<{ bookmarks: unknown[] }>)[0]; + const mainScene = ( + result.project.scenes as Array<{ bookmarks: unknown[] }> + )[0]; expect(mainScene.bookmarks).toEqual([ { time: 1, note: "Intro", color: "#ef4444" }, { time: 5.5, duration: 2 }, diff --git a/apps/web/src/services/storage/service.ts b/apps/web/src/services/storage/service.ts index e4fe39e1..aac152da 100644 --- a/apps/web/src/services/storage/service.ts +++ b/apps/web/src/services/storage/service.ts @@ -22,7 +22,11 @@ function normalizeBookmarks({ raw }: { raw: unknown }): Bookmark[] { .map((item): Bookmark | null => { if (typeof item === "number") return { time: item }; const obj = item as Record; - if (typeof obj !== "object" || obj === null || typeof obj.time !== "number") { + if ( + typeof obj !== "object" || + obj === null || + typeof obj.time !== "number" + ) { return null; } return { diff --git a/apps/web/src/types/eyedropper.d.ts b/apps/web/src/types/eyedropper.d.ts index 6f9a3f8a..2d0e17e8 100644 --- a/apps/web/src/types/eyedropper.d.ts +++ b/apps/web/src/types/eyedropper.d.ts @@ -9,5 +9,5 @@ interface EyeDropper { declare const EyeDropper: | { new (): EyeDropper; - } + } | undefined; diff --git a/apps/web/src/utils/color.ts b/apps/web/src/utils/color.ts index a17c04a3..7b01a92e 100644 --- a/apps/web/src/utils/color.ts +++ b/apps/web/src/utils/color.ts @@ -1,10 +1,4 @@ -import { - converter, - formatHex, - formatHex8, - parse, - type Rgb, -} from "culori"; +import { converter, formatHex, formatHex8, parse, type Rgb } from "culori"; export type ColorFormat = "hex" | "rgb" | "hsl" | "hsv"; @@ -22,7 +16,11 @@ export function hsvToHex({ h, s, v, -}: { h: number; s: number; v: number }): string { +}: { + h: number; + s: number; + v: number; +}): string { const hex = formatHex({ mode: "hsv", h, s, v }); return hex.slice(1); } @@ -44,7 +42,10 @@ export function parseHexAlpha({ hex }: { hex: string }): { export function appendAlpha({ rgbHex, alpha, -}: { rgbHex: string; alpha: number }): string { +}: { + rgbHex: string; + alpha: number; +}): string { if (alpha >= 1) return rgbHex; const hex8 = formatHex8({ mode: "rgb", r: 0, g: 0, b: 0, alpha }); const alphaHex = hex8.slice(7, 9); @@ -78,16 +79,16 @@ function colorToHexWithAlpha({ color }: { color: Rgb }): string { export function extractColorFromText({ text, -}: { text: string }): string | null { +}: { + text: string; +}): string | null { const cleaned = stripCssNoise({ text }); const color = toRgb(cleaned); if (color) return colorToHexWithAlpha({ color }); // bare hex without # (culori needs the prefix) - const bareHexMatch = cleaned.match( - /^([0-9a-fA-F]{3,8})$/, - ); + const bareHexMatch = cleaned.match(/^([0-9a-fA-F]{3,8})$/); if (bareHexMatch) { const withHash = toRgb(`#${bareHexMatch[1]}`); if (withHash) return colorToHexWithAlpha({ color: withHash }); @@ -147,19 +148,34 @@ export function parseColorInput({ case "rgb": { const parts = input.split(",").map((part) => parseInt(part.trim(), 10)); if (parts.length < 3 || parts.some(Number.isNaN)) return null; - const color = { mode: "rgb" as const, r: parts[0] / 255, g: parts[1] / 255, b: parts[2] / 255 }; + const color = { + mode: "rgb" as const, + r: parts[0] / 255, + g: parts[1] / 255, + b: parts[2] / 255, + }; return formatHex(color).slice(1); } case "hsl": { const parts = input.split(",").map((part) => parseFloat(part.trim())); if (parts.length < 3 || parts.some(Number.isNaN)) return null; - const color = { mode: "hsl" as const, h: parts[0], s: parts[1] / 100, l: parts[2] / 100 }; + const color = { + mode: "hsl" as const, + h: parts[0], + s: parts[1] / 100, + l: parts[2] / 100, + }; return formatHex(color).slice(1); } case "hsv": { const parts = input.split(",").map((part) => parseFloat(part.trim())); if (parts.length < 3 || parts.some(Number.isNaN)) return null; - const color = { mode: "hsv" as const, h: parts[0], s: parts[1] / 100, v: parts[2] / 100 }; + const color = { + mode: "hsv" as const, + h: parts[0], + s: parts[1] / 100, + v: parts[2] / 100, + }; return formatHex(color).slice(1); } }