cleanup+ readme updates

This commit is contained in:
Siddharth
2025-11-18 00:58:09 -07:00
parent fd8417b221
commit d9a9f48ab9
18 changed files with 40 additions and 147 deletions
-2
View File
@@ -104,7 +104,6 @@ export function SourceSelector() {
</div>
</Card>
))}
{/* Removed scroll hint gradient for clean look */}
</div>
</TabsContent>
<TabsContent value="windows" className="h-full">
@@ -144,7 +143,6 @@ export function SourceSelector() {
</div>
</Card>
))}
{/* Removed scroll hint gradient for clean look */}
</div>
</TabsContent>
</div>
@@ -23,7 +23,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
const [initialCrop, setInitialCrop] = useState<CropRegion>(cropRegion);
// Draw video preview at high quality
useEffect(() => {
if (!videoElement || !canvasRef.current) return;
@@ -31,7 +30,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
const ctx = canvas.getContext('2d', { alpha: false });
if (!ctx) return;
// Set canvas to actual video dimensions for high quality
canvas.width = videoElement.videoWidth || 1920;
canvas.height = videoElement.videoHeight || 1080;
@@ -62,7 +60,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
});
setInitialCrop(cropRegion);
// Capture pointer for smooth dragging
e.currentTarget.setPointerCapture(e.pointerId);
};
@@ -79,11 +76,8 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
switch (isDragging) {
case 'top': {
// Calculate new y position
const newY = Math.max(0, initialCrop.y + deltaY);
// Calculate the bottom edge (which should stay fixed)
const bottom = initialCrop.y + initialCrop.height;
// Ensure minimum height of 0.1
newCrop.y = Math.min(newY, bottom - 0.1);
newCrop.height = bottom - newCrop.y;
break;
@@ -92,11 +86,8 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
newCrop.height = Math.max(0.1, Math.min(initialCrop.height + deltaY, 1 - initialCrop.y));
break;
case 'left': {
// Calculate new x position
const newX = Math.max(0, initialCrop.x + deltaX);
// Calculate the right edge (which should stay fixed)
const right = initialCrop.x + initialCrop.width;
// Ensure minimum width of 0.1
newCrop.x = Math.min(newX, right - 0.1);
newCrop.width = right - newCrop.x;
break;
@@ -114,7 +105,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
try {
e.currentTarget.releasePointerCapture(e.pointerId);
} catch {
// ignore
}
}
setIsDragging(null);
@@ -140,7 +130,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
style={{ imageRendering: 'auto' }}
/>
{/* Dark overlay outside crop */}
<div className="absolute inset-0 pointer-events-none" style={{ transition: 'none' }}>
<svg width="100%" height="100%" className="absolute inset-0" style={{ transition: 'none' }}>
<defs>
@@ -167,8 +156,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
</svg>
</div>
{/* Crop region - 4 straight lines */}
{/* Top line */}
<div
className={cn(
"absolute h-[3px] cursor-ns-resize z-20 pointer-events-auto bg-green-500"
@@ -184,7 +171,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
onPointerDown={(e) => handlePointerDown(e, 'top')}
/>
{/* Bottom line */}
<div
className={cn(
"absolute h-[3px] cursor-ns-resize z-20 pointer-events-auto bg-green-500"
@@ -200,7 +186,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
onPointerDown={(e) => handlePointerDown(e, 'bottom')}
/>
{/* Left line */}
<div
className={cn(
"absolute w-[3px] cursor-ew-resize z-20 pointer-events-auto bg-green-500"
@@ -216,7 +201,6 @@ export function CropControl({ videoElement, cropRegion, onCropChange }: CropCont
onPointerDown={(e) => handlePointerDown(e, 'left')}
/>
{/* Right line */}
<div
className={cn(
"absolute w-[3px] cursor-ew-resize z-20 pointer-events-auto bg-green-500"
@@ -210,15 +210,13 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
{(wallpaperPaths.length > 0 ? wallpaperPaths : WALLPAPER_RELATIVE.map(p => `/${p}`)).map((path, idx) => {
const isSelected = (() => {
if (!selected) return false;
// exact match
if (selected === path) return true;
// file:// vs absolute path mismatch: compare by filename suffix
try {
const clean = (s: string) => s.replace(/^file:\/\//, '').replace(/^\//, '')
if (clean(selected).endsWith(clean(path))) return true;
if (clean(path).endsWith(clean(selected))) return true;
} catch {
// ignore
}
return false;
})();
@@ -181,13 +181,11 @@ export default function VideoEditor() {
setExportError(null);
try {
// Pause video during export
const wasPlaying = isPlaying;
if (wasPlaying) {
videoPlaybackRef.current?.pause();
}
// Always export at 1920x1080 (16:9)
const width = 1920;
const height = 1080;
@@ -212,7 +210,6 @@ export default function VideoEditor() {
const result = await exporter.export();
if (result.success && result.blob) {
// Save the blob using Electron
const arrayBuffer = await result.blob.arrayBuffer();
const timestamp = Date.now();
const fileName = `export-${timestamp}.mp4`;
@@ -230,7 +227,6 @@ export default function VideoEditor() {
toast.error(result.error || 'Export failed');
}
// Resume playback if it was playing
if (wasPlaying) {
videoPlaybackRef.current?.play();
}
+1 -16
View File
@@ -165,7 +165,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
}
}, [updateOverlayForRegion, cropRegion]);
// Keep layoutVideoContent ref updated
useEffect(() => {
layoutVideoContentRef.current = layoutVideoContent;
}, [layoutVideoContent]);
@@ -262,7 +261,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
try {
event.currentTarget.releasePointerCapture(event.pointerId);
} catch {
// ignore release errors when pointer capture is already cleared
}
};
@@ -286,7 +285,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
isPlayingRef.current = isPlaying;
}, [isPlaying]);
// Reset animation state and transforms when crop changes
useEffect(() => {
if (!pixiReady || !videoReady) return;
@@ -306,7 +304,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
video.pause();
}
// Reset animation state so the ticker starts from identity once it resumes
animationStateRef.current = {
scale: 1,
focusX: DEFAULT_FOCUS.cx,
@@ -317,7 +314,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
blurFilterRef.current.blur = 0;
}
// Defer layout to the next frame so DOM measurements include the new crop UI state
requestAnimationFrame(() => {
const container = cameraContainerRef.current;
const videoStage = videoContainerRef.current;
@@ -327,7 +323,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
return;
}
// Reset all transform hierarchies to identity
container.scale.set(1);
container.position.set(0, 0);
videoStage.scale.set(1);
@@ -335,10 +330,8 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
sprite.scale.set(1);
sprite.position.set(0, 0);
// Now layoutVideoContent will apply the correct transforms for the new crop
layoutVideoContent();
// Apply an explicit identity transform to ensure no residual camera offset
applyZoomTransform({
cameraContainer: container,
blurFilter: blurFilterRef.current,
@@ -351,12 +344,10 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
isPlaying: false,
});
// Restart ticker on a second frame to avoid running mid-layout
requestAnimationFrame(() => {
const finalApp = appRef.current;
if (wasPlaying && video) {
video.play().catch(() => {
/* ignore */
});
}
if (tickerWasStarted && finalApp?.ticker) {
@@ -402,7 +393,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
overlayEl.style.pointerEvents = isPlaying ? 'none' : 'auto';
}, [selectedZoom, isPlaying]);
// Initialize PixiJS application
useEffect(() => {
const container = containerRef.current;
if (!container) return;
@@ -410,7 +400,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
let mounted = true;
let app: PIXI.Application | null = null;
// Initialize the app
(async () => {
app = new PIXI.Application();
@@ -423,7 +412,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
autoDensity: true,
});
// Lock ticker to 60fps for consistent animation speed across all displays
app.ticker.maxFPS = 60;
if (!mounted) {
@@ -690,7 +678,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
return
}
// If it's a solid color or CSS gradient, use it directly
if (wallpaper.startsWith('#') || wallpaper.startsWith('linear-gradient') || wallpaper.startsWith('radial-gradient')) {
if (mounted) setResolvedWallpaper(wallpaper)
return
@@ -708,8 +695,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
if (mounted) setResolvedWallpaper(wallpaper)
return
}
// Otherwise assume it's a relative path like 'wallpapers/wallpaper1.jpg'
const p = await getAssetPath(wallpaper.replace(/^\//, ''))
if (mounted) setResolvedWallpaper(p)
} catch (err) {
@@ -151,7 +151,7 @@ function PlaybackCursor({
<div
className="absolute top-0 bottom-0 pointer-events-none z-50"
style={{
[sideProperty === "right" ? "marginRight" : "marginLeft"]: `${sidebarWidth - 8}px`, // reduce margin
[sideProperty === "right" ? "marginRight" : "marginLeft"]: `${sidebarWidth - 8}px`,
}}
>
<div
@@ -185,7 +185,6 @@ function PlaybackCursor({
</div>
</div>
{/* Subtle glow at top */}
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-red-500/30 rounded-full blur-sm" />
</div>
</div>
@@ -403,7 +402,6 @@ export default function TimelineEditor({
// Snap if gap is 2ms or less
return zoomRegions.some((region) => {
if (region.id === excludeId) return false;
// If the new span is within 2ms of another region, treat as overlap (snap)
const gapBefore = newSpan.start - region.endMs;
const gapAfter = region.startMs - newSpan.end;
if (gapBefore > 0 && gapBefore <= 2) return true;
@@ -149,8 +149,6 @@ export default function TimelineWrapper({
[clampRange, onRangeChange, totalMs],
);
// To maximize granularity, disable grid snapping by not passing rangeGridSizeDefinition
// and allow pixel-level movement for items.
return (
<TimelineContext
range={range}
@@ -158,7 +156,6 @@ export default function TimelineWrapper({
onResizeEnd={onResizeEnd}
onDragEnd={onDragEnd}
autoScroll={{ enabled: false }}
// Remove rangeGridSizeDefinition to avoid snap effect
>
{children}
</TimelineContext>
@@ -18,8 +18,7 @@ export function clampFocusToStage(
const windowWidth = stageSize.width / zoomScale;
const windowHeight = stageSize.height / zoomScale;
// Calculate margins - focus must stay far enough from edges so zoom window stays within stage bounds
const marginX = windowWidth / (2 * stageSize.width);
const marginY = windowHeight / (2 * stageSize.height);
@@ -24,7 +24,7 @@ interface LayoutResult {
export function layoutVideoContent(params: LayoutParams): LayoutResult | null {
const { container, app, videoSprite, maskGraphics, videoElement, cropRegion, lockedVideoDimensions } = params;
// Use locked dimensions if available, otherwise use current video dimensions
const videoWidth = lockedVideoDimensions?.width || videoElement.videoWidth;
const videoHeight = lockedVideoDimensions?.height || videoElement.videoHeight;
@@ -37,7 +37,6 @@ export function createVideoEventHandlers(params: VideoEventHandlersParams) {
}
const handlePlay = () => {
// Prevent autoplay during seek operations
if (isSeekingRef.current) {
video.pause();
return;
@@ -69,7 +68,6 @@ export function createVideoEventHandlers(params: VideoEventHandlersParams) {
const handleSeeked = () => {
isSeekingRef.current = false;
// Keep video paused after seek if it wasn't playing
if (!isPlayingRef.current && !video.paused) {
video.pause();
}
@@ -79,7 +77,6 @@ export function createVideoEventHandlers(params: VideoEventHandlersParams) {
const handleSeeking = () => {
isSeekingRef.current = true;
// Prevent autoplay during seek if video was paused
if (!isPlayingRef.current && !video.paused) {
video.pause();
}