bemo custom shape example (#4174)
Adds a custom shape bemo example. Instead of having to create a schema ahead of time, here i've added bindingUtils/shapeUtils props to our sync hooks to match the ones we have in the `<Tldraw />` component. Not 100% about this though, I could easily be convinced to go with just the schema prop. ### Change type - [x] `other`
This commit is contained in:
parent
c8ebe57e24
commit
43811d54ba
14 changed files with 279 additions and 60 deletions
|
@ -92,6 +92,7 @@ function MultiplayerExampleWrapper({
|
|||
<WifiIcon />
|
||||
<div>Live Example</div>
|
||||
<button
|
||||
className="MultiplayerExampleWrapper-copy"
|
||||
onClick={() => {
|
||||
// copy current url with roomId=roomId to clipboard
|
||||
navigator.clipboard.writeText(window.location.href.split('?')[0] + `?roomId=${roomId}`)
|
||||
|
@ -99,7 +100,8 @@ function MultiplayerExampleWrapper({
|
|||
}}
|
||||
aria-label="join"
|
||||
>
|
||||
{confirm ? 'Copied!' : 'Copy Share URL'}
|
||||
Copy link
|
||||
{confirm && <div className="MultiplayerExampleWrapper-copied">Copied!</div>}
|
||||
</button>
|
||||
</div>
|
||||
<div className="MultiplayerExampleWrapper-example">
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { MouseEvent } from 'react'
|
||||
import {
|
||||
BaseBoxShapeTool,
|
||||
BaseBoxShapeUtil,
|
||||
HTMLContainer,
|
||||
T,
|
||||
TLBaseShape,
|
||||
stopEventPropagation,
|
||||
} from 'tldraw'
|
||||
|
||||
type CounterShape = TLBaseShape<'counter', { w: number; h: number; count: number }>
|
||||
|
||||
export class CounterShapeUtil extends BaseBoxShapeUtil<CounterShape> {
|
||||
static override type = 'counter' as const
|
||||
static override props = {
|
||||
w: T.positiveNumber,
|
||||
h: T.positiveNumber,
|
||||
count: T.number,
|
||||
}
|
||||
|
||||
override getDefaultProps() {
|
||||
return {
|
||||
w: 200,
|
||||
h: 200,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
override component(shape: CounterShape) {
|
||||
const onClick = (event: MouseEvent, change: number) => {
|
||||
event.stopPropagation()
|
||||
this.editor.updateShape({
|
||||
id: shape.id,
|
||||
type: 'counter',
|
||||
props: { count: shape.props.count + change },
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
style={{
|
||||
pointerEvents: 'all',
|
||||
background: '#efefef',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<button onClick={(e) => onClick(e, -1)} onPointerDown={stopEventPropagation}>
|
||||
-
|
||||
</button>
|
||||
<span>{shape.props.count}</span>
|
||||
<button onClick={(e) => onClick(e, 1)} onPointerDown={stopEventPropagation}>
|
||||
+
|
||||
</button>
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
override indicator(shape: CounterShape) {
|
||||
return <rect width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
}
|
||||
|
||||
export class CounterShapeTool extends BaseBoxShapeTool {
|
||||
static override id = 'counter'
|
||||
override shapeType = 'counter'
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { useMultiplayerDemo } from '@tldraw/sync'
|
||||
import { Tldraw } from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import { CounterShapeTool, CounterShapeUtil } from './CounterShape'
|
||||
import { components, uiOverrides } from './ui'
|
||||
|
||||
const customShapes = [CounterShapeUtil]
|
||||
const customTools = [CounterShapeTool]
|
||||
|
||||
export default function MultiplayerCustomShapeExample({ roomId }: { roomId: string }) {
|
||||
const store = useMultiplayerDemo({ roomId, shapeUtils: customShapes })
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
store={store}
|
||||
shapeUtils={customShapes}
|
||||
tools={customTools}
|
||||
overrides={uiOverrides}
|
||||
components={components}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Multiplayer demo with custom shape
|
||||
component: ./MultiplayerDemoExample.tsx
|
||||
category: basic
|
||||
priority: 3
|
||||
keywords: [basic, intro, simple, quick, start, multiplayer, sync, collaboration, custom shape]
|
||||
multiplayer: true
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
The `useMultiplayerDemo` hook can be used to quickly prototype multiplayer experiences in tldraw using a demo backend that we host. Data is wiped after one day.
|
38
apps/examples/src/examples/multiplayer-custom-shape/ui.tsx
Normal file
38
apps/examples/src/examples/multiplayer-custom-shape/ui.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
DefaultToolbar,
|
||||
DefaultToolbarContent,
|
||||
TLComponents,
|
||||
TLUiOverrides,
|
||||
TldrawUiMenuItem,
|
||||
useIsToolSelected,
|
||||
useTools,
|
||||
} from 'tldraw'
|
||||
|
||||
export const uiOverrides: TLUiOverrides = {
|
||||
tools(editor, tools) {
|
||||
// Create a tool item in the ui's context.
|
||||
tools.counter = {
|
||||
id: 'counter',
|
||||
icon: 'color',
|
||||
label: 'counter',
|
||||
kbd: 'c',
|
||||
onSelect: () => {
|
||||
editor.setCurrentTool('counter')
|
||||
},
|
||||
}
|
||||
return tools
|
||||
},
|
||||
}
|
||||
|
||||
export const components: TLComponents = {
|
||||
Toolbar: (props) => {
|
||||
const tools = useTools()
|
||||
const isCounterSelected = useIsToolSelected(tools['counter'])
|
||||
return (
|
||||
<DefaultToolbar {...props}>
|
||||
<TldrawUiMenuItem {...tools['counter']} isSelected={isCounterSelected} />
|
||||
<DefaultToolbarContent />
|
||||
</DefaultToolbar>
|
||||
)
|
||||
},
|
||||
}
|
|
@ -469,14 +469,29 @@ a.example__sidebar__header-link {
|
|||
color: var(--text);
|
||||
font-size: 14px;
|
||||
}
|
||||
.MultiplayerExampleWrapper-picker button {
|
||||
.MultiplayerExampleWrapper-copy {
|
||||
background: var(--black-transparent-lighter);
|
||||
border: 1px solid var(--gray-dark);
|
||||
border-radius: 4px;
|
||||
padding: 4px 12px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
.MultiplayerExampleWrapper-picker button:hover {
|
||||
.MultiplayerExampleWrapper-copy:has(.MultiplayerExampleWrapper-copied) {
|
||||
color: transparent;
|
||||
}
|
||||
.MultiplayerExampleWrapper-copied {
|
||||
color: var(--text);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.MultiplayerExampleWrapper-copy:hover {
|
||||
border: 1px solid var(--black-transparent-lighter);
|
||||
}
|
||||
.MultiplayerExampleWrapper-example {
|
||||
|
|
|
@ -490,6 +490,9 @@ export function counterClockwiseAngleDist(a0: number, a1: number): number;
|
|||
// @public
|
||||
export function createSessionStateSnapshotSignal(store: TLStore): Signal<null | TLSessionStateSnapshot>;
|
||||
|
||||
// @public
|
||||
export function createTLSchemaFromUtils(opts: TLStoreSchemaOptions): StoreSchema<TLRecord, TLStoreProps>;
|
||||
|
||||
// @public
|
||||
export function createTLStore({ initialData, defaultName, id, assets, onEditorMount, multiplayerStatus, ...rest }?: TLStoreOptions): TLStore;
|
||||
|
||||
|
@ -3273,15 +3276,18 @@ export interface TLStoreBaseOptions {
|
|||
export type TLStoreEventInfo = HistoryEntry<TLRecord>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStoreOptions = TLStoreBaseOptions & ({
|
||||
bindingUtils?: readonly TLAnyBindingUtilConstructor[];
|
||||
export type TLStoreOptions = TLStoreBaseOptions & {
|
||||
id?: string;
|
||||
} & TLStoreSchemaOptions;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStoreSchemaOptions = {
|
||||
bindingUtils?: readonly TLAnyBindingUtilConstructor[];
|
||||
migrations?: readonly MigrationSequence[];
|
||||
shapeUtils?: readonly TLAnyShapeUtilConstructor[];
|
||||
} | {
|
||||
id?: string;
|
||||
schema?: StoreSchema<TLRecord, TLStoreProps>;
|
||||
});
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStoreWithStatus = {
|
||||
|
@ -3519,7 +3525,7 @@ export function useSelectionEvents(handle: TLSelectionHandle): {
|
|||
export function useShallowArrayIdentity<T>(arr: readonly T[]): readonly T[];
|
||||
|
||||
// @internal (undocumented)
|
||||
export function useShallowObjectIdentity<T extends object>(arr: T): T;
|
||||
export function useShallowObjectIdentity<T extends object>(obj: T): T;
|
||||
|
||||
export { useStateTracking }
|
||||
|
||||
|
@ -3528,6 +3534,9 @@ export function useSvgExportContext(): {
|
|||
isDarkMode: boolean;
|
||||
} | null;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useTLSchemaFromUtils(opts: TLStoreSchemaOptions): StoreSchema<TLRecord, TLStoreProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useTLStore(opts: TLStoreOptions & {
|
||||
snapshot?: TLEditorSnapshot | TLStoreSnapshot;
|
||||
|
|
|
@ -125,10 +125,12 @@ export {
|
|||
type TLUserPreferences,
|
||||
} from './lib/config/TLUserPreferences'
|
||||
export {
|
||||
createTLSchemaFromUtils,
|
||||
createTLStore,
|
||||
type TLStoreBaseOptions,
|
||||
type TLStoreEventInfo,
|
||||
type TLStoreOptions,
|
||||
type TLStoreSchemaOptions,
|
||||
} from './lib/config/createTLStore'
|
||||
export { createTLUser, type TLUser } from './lib/config/createTLUser'
|
||||
export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings'
|
||||
|
@ -280,7 +282,7 @@ export { usePresence } from './lib/hooks/usePresence'
|
|||
export { useRefState } from './lib/hooks/useRefState'
|
||||
export { useSafeId } from './lib/hooks/useSafeId'
|
||||
export { useSelectionEvents } from './lib/hooks/useSelectionEvents'
|
||||
export { useTLStore } from './lib/hooks/useTLStore'
|
||||
export { useTLSchemaFromUtils, useTLStore } from './lib/hooks/useTLStore'
|
||||
export { useTransform } from './lib/hooks/useTransform'
|
||||
export { defaultTldrawOptions, type TldrawOptions } from './lib/options'
|
||||
export {
|
||||
|
|
|
@ -32,19 +32,18 @@ export interface TLStoreBaseOptions {
|
|||
}
|
||||
|
||||
/** @public */
|
||||
export type TLStoreOptions = TLStoreBaseOptions &
|
||||
(
|
||||
export type TLStoreSchemaOptions =
|
||||
| {
|
||||
schema?: StoreSchema<TLRecord, TLStoreProps>
|
||||
}
|
||||
| {
|
||||
id?: string
|
||||
shapeUtils?: readonly TLAnyShapeUtilConstructor[]
|
||||
migrations?: readonly MigrationSequence[]
|
||||
bindingUtils?: readonly TLAnyBindingUtilConstructor[]
|
||||
}
|
||||
| {
|
||||
id?: string
|
||||
schema?: StoreSchema<TLRecord, TLStoreProps>
|
||||
}
|
||||
)
|
||||
|
||||
/** @public */
|
||||
export type TLStoreOptions = TLStoreBaseOptions & { id?: string } & TLStoreSchemaOptions
|
||||
|
||||
/** @public */
|
||||
export type TLStoreEventInfo = HistoryEntry<TLRecord>
|
||||
|
@ -55,12 +54,39 @@ export const defaultAssetStore: TLAssetStore = {
|
|||
resolve: (asset) => asset.props.src,
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for creating a TLStore schema from either an object with shapeUtils, bindingUtils, and
|
||||
* migrations, or a schema.
|
||||
*
|
||||
* @param opts - Options for creating the schema.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createTLSchemaFromUtils(
|
||||
opts: TLStoreSchemaOptions
|
||||
): StoreSchema<TLRecord, TLStoreProps> {
|
||||
if ('schema' in opts && opts.schema) return opts.schema
|
||||
|
||||
return createTLSchema({
|
||||
shapes:
|
||||
'shapeUtils' in opts && opts.shapeUtils
|
||||
? utilsToMap(checkShapesAndAddCore(opts.shapeUtils))
|
||||
: undefined,
|
||||
bindings:
|
||||
'bindingUtils' in opts && opts.bindingUtils
|
||||
? utilsToMap(checkBindings(opts.bindingUtils))
|
||||
: undefined,
|
||||
migrations: 'migrations' in opts ? opts.migrations : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for creating a TLStore.
|
||||
*
|
||||
* @param opts - Options for creating the store.
|
||||
*
|
||||
* @public */
|
||||
* @public
|
||||
*/
|
||||
export function createTLStore({
|
||||
initialData,
|
||||
defaultName = '',
|
||||
|
@ -70,22 +96,7 @@ export function createTLStore({
|
|||
multiplayerStatus,
|
||||
...rest
|
||||
}: TLStoreOptions = {}): TLStore {
|
||||
const schema =
|
||||
'schema' in rest && rest.schema
|
||||
? // we have a schema
|
||||
rest.schema
|
||||
: // we need a schema
|
||||
createTLSchema({
|
||||
shapes:
|
||||
'shapeUtils' in rest && rest.shapeUtils
|
||||
? utilsToMap(checkShapesAndAddCore(rest.shapeUtils))
|
||||
: undefined,
|
||||
bindings:
|
||||
'bindingUtils' in rest && rest.bindingUtils
|
||||
? utilsToMap(checkBindings(rest.bindingUtils))
|
||||
: undefined,
|
||||
migrations: 'migrations' in rest ? rest.migrations : undefined,
|
||||
})
|
||||
const schema = createTLSchemaFromUtils(rest)
|
||||
|
||||
return new Store({
|
||||
id,
|
||||
|
|
|
@ -16,6 +16,6 @@ export function useShallowArrayIdentity<T>(arr: readonly T[]): readonly T[] {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
export function useShallowObjectIdentity<T extends object>(arr: T): T {
|
||||
return useIdentity(arr, areObjectsShallowEqual)
|
||||
export function useShallowObjectIdentity<T extends object>(obj: T): T {
|
||||
return useIdentity(obj, areObjectsShallowEqual)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,12 @@ import { TLStoreSnapshot } from '@tldraw/tlschema'
|
|||
import { areObjectsShallowEqual } from '@tldraw/utils'
|
||||
import { useState } from 'react'
|
||||
import { TLEditorSnapshot, loadSnapshot } from '../config/TLEditorSnapshot'
|
||||
import { TLStoreOptions, createTLStore } from '../config/createTLStore'
|
||||
import {
|
||||
TLStoreOptions,
|
||||
TLStoreSchemaOptions,
|
||||
createTLSchemaFromUtils,
|
||||
createTLStore,
|
||||
} from '../config/createTLStore'
|
||||
|
||||
/** @public */
|
||||
type UseTLStoreOptions = TLStoreOptions & {
|
||||
|
@ -31,3 +36,16 @@ export function useTLStore(
|
|||
|
||||
return current.store
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function useTLSchemaFromUtils(opts: TLStoreSchemaOptions) {
|
||||
const [current, setCurrent] = useState(() => ({ opts, schema: createTLSchemaFromUtils(opts) }))
|
||||
|
||||
if (!areObjectsShallowEqual(current.opts, opts)) {
|
||||
const next = createTLSchemaFromUtils(opts)
|
||||
setCurrent({ opts, schema: next })
|
||||
return next
|
||||
}
|
||||
|
||||
return current.schema
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { Editor } from 'tldraw';
|
||||
import { Signal } from 'tldraw';
|
||||
import { TLAssetStore } from 'tldraw';
|
||||
import { TLSchema } from 'tldraw';
|
||||
import { TLStoreSchemaOptions } from 'tldraw';
|
||||
import { TLStoreWithStatus } from 'tldraw';
|
||||
import { TLUserPreferences } from 'tldraw';
|
||||
|
||||
|
@ -19,7 +19,7 @@ export type RemoteTLStoreWithStatus = Exclude<TLStoreWithStatus, {
|
|||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus;
|
||||
export function useMultiplayerDemo(options: UseMultiplayerDemoOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseMultiplayerDemoOptions {
|
||||
|
@ -28,13 +28,11 @@ export interface UseMultiplayerDemoOptions {
|
|||
// (undocumented)
|
||||
roomId: string;
|
||||
// (undocumented)
|
||||
schema?: TLSchema;
|
||||
// (undocumented)
|
||||
userPreferences?: Signal<TLUserPreferences>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLStoreWithStatus;
|
||||
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseMultiplayerSyncOptions {
|
||||
|
@ -45,8 +43,6 @@ export interface UseMultiplayerSyncOptions {
|
|||
// (undocumented)
|
||||
roomId?: string;
|
||||
// (undocumented)
|
||||
schema?: TLSchema;
|
||||
// (undocumented)
|
||||
trackAnalyticsEvent?(name: string, data: {
|
||||
[key: string]: any;
|
||||
}): void;
|
||||
|
|
|
@ -13,18 +13,18 @@ import {
|
|||
TAB_ID,
|
||||
TLAssetStore,
|
||||
TLRecord,
|
||||
TLSchema,
|
||||
TLStore,
|
||||
TLStoreSchemaOptions,
|
||||
TLStoreWithStatus,
|
||||
TLUserPreferences,
|
||||
computed,
|
||||
createPresenceStateDerivation,
|
||||
createTLSchema,
|
||||
createTLStore,
|
||||
defaultUserPreferences,
|
||||
getUserPreferences,
|
||||
uniqueId,
|
||||
useRefState,
|
||||
useTLSchemaFromUtils,
|
||||
useValue,
|
||||
} from 'tldraw'
|
||||
|
||||
|
@ -37,7 +37,9 @@ export type RemoteTLStoreWithStatus = Exclude<
|
|||
>
|
||||
|
||||
/** @public */
|
||||
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLStoreWithStatus {
|
||||
export function useMultiplayerSync(
|
||||
opts: UseMultiplayerSyncOptions & TLStoreSchemaOptions
|
||||
): RemoteTLStoreWithStatus {
|
||||
const [state, setState] = useRefState<{
|
||||
readyClient?: TLSyncClient<TLRecord, TLStore>
|
||||
error?: Error
|
||||
|
@ -49,9 +51,11 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto
|
|||
assets,
|
||||
onEditorMount,
|
||||
trackAnalyticsEvent: track,
|
||||
schema,
|
||||
...schemaOpts
|
||||
} = opts
|
||||
|
||||
const schema = useTLSchemaFromUtils(schemaOpts)
|
||||
|
||||
useEffect(() => {
|
||||
const storeId = uniqueId()
|
||||
|
||||
|
@ -89,7 +93,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto
|
|||
|
||||
const store = createTLStore({
|
||||
id: storeId,
|
||||
schema: schema ?? createTLSchema(),
|
||||
schema,
|
||||
assets,
|
||||
onEditorMount,
|
||||
multiplayerStatus: computed('multiplayer status', () =>
|
||||
|
@ -160,5 +164,4 @@ export interface UseMultiplayerSyncOptions {
|
|||
trackAnalyticsEvent?(name: string, data: { [key: string]: any }): void
|
||||
assets?: Partial<TLAssetStore>
|
||||
onEditorMount?: (editor: Editor) => void
|
||||
schema?: TLSchema
|
||||
}
|
||||
|
|
|
@ -6,10 +6,13 @@ import {
|
|||
Signal,
|
||||
TLAsset,
|
||||
TLAssetStore,
|
||||
TLSchema,
|
||||
TLStoreSchemaOptions,
|
||||
TLUserPreferences,
|
||||
defaultBindingUtils,
|
||||
defaultShapeUtils,
|
||||
getHashForString,
|
||||
uniqueId,
|
||||
useShallowObjectIdentity,
|
||||
} from 'tldraw'
|
||||
import { RemoteTLStoreWithStatus, useMultiplayerSync } from './useMultiplayerSync'
|
||||
|
||||
|
@ -19,7 +22,6 @@ export interface UseMultiplayerDemoOptions {
|
|||
userPreferences?: Signal<TLUserPreferences>
|
||||
/** @internal */
|
||||
host?: string
|
||||
schema?: TLSchema
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,16 +44,34 @@ const DEMO_WORKER = getEnv(() => process.env.TLDRAW_BEMO_URL) ?? 'https://demo.t
|
|||
const IMAGE_WORKER = getEnv(() => process.env.TLDRAW_IMAGE_URL) ?? 'https://images.tldraw.xyz'
|
||||
|
||||
/** @public */
|
||||
export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus {
|
||||
const { roomId, userPreferences, host = DEMO_WORKER, schema } = options
|
||||
export function useMultiplayerDemo(
|
||||
options: UseMultiplayerDemoOptions & TLStoreSchemaOptions
|
||||
): RemoteTLStoreWithStatus {
|
||||
const { roomId, userPreferences, host = DEMO_WORKER, ..._schemaOpts } = options
|
||||
const assets = useMemo(() => createDemoAssetStore(host), [host])
|
||||
|
||||
const schemaOpts = useShallowObjectIdentity(_schemaOpts)
|
||||
const schemaOptsWithDefaults = useMemo((): TLStoreSchemaOptions => {
|
||||
if ('schema' in schemaOpts && schemaOpts.schema) return schemaOpts
|
||||
|
||||
return {
|
||||
...schemaOpts,
|
||||
shapeUtils:
|
||||
'shapeUtils' in schemaOpts
|
||||
? [...defaultShapeUtils, ...(schemaOpts.shapeUtils ?? [])]
|
||||
: defaultShapeUtils,
|
||||
bindingUtils:
|
||||
'bindingUtils' in schemaOpts
|
||||
? [...defaultBindingUtils, ...(schemaOpts.bindingUtils ?? [])]
|
||||
: defaultBindingUtils,
|
||||
}
|
||||
}, [schemaOpts])
|
||||
|
||||
return useMultiplayerSync({
|
||||
uri: `${host}/connect/${encodeURIComponent(roomId)}`,
|
||||
roomId,
|
||||
userPreferences,
|
||||
assets,
|
||||
schema,
|
||||
onEditorMount: useCallback(
|
||||
(editor: Editor) => {
|
||||
editor.registerExternalAssetHandler('url', async ({ url }) => {
|
||||
|
@ -60,6 +80,7 @@ export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTL
|
|||
},
|
||||
[host]
|
||||
),
|
||||
...schemaOptsWithDefaults,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue