[2/4] Rename sync hooks, add bookmarks to demo (#4094)
Adds a new `onEditorMount` callback to the store, allowing store creators to do things like registering bookmark handlers. We use this in the new demo hook. This also renames `useRemoteSyncClient` to `useMultiplayerSync`, and `useRemoteSyncDemo` to `useMultiplayerDemo`. Closes TLD-2601 ### Change type - [x] `api`
This commit is contained in:
parent
965bc10997
commit
627c84c2af
13 changed files with 127 additions and 39 deletions
|
@ -1,5 +1,5 @@
|
||||||
main = "src/worker.ts"
|
main = "src/worker.ts"
|
||||||
compatibility_date = "2024-06-25"
|
compatibility_date = "2024-06-20"
|
||||||
upload_source_maps = true
|
upload_source_maps = true
|
||||||
|
|
||||||
[dev]
|
[dev]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ROOM_OPEN_MODE, RoomOpenModeToPath, type RoomOpenMode } from '@tldraw/dotcom-shared'
|
import { ROOM_OPEN_MODE, RoomOpenModeToPath, type RoomOpenMode } from '@tldraw/dotcom-shared'
|
||||||
import { useRemoteSyncClient } from '@tldraw/sync-react'
|
import { useMultiplayerSync } from '@tldraw/sync-react'
|
||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect } from 'react'
|
||||||
import {
|
import {
|
||||||
DefaultContextMenu,
|
DefaultContextMenu,
|
||||||
|
@ -112,7 +112,7 @@ export function MultiplayerEditor({
|
||||||
}) {
|
}) {
|
||||||
const handleUiEvent = useHandleUiEvents()
|
const handleUiEvent = useHandleUiEvents()
|
||||||
|
|
||||||
const storeWithStatus = useRemoteSyncClient({
|
const storeWithStatus = useMultiplayerSync({
|
||||||
uri: `${MULTIPLAYER_SERVER}/${RoomOpenModeToPath[roomOpenMode]}/${roomSlug}`,
|
uri: `${MULTIPLAYER_SERVER}/${RoomOpenModeToPath[roomOpenMode]}/${roomSlug}`,
|
||||||
roomId: roomSlug,
|
roomId: roomSlug,
|
||||||
assets: multiplayerAssetStore,
|
assets: multiplayerAssetStore,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useDemoRemoteSyncClient } from '@tldraw/sync-react'
|
import { useMultiplayerDemo } from '@tldraw/sync-react'
|
||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect } from 'react'
|
||||||
import { DefaultContextMenu, DefaultContextMenuContent, TLComponents, Tldraw, atom } from 'tldraw'
|
import { DefaultContextMenu, DefaultContextMenuContent, TLComponents, Tldraw, atom } from 'tldraw'
|
||||||
import { UrlStateParams, useUrlState } from '../hooks/useUrlState'
|
import { UrlStateParams, useUrlState } from '../hooks/useUrlState'
|
||||||
|
@ -37,7 +37,7 @@ const components: TLComponents = {
|
||||||
export function TemporaryBemoDevEditor({ slug }: { slug: string }) {
|
export function TemporaryBemoDevEditor({ slug }: { slug: string }) {
|
||||||
const handleUiEvent = useHandleUiEvents()
|
const handleUiEvent = useHandleUiEvents()
|
||||||
|
|
||||||
const storeWithStatus = useDemoRemoteSyncClient({ host: 'http://127.0.0.1:8989', roomId: slug })
|
const storeWithStatus = useMultiplayerDemo({ host: 'http://127.0.0.1:8989', roomId: slug })
|
||||||
|
|
||||||
const isOffline =
|
const isOffline =
|
||||||
storeWithStatus.status === 'synced-remote' && storeWithStatus.connectionStatus === 'offline'
|
storeWithStatus.status === 'synced-remote' && storeWithStatus.connectionStatus === 'offline'
|
||||||
|
@ -48,14 +48,6 @@ export function TemporaryBemoDevEditor({ slug }: { slug: string }) {
|
||||||
const fileSystemUiOverrides = useFileSystem({ isMultiplayer: true })
|
const fileSystemUiOverrides = useFileSystem({ isMultiplayer: true })
|
||||||
const cursorChatOverrides = useCursorChat()
|
const cursorChatOverrides = useCursorChat()
|
||||||
|
|
||||||
// TODO: handle bookmarks
|
|
||||||
// const handleMount = useCallback(
|
|
||||||
// (editor: Editor) => {
|
|
||||||
// editor.registerExternalAssetHandler('url', createAssetFromUrl)
|
|
||||||
// },
|
|
||||||
// []
|
|
||||||
// )
|
|
||||||
|
|
||||||
if (storeWithStatus.error) {
|
if (storeWithStatus.error) {
|
||||||
return <StoreErrorScreen error={storeWithStatus.error} />
|
return <StoreErrorScreen error={storeWithStatus.error} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -487,7 +487,7 @@ export function counterClockwiseAngleDist(a0: number, a1: number): number;
|
||||||
export function createSessionStateSnapshotSignal(store: TLStore): Signal<null | TLSessionStateSnapshot>;
|
export function createSessionStateSnapshotSignal(store: TLStore): Signal<null | TLSessionStateSnapshot>;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function createTLStore({ initialData, defaultName, id, assets, ...rest }?: TLStoreOptions): TLStore;
|
export function createTLStore({ initialData, defaultName, id, assets, onEditorMount, ...rest }?: TLStoreOptions): TLStore;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function createTLUser(opts?: {
|
export function createTLUser(opts?: {
|
||||||
|
@ -3246,6 +3246,7 @@ export interface TLStoreBaseOptions {
|
||||||
assets?: Partial<TLAssetStore>;
|
assets?: Partial<TLAssetStore>;
|
||||||
defaultName?: string;
|
defaultName?: string;
|
||||||
initialData?: SerializedStore<TLRecord>;
|
initialData?: SerializedStore<TLRecord>;
|
||||||
|
onEditorMount?: (editor: Editor) => (() => void) | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -3422,6 +3423,9 @@ export function useLocalStore(options: {
|
||||||
snapshot?: TLEditorSnapshot | TLStoreSnapshot;
|
snapshot?: TLEditorSnapshot | TLStoreSnapshot;
|
||||||
} & TLStoreOptions): TLStoreWithStatus;
|
} & TLStoreOptions): TLStoreWithStatus;
|
||||||
|
|
||||||
|
// @internal (undocumented)
|
||||||
|
export function useOnMount(onMount?: TLOnMountHandler): void;
|
||||||
|
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export function usePeerIds(): string[];
|
export function usePeerIds(): string[];
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ export {
|
||||||
ErrorScreen,
|
ErrorScreen,
|
||||||
LoadingScreen,
|
LoadingScreen,
|
||||||
TldrawEditor,
|
TldrawEditor,
|
||||||
|
useOnMount,
|
||||||
type LoadingScreenProps,
|
type LoadingScreenProps,
|
||||||
type TLOnMountHandler,
|
type TLOnMountHandler,
|
||||||
type TldrawEditorBaseProps,
|
type TldrawEditorBaseProps,
|
||||||
|
|
|
@ -450,7 +450,15 @@ function Layout({ children, onMount }: { children: ReactNode; onMount?: TLOnMoun
|
||||||
useCursor()
|
useCursor()
|
||||||
useDarkMode()
|
useDarkMode()
|
||||||
useForceUpdate()
|
useForceUpdate()
|
||||||
useOnMount(onMount)
|
useOnMount((editor) => {
|
||||||
|
const teardownStore = editor.store.props.onEditorMount(editor)
|
||||||
|
const teardownCallback = onMount?.(editor)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
teardownStore?.()
|
||||||
|
teardownCallback?.()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
|
@ -474,7 +482,8 @@ export function ErrorScreen({ children }: LoadingScreenProps) {
|
||||||
return <div className="tl-loading">{children}</div>
|
return <div className="tl-loading">{children}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function useOnMount(onMount?: TLOnMountHandler) {
|
/** @internal */
|
||||||
|
export function useOnMount(onMount?: TLOnMountHandler) {
|
||||||
const editor = useEditor()
|
const editor = useEditor()
|
||||||
|
|
||||||
const onMountEvent = useEvent((editor: Editor) => {
|
const onMountEvent = useEvent((editor: Editor) => {
|
||||||
|
|
|
@ -7,7 +7,8 @@ import {
|
||||||
TLStoreProps,
|
TLStoreProps,
|
||||||
createTLSchema,
|
createTLSchema,
|
||||||
} from '@tldraw/tlschema'
|
} from '@tldraw/tlschema'
|
||||||
import { FileHelpers } from '@tldraw/utils'
|
import { FileHelpers, assert } from '@tldraw/utils'
|
||||||
|
import { Editor } from '../editor/Editor'
|
||||||
import { TLAnyBindingUtilConstructor, checkBindings } from './defaultBindings'
|
import { TLAnyBindingUtilConstructor, checkBindings } from './defaultBindings'
|
||||||
import { TLAnyShapeUtilConstructor, checkShapesAndAddCore } from './defaultShapes'
|
import { TLAnyShapeUtilConstructor, checkShapesAndAddCore } from './defaultShapes'
|
||||||
|
|
||||||
|
@ -21,6 +22,9 @@ export interface TLStoreBaseOptions {
|
||||||
|
|
||||||
/** How should this store upload & resolve assets? */
|
/** How should this store upload & resolve assets? */
|
||||||
assets?: Partial<TLAssetStore>
|
assets?: Partial<TLAssetStore>
|
||||||
|
|
||||||
|
/** Called when the store is connected to an {@link Editor}. */
|
||||||
|
onEditorMount?: (editor: Editor) => void | (() => void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -58,6 +62,7 @@ export function createTLStore({
|
||||||
defaultName = '',
|
defaultName = '',
|
||||||
id,
|
id,
|
||||||
assets,
|
assets,
|
||||||
|
onEditorMount,
|
||||||
...rest
|
...rest
|
||||||
}: TLStoreOptions = {}): TLStore {
|
}: TLStoreOptions = {}): TLStore {
|
||||||
const schema =
|
const schema =
|
||||||
|
@ -87,6 +92,10 @@ export function createTLStore({
|
||||||
...defaultAssetStore,
|
...defaultAssetStore,
|
||||||
...assets,
|
...assets,
|
||||||
},
|
},
|
||||||
|
onEditorMount: (editor) => {
|
||||||
|
assert(editor instanceof Editor)
|
||||||
|
onEditorMount?.(editor)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
export { useDemoRemoteSyncClient, type UseDemoSyncClientConfig } from './useDemoSyncClient'
|
export {
|
||||||
export { useRemoteSyncClient, type RemoteTLStoreWithStatus } from './useRemoteSyncClient'
|
useMultiplayerSync,
|
||||||
|
type RemoteTLStoreWithStatus,
|
||||||
|
type UseMultiplayerSyncOptions,
|
||||||
|
} from './useMultiplayerSync'
|
||||||
|
export { useMultiplayerDemo, type UseMultiplayerDemoOptions } from './useMutliplayerDemo'
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from '@tldraw/sync'
|
} from '@tldraw/sync'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
|
Editor,
|
||||||
Signal,
|
Signal,
|
||||||
TAB_ID,
|
TAB_ID,
|
||||||
TLAssetStore,
|
TLAssetStore,
|
||||||
|
@ -33,14 +34,14 @@ export type RemoteTLStoreWithStatus = Exclude<
|
||||||
>
|
>
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function useRemoteSyncClient(opts: UseSyncClientConfig): RemoteTLStoreWithStatus {
|
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLStoreWithStatus {
|
||||||
const [state, setState] = useState<{
|
const [state, setState] = useState<{
|
||||||
readyClient?: TLSyncClient<TLRecord, TLStore>
|
readyClient?: TLSyncClient<TLRecord, TLStore>
|
||||||
error?: Error
|
error?: Error
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const { uri, roomId = 'default', userPreferences: prefs, assets } = opts
|
const { uri, roomId = 'default', userPreferences: prefs, assets, onEditorMount } = opts
|
||||||
|
|
||||||
const store = useTLStore({ schema, assets })
|
const store = useTLStore({ schema, assets, onEditorMount })
|
||||||
|
|
||||||
const error: NonNullable<typeof state>['error'] = state?.error ?? undefined
|
const error: NonNullable<typeof state>['error'] = state?.error ?? undefined
|
||||||
const track = opts.trackAnalyticsEvent
|
const track = opts.trackAnalyticsEvent
|
||||||
|
@ -134,11 +135,12 @@ export function useRemoteSyncClient(opts: UseSyncClientConfig): RemoteTLStoreWit
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface UseSyncClientConfig {
|
export interface UseMultiplayerSyncOptions {
|
||||||
uri: string
|
uri: string
|
||||||
roomId?: string
|
roomId?: string
|
||||||
userPreferences?: Signal<TLUserPreferences>
|
userPreferences?: Signal<TLUserPreferences>
|
||||||
/* @internal */
|
/* @internal */
|
||||||
trackAnalyticsEvent?(name: string, data: { [key: string]: any }): void
|
trackAnalyticsEvent?(name: string, data: { [key: string]: any }): void
|
||||||
assets?: Partial<TLAssetStore>
|
assets?: Partial<TLAssetStore>
|
||||||
|
onEditorMount?: (editor: Editor) => void
|
||||||
}
|
}
|
|
@ -1,9 +1,19 @@
|
||||||
import { useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import { MediaHelpers, Signal, TLAssetStore, TLUserPreferences, uniqueId } from 'tldraw'
|
import {
|
||||||
import { RemoteTLStoreWithStatus, useRemoteSyncClient } from './useRemoteSyncClient'
|
AssetRecordType,
|
||||||
|
Editor,
|
||||||
|
MediaHelpers,
|
||||||
|
Signal,
|
||||||
|
TLAsset,
|
||||||
|
TLAssetStore,
|
||||||
|
TLUserPreferences,
|
||||||
|
getHashForString,
|
||||||
|
uniqueId,
|
||||||
|
} from 'tldraw'
|
||||||
|
import { RemoteTLStoreWithStatus, useMultiplayerSync } from './useMultiplayerSync'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface UseDemoSyncClientConfig {
|
export interface UseMultiplayerDemoOptions {
|
||||||
roomId: string
|
roomId: string
|
||||||
userPreferences?: Signal<TLUserPreferences>
|
userPreferences?: Signal<TLUserPreferences>
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -29,18 +39,26 @@ function getEnv(cb: () => string | undefined): string | undefined {
|
||||||
const DEMO_WORKER = getEnv(() => process.env.DEMO_WORKER) ?? 'https://demo.tldraw.xyz'
|
const DEMO_WORKER = getEnv(() => process.env.DEMO_WORKER) ?? 'https://demo.tldraw.xyz'
|
||||||
const IMAGE_WORKER = getEnv(() => process.env.IMAGE_WORKER) ?? 'https://images.tldraw.xyz'
|
const IMAGE_WORKER = getEnv(() => process.env.IMAGE_WORKER) ?? 'https://images.tldraw.xyz'
|
||||||
|
|
||||||
export function useDemoRemoteSyncClient({
|
export function useMultiplayerDemo({
|
||||||
roomId,
|
roomId,
|
||||||
userPreferences,
|
userPreferences,
|
||||||
host = DEMO_WORKER,
|
host = DEMO_WORKER,
|
||||||
}: UseDemoSyncClientConfig): RemoteTLStoreWithStatus {
|
}: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus {
|
||||||
const assets = useMemo(() => createDemoAssetStore(host), [host])
|
const assets = useMemo(() => createDemoAssetStore(host), [host])
|
||||||
|
|
||||||
return useRemoteSyncClient({
|
return useMultiplayerSync({
|
||||||
uri: `${host}/connect/${roomId}`,
|
uri: `${host}/connect/${roomId}`,
|
||||||
roomId,
|
roomId,
|
||||||
userPreferences,
|
userPreferences,
|
||||||
assets,
|
assets,
|
||||||
|
onEditorMount: useCallback(
|
||||||
|
(editor: Editor) => {
|
||||||
|
editor.registerExternalAssetHandler('url', async ({ url }) => {
|
||||||
|
return await createAssetFromUrlUsingDemoServer(host, url)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[host]
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,3 +134,49 @@ function createDemoAssetStore(host: string): TLAssetStore {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createAssetFromUrlUsingDemoServer(host: string, url: string): Promise<TLAsset> {
|
||||||
|
const urlHash = getHashForString(url)
|
||||||
|
try {
|
||||||
|
// First, try to get the meta data from our endpoint
|
||||||
|
const fetchUrl = new URL(`${host}/bookmarks/unfurl`)
|
||||||
|
fetchUrl.searchParams.set('url', url)
|
||||||
|
|
||||||
|
const meta = (await (await fetch(fetchUrl)).json()) as {
|
||||||
|
description?: string
|
||||||
|
image?: string
|
||||||
|
favicon?: string
|
||||||
|
title?: string
|
||||||
|
} | null
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: AssetRecordType.createId(urlHash),
|
||||||
|
typeName: 'asset',
|
||||||
|
type: 'bookmark',
|
||||||
|
props: {
|
||||||
|
src: url,
|
||||||
|
description: meta?.description ?? '',
|
||||||
|
image: meta?.image ?? '',
|
||||||
|
favicon: meta?.favicon ?? '',
|
||||||
|
title: meta?.title ?? '',
|
||||||
|
},
|
||||||
|
meta: {},
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Otherwise, fallback to a blank bookmark
|
||||||
|
console.error(error)
|
||||||
|
return {
|
||||||
|
id: AssetRecordType.createId(urlHash),
|
||||||
|
typeName: 'asset',
|
||||||
|
type: 'bookmark',
|
||||||
|
props: {
|
||||||
|
src: url,
|
||||||
|
description: '',
|
||||||
|
image: '',
|
||||||
|
favicon: '',
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
meta: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ import {
|
||||||
DEFAULT_SUPPORTED_IMAGE_TYPES,
|
DEFAULT_SUPPORTED_IMAGE_TYPES,
|
||||||
DEFAULT_SUPPORT_VIDEO_TYPES,
|
DEFAULT_SUPPORT_VIDEO_TYPES,
|
||||||
DefaultSpinner,
|
DefaultSpinner,
|
||||||
Editor,
|
|
||||||
ErrorScreen,
|
ErrorScreen,
|
||||||
LoadingScreen,
|
LoadingScreen,
|
||||||
TLEditorComponents,
|
TLEditorComponents,
|
||||||
|
@ -12,11 +11,11 @@ import {
|
||||||
TldrawEditorStoreProps,
|
TldrawEditorStoreProps,
|
||||||
useEditor,
|
useEditor,
|
||||||
useEditorComponents,
|
useEditorComponents,
|
||||||
useEvent,
|
useOnMount,
|
||||||
useShallowArrayIdentity,
|
useShallowArrayIdentity,
|
||||||
useShallowObjectIdentity,
|
useShallowObjectIdentity,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import { useLayoutEffect, useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { TldrawHandles } from './canvas/TldrawHandles'
|
import { TldrawHandles } from './canvas/TldrawHandles'
|
||||||
import { TldrawScribble } from './canvas/TldrawScribble'
|
import { TldrawScribble } from './canvas/TldrawScribble'
|
||||||
import { TldrawSelectionBackground } from './canvas/TldrawSelectionBackground'
|
import { TldrawSelectionBackground } from './canvas/TldrawSelectionBackground'
|
||||||
|
@ -148,7 +147,7 @@ function InsideOfEditorAndUiContext({
|
||||||
const toasts = useToasts()
|
const toasts = useToasts()
|
||||||
const msg = useTranslation()
|
const msg = useTranslation()
|
||||||
|
|
||||||
const onMountEvent = useEvent((editor: Editor) => {
|
useOnMount(() => {
|
||||||
const unsubs: (void | (() => void) | undefined)[] = []
|
const unsubs: (void | (() => void) | undefined)[] = []
|
||||||
|
|
||||||
unsubs.push(...registerDefaultSideEffects(editor))
|
unsubs.push(...registerDefaultSideEffects(editor))
|
||||||
|
@ -168,7 +167,10 @@ function InsideOfEditorAndUiContext({
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ...then we run the onMount prop, which may override the above
|
// ...then we call the store's on mount which may override them...
|
||||||
|
unsubs.push(editor.store.props.onEditorMount(editor))
|
||||||
|
|
||||||
|
// ...then we run the user's onMount prop, which may override things again.
|
||||||
unsubs.push(onMount?.(editor))
|
unsubs.push(onMount?.(editor))
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -176,10 +178,6 @@ function InsideOfEditorAndUiContext({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (editor) return onMountEvent?.(editor)
|
|
||||||
}, [editor, onMountEvent])
|
|
||||||
|
|
||||||
const { Canvas } = useEditorComponents()
|
const { Canvas } = useEditorComponents()
|
||||||
const { ContextMenu } = useTldrawUiComponents()
|
const { ContextMenu } = useTldrawUiComponents()
|
||||||
|
|
||||||
|
|
|
@ -1499,6 +1499,7 @@ export interface TLStoreProps {
|
||||||
assets: TLAssetStore;
|
assets: TLAssetStore;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
defaultName: string;
|
defaultName: string;
|
||||||
|
onEditorMount: (editor: unknown) => (() => void) | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
|
|
@ -94,6 +94,10 @@ export interface TLAssetStore {
|
||||||
export interface TLStoreProps {
|
export interface TLStoreProps {
|
||||||
defaultName: string
|
defaultName: string
|
||||||
assets: TLAssetStore
|
assets: TLAssetStore
|
||||||
|
/**
|
||||||
|
* Called an {@link @tldraw/editor#Editor} connected to this store is mounted.
|
||||||
|
*/
|
||||||
|
onEditorMount: (editor: unknown) => void | (() => void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
Loading…
Reference in a new issue