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 />
|
<WifiIcon />
|
||||||
<div>Live Example</div>
|
<div>Live Example</div>
|
||||||
<button
|
<button
|
||||||
|
className="MultiplayerExampleWrapper-copy"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// copy current url with roomId=roomId to clipboard
|
// copy current url with roomId=roomId to clipboard
|
||||||
navigator.clipboard.writeText(window.location.href.split('?')[0] + `?roomId=${roomId}`)
|
navigator.clipboard.writeText(window.location.href.split('?')[0] + `?roomId=${roomId}`)
|
||||||
|
@ -99,7 +100,8 @@ function MultiplayerExampleWrapper({
|
||||||
}}
|
}}
|
||||||
aria-label="join"
|
aria-label="join"
|
||||||
>
|
>
|
||||||
{confirm ? 'Copied!' : 'Copy Share URL'}
|
Copy link
|
||||||
|
{confirm && <div className="MultiplayerExampleWrapper-copied">Copied!</div>}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="MultiplayerExampleWrapper-example">
|
<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);
|
color: var(--text);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.MultiplayerExampleWrapper-picker button {
|
.MultiplayerExampleWrapper-copy {
|
||||||
background: var(--black-transparent-lighter);
|
background: var(--black-transparent-lighter);
|
||||||
border: 1px solid var(--gray-dark);
|
border: 1px solid var(--gray-dark);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
|
height: 24px;
|
||||||
cursor: pointer;
|
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);
|
border: 1px solid var(--black-transparent-lighter);
|
||||||
}
|
}
|
||||||
.MultiplayerExampleWrapper-example {
|
.MultiplayerExampleWrapper-example {
|
||||||
|
|
|
@ -490,6 +490,9 @@ export function counterClockwiseAngleDist(a0: number, a1: number): number;
|
||||||
// @public
|
// @public
|
||||||
export function createSessionStateSnapshotSignal(store: TLStore): Signal<null | TLSessionStateSnapshot>;
|
export function createSessionStateSnapshotSignal(store: TLStore): Signal<null | TLSessionStateSnapshot>;
|
||||||
|
|
||||||
|
// @public
|
||||||
|
export function createTLSchemaFromUtils(opts: TLStoreSchemaOptions): StoreSchema<TLRecord, TLStoreProps>;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function createTLStore({ initialData, defaultName, id, assets, onEditorMount, multiplayerStatus, ...rest }?: TLStoreOptions): TLStore;
|
export function createTLStore({ initialData, defaultName, id, assets, onEditorMount, multiplayerStatus, ...rest }?: TLStoreOptions): TLStore;
|
||||||
|
|
||||||
|
@ -3273,15 +3276,18 @@ export interface TLStoreBaseOptions {
|
||||||
export type TLStoreEventInfo = HistoryEntry<TLRecord>;
|
export type TLStoreEventInfo = HistoryEntry<TLRecord>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLStoreOptions = TLStoreBaseOptions & ({
|
export type TLStoreOptions = TLStoreBaseOptions & {
|
||||||
bindingUtils?: readonly TLAnyBindingUtilConstructor[];
|
|
||||||
id?: string;
|
id?: string;
|
||||||
|
} & TLStoreSchemaOptions;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export type TLStoreSchemaOptions = {
|
||||||
|
bindingUtils?: readonly TLAnyBindingUtilConstructor[];
|
||||||
migrations?: readonly MigrationSequence[];
|
migrations?: readonly MigrationSequence[];
|
||||||
shapeUtils?: readonly TLAnyShapeUtilConstructor[];
|
shapeUtils?: readonly TLAnyShapeUtilConstructor[];
|
||||||
} | {
|
} | {
|
||||||
id?: string;
|
|
||||||
schema?: StoreSchema<TLRecord, TLStoreProps>;
|
schema?: StoreSchema<TLRecord, TLStoreProps>;
|
||||||
});
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLStoreWithStatus = {
|
export type TLStoreWithStatus = {
|
||||||
|
@ -3519,7 +3525,7 @@ export function useSelectionEvents(handle: TLSelectionHandle): {
|
||||||
export function useShallowArrayIdentity<T>(arr: readonly T[]): readonly T[];
|
export function useShallowArrayIdentity<T>(arr: readonly T[]): readonly T[];
|
||||||
|
|
||||||
// @internal (undocumented)
|
// @internal (undocumented)
|
||||||
export function useShallowObjectIdentity<T extends object>(arr: T): T;
|
export function useShallowObjectIdentity<T extends object>(obj: T): T;
|
||||||
|
|
||||||
export { useStateTracking }
|
export { useStateTracking }
|
||||||
|
|
||||||
|
@ -3528,6 +3534,9 @@ export function useSvgExportContext(): {
|
||||||
isDarkMode: boolean;
|
isDarkMode: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export function useTLSchemaFromUtils(opts: TLStoreSchemaOptions): StoreSchema<TLRecord, TLStoreProps>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useTLStore(opts: TLStoreOptions & {
|
export function useTLStore(opts: TLStoreOptions & {
|
||||||
snapshot?: TLEditorSnapshot | TLStoreSnapshot;
|
snapshot?: TLEditorSnapshot | TLStoreSnapshot;
|
||||||
|
|
|
@ -125,10 +125,12 @@ export {
|
||||||
type TLUserPreferences,
|
type TLUserPreferences,
|
||||||
} from './lib/config/TLUserPreferences'
|
} from './lib/config/TLUserPreferences'
|
||||||
export {
|
export {
|
||||||
|
createTLSchemaFromUtils,
|
||||||
createTLStore,
|
createTLStore,
|
||||||
type TLStoreBaseOptions,
|
type TLStoreBaseOptions,
|
||||||
type TLStoreEventInfo,
|
type TLStoreEventInfo,
|
||||||
type TLStoreOptions,
|
type TLStoreOptions,
|
||||||
|
type TLStoreSchemaOptions,
|
||||||
} from './lib/config/createTLStore'
|
} from './lib/config/createTLStore'
|
||||||
export { createTLUser, type TLUser } from './lib/config/createTLUser'
|
export { createTLUser, type TLUser } from './lib/config/createTLUser'
|
||||||
export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings'
|
export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings'
|
||||||
|
@ -280,7 +282,7 @@ export { usePresence } from './lib/hooks/usePresence'
|
||||||
export { useRefState } from './lib/hooks/useRefState'
|
export { useRefState } from './lib/hooks/useRefState'
|
||||||
export { useSafeId } from './lib/hooks/useSafeId'
|
export { useSafeId } from './lib/hooks/useSafeId'
|
||||||
export { useSelectionEvents } from './lib/hooks/useSelectionEvents'
|
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 { useTransform } from './lib/hooks/useTransform'
|
||||||
export { defaultTldrawOptions, type TldrawOptions } from './lib/options'
|
export { defaultTldrawOptions, type TldrawOptions } from './lib/options'
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -32,19 +32,18 @@ export interface TLStoreBaseOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLStoreOptions = TLStoreBaseOptions &
|
export type TLStoreSchemaOptions =
|
||||||
(
|
| {
|
||||||
| {
|
schema?: StoreSchema<TLRecord, TLStoreProps>
|
||||||
id?: string
|
}
|
||||||
shapeUtils?: readonly TLAnyShapeUtilConstructor[]
|
| {
|
||||||
migrations?: readonly MigrationSequence[]
|
shapeUtils?: readonly TLAnyShapeUtilConstructor[]
|
||||||
bindingUtils?: readonly TLAnyBindingUtilConstructor[]
|
migrations?: readonly MigrationSequence[]
|
||||||
}
|
bindingUtils?: readonly TLAnyBindingUtilConstructor[]
|
||||||
| {
|
}
|
||||||
id?: string
|
|
||||||
schema?: StoreSchema<TLRecord, TLStoreProps>
|
/** @public */
|
||||||
}
|
export type TLStoreOptions = TLStoreBaseOptions & { id?: string } & TLStoreSchemaOptions
|
||||||
)
|
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type TLStoreEventInfo = HistoryEntry<TLRecord>
|
export type TLStoreEventInfo = HistoryEntry<TLRecord>
|
||||||
|
@ -55,12 +54,39 @@ export const defaultAssetStore: TLAssetStore = {
|
||||||
resolve: (asset) => asset.props.src,
|
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.
|
* A helper for creating a TLStore.
|
||||||
*
|
*
|
||||||
* @param opts - Options for creating the store.
|
* @param opts - Options for creating the store.
|
||||||
*
|
*
|
||||||
* @public */
|
* @public
|
||||||
|
*/
|
||||||
export function createTLStore({
|
export function createTLStore({
|
||||||
initialData,
|
initialData,
|
||||||
defaultName = '',
|
defaultName = '',
|
||||||
|
@ -70,22 +96,7 @@ export function createTLStore({
|
||||||
multiplayerStatus,
|
multiplayerStatus,
|
||||||
...rest
|
...rest
|
||||||
}: TLStoreOptions = {}): TLStore {
|
}: TLStoreOptions = {}): TLStore {
|
||||||
const schema =
|
const schema = createTLSchemaFromUtils(rest)
|
||||||
'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,
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Store({
|
return new Store({
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -16,6 +16,6 @@ export function useShallowArrayIdentity<T>(arr: readonly T[]): readonly T[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function useShallowObjectIdentity<T extends object>(arr: T): T {
|
export function useShallowObjectIdentity<T extends object>(obj: T): T {
|
||||||
return useIdentity(arr, areObjectsShallowEqual)
|
return useIdentity(obj, areObjectsShallowEqual)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,12 @@ import { TLStoreSnapshot } from '@tldraw/tlschema'
|
||||||
import { areObjectsShallowEqual } from '@tldraw/utils'
|
import { areObjectsShallowEqual } from '@tldraw/utils'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { TLEditorSnapshot, loadSnapshot } from '../config/TLEditorSnapshot'
|
import { TLEditorSnapshot, loadSnapshot } from '../config/TLEditorSnapshot'
|
||||||
import { TLStoreOptions, createTLStore } from '../config/createTLStore'
|
import {
|
||||||
|
TLStoreOptions,
|
||||||
|
TLStoreSchemaOptions,
|
||||||
|
createTLSchemaFromUtils,
|
||||||
|
createTLStore,
|
||||||
|
} from '../config/createTLStore'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
type UseTLStoreOptions = TLStoreOptions & {
|
type UseTLStoreOptions = TLStoreOptions & {
|
||||||
|
@ -31,3 +36,16 @@ export function useTLStore(
|
||||||
|
|
||||||
return current.store
|
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 { Editor } from 'tldraw';
|
||||||
import { Signal } from 'tldraw';
|
import { Signal } from 'tldraw';
|
||||||
import { TLAssetStore } from 'tldraw';
|
import { TLAssetStore } from 'tldraw';
|
||||||
import { TLSchema } from 'tldraw';
|
import { TLStoreSchemaOptions } from 'tldraw';
|
||||||
import { TLStoreWithStatus } from 'tldraw';
|
import { TLStoreWithStatus } from 'tldraw';
|
||||||
import { TLUserPreferences } from 'tldraw';
|
import { TLUserPreferences } from 'tldraw';
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export type RemoteTLStoreWithStatus = Exclude<TLStoreWithStatus, {
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus;
|
export function useMultiplayerDemo(options: UseMultiplayerDemoOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface UseMultiplayerDemoOptions {
|
export interface UseMultiplayerDemoOptions {
|
||||||
|
@ -28,13 +28,11 @@ export interface UseMultiplayerDemoOptions {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
roomId: string;
|
roomId: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
schema?: TLSchema;
|
|
||||||
// (undocumented)
|
|
||||||
userPreferences?: Signal<TLUserPreferences>;
|
userPreferences?: Signal<TLUserPreferences>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLStoreWithStatus;
|
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface UseMultiplayerSyncOptions {
|
export interface UseMultiplayerSyncOptions {
|
||||||
|
@ -45,8 +43,6 @@ export interface UseMultiplayerSyncOptions {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
schema?: TLSchema;
|
|
||||||
// (undocumented)
|
|
||||||
trackAnalyticsEvent?(name: string, data: {
|
trackAnalyticsEvent?(name: string, data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}): void;
|
}): void;
|
||||||
|
|
|
@ -13,18 +13,18 @@ import {
|
||||||
TAB_ID,
|
TAB_ID,
|
||||||
TLAssetStore,
|
TLAssetStore,
|
||||||
TLRecord,
|
TLRecord,
|
||||||
TLSchema,
|
|
||||||
TLStore,
|
TLStore,
|
||||||
|
TLStoreSchemaOptions,
|
||||||
TLStoreWithStatus,
|
TLStoreWithStatus,
|
||||||
TLUserPreferences,
|
TLUserPreferences,
|
||||||
computed,
|
computed,
|
||||||
createPresenceStateDerivation,
|
createPresenceStateDerivation,
|
||||||
createTLSchema,
|
|
||||||
createTLStore,
|
createTLStore,
|
||||||
defaultUserPreferences,
|
defaultUserPreferences,
|
||||||
getUserPreferences,
|
getUserPreferences,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
useRefState,
|
useRefState,
|
||||||
|
useTLSchemaFromUtils,
|
||||||
useValue,
|
useValue,
|
||||||
} from 'tldraw'
|
} from 'tldraw'
|
||||||
|
|
||||||
|
@ -37,7 +37,9 @@ export type RemoteTLStoreWithStatus = Exclude<
|
||||||
>
|
>
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLStoreWithStatus {
|
export function useMultiplayerSync(
|
||||||
|
opts: UseMultiplayerSyncOptions & TLStoreSchemaOptions
|
||||||
|
): RemoteTLStoreWithStatus {
|
||||||
const [state, setState] = useRefState<{
|
const [state, setState] = useRefState<{
|
||||||
readyClient?: TLSyncClient<TLRecord, TLStore>
|
readyClient?: TLSyncClient<TLRecord, TLStore>
|
||||||
error?: Error
|
error?: Error
|
||||||
|
@ -49,9 +51,11 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto
|
||||||
assets,
|
assets,
|
||||||
onEditorMount,
|
onEditorMount,
|
||||||
trackAnalyticsEvent: track,
|
trackAnalyticsEvent: track,
|
||||||
schema,
|
...schemaOpts
|
||||||
} = opts
|
} = opts
|
||||||
|
|
||||||
|
const schema = useTLSchemaFromUtils(schemaOpts)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storeId = uniqueId()
|
const storeId = uniqueId()
|
||||||
|
|
||||||
|
@ -89,7 +93,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto
|
||||||
|
|
||||||
const store = createTLStore({
|
const store = createTLStore({
|
||||||
id: storeId,
|
id: storeId,
|
||||||
schema: schema ?? createTLSchema(),
|
schema,
|
||||||
assets,
|
assets,
|
||||||
onEditorMount,
|
onEditorMount,
|
||||||
multiplayerStatus: computed('multiplayer status', () =>
|
multiplayerStatus: computed('multiplayer status', () =>
|
||||||
|
@ -160,5 +164,4 @@ export interface UseMultiplayerSyncOptions {
|
||||||
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
|
onEditorMount?: (editor: Editor) => void
|
||||||
schema?: TLSchema
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,13 @@ import {
|
||||||
Signal,
|
Signal,
|
||||||
TLAsset,
|
TLAsset,
|
||||||
TLAssetStore,
|
TLAssetStore,
|
||||||
TLSchema,
|
TLStoreSchemaOptions,
|
||||||
TLUserPreferences,
|
TLUserPreferences,
|
||||||
|
defaultBindingUtils,
|
||||||
|
defaultShapeUtils,
|
||||||
getHashForString,
|
getHashForString,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
|
useShallowObjectIdentity,
|
||||||
} from 'tldraw'
|
} from 'tldraw'
|
||||||
import { RemoteTLStoreWithStatus, useMultiplayerSync } from './useMultiplayerSync'
|
import { RemoteTLStoreWithStatus, useMultiplayerSync } from './useMultiplayerSync'
|
||||||
|
|
||||||
|
@ -19,7 +22,6 @@ export interface UseMultiplayerDemoOptions {
|
||||||
userPreferences?: Signal<TLUserPreferences>
|
userPreferences?: Signal<TLUserPreferences>
|
||||||
/** @internal */
|
/** @internal */
|
||||||
host?: string
|
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'
|
const IMAGE_WORKER = getEnv(() => process.env.TLDRAW_IMAGE_URL) ?? 'https://images.tldraw.xyz'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus {
|
export function useMultiplayerDemo(
|
||||||
const { roomId, userPreferences, host = DEMO_WORKER, schema } = options
|
options: UseMultiplayerDemoOptions & TLStoreSchemaOptions
|
||||||
|
): RemoteTLStoreWithStatus {
|
||||||
|
const { roomId, userPreferences, host = DEMO_WORKER, ..._schemaOpts } = options
|
||||||
const assets = useMemo(() => createDemoAssetStore(host), [host])
|
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({
|
return useMultiplayerSync({
|
||||||
uri: `${host}/connect/${encodeURIComponent(roomId)}`,
|
uri: `${host}/connect/${encodeURIComponent(roomId)}`,
|
||||||
roomId,
|
roomId,
|
||||||
userPreferences,
|
userPreferences,
|
||||||
assets,
|
assets,
|
||||||
schema,
|
|
||||||
onEditorMount: useCallback(
|
onEditorMount: useCallback(
|
||||||
(editor: Editor) => {
|
(editor: Editor) => {
|
||||||
editor.registerExternalAssetHandler('url', async ({ url }) => {
|
editor.registerExternalAssetHandler('url', async ({ url }) => {
|
||||||
|
@ -60,6 +80,7 @@ export function useMultiplayerDemo(options: UseMultiplayerDemoOptions): RemoteTL
|
||||||
},
|
},
|
||||||
[host]
|
[host]
|
||||||
),
|
),
|
||||||
|
...schemaOptsWithDefaults,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue