more zoom options, info popup

This commit is contained in:
Siddharth
2025-11-25 21:43:30 -07:00
parent ddf30ed60e
commit 6baeebec96
4 changed files with 68 additions and 29 deletions
@@ -0,0 +1,34 @@
import { HelpCircle } from "lucide-react";
export function KeyboardShortcutsHelp() {
return (
<div className="relative group">
<HelpCircle className="w-4 h-4 text-slate-500 hover:text-[#34B27B] transition-colors cursor-help" />
<div className="absolute right-0 top-full mt-2 w-64 bg-[#09090b] border border-white/10 rounded-lg p-3 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 shadow-xl z-50">
<div className="text-xs font-semibold text-slate-200 mb-2">Keyboard Shortcuts</div>
<div className="space-y-1.5 text-[10px]">
<div className="flex items-center justify-between">
<span className="text-slate-400">Add Zoom</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">Z</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Add Keyframe</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">F</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Delete Selected</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono"> + D</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Pan Timeline</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono"> + + Scroll</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Zoom Timeline</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono"> + Scroll</kbd>
</div>
</div>
</div>
</div>
);
}
+11 -6
View File
@@ -13,6 +13,7 @@ import { GiHearts } from "react-icons/gi";
import { toast } from "sonner";
import type { ZoomDepth, CropRegion } from "./types";
import { CropControl } from "./CropControl";
import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp";
const WALLPAPER_COUNT = 23;
const WALLPAPER_RELATIVE = Array.from({ length: WALLPAPER_COUNT }, (_, i) => `wallpapers/wallpaper${i + 1}.jpg`);
@@ -68,6 +69,7 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [
{ depth: 3, label: "1.8×" },
{ depth: 4, label: "2.2×" },
{ depth: 5, label: "3.5×" },
{ depth: 6, label: "5×" },
];
export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth, onZoomDepthChange, selectedZoomId, onZoomDelete, shadowIntensity = 0, onShadowChange, showBlur, onBlurChange, cropRegion, onCropChange, videoElement, onExport }: SettingsPanelProps) {
@@ -151,13 +153,16 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
<div className="mb-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-slate-200">Zoom Level</span>
{zoomEnabled && selectedZoomDepth && (
<span className="text-[10px] uppercase tracking-wider font-medium text-[#34B27B] bg-[#34B27B]/10 px-2 py-1 rounded-full">
{ZOOM_DEPTH_OPTIONS.find(o => o.depth === selectedZoomDepth)?.label} Active
</span>
)}
<div className="flex items-center gap-3">
{zoomEnabled && selectedZoomDepth && (
<span className="text-[10px] uppercase tracking-wider font-medium text-[#34B27B] bg-[#34B27B]/10 px-2 py-1 rounded-full">
{ZOOM_DEPTH_OPTIONS.find(o => o.depth === selectedZoomDepth)?.label} Active
</span>
)}
<KeyboardShortcutsHelp />
</div>
</div>
<div className="grid grid-cols-5 gap-2">
<div className="grid grid-cols-6 gap-2">
{ZOOM_DEPTH_OPTIONS.map((option) => {
const isActive = selectedZoomDepth === option.depth;
return (
@@ -401,24 +401,6 @@ export default function TimelineEditor({
onSelectZoom(null);
}, [selectedZoomId, onZoomDelete, onSelectZoom]);
// Listen for F key to add keyframe, Ctrl+D to remove selected keyframe or zoom item
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'f' || e.key === 'F') {
addKeyframe();
}
if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey)) {
if (selectedKeyframeId) {
deleteSelectedKeyframe();
} else if (selectedZoomId) {
deleteSelectedZoom();
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [addKeyframe, deleteSelectedKeyframe, deleteSelectedZoom, selectedKeyframeId, selectedZoomId]);
useEffect(() => {
setRange(createInitialRange(totalMs));
}, [totalMs]);
@@ -484,6 +466,27 @@ export default function TimelineEditor({
onZoomAdded({ start: startPos, end: startPos + actualDuration });
}, [videoDuration, totalMs, currentTimeMs, zoomRegions, onZoomAdded]);
// Listen for F key to add keyframe, Z key to add zoom, Ctrl+D to remove selected keyframe or zoom item
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'f' || e.key === 'F') {
addKeyframe();
}
if (e.key === 'z' || e.key === 'Z') {
handleAddZoom();
}
if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey)) {
if (selectedKeyframeId) {
deleteSelectedKeyframe();
} else if (selectedZoomId) {
deleteSelectedZoom();
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [addKeyframe, handleAddZoom, deleteSelectedKeyframe, deleteSelectedZoom, selectedKeyframeId, selectedZoomId]);
const clampedRange = useMemo<Range>(() => {
if (totalMs === 0) {
return range;
@@ -543,10 +546,6 @@ export default function TimelineEditor({
<kbd className="px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-sans"> + Scroll</kbd>
<span>Zoom</span>
</span>
<span className="flex items-center gap-1.5">
<kbd className="px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-sans">F</kbd>
<span>Add Keyframe</span>
</span>
</div>
</div>
<div className="flex-1 overflow-hidden bg-[#09090b] relative"
+2 -1
View File
@@ -1,4 +1,4 @@
export type ZoomDepth = 1 | 2 | 3 | 4 | 5;
export type ZoomDepth = 1 | 2 | 3 | 4 | 5 | 6;
export interface ZoomFocus {
cx: number; // normalized horizontal center (0-1)
@@ -33,6 +33,7 @@ export const ZOOM_DEPTH_SCALES: Record<ZoomDepth, number> = {
3: 1.8,
4: 2.2,
5: 3.5,
6: 5.0,
};
export const DEFAULT_ZOOM_DEPTH: ZoomDepth = 3;