From 6079f0ad15270e1926b04bc9cfa532cabafd2e51 Mon Sep 17 00:00:00 2001 From: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:30:45 +0700 Subject: [PATCH] fix camera history --- src/hooks/useCameraControls.ts | 152 ++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 58 deletions(-) diff --git a/src/hooks/useCameraControls.ts b/src/hooks/useCameraControls.ts index ebf09af..48aacba 100644 --- a/src/hooks/useCameraControls.ts +++ b/src/hooks/useCameraControls.ts @@ -1,12 +1,42 @@ import { useEffect } from 'react'; -import { Editor, TLFrameShape, TLParentId } from 'tldraw'; +import { Editor, TLEventMap, TLFrameShape, TLParentId } from 'tldraw'; import { useSearchParams } from 'react-router-dom'; -const initialCamera = { x: 0, y: 0, z: 1 }; +// Define camera state interface +interface CameraState { + x: number; + y: number; + z: number; +} + +const MAX_HISTORY = 10; +let cameraHistory: CameraState[] = []; + +// Improved camera change tracking with debouncing +const trackCameraChange = (editor: Editor) => { + // Only track if not in animation + if (editor.getCameraState() === 'moving') return; + + const currentCamera = editor.getCamera(); + const lastPosition = cameraHistory[cameraHistory.length - 1]; + + // Enhanced threshold check for meaningful changes + if (!lastPosition || + (Math.abs(lastPosition.x - currentCamera.x) > 1 || + Math.abs(lastPosition.y - currentCamera.y) > 1 || + Math.abs(lastPosition.z - currentCamera.z) > 0.1)) { + + cameraHistory.push({ ...currentCamera }); + if (cameraHistory.length > MAX_HISTORY) { + cameraHistory.shift(); + } + } +}; export function useCameraControls(editor: Editor | null) { const [searchParams] = useSearchParams(); + // Handle URL-based camera positioning useEffect(() => { if (!editor) return; @@ -15,82 +45,88 @@ export function useCameraControls(editor: Editor | null) { const y = searchParams.get('y'); const zoom = searchParams.get('zoom'); - console.log('Loading camera position:', { frameId, x, y, zoom }); - if (x && y && zoom) { editor.setCamera({ x: parseFloat(x), y: parseFloat(y), z: parseFloat(zoom) }); - console.log('Camera position set from URL params'); return; } - if (!frameId) return; - - const frame = editor.getShape(frameId as TLParentId) as TLFrameShape; - if (!frame) { - console.warn('Frame not found:', frameId); - return; - } - - editor.zoomToBounds( - editor.getShapePageBounds(frame)!, - { - inset: 32, - targetZoom: editor.getCamera().z, + if (frameId) { + const frame = editor.getShape(frameId as TLParentId) as TLFrameShape; + if (!frame) { + console.warn('Frame not found:', frameId); + return; } - ); - const newUrl = new URL(window.location.href); - newUrl.searchParams.set('frameId', frameId); - window.history.replaceState(null, '', newUrl.toString()); + // Use editor's built-in zoomToBounds with animation + editor.zoomToBounds( + editor.getShapePageBounds(frame)!, + { + inset: 32, + animation: { duration: 500 } + } + ); + } }, [editor, searchParams]); - const copyLocationLink = () => { - if (!editor) return; - const camera = editor.getCamera(); - const url = new URL(window.location.href); - url.searchParams.set('x', camera.x.toString()); - url.searchParams.set('y', camera.y.toString()); - url.searchParams.set('zoom', camera.z.toString()); - console.log('Copying location link:', url.toString()); - navigator.clipboard.writeText(url.toString()); - }; - - const zoomToFrame = (frameId: string) => { + // Track camera changes + useEffect(() => { if (!editor) return; - const frame = editor.getShape(frameId as TLParentId) as TLFrameShape; - if (!frame) { - console.warn('Frame not found:', frameId); - return; - } - - editor.zoomToBounds( - editor.getShapePageBounds(frame)!, - { - inset: 32, - targetZoom: editor.getCamera().z, + const handler = () => { + if (editor.getCameraState() !== 'moving') { + trackCameraChange(editor); } - ); - }; + }; - const copyFrameLink = (frameId: string) => { - const url = new URL(window.location.href); - url.searchParams.set('frameId', frameId); - console.log('Copying frame link:', url.toString()); - navigator.clipboard.writeText(url.toString()); - }; + editor.on('viewportChange' as keyof TLEventMap, handler); + return () => { + editor.off('viewportChange' as keyof TLEventMap, handler); + }; + }, [editor]); + + // Enhanced camera control functions return { - zoomToFrame, - copyFrameLink, - copyLocationLink, + zoomToFrame: (frameId: string) => { + if (!editor) return; + const frame = editor.getShape(frameId as TLParentId) as TLFrameShape; + if (!frame) return; + + editor.zoomToBounds( + editor.getShapePageBounds(frame)!, + { + inset: 32, + animation: { duration: 500 } + } + ); + }, + + copyFrameLink: (frameId: string) => { + const url = new URL(window.location.href); + url.searchParams.set('frameId', frameId); + navigator.clipboard.writeText(url.toString()); + }, + + copyLocationLink: () => { + if (!editor) return; + const camera = editor.getCamera(); + const url = new URL(window.location.href); + url.searchParams.set('x', camera.x.toString()); + url.searchParams.set('y', camera.y.toString()); + url.searchParams.set('zoom', camera.z.toString()); + navigator.clipboard.writeText(url.toString()); + }, + revertCamera: () => { - if (!editor) return - editor.setCamera(initialCamera) + if (!editor || cameraHistory.length === 0) return; + const previousCamera = cameraHistory.pop(); + if (previousCamera) { + editor.setCamera(previousCamera, { animation: { duration: 200 } }); + } } }; } \ No newline at end of file