fix camera history
This commit is contained in:
parent
1d817c8e0f
commit
7f497ae8d8
|
|
@ -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 } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue