From 29fdd8568dbb9c67ee19492781f3f03eb2c3bd24 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sun, 31 Aug 2025 14:10:59 +0200 Subject: refactor: improve types, fix background blur, some other stuff --- apps/web/src/components/editor/media-panel/views/settings.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'apps/web/src/components/editor/media-panel/views/settings.tsx') diff --git a/apps/web/src/components/editor/media-panel/views/settings.tsx b/apps/web/src/components/editor/media-panel/views/settings.tsx index df1ec54f..6cdd1b65 100644 --- a/apps/web/src/components/editor/media-panel/views/settings.tsx +++ b/apps/web/src/components/editor/media-panel/views/settings.tsx @@ -16,6 +16,7 @@ import { } from "../../properties-panel/property-item"; import { FPS_PRESETS } from "@/constants/timeline-constants"; import { useProjectStore } from "@/stores/project-store"; +import type { BlurIntensity } from "@/types/project"; import { useEditorStore } from "@/stores/editor-store"; import { useAspectRatio } from "@/hooks/use-aspect-ratio"; import Image from "next/image"; @@ -215,7 +216,7 @@ BackgroundPreviews.displayName = "BackgroundPreviews"; function BackgroundView() { const { activeProject, updateBackgroundType } = useProjectStore(); - const blurLevels = useMemo( + const blurLevels = useMemo>( () => [ { label: "Light", value: 4 }, { label: "Medium", value: 8 }, @@ -225,7 +226,7 @@ function BackgroundView() { ); const handleBlurSelect = useCallback( - async (blurIntensity: number) => { + async (blurIntensity: BlurIntensity) => { await updateBackgroundType("blur", { blurIntensity }); }, [updateBackgroundType] -- cgit v1.3.1 From 73a156c002b9f97b133d89b1e07f11eec7641576 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sun, 31 Aug 2025 14:35:24 +0200 Subject: gradient backgrounds now work --- .../editor/media-panel/views/settings.tsx | 4 +- apps/web/src/lib/canvas-gradients.ts | 169 +++++++++++++++++++++ apps/web/src/lib/timeline-renderer.ts | 16 +- 3 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/lib/canvas-gradients.ts (limited to 'apps/web/src/components/editor/media-panel/views/settings.tsx') diff --git a/apps/web/src/components/editor/media-panel/views/settings.tsx b/apps/web/src/components/editor/media-panel/views/settings.tsx index 6cdd1b65..d2d21f36 100644 --- a/apps/web/src/components/editor/media-panel/views/settings.tsx +++ b/apps/web/src/components/editor/media-panel/views/settings.tsx @@ -178,9 +178,9 @@ const BackgroundPreviews = memo( }) => { return useMemo( () => - backgrounds.map((bg) => ( + backgrounds.map((bg, index) => (
{ + if (!p) return (full || 0) / 2; + if (p.endsWith("%")) return (parseFloat(p) / 100) * (full || 0); + if (p === "left") return 0; + if (p === "right") return full || 0; + if (p === "top") return 0; + if (p === "bottom") return full || 0; + if (p === "center") return (full || 0) / 2; + return (full || 0) / 2; + }; + if (px && py) { + cx = parsePos(px, width); + cy = parsePos(py, height); + } else if (px) { + cx = parsePos(px, width); + cy = parsePos(px, height); + } + } else { + parts.unshift(first); + } + const r = Math.hypot(width, height); + const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r); + const colorStops = parts; + const n = Math.max(1, colorStops.length - 1); + for (let i = 0; i < colorStops.length; i += 1) { + const color = extractColorFromStop(colorStops[i] as string); + grad.addColorStop(Math.min(1, Math.max(0, i / n)), color); + } + ctx.fillStyle = grad; + ctx.fillRect(0, 0, width, height); +} + +export function drawCssBackground( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + css: string +): void { + const layers = splitBackgroundLayers(css).filter(Boolean); + for (let i = layers.length - 1; i >= 0; i -= 1) { + const layer = layers[i] as string; + if (layer.startsWith("linear-gradient(")) { + drawLinearGradient(ctx, width, height, layer); + } else if (layer.startsWith("radial-gradient(")) { + drawRadialGradient(ctx, width, height, layer); + } else if ( + layer.startsWith("#") || + layer.startsWith("rgb") || + layer.startsWith("hsl") + ) { + ctx.fillStyle = layer; + ctx.fillRect(0, 0, width, height); + } + } +} diff --git a/apps/web/src/lib/timeline-renderer.ts b/apps/web/src/lib/timeline-renderer.ts index ddc361b5..4d00129c 100644 --- a/apps/web/src/lib/timeline-renderer.ts +++ b/apps/web/src/lib/timeline-renderer.ts @@ -2,6 +2,7 @@ import type { TimelineTrack } from "@/types/timeline"; import type { MediaFile } from "@/types/media"; import type { BlurIntensity } from "@/types/project"; import { videoCache } from "./video-cache"; +import { drawCssBackground } from "./canvas-gradients"; export interface RenderContext { ctx: CanvasRenderingContext2D; @@ -48,11 +49,24 @@ export async function renderTimelineFrame({ }: RenderContext): Promise { // Background ctx.clearRect(0, 0, canvasWidth, canvasHeight); - if (backgroundColor && backgroundColor !== "transparent") { + if ( + backgroundColor && + backgroundColor !== "transparent" && + !backgroundColor.includes("gradient") + ) { ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvasWidth, canvasHeight); } + // If backgroundColor is a CSS gradient string, draw it using JS (no foreignObject) to avoid CORS tainting + if (backgroundColor && backgroundColor.includes("gradient")) { + try { + drawCssBackground(ctx, canvasWidth, canvasHeight, backgroundColor); + } catch { + // best-effort; ignore failures + } + } + const scaleX = projectCanvasSize ? canvasWidth / projectCanvasSize.width : 1; const scaleY = projectCanvasSize ? canvasHeight / projectCanvasSize.height -- cgit v1.3.1 From 8eabbeae7c4c24bf8369deb336eaf38f4967f285 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sun, 31 Aug 2025 21:51:31 +0200 Subject: more changes --- .../src/components/editor/media-panel/tabbar.tsx | 52 +++++++++++++++++++++- .../editor/media-panel/views/settings.tsx | 38 +++++++++++++--- apps/web/src/components/editor/panel-base-view.tsx | 2 +- 3 files changed, 84 insertions(+), 8 deletions(-) (limited to 'apps/web/src/components/editor/media-panel/views/settings.tsx') diff --git a/apps/web/src/components/editor/media-panel/tabbar.tsx b/apps/web/src/components/editor/media-panel/tabbar.tsx index 56437508..e2bfdff7 100644 --- a/apps/web/src/components/editor/media-panel/tabbar.tsx +++ b/apps/web/src/components/editor/media-panel/tabbar.tsx @@ -7,13 +7,45 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; +import { useEffect, useRef, useState } from "react"; export function TabBar() { const { activeTab, setActiveTab } = useMediaPanelStore(); + const scrollRef = useRef(null); + const [showTopFade, setShowTopFade] = useState(false); + const [showBottomFade, setShowBottomFade] = useState(false); + + const checkScrollPosition = () => { + const element = scrollRef.current; + if (!element) return; + + const { scrollTop, scrollHeight, clientHeight } = element; + setShowTopFade(scrollTop > 0); + setShowBottomFade(scrollTop < scrollHeight - clientHeight - 1); + }; + + useEffect(() => { + const element = scrollRef.current; + if (!element) return; + + checkScrollPosition(); + element.addEventListener("scroll", checkScrollPosition); + + const resizeObserver = new ResizeObserver(checkScrollPosition); + resizeObserver.observe(element); + + return () => { + element.removeEventListener("scroll", checkScrollPosition); + resizeObserver.disconnect(); + }; + }, []); return ( -
-
+
+
{(Object.keys(tabs) as Tab[]).map((tabKey) => { const tab = tabs[tabKey]; return ( @@ -46,6 +78,22 @@ export function TabBar() { ); })}
+ + +
); } + +function FadeOverlay({ direction, show }: { direction: "top" | "bottom", show: boolean }) { + return ( +
+ ); +} \ No newline at end of file diff --git a/apps/web/src/components/editor/media-panel/views/settings.tsx b/apps/web/src/components/editor/media-panel/views/settings.tsx index d2d21f36..be54a9c4 100644 --- a/apps/web/src/components/editor/media-panel/views/settings.tsx +++ b/apps/web/src/components/editor/media-panel/views/settings.tsx @@ -23,9 +23,11 @@ import Image from "next/image"; import { cn } from "@/lib/utils"; import { colors } from "@/data/colors/solid"; import { patternCraftGradients } from "@/data/colors/pattern-craft"; -import { PipetteIcon } from "lucide-react"; +import { PipetteIcon, PlusIcon } from "lucide-react"; import { useMemo, memo, useCallback } from "react"; import { syntaxUIGradients } from "@/data/colors/syntax-ui"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; export function SettingsView() { return ; @@ -39,14 +41,40 @@ function ProjectSettingsTabs() { { value: "project-info", label: "Project info", - content: , + content: ( +
+ +
+ ), }, { value: "background", label: "Background", - content: , + content: ( +
+
+ +
+
+ + +
+ + {/* Another UI, looks so beautiful i don't wanna remove it */} + {/*
+ +
*/} +
+ ), }, ]} + className="flex flex-col justify-between h-full p-0" /> ); } @@ -258,7 +286,7 @@ function BackgroundView() { ); return ( -
+
{blurPreviews}
@@ -278,7 +306,7 @@ function BackgroundView() {
- +
- {tab.content} + {tab.content} ))} -- cgit v1.3.1