[bemo] allow custom shapes (#4144)
I tested this by adding a custom shape on the bemo example page, it
works 👍🏼
### Change type
- [ ] `bugfix`
- [ ] `improvement`
- [x] `feature`
- [ ] `api`
- [ ] `other`
### Test plan
1. Create a shape...
2.
- [ ] Unit tests
- [ ] End to end tests
### Release notes
- Fixed a bug with…
This commit is contained in:
parent
69a1c17b46
commit
34eaf12bff
14 changed files with 79 additions and 19 deletions
|
@ -5,6 +5,7 @@ import { T } from '@tldraw/validate'
|
|||
import { createPersistQueue, createSentry, parseRequestQuery } from '@tldraw/worker-shared'
|
||||
import { DurableObject } from 'cloudflare:workers'
|
||||
import { IRequest, Router } from 'itty-router'
|
||||
import { makePermissiveSchema } from './makePermissiveSchema'
|
||||
import { Environment } from './types'
|
||||
|
||||
const connectRequestQuery = T.object({
|
||||
|
@ -106,6 +107,7 @@ export class BemoDO extends DurableObject<Environment> {
|
|||
if (!this._room) {
|
||||
this._room = this.loadFromDatabase(slug).then((result) => {
|
||||
return new TLSocketRoom<TLRecord, void>({
|
||||
schema: makePermissiveSchema(),
|
||||
initialSnapshot: result.type === 'room_found' ? result.snapshot : undefined,
|
||||
onSessionRemoved: async (room, args) => {
|
||||
if (args.numSessionsRemaining > 0) return
|
||||
|
|
49
apps/bemo-worker/src/makePermissiveSchema.ts
Normal file
49
apps/bemo-worker/src/makePermissiveSchema.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import {
|
||||
TLBaseBinding,
|
||||
TLBaseShape,
|
||||
TLSchema,
|
||||
bindingIdValidator,
|
||||
createTLSchema,
|
||||
opacityValidator,
|
||||
parentIdValidator,
|
||||
shapeIdValidator,
|
||||
} from '@tldraw/tlschema'
|
||||
import { T } from '@tldraw/validate'
|
||||
|
||||
export function makePermissiveSchema(): TLSchema {
|
||||
const schema = createTLSchema()
|
||||
|
||||
const shapeValidator = T.object<TLBaseShape<any, any>>({
|
||||
id: shapeIdValidator,
|
||||
typeName: T.literal('shape'),
|
||||
x: T.number,
|
||||
y: T.number,
|
||||
rotation: T.number,
|
||||
index: T.indexKey,
|
||||
parentId: parentIdValidator,
|
||||
type: T.string,
|
||||
isLocked: T.boolean,
|
||||
opacity: opacityValidator,
|
||||
props: T.jsonValue as any,
|
||||
meta: T.jsonValue as any,
|
||||
}) as (typeof schema)['types']['shape']['validator']
|
||||
|
||||
const shapeType = schema.getType('shape')
|
||||
// @ts-expect-error
|
||||
shapeType.validator = shapeValidator
|
||||
|
||||
const bindingValidator = T.object<TLBaseBinding<any, any>>({
|
||||
id: bindingIdValidator,
|
||||
typeName: T.literal('binding'),
|
||||
type: T.string,
|
||||
fromId: shapeIdValidator,
|
||||
toId: shapeIdValidator,
|
||||
props: T.jsonValue as any,
|
||||
meta: T.jsonValue as any,
|
||||
}) as (typeof schema)['types']['binding']['validator']
|
||||
|
||||
const bindingType = schema.getType('binding')
|
||||
// @ts-expect-error
|
||||
bindingType.validator = bindingValidator
|
||||
return schema
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { CreateRoomRequestBody } from '@tldraw/dotcom-shared'
|
||||
import { RoomSnapshot, schema } from '@tldraw/sync-core'
|
||||
import { RoomSnapshot } from '@tldraw/sync-core'
|
||||
import { createTLSchema } from '@tldraw/tlschema'
|
||||
import { IRequest } from 'itty-router'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { getR2KeyForRoom } from '../r2'
|
||||
|
@ -22,7 +23,7 @@ export async function createRoom(request: IRequest, env: Environment): Promise<R
|
|||
|
||||
// Create the new snapshot
|
||||
const snapshot: RoomSnapshot = {
|
||||
schema: schema.serialize(),
|
||||
schema: createTLSchema().serialize(),
|
||||
clock: 0,
|
||||
documents: Object.values(snapshotResult.value).map((r) => ({
|
||||
state: r,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { SerializedSchema, SerializedStore } from '@tldraw/store'
|
||||
import { schema } from '@tldraw/sync-core'
|
||||
import { TLRecord } from '@tldraw/tlschema'
|
||||
import { TLRecord, createTLSchema } from '@tldraw/tlschema'
|
||||
import { Result, objectMapEntries } from '@tldraw/utils'
|
||||
|
||||
interface SnapshotRequestBody {
|
||||
|
@ -8,6 +7,8 @@ interface SnapshotRequestBody {
|
|||
snapshot: SerializedStore<TLRecord>
|
||||
}
|
||||
|
||||
const schema = createTLSchema()
|
||||
|
||||
export function validateSnapshot(
|
||||
body: SnapshotRequestBody
|
||||
): Result<SerializedStore<TLRecord>, string> {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { ROOM_PREFIX, Snapshot } from '@tldraw/dotcom-shared'
|
||||
import { schema } from '@tldraw/sync-core'
|
||||
import { Navigate } from 'react-router-dom'
|
||||
import { createTLSchema } from 'tldraw'
|
||||
import '../../styles/globals.css'
|
||||
import { ErrorPage } from '../components/ErrorPage/ErrorPage'
|
||||
import { defineLoader } from '../utils/defineLoader'
|
||||
import { isInIframe } from '../utils/iFrame'
|
||||
import { getNewRoomResponse } from '../utils/sharing'
|
||||
|
||||
const schema = createTLSchema()
|
||||
|
||||
const { loader, useData } = defineLoader(async (_args) => {
|
||||
if (isInIframe()) return null
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { CreateRoomRequestBody, ROOM_PREFIX, Snapshot } from '@tldraw/dotcom-shared'
|
||||
import { schema } from '@tldraw/sync-core'
|
||||
import { useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { TldrawUiButton, fetch } from 'tldraw'
|
||||
import { TldrawUiButton, createTLSchema, fetch } from 'tldraw'
|
||||
import '../../styles/globals.css'
|
||||
import { getParentOrigin } from '../utils/iFrame'
|
||||
|
||||
const schema = createTLSchema()
|
||||
|
||||
export function Component() {
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const [isSessionStarted, setIsSessionStarted] = useState(false)
|
||||
|
|
|
@ -75,6 +75,7 @@ export class MyShapeUtil extends ShapeUtil<ICustomShape> {
|
|||
|
||||
// [3]
|
||||
const customShape = [MyShapeUtil]
|
||||
|
||||
export default function CustomShapeExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
|
|
|
@ -33,5 +33,4 @@ export {
|
|||
type TLSocketClientSentEvent,
|
||||
type TLSocketServerSentEvent,
|
||||
} from './lib/protocol'
|
||||
export { schema } from './lib/schema'
|
||||
export type { PersistedRoomSnapshotForSupabase } from './lib/server-types'
|
||||
|
|
|
@ -22,7 +22,7 @@ export class TLSocketRoom<R extends UnknownRecord, SessionMeta> {
|
|||
constructor(
|
||||
public readonly opts: {
|
||||
initialSnapshot?: RoomSnapshot
|
||||
schema?: StoreSchema<R>
|
||||
schema?: StoreSchema<R, any>
|
||||
// how long to wait for a client to communicate before disconnecting them
|
||||
clientTimeout?: number
|
||||
log?: TLSyncLog
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { createTLSchema, defaultShapeSchemas } from '@tldraw/tlschema'
|
||||
|
||||
export const schema = createTLSchema({
|
||||
shapes: defaultShapeSchemas,
|
||||
})
|
|
@ -18,8 +18,8 @@ import {
|
|||
TLSyncRoom,
|
||||
TOMBSTONE_PRUNE_BUFFER_SIZE,
|
||||
} from '../lib/TLSyncRoom'
|
||||
import { schema } from '../lib/schema'
|
||||
|
||||
const schema = createTLSchema()
|
||||
const compareById = (a: { id: string }, b: { id: string }) => a.id.localeCompare(b.id)
|
||||
|
||||
const records = [
|
||||
|
|
|
@ -7,17 +7,19 @@ import {
|
|||
TLStore,
|
||||
computed,
|
||||
createPresenceStateDerivation,
|
||||
createTLSchema,
|
||||
createTLStore,
|
||||
isRecordsDiffEmpty,
|
||||
} from 'tldraw'
|
||||
import { prettyPrintDiff } from '../../../tldraw/src/test/testutils/pretty'
|
||||
import { TLSyncClient } from '../lib/TLSyncClient'
|
||||
import { schema } from '../lib/schema'
|
||||
import { FuzzEditor, Op } from './FuzzEditor'
|
||||
import { RandomSource } from './RandomSource'
|
||||
import { TestServer } from './TestServer'
|
||||
import { TestSocketPair } from './TestSocketPair'
|
||||
|
||||
const schema = createTLSchema()
|
||||
|
||||
jest.mock('@tldraw/editor/src/lib/editor/managers/TickManager.ts', () => {
|
||||
return {
|
||||
TickManager: class {
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
TLPersistentClientSocketStatus,
|
||||
TLRemoteSyncError,
|
||||
TLSyncClient,
|
||||
schema,
|
||||
} from '@tldraw/sync-core'
|
||||
import { useEffect } from 'react'
|
||||
import {
|
||||
|
@ -14,11 +13,13 @@ import {
|
|||
TAB_ID,
|
||||
TLAssetStore,
|
||||
TLRecord,
|
||||
TLSchema,
|
||||
TLStore,
|
||||
TLStoreWithStatus,
|
||||
TLUserPreferences,
|
||||
computed,
|
||||
createPresenceStateDerivation,
|
||||
createTLSchema,
|
||||
createTLStore,
|
||||
defaultUserPreferences,
|
||||
getUserPreferences,
|
||||
|
@ -48,6 +49,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto
|
|||
assets,
|
||||
onEditorMount,
|
||||
trackAnalyticsEvent: track,
|
||||
schema,
|
||||
} = opts
|
||||
|
||||
const error: NonNullable<typeof state>['error'] = state?.error ?? undefined
|
||||
|
@ -91,7 +93,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto
|
|||
|
||||
const store = createTLStore({
|
||||
id: storeId,
|
||||
schema,
|
||||
schema: schema ?? createTLSchema(),
|
||||
assets,
|
||||
onEditorMount,
|
||||
multiplayerStatus: computed('multiplayer status', () =>
|
||||
|
@ -133,7 +135,7 @@ export function useMultiplayerSync(opts: UseMultiplayerSyncOptions): RemoteTLSto
|
|||
client.close()
|
||||
socket.close()
|
||||
}
|
||||
}, [assets, error, onEditorMount, prefs, roomId, setState, track, uri])
|
||||
}, [assets, error, onEditorMount, prefs, roomId, setState, track, uri, schema])
|
||||
|
||||
return useValue<RemoteTLStoreWithStatus>(
|
||||
'remote synced store',
|
||||
|
@ -161,4 +163,5 @@ export interface UseMultiplayerSyncOptions {
|
|||
trackAnalyticsEvent?(name: string, data: { [key: string]: any }): void
|
||||
assets?: Partial<TLAssetStore>
|
||||
onEditorMount?: (editor: Editor) => void
|
||||
schema?: TLSchema
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
Signal,
|
||||
TLAsset,
|
||||
TLAssetStore,
|
||||
TLSchema,
|
||||
TLUserPreferences,
|
||||
getHashForString,
|
||||
uniqueId,
|
||||
|
@ -18,6 +19,7 @@ export interface UseMultiplayerDemoOptions {
|
|||
userPreferences?: Signal<TLUserPreferences>
|
||||
/** @internal */
|
||||
host?: string
|
||||
schema?: TLSchema
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,6 +45,7 @@ export function useMultiplayerDemo({
|
|||
roomId,
|
||||
userPreferences,
|
||||
host = DEMO_WORKER,
|
||||
schema,
|
||||
}: UseMultiplayerDemoOptions): RemoteTLStoreWithStatus {
|
||||
const assets = useMemo(() => createDemoAssetStore(host), [host])
|
||||
|
||||
|
@ -51,6 +54,7 @@ export function useMultiplayerDemo({
|
|||
roomId,
|
||||
userPreferences,
|
||||
assets,
|
||||
schema,
|
||||
onEditorMount: useCallback(
|
||||
(editor: Editor) => {
|
||||
editor.registerExternalAssetHandler('url', async ({ url }) => {
|
||||
|
|
Loading…
Reference in a new issue