UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

123 lines (117 loc) 3.4 kB
import { BoxModel, PageRecordType, TLPageId, TLShapeId, createShapeId } from '@tldraw/tlschema' import { exhaustiveSwitchError } from '@tldraw/utils' import { Editor } from '../editor/Editor' import { Box } from '../primitives/Box' /** @public */ export type TLDeepLink = | { type: 'shapes' shapeIds: TLShapeId[] } | { type: 'viewport'; bounds: BoxModel; pageId?: TLPageId } | { type: 'page'; pageId: TLPageId } /** * Converts a deep link descriptor to a url-safe string * * @example * ```ts * const url = `https://example.com?d=${createDeepLinkString({ type: 'shapes', shapeIds: ['shape:1', 'shape:2'] })}` * navigator.clipboard.writeText(url) * ``` * * @param deepLink - the deep link descriptor * @returns a url-safe string * * @public */ export function createDeepLinkString(deepLink: TLDeepLink): string { switch (deepLink.type) { case 'shapes': { const ids = deepLink.shapeIds.map((id) => encodeId(id.slice('shape:'.length))) return `s${ids.join('.')}` } case 'page': { return 'p' + encodeId(PageRecordType.parseId(deepLink.pageId)) } case 'viewport': { const { bounds, pageId } = deepLink let res = `v${Math.round(bounds.x)}.${Math.round(bounds.y)}.${Math.round(bounds.w)}.${Math.round(bounds.h)}` if (pageId) { res += '.' + encodeId(PageRecordType.parseId(pageId)) } return res } default: exhaustiveSwitchError(deepLink) } } /** * Parses a string created by {@link createDeepLinkString} back into a deep link descriptor. * * @param deepLinkString - the deep link string * @returns a deep link descriptor * * @public */ export function parseDeepLinkString(deepLinkString: string): TLDeepLink { const type = deepLinkString[0] switch (type) { case 's': { const shapeIds = deepLinkString .slice(1) .split('.') .filter(Boolean) .map((id) => createShapeId(decodeURIComponent(id))) return { type: 'shapes', shapeIds } } case 'p': { const pageId = PageRecordType.createId(decodeURIComponent(deepLinkString.slice(1))) return { type: 'page', pageId } } case 'v': { const [x, y, w, h, pageId] = deepLinkString.slice(1).split('.') return { type: 'viewport', bounds: new Box(Number(x), Number(y), Number(w), Number(h)), pageId: pageId ? PageRecordType.createId(decodeURIComponent(pageId)) : undefined, } } default: throw Error('Invalid deep link string') } } function encodeId(str: string): string { // need to encode dots because they are used as separators return encodeURIComponent(str).replace(/\./g, '%2E') } /** @public */ export interface TLDeepLinkOptions { /** * The name of the url search param to use for the deep link. * * Defaults to `'d'` */ param?: string /** * The debounce time in ms for updating the url. */ debounceMs?: number /** * Should return the current url to augment with a deep link query parameter. * If you supply this function, you must also supply an `onChange` function. */ getUrl?(editor: Editor): string | URL /** * Should return the current deep link target. * Defaults to returning the current page and viewport position. */ getTarget?(editor: Editor): TLDeepLink /** * This is fired when the URL is updated. * * If not supplied, the default behavior is to update `window.location`. * * @param url - the updated URL */ onChange?(url: URL, editor: Editor): void }