fix camera history

This commit is contained in:
Jeff Emmett 2024-11-27 13:30:45 +07:00
parent 1d817c8e0f
commit 7f497ae8d8
1 changed files with 94 additions and 58 deletions

View File

@ -1,12 +1,42 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Editor, TLFrameShape, TLParentId } from 'tldraw'; import { Editor, TLEventMap, TLFrameShape, TLParentId } from 'tldraw';
import { useSearchParams } from 'react-router-dom'; 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) { export function useCameraControls(editor: Editor | null) {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
// Handle URL-based camera positioning
useEffect(() => { useEffect(() => {
if (!editor) return; if (!editor) return;
@ -15,82 +45,88 @@ export function useCameraControls(editor: Editor | null) {
const y = searchParams.get('y'); const y = searchParams.get('y');
const zoom = searchParams.get('zoom'); const zoom = searchParams.get('zoom');
console.log('Loading camera position:', { frameId, x, y, zoom });
if (x && y && zoom) { if (x && y && zoom) {
editor.setCamera({ editor.setCamera({
x: parseFloat(x), x: parseFloat(x),
y: parseFloat(y), y: parseFloat(y),
z: parseFloat(zoom) z: parseFloat(zoom)
}); });
console.log('Camera position set from URL params');
return; return;
} }
if (!frameId) return; if (frameId) {
const frame = editor.getShape(frameId as TLParentId) as TLFrameShape;
const frame = editor.getShape(frameId as TLParentId) as TLFrameShape; if (!frame) {
if (!frame) { console.warn('Frame not found:', frameId);
console.warn('Frame not found:', frameId); return;
return;
}
editor.zoomToBounds(
editor.getShapePageBounds(frame)!,
{
inset: 32,
targetZoom: editor.getCamera().z,
} }
);
const newUrl = new URL(window.location.href); // Use editor's built-in zoomToBounds with animation
newUrl.searchParams.set('frameId', frameId); editor.zoomToBounds(
window.history.replaceState(null, '', newUrl.toString()); editor.getShapePageBounds(frame)!,
{
inset: 32,
animation: { duration: 500 }
}
);
}
}, [editor, searchParams]); }, [editor, searchParams]);
const copyLocationLink = () => { // Track camera changes
if (!editor) return; useEffect(() => {
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) => {
if (!editor) return; if (!editor) return;
const frame = editor.getShape(frameId as TLParentId) as TLFrameShape; const handler = () => {
if (!frame) { if (editor.getCameraState() !== 'moving') {
console.warn('Frame not found:', frameId); trackCameraChange(editor);
return;
}
editor.zoomToBounds(
editor.getShapePageBounds(frame)!,
{
inset: 32,
targetZoom: editor.getCamera().z,
} }
); };
};
const copyFrameLink = (frameId: string) => { editor.on('viewportChange' as keyof TLEventMap, handler);
const url = new URL(window.location.href);
url.searchParams.set('frameId', frameId);
console.log('Copying frame link:', url.toString());
navigator.clipboard.writeText(url.toString());
};
return () => {
editor.off('viewportChange' as keyof TLEventMap, handler);
};
}, [editor]);
// Enhanced camera control functions
return { return {
zoomToFrame, zoomToFrame: (frameId: string) => {
copyFrameLink, if (!editor) return;
copyLocationLink, 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: () => { revertCamera: () => {
if (!editor) return if (!editor || cameraHistory.length === 0) return;
editor.setCamera(initialCamera) const previousCamera = cameraHistory.pop();
if (previousCamera) {
editor.setCamera(previousCamera, { animation: { duration: 200 } });
}
} }
}; };
} }