From 3640b6339c2961a6c4ee8778846ac75414f8dff4 Mon Sep 17 00:00:00 2001 From: tokenflow1m Date: Sun, 15 Mar 2026 17:10:01 +0900 Subject: [PATCH] Add per-slider reset buttons via SliderControl component --- src/components/video-editor/SettingsPanel.tsx | 139 ++++++++---------- src/components/video-editor/SliderControl.tsx | 109 ++++++++++++++ 2 files changed, 174 insertions(+), 74 deletions(-) create mode 100644 src/components/video-editor/SliderControl.tsx diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 39214bb6..d1946fdf 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -2,7 +2,7 @@ import { cn } from "@/lib/utils"; import { useEffect, useRef } from "react"; import { getAssetPath, getRenderableAssetUrl } from "@/lib/assetPath"; import { BUILT_IN_WALLPAPERS, WALLPAPER_PATHS, WALLPAPER_RELATIVE_PATHS } from "@/lib/wallpapers"; -import { Slider } from "@/components/ui/slider"; +import { SliderControl } from "./SliderControl"; import { Switch } from "@/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; @@ -12,7 +12,7 @@ import { Trash2, Download, Crop, X, Bug, Upload, Star, Film, Image, Sparkles, Pa import { toast } from "sonner"; import { useI18n, useScopedT } from "../../contexts/I18nContext"; import type { ZoomDepth, CropRegion, AnnotationRegion, AnnotationType, PlaybackSpeed } from "./types"; -import { SPEED_OPTIONS } from "./types"; +import { SPEED_OPTIONS, DEFAULT_CURSOR_SIZE, DEFAULT_CURSOR_SMOOTHING, DEFAULT_CURSOR_MOTION_BLUR, DEFAULT_CURSOR_CLICK_BOUNCE, DEFAULT_ZOOM_MOTION_BLUR } from "./types"; import { CropControl } from "./CropControl"; import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; @@ -457,34 +457,32 @@ export function SettingsPanel({ />
-
-
{tSettings('effects.backgroundBlur')}
- {backgroundBlur.toFixed(1)}px -
- onBackgroundBlurChange?.(values[0])} + onBackgroundBlurChange?.(v)} + formatValue={(v) => `${v.toFixed(1)}px`} + parseInput={(t) => parseFloat(t.replace(/px$/, ''))} />
-
-
{tSettings('effects.zoomMotionBlur')}
- {zoomMotionBlur.toFixed(2)}× -
- onZoomMotionBlurChange?.(values[0])} + onZoomMotionBlurChange?.(v)} + formatValue={(v) => `${v.toFixed(2)}×`} + parseInput={(t) => parseFloat(t.replace(/×$/, ''))} />
@@ -500,107 +498,100 @@ export function SettingsPanel({
-
-
{tSettings('effects.cursorSize')}
- {cursorSize.toFixed(2)}× -
- onCursorSizeChange?.(values[0])} + onCursorSizeChange?.(v)} + formatValue={(v) => `${v.toFixed(2)}×`} + parseInput={(t) => parseFloat(t.replace(/×$/, ''))} />
-
-
{tSettings('effects.cursorSmoothing')}
- {cursorSmoothing <= 0 ? tSettings('effects.off') : cursorSmoothing.toFixed(2)} -
- onCursorSmoothingChange?.(values[0])} + onCursorSmoothingChange?.(v)} + formatValue={(v) => v <= 0 ? 'Off' : v.toFixed(2)} + parseInput={(t) => parseFloat(t)} />
-
-
{tSettings('effects.cursorMotionBlur')}
- {cursorMotionBlur.toFixed(2)}× -
- onCursorMotionBlurChange?.(values[0])} + onCursorMotionBlurChange?.(v)} + formatValue={(v) => `${v.toFixed(2)}×`} + parseInput={(t) => parseFloat(t.replace(/×$/, ''))} />
-
-
{tSettings('effects.cursorClickBounce')}
- {cursorClickBounce.toFixed(2)}× -
- onCursorClickBounceChange?.(values[0])} + onCursorClickBounceChange?.(v)} + formatValue={(v) => `${v.toFixed(2)}×`} + parseInput={(t) => parseFloat(t.replace(/×$/, ''))} />
-
-
{tSettings('effects.shadow')}
- {Math.round(shadowIntensity * 100)}% -
- onShadowChange?.(values[0])} + onShadowChange?.(v)} + formatValue={(v) => `${Math.round(v * 100)}%`} + parseInput={(t) => parseFloat(t.replace(/%$/, '')) / 100} />
-
-
{tSettings('effects.roundness')}
- {borderRadius}px -
- onBorderRadiusChange?.(values[0])} + onBorderRadiusChange?.(v)} + formatValue={(v) => `${v}px`} + parseInput={(t) => parseFloat(t.replace(/px$/, ''))} />
-
-
{tSettings('effects.padding')}
- {padding}% -
- onPaddingChange?.(values[0])} + onPaddingChange?.(v)} + formatValue={(v) => `${v}%`} + parseInput={(t) => parseFloat(t.replace(/%$/, ''))} />
diff --git a/src/components/video-editor/SliderControl.tsx b/src/components/video-editor/SliderControl.tsx new file mode 100644 index 00000000..7025bc65 --- /dev/null +++ b/src/components/video-editor/SliderControl.tsx @@ -0,0 +1,109 @@ +import { useState, useRef, useEffect } from "react"; +import { Slider } from "@/components/ui/slider"; +import { RotateCcw } from "lucide-react"; + +interface SliderControlProps { + label: string; + value: number; + defaultValue: number; + min: number; + max: number; + step: number; + onChange: (value: number) => void; + formatValue: (value: number) => string; + parseInput: (text: string) => number | null; + accentColor?: "purple" | "blue"; +} + +export function SliderControl({ + label, + value, + defaultValue, + min, + max, + step, + onChange, + formatValue, + parseInput, + accentColor = "blue", +}: SliderControlProps) { + const [editing, setEditing] = useState(false); + const [editText, setEditText] = useState(""); + const inputRef = useRef(null); + const isModified = value !== defaultValue; + + useEffect(() => { + if (editing && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [editing]); + + const commitEdit = () => { + const parsed = parseInput(editText); + if (parsed != null && !isNaN(parsed)) { + onChange(Math.min(max, Math.max(min, parsed))); + } + setEditing(false); + }; + + const cancelEdit = () => { + setEditing(false); + }; + + return ( + <> +
+
+
{label}
+ {isModified && ( + + )} +
+ {editing ? ( + setEditText(e.target.value)} + onBlur={commitEdit} + onKeyDown={(e) => { + if (e.key === "Enter") commitEdit(); + if (e.key === "Escape") cancelEdit(); + }} + className="w-14 text-[10px] text-right font-mono bg-white/10 border border-white/20 rounded px-1 py-0 text-slate-200 outline-none focus:border-white/40" + /> + ) : ( + { + setEditText(formatValue(value)); + setEditing(true); + }} + > + {formatValue(value)} + + )} +
+ onChange(values[0])} + min={min} + max={max} + step={step} + className={ + accentColor === "purple" + ? "w-full [&_[role=slider]]:bg-[#8b5cf6] [&_[role=slider]]:border-[#8b5cf6] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" + : "w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" + } + /> + + ); +}