[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:
alex 2024-07-10 14:15:44 +01:00 committed by GitHub
parent 965bc10997
commit 627c84c2af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 127 additions and 39 deletions

View file

@ -1,2 +1,6 @@
export { useDemoRemoteSyncClient, type UseDemoSyncClientConfig } from './useDemoSyncClient'
export { useRemoteSyncClient, type RemoteTLStoreWithStatus } from './useRemoteSyncClient'
export {
useMultiplayerSync,
type RemoteTLStoreWithStatus,
type UseMultiplayerSyncOptions,
} from './useMultiplayerSync'
export { useMultiplayerDemo, type UseMultiplayerDemoOptions } from './useMutliplayerDemo'

View file

@ -9,6 +9,7 @@ import {
} from '@tldraw/sync'
import { useEffect, useState } from 'react'
import {
Editor,
Signal,
TAB_ID,
TLAssetStore,
@ -33,14 +34,14 @@ export type RemoteTLStoreWithStatus = Exclude<
>
/** @public */
export function useRemoteSyncClient(opts: UseSyncClientConfig): RemoteTLStoreWithStatus {
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLStoreWithStatus {
const [state, setState] = useState<{
readyClient?: TLSyncClient<TLRecord, TLStore>
error?: Error
} | 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 track = opts.trackAnalyticsEvent
@ -134,11 +135,12 @@ export function useRemoteSyncClient(opts: UseSyncClientConfig): RemoteTLStoreWit
}
/** @public */
export interface UseSyncClientConfig {
export interface UseMultiplayerSyncOptions {
uri: string
roomId?: string
userPreferences?: Signal<TLUserPreferences>
/* @internal */
trackAnalyticsEvent?(name: string, data: { [key: string]: any }): void
assets?: Partial<TLAssetStore>
onEditorMount?: (editor: Editor) => void
}

View file

@ -1,9 +1,19 @@
import { useMemo } from 'react'
import { MediaHelpers, Signal, TLAssetStore, TLUserPreferences, uniqueId } from 'tldraw'
import { RemoteTLStoreWithStatus, useRemoteSyncClient } from './useRemoteSyncClient'
import { useCallback, useMemo } from 'react'
import {
AssetRecordType,
Editor,
MediaHelpers,
Signal,
TLAsset,
TLAssetStore,
TLUserPreferences,
getHashForString,
uniqueId,
} from 'tldraw'
import { RemoteTLStoreWithStatus, useMultiplayerSync } from './useMultiplayerSync'
/** @public */
export interface UseDemoSyncClientConfig {
export interface UseMultiplayerDemoOptions {
roomId: string
userPreferences?: Signal<TLUserPreferences>
/** @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 IMAGE_WORKER = getEnv(() => process.env.IMAGE_WORKER) ?? 'https://images.tldraw.xyz'
export function useDemoRemoteSyncClient({
export function useMultiplayerDemo({
roomId,
userPreferences,
host = DEMO_WORKER,
}: UseDemoSyncClientConfig): RemoteTLStoreWithStatus {
}: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus {
const assets = useMemo(() => createDemoAssetStore(host), [host])
return useRemoteSyncClient({
return useMultiplayerSync({
uri: `${host}/connect/${roomId}`,
roomId,
userPreferences,
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: {},
}
}
}