kopia lustrzana https://github.com/Tldraw/Tldraw
100 wiersze
2.8 KiB
TypeScript
100 wiersze
2.8 KiB
TypeScript
import { default as React, useEffect } from 'react'
|
|
import { Editor, TLPageId, Vec, clamp, debounce, react, useEditor } from 'tldraw'
|
|
|
|
const PARAMS = {
|
|
// deprecated
|
|
viewport: 'viewport',
|
|
page: 'page',
|
|
// current
|
|
v: 'v',
|
|
p: 'p',
|
|
} as const
|
|
|
|
export type UrlStateParams = Partial<Record<keyof typeof PARAMS, string>>
|
|
|
|
const viewportFromString = (str: string) => {
|
|
const [x, y, w, h] = str.split(',').map((n) => parseInt(n, 10))
|
|
return { x, y, w, h }
|
|
}
|
|
|
|
const viewportToString = (
|
|
{ x, y, w, h }: { x: number; y: number; w: number; h: number },
|
|
precision = 0
|
|
) => {
|
|
return `${x.toFixed(precision)},${y.toFixed(precision)},${w.toFixed(precision)},${h.toFixed(
|
|
precision
|
|
)}`
|
|
}
|
|
|
|
/**
|
|
* @param app - The app instance.
|
|
* @public
|
|
*/
|
|
export const getViewportUrlQuery = (editor: Editor): UrlStateParams | null => {
|
|
if (!editor.getViewportPageBounds()) return null
|
|
return {
|
|
[PARAMS.v]: viewportToString(editor.getViewportPageBounds()),
|
|
[PARAMS.p]: editor.getCurrentPageId()?.split(':')[1],
|
|
}
|
|
}
|
|
|
|
/** @public */
|
|
export function useUrlState(onChangeUrl: (params: UrlStateParams) => void) {
|
|
const editor = useEditor()
|
|
const onChangeUrlRef = React.useRef(onChangeUrl)
|
|
onChangeUrlRef.current = onChangeUrl
|
|
|
|
// Load initial data
|
|
useEffect(() => {
|
|
if (!editor) return
|
|
|
|
const url = new URL(location.href)
|
|
|
|
// We need to check the page first so that any changes to the camera will be applied to the correct page.
|
|
if (url.searchParams.has(PARAMS.page) || url.searchParams.has(PARAMS.p)) {
|
|
const newPageId =
|
|
url.searchParams.get(PARAMS.page) ?? 'page:' + url.searchParams.get(PARAMS.p)
|
|
if (newPageId) {
|
|
if (editor.store.has(newPageId as TLPageId)) {
|
|
editor.setCurrentPage(newPageId as TLPageId)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (url.searchParams.has(PARAMS.viewport) || url.searchParams.has(PARAMS.v)) {
|
|
const newViewportRaw = url.searchParams.get(PARAMS.viewport) ?? url.searchParams.get(PARAMS.v)
|
|
if (newViewportRaw) {
|
|
try {
|
|
const viewport = viewportFromString(newViewportRaw)
|
|
const { x, y, w, h } = viewport
|
|
const { w: sw, h: sh } = editor.getViewportScreenBounds()
|
|
const initialZoom = editor.getInitialZoom()
|
|
const { zoomSteps } = editor.getCameraOptions()
|
|
const zoomMin = zoomSteps[0]
|
|
const zoomMax = zoomSteps[zoomSteps.length - 1]
|
|
const zoom = clamp(Math.min(sw / w, sh / h), zoomMin * initialZoom, zoomMax * initialZoom)
|
|
editor.setCamera(
|
|
new Vec(-x + (sw - w * zoom) / 2 / zoom, -y + (sh - h * zoom) / 2 / zoom, zoom),
|
|
{ immediate: true }
|
|
)
|
|
} catch (err) {
|
|
console.error(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleChange = debounce((params: UrlStateParams | null) => {
|
|
if (params) onChangeUrlRef.current(params)
|
|
}, 500)
|
|
|
|
const unsubscribe = react('urlState', () => {
|
|
handleChange(getViewportUrlQuery(editor))
|
|
})
|
|
|
|
return () => {
|
|
handleChange.cancel()
|
|
unsubscribe()
|
|
}
|
|
}, [editor])
|
|
}
|