camera initialization fixed
This commit is contained in:
parent
e7e911c5bb
commit
47db716af3
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { Editor, TLEventMap, TLFrameShape, TLParentId, TLShapeId } from "tldraw"
|
import { Editor, TLEventMap, TLFrameShape, TLParentId } from "tldraw"
|
||||||
import { useSearchParams } from "react-router-dom"
|
import { cameraHistory } from "@/ui/cameraUtils"
|
||||||
|
|
||||||
// Define camera state interface
|
// Define camera state interface
|
||||||
interface CameraState {
|
interface CameraState {
|
||||||
|
|
@ -10,16 +10,12 @@ interface CameraState {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_HISTORY = 10
|
const MAX_HISTORY = 10
|
||||||
let cameraHistory: CameraState[] = []
|
|
||||||
|
|
||||||
// TODO: use this
|
// Track camera changes
|
||||||
|
|
||||||
// Improved camera change tracking with debouncing
|
|
||||||
const trackCameraChange = (editor: Editor) => {
|
const trackCameraChange = (editor: Editor) => {
|
||||||
const currentCamera = editor.getCamera()
|
const currentCamera = editor.getCamera()
|
||||||
const lastPosition = cameraHistory[cameraHistory.length - 1]
|
const lastPosition = cameraHistory[cameraHistory.length - 1]
|
||||||
|
|
||||||
// Store any viewport change that's not from a revert operation
|
|
||||||
if (
|
if (
|
||||||
!lastPosition ||
|
!lastPosition ||
|
||||||
currentCamera.x !== lastPosition.x ||
|
currentCamera.x !== lastPosition.x ||
|
||||||
|
|
@ -34,80 +30,6 @@ const trackCameraChange = (editor: Editor) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCameraControls(editor: Editor | null) {
|
export function useCameraControls(editor: Editor | null) {
|
||||||
const [searchParams] = useSearchParams()
|
|
||||||
|
|
||||||
// Handle URL-based camera positioning
|
|
||||||
useEffect(() => {
|
|
||||||
if (!editor || !editor.store || !editor.getInstanceState().isFocused) {
|
|
||||||
console.log("Editor not ready:", {
|
|
||||||
editor: !!editor,
|
|
||||||
store: !!editor?.store,
|
|
||||||
isFocused: editor?.getInstanceState().isFocused,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const x = searchParams.get("x")
|
|
||||||
const y = searchParams.get("y")
|
|
||||||
const zoom = searchParams.get("zoom")
|
|
||||||
const frameId = searchParams.get("frameId")
|
|
||||||
const isLocked = searchParams.get("isLocked") === "true"
|
|
||||||
|
|
||||||
console.log("Setting camera:", { x, y, zoom, frameId, isLocked })
|
|
||||||
|
|
||||||
// Set camera position if coordinates exist
|
|
||||||
if (x && y && zoom) {
|
|
||||||
const position = {
|
|
||||||
x: Math.round(parseFloat(x)),
|
|
||||||
y: Math.round(parseFloat(y)),
|
|
||||||
z: Math.round(parseFloat(zoom)),
|
|
||||||
}
|
|
||||||
console.log("Camera position:", position)
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
editor.setCamera(position, { animation: { duration: 0 } })
|
|
||||||
|
|
||||||
// Apply camera lock immediately after setting position if needed
|
|
||||||
if (isLocked) {
|
|
||||||
editor.setCameraOptions({ isLocked: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Current camera:", editor.getCamera())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle frame-specific logic
|
|
||||||
if (frameId) {
|
|
||||||
const frame = editor.getShape(frameId as TLShapeId)
|
|
||||||
if (frame) {
|
|
||||||
editor.select(frameId as TLShapeId)
|
|
||||||
|
|
||||||
// If x/y/zoom are not provided in URL, zoom to frame bounds
|
|
||||||
if (!x || !y || !zoom) {
|
|
||||||
const bounds = editor.getShapePageBounds(frame)!
|
|
||||||
const viewportPageBounds = editor.getViewportPageBounds()
|
|
||||||
const targetZoom = Math.min(
|
|
||||||
viewportPageBounds.width / bounds.width,
|
|
||||||
viewportPageBounds.height / bounds.height,
|
|
||||||
1, // Cap at 1x zoom, matching lockCameraToFrame
|
|
||||||
)
|
|
||||||
|
|
||||||
editor.zoomToBounds(bounds, {
|
|
||||||
animation: { duration: 0 },
|
|
||||||
targetZoom,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply camera lock after camera is positioned
|
|
||||||
if (isLocked) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
editor.setCameraOptions({ isLocked: true })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [editor, searchParams])
|
|
||||||
|
|
||||||
// Track camera changes
|
// Track camera changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return
|
if (!editor) return
|
||||||
|
|
@ -116,7 +38,6 @@ export function useCameraControls(editor: Editor | null) {
|
||||||
trackCameraChange(editor)
|
trackCameraChange(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track both viewport changes and user interaction end
|
|
||||||
editor.on("viewportChange" as keyof TLEventMap, handler)
|
editor.on("viewportChange" as keyof TLEventMap, handler)
|
||||||
editor.on("userChangeEnd" as keyof TLEventMap, handler)
|
editor.on("userChangeEnd" as keyof TLEventMap, handler)
|
||||||
|
|
||||||
|
|
@ -126,7 +47,7 @@ export function useCameraControls(editor: Editor | null) {
|
||||||
}
|
}
|
||||||
}, [editor])
|
}, [editor])
|
||||||
|
|
||||||
// Enhanced camera control functions
|
// Camera control functions
|
||||||
return {
|
return {
|
||||||
zoomToFrame: (frameId: string) => {
|
zoomToFrame: (frameId: string) => {
|
||||||
if (!editor) return
|
if (!editor) return
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { makeRealSettings, applySettingsMigrations } from "@/lib/settings"
|
||||||
import { PromptShapeTool } from "@/tools/PromptShapeTool"
|
import { PromptShapeTool } from "@/tools/PromptShapeTool"
|
||||||
import { PromptShape } from "@/shapes/PromptShapeUtil"
|
import { PromptShape } from "@/shapes/PromptShapeUtil"
|
||||||
import { llm } from "@/utils/llmUtils"
|
import { llm } from "@/utils/llmUtils"
|
||||||
|
import { setInitialCameraFromUrl } from "@/ui/cameraUtils"
|
||||||
|
|
||||||
// Default to production URL if env var isn't available
|
// Default to production URL if env var isn't available
|
||||||
export const WORKER_URL = "https://jeffemmett-canvas.jeffemmett.workers.dev"
|
export const WORKER_URL = "https://jeffemmett-canvas.jeffemmett.workers.dev"
|
||||||
|
|
@ -115,6 +116,7 @@ export function Board() {
|
||||||
setEditor(editor)
|
setEditor(editor)
|
||||||
editor.registerExternalAssetHandler("url", unfurlBookmarkUrl)
|
editor.registerExternalAssetHandler("url", unfurlBookmarkUrl)
|
||||||
editor.setCurrentTool("hand")
|
editor.setCurrentTool("hand")
|
||||||
|
setInitialCameraFromUrl(editor)
|
||||||
handleInitialPageLoad(editor)
|
handleInitialPageLoad(editor)
|
||||||
registerPropagators(editor, [
|
registerPropagators(editor, [
|
||||||
TickPropagator,
|
TickPropagator,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Editor } from "tldraw"
|
import { Editor, TLFrameShape, TLParentId, TLShape, TLShapeId } from "tldraw"
|
||||||
|
|
||||||
export const cameraHistory: { x: number; y: number; z: number }[] = []
|
export const cameraHistory: { x: number; y: number; z: number }[] = []
|
||||||
const MAX_HISTORY = 10 // Keep last 10 camera positions
|
const MAX_HISTORY = 10 // Keep last 10 camera positions
|
||||||
|
|
@ -222,3 +222,69 @@ export const lockCameraToFrame = async (editor: Editor) => {
|
||||||
alert("Failed to copy frame link. Please check clipboard permissions.")
|
alert("Failed to copy frame link. Please check clipboard permissions.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setInitialCameraFromUrl = (editor: Editor) => {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const x = url.searchParams.get("x")
|
||||||
|
const y = url.searchParams.get("y")
|
||||||
|
const zoom = url.searchParams.get("zoom")
|
||||||
|
const shapeId = url.searchParams.get("shapeId")
|
||||||
|
const frameId = url.searchParams.get("frameId")
|
||||||
|
//const isLocked = url.searchParams.get("isLocked") === "true"
|
||||||
|
|
||||||
|
console.log('Setting initial camera from URL:', { x, y, zoom, shapeId, frameId })
|
||||||
|
|
||||||
|
if (x && y && zoom) {
|
||||||
|
editor.stopCameraAnimation()
|
||||||
|
editor.setCamera(
|
||||||
|
{
|
||||||
|
x: parseFloat(x),
|
||||||
|
y: parseFloat(y),
|
||||||
|
z: parseFloat(zoom)
|
||||||
|
},
|
||||||
|
{ animation: { duration: 0 } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle shape/frame selection and zoom
|
||||||
|
if (shapeId) {
|
||||||
|
editor.select(shapeId as TLShapeId)
|
||||||
|
const bounds = editor.getSelectionPageBounds()
|
||||||
|
if (bounds && !x && !y && !zoom) {
|
||||||
|
zoomToSelection(editor)
|
||||||
|
}
|
||||||
|
} else if (frameId) {
|
||||||
|
editor.select(frameId as TLShapeId)
|
||||||
|
const frame = editor.getShape(frameId as TLShapeId)
|
||||||
|
if (frame && !x && !y && !zoom) {
|
||||||
|
const bounds = editor.getShapePageBounds(frame as TLShape)
|
||||||
|
if (bounds) {
|
||||||
|
editor.zoomToBounds(bounds, {
|
||||||
|
targetZoom: 1,
|
||||||
|
animation: { duration: 0 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (isLocked) {
|
||||||
|
// editor.setCameraOptions({ isLocked: true })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const zoomToFrame = (editor: Editor, 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 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const copyFrameLink = (_editor: Editor, frameId: string) => {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.searchParams.set("frameId", frameId)
|
||||||
|
navigator.clipboard.writeText(url.toString())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue