[refactor] restore createTLSchema (#1444)
This PR restores `createTLSchema`. It also: - removes `TldrawEditorConfig.default` - makes `config` a required property of `<TldrawEditor>`, though it's created automatically in `<Tldraw>`. - makes `config` a required property of `App` - removes `TLShapeType` and replaces the rare usage with `TLShape["type"]` - adds `TLDefaultShape` for a union of our default shapes - makes `TLShape` a union of `TLDefaultShape` and `TLUnknownShape` ### Change Type - [x] `major` — Breaking Change ### Release Notes - [editor] Simplifies custom shape definition - [tldraw] Updates props for <TldrawEditor> component to require a `TldrawEditorConfig`.
This commit is contained in:
parent
f3182c9874
commit
eb26964130
31 changed files with 377 additions and 285 deletions
|
@ -1,13 +1,15 @@
|
|||
import { Canvas, TldrawEditor, useApp } from '@tldraw/tldraw'
|
||||
import { Canvas, TldrawEditor, TldrawEditorConfig, useApp } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import { useEffect } from 'react'
|
||||
import { track } from 'signia-react'
|
||||
import './custom-ui.css'
|
||||
|
||||
const config = new TldrawEditorConfig()
|
||||
|
||||
export default function Example() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<TldrawEditor autoFocus>
|
||||
<TldrawEditor config={config} autoFocus>
|
||||
<Canvas />
|
||||
<CustomUi />
|
||||
</TldrawEditor>
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
ContextMenu,
|
||||
getUserData,
|
||||
TldrawEditor,
|
||||
TldrawEditorConfig,
|
||||
TldrawUi,
|
||||
TLInstance,
|
||||
useLocalSyncClient,
|
||||
|
@ -12,10 +13,13 @@ import '@tldraw/tldraw/ui.css'
|
|||
|
||||
const instanceId = TLInstance.createCustomId('example')
|
||||
|
||||
const config = new TldrawEditorConfig()
|
||||
|
||||
export default function Example() {
|
||||
const userData = getUserData()
|
||||
|
||||
const syncedStore = useLocalSyncClient({
|
||||
config,
|
||||
instanceId,
|
||||
userId: userData.id,
|
||||
universalPersistenceKey: 'exploded-example',
|
||||
|
@ -24,7 +28,13 @@ export default function Example() {
|
|||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<TldrawEditor instanceId={instanceId} userId={userData.id} store={syncedStore} autoFocus>
|
||||
<TldrawEditor
|
||||
instanceId={instanceId}
|
||||
userId={userData.id}
|
||||
store={syncedStore}
|
||||
config={config}
|
||||
autoFocus
|
||||
>
|
||||
<TldrawUi>
|
||||
<ContextMenu>
|
||||
<Canvas />
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
ErrorBoundary,
|
||||
setRuntimeOverrides,
|
||||
TldrawEditor,
|
||||
TldrawEditorConfig,
|
||||
TLUserId,
|
||||
} from '@tldraw/editor'
|
||||
import { linksUiOverrides } from './utils/links'
|
||||
|
@ -24,6 +25,8 @@ import { FullPageMessage } from './FullPageMessage'
|
|||
import { onCreateBookmarkFromUrl } from './utils/bookmarks'
|
||||
import { vscode } from './utils/vscode'
|
||||
|
||||
const config = new TldrawEditorConfig()
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
setRuntimeOverrides({
|
||||
|
@ -96,6 +99,7 @@ export const TldrawWrapper = () => {
|
|||
uri: message.data.uri,
|
||||
userId: message.data.userId as TLUserId,
|
||||
isDarkMode: message.data.isDarkMode,
|
||||
config,
|
||||
})
|
||||
// We only want to listen for this message once
|
||||
window.removeEventListener('message', handleMessage)
|
||||
|
@ -126,20 +130,30 @@ export type TLDrawInnerProps = {
|
|||
uri: string
|
||||
userId: TLUserId
|
||||
isDarkMode: boolean
|
||||
config: TldrawEditorConfig
|
||||
}
|
||||
|
||||
function TldrawInner({ uri, assetSrc, userId, isDarkMode, fileContents }: TLDrawInnerProps) {
|
||||
function TldrawInner({
|
||||
uri,
|
||||
config,
|
||||
assetSrc,
|
||||
userId,
|
||||
isDarkMode,
|
||||
fileContents,
|
||||
}: TLDrawInnerProps) {
|
||||
const instanceId = TAB_ID
|
||||
const syncedStore = useLocalSyncClient({
|
||||
universalPersistenceKey: uri,
|
||||
instanceId,
|
||||
userId,
|
||||
config,
|
||||
})
|
||||
|
||||
const assetUrls = useMemo(() => getAssetUrlsByImport({ baseUrl: assetSrc }), [assetSrc])
|
||||
|
||||
return (
|
||||
<TldrawEditor
|
||||
config={config}
|
||||
assetUrls={assetUrls}
|
||||
instanceId={TAB_ID}
|
||||
userId={userId}
|
||||
|
|
|
@ -4,13 +4,13 @@ import * as vscode from 'vscode'
|
|||
|
||||
export const defaultFileContents: TldrawFile = {
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
records: [],
|
||||
}
|
||||
|
||||
export const fileContentWithErrors: TldrawFile = {
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
records: [{ typeName: 'shape', id: null } as any],
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,16 @@ export async function pointWithinActiveArea(x: number, y: number) {
|
|||
}
|
||||
|
||||
export async function waitForReady() {
|
||||
await browser.waitUntil(() => {
|
||||
return browser.execute(() => {
|
||||
return window.tldrawReady
|
||||
})
|
||||
})
|
||||
await Promise.any([
|
||||
new Promise<boolean>((r) => {
|
||||
browser.waitUntil(() => browser.execute(() => window.tldrawReady)).then(() => r(true))
|
||||
}),
|
||||
new Promise<boolean>((r) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('waitFor failed, using timeout')
|
||||
setTimeout(() => r(true), 2000)
|
||||
}),
|
||||
])
|
||||
|
||||
// Make sure the window is focused... maybe
|
||||
await ui.canvas.click(100, 100)
|
||||
|
|
|
@ -44,7 +44,6 @@ import { sortByIndex } from '@tldraw/indices';
|
|||
import { StoreSchema } from '@tldraw/tlstore';
|
||||
import { StoreSnapshot } from '@tldraw/tlstore';
|
||||
import { StrokePoint } from '@tldraw/primitives';
|
||||
import { T } from '@tldraw/tlvalidate';
|
||||
import { TLAlignType } from '@tldraw/tlschema';
|
||||
import { TLArrowheadType } from '@tldraw/tlschema';
|
||||
import { TLArrowShape } from '@tldraw/tlschema';
|
||||
|
@ -87,7 +86,6 @@ import { TLShapeId } from '@tldraw/tlschema';
|
|||
import { TLShapePartial } from '@tldraw/tlschema';
|
||||
import { TLShapeProp } from '@tldraw/tlschema';
|
||||
import { TLShapeProps } from '@tldraw/tlschema';
|
||||
import { TLShapeType } from '@tldraw/tlschema';
|
||||
import { TLSizeStyle } from '@tldraw/tlschema';
|
||||
import { TLSizeType } from '@tldraw/tlschema';
|
||||
import { TLStore } from '@tldraw/tlschema';
|
||||
|
@ -274,7 +272,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
getPageTransform(shape: TLShape): Matrix2d | undefined;
|
||||
getPageTransformById(id: TLShapeId): Matrix2d | undefined;
|
||||
// (undocumented)
|
||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShapeType): TLPageId | TLShapeId;
|
||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']): TLPageId | TLShapeId;
|
||||
getParentPageId(shape?: TLShape): TLPageId | undefined;
|
||||
getParentShape(shape?: TLShape): TLShape | undefined;
|
||||
getParentsMappedToChildren(ids: TLShapeId[]): Map<TLParentId, Set<TLShape>>;
|
||||
|
@ -556,7 +554,7 @@ export function applyRotationToSnapshotShapes({ delta, app, snapshot, stage, }:
|
|||
|
||||
// @public (undocumented)
|
||||
export interface AppOptions {
|
||||
config?: TldrawEditorConfig;
|
||||
config: TldrawEditorConfig;
|
||||
getContainer: () => HTMLElement;
|
||||
store: TLStore;
|
||||
}
|
||||
|
@ -1793,7 +1791,7 @@ export function TldrawEditor(props: TldrawEditorProps): JSX.Element;
|
|||
|
||||
// @public (undocumented)
|
||||
export class TldrawEditorConfig {
|
||||
constructor(opts: TldrawEditorConfigOptions);
|
||||
constructor(opts?: TldrawEditorConfigOptions);
|
||||
// (undocumented)
|
||||
createStore(config: {
|
||||
initialData?: StoreSnapshot<TLRecord>;
|
||||
|
@ -1801,13 +1799,7 @@ export class TldrawEditorConfig {
|
|||
instanceId: TLInstanceId;
|
||||
}): TLStore;
|
||||
// (undocumented)
|
||||
static readonly default: TldrawEditorConfig;
|
||||
// (undocumented)
|
||||
readonly shapeMigrations: MigrationsForShapes<TLShape>;
|
||||
// (undocumented)
|
||||
readonly shapeUtils: UtilsForShapes<TLShape>;
|
||||
// (undocumented)
|
||||
readonly shapeValidators: Record<TLShape['type'], T.Validator<any>>;
|
||||
readonly shapeUtils: Record<TLShape['type'], TLShapeUtilConstructor<any>>;
|
||||
// (undocumented)
|
||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>;
|
||||
// (undocumented)
|
||||
|
@ -1823,7 +1815,7 @@ export interface TldrawEditorProps {
|
|||
// (undocumented)
|
||||
children?: any;
|
||||
components?: Partial<TLEditorComponents>;
|
||||
config?: TldrawEditorConfig;
|
||||
config: TldrawEditorConfig;
|
||||
instanceId?: TLInstanceId;
|
||||
isDarkMode?: boolean;
|
||||
onCreateAssetFromFile?: (file: File) => Promise<TLAsset>;
|
||||
|
@ -2044,7 +2036,7 @@ export class TLFrameUtil extends TLBoxUtil<TLFrameShape> {
|
|||
// (undocumented)
|
||||
canEdit: () => boolean;
|
||||
// (undocumented)
|
||||
canReceiveNewChildrenOfType: (_type: TLShapeType) => boolean;
|
||||
canReceiveNewChildrenOfType: (_type: TLShape['type']) => boolean;
|
||||
// (undocumented)
|
||||
defaultProps(): TLFrameShape['props'];
|
||||
// (undocumented)
|
||||
|
@ -2456,7 +2448,7 @@ export abstract class TLShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
canCrop: TLShapeUtilFlag<T>;
|
||||
canDropShapes(shape: T, shapes: TLShape[]): boolean;
|
||||
canEdit: TLShapeUtilFlag<T>;
|
||||
canReceiveNewChildrenOfType(type: TLShapeType): boolean;
|
||||
canReceiveNewChildrenOfType(type: TLShape['type']): boolean;
|
||||
canResize: TLShapeUtilFlag<T>;
|
||||
canScroll: TLShapeUtilFlag<T>;
|
||||
canUnmount: TLShapeUtilFlag<T>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TLAsset, TLInstance, TLInstanceId, TLStore, TLUser, TLUserId } from '@tldraw/tlschema'
|
||||
import { Store } from '@tldraw/tlstore'
|
||||
import { annotateError } from '@tldraw/utils'
|
||||
import React, { useCallback, useSyncExternalStore } from 'react'
|
||||
import React, { useCallback, useEffect, useState, useSyncExternalStore } from 'react'
|
||||
import { App } from './app/App'
|
||||
import { EditorAssetUrls, defaultEditorAssetUrls } from './assetUrls'
|
||||
import { OptionalErrorBoundary } from './components/ErrorBoundary'
|
||||
|
@ -28,12 +28,12 @@ import { useZoomCss } from './hooks/useZoomCss'
|
|||
/** @public */
|
||||
export interface TldrawEditorProps {
|
||||
children?: any
|
||||
/** A configuration defining major customizations to the app, such as custom shapes and new tools */
|
||||
config: TldrawEditorConfig
|
||||
/** Overrides for the tldraw components */
|
||||
components?: Partial<TLEditorComponents>
|
||||
/** Whether to display the dark mode. */
|
||||
isDarkMode?: boolean
|
||||
/** A configuration defining major customizations to the app, such as custom shapes and new tools */
|
||||
config?: TldrawEditorConfig
|
||||
/**
|
||||
* Called when the app has mounted.
|
||||
*
|
||||
|
@ -133,7 +133,7 @@ export function TldrawEditor(props: TldrawEditorProps) {
|
|||
}
|
||||
|
||||
function TldrawEditorBeforeLoading({
|
||||
config = TldrawEditorConfig.default,
|
||||
config,
|
||||
userId,
|
||||
instanceId,
|
||||
store,
|
||||
|
@ -143,26 +143,43 @@ function TldrawEditorBeforeLoading({
|
|||
props.assetUrls ?? defaultEditorAssetUrls
|
||||
)
|
||||
|
||||
store ??= config.createStore({
|
||||
const [_store, _setStore] = useState<TLStore | SyncedStore>(() => {
|
||||
return (
|
||||
store ??
|
||||
config.createStore({
|
||||
userId: userId ?? TLUser.createId(),
|
||||
instanceId: instanceId ?? TLInstance.createId(),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
let loadedStore
|
||||
if (!(store instanceof Store)) {
|
||||
if (store.error) {
|
||||
useEffect(() => {
|
||||
_setStore(() => {
|
||||
return (
|
||||
store ??
|
||||
config.createStore({
|
||||
userId: userId ?? TLUser.createId(),
|
||||
instanceId: instanceId ?? TLInstance.createId(),
|
||||
})
|
||||
)
|
||||
})
|
||||
}, [store, config, userId, instanceId])
|
||||
|
||||
let loadedStore: TLStore | SyncedStore
|
||||
if (!(_store instanceof Store)) {
|
||||
if (_store.error) {
|
||||
// for error handling, we fall back to the default error boundary.
|
||||
// if users want to handle this error differently, they can render
|
||||
// their own error screen before the TldrawEditor component
|
||||
throw store.error
|
||||
throw _store.error
|
||||
}
|
||||
if (!store.store) {
|
||||
if (!_store.store) {
|
||||
return <LoadingScreen>Connecting...</LoadingScreen>
|
||||
}
|
||||
|
||||
loadedStore = store.store
|
||||
loadedStore = _store.store
|
||||
} else {
|
||||
loadedStore = store
|
||||
loadedStore = _store
|
||||
}
|
||||
|
||||
if (instanceId && loadedStore.props.instanceId !== instanceId) {
|
||||
|
@ -209,8 +226,8 @@ function TldrawEditorAfterLoading({
|
|||
React.useLayoutEffect(() => {
|
||||
const app = new App({
|
||||
store,
|
||||
getContainer: () => container,
|
||||
config,
|
||||
getContainer: () => container,
|
||||
})
|
||||
setApp(app)
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@ import {
|
|||
TLShapeId,
|
||||
TLShapePartial,
|
||||
TLShapeProp,
|
||||
TLShapeType,
|
||||
TLSizeStyle,
|
||||
TLStore,
|
||||
TLUnknownShape,
|
||||
|
@ -160,7 +159,7 @@ export interface AppOptions {
|
|||
*/
|
||||
store: TLStore
|
||||
/** A configuration defining major customizations to the app, such as custom shapes and new tools */
|
||||
config?: TldrawEditorConfig
|
||||
config: TldrawEditorConfig
|
||||
/**
|
||||
* Should return a containing html element which has all the styles applied to the app. If not
|
||||
* given, the body element will be used.
|
||||
|
@ -175,14 +174,15 @@ export function isShapeWithHandles(shape: TLShape) {
|
|||
|
||||
/** @public */
|
||||
export class App extends EventEmitter<TLEventMap> {
|
||||
constructor({ config = TldrawEditorConfig.default, store, getContainer }: AppOptions) {
|
||||
constructor({ config, store, getContainer }: AppOptions) {
|
||||
super()
|
||||
|
||||
if (store.schema !== config.storeSchema) {
|
||||
this.config = config
|
||||
|
||||
if (store.schema !== this.config.storeSchema) {
|
||||
throw new Error('Store schema does not match schema given to App')
|
||||
}
|
||||
|
||||
this.config = config
|
||||
this.store = store
|
||||
|
||||
this.getContainer = getContainer ?? (() => document.body)
|
||||
|
@ -191,7 +191,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
// Set the shape utils
|
||||
this.shapeUtils = Object.fromEntries(
|
||||
Object.entries(config.shapeUtils).map(([type, Util]) => [type, new Util(this, type)])
|
||||
Object.entries(this.config.shapeUtils).map(([type, Util]) => [type, new Util(this, type)])
|
||||
)
|
||||
|
||||
if (typeof window !== 'undefined' && 'navigator' in window) {
|
||||
|
@ -209,7 +209,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
this.root = new RootState(this)
|
||||
if (this.root.children) {
|
||||
config.tools.forEach((Ctor) => {
|
||||
this.config.tools.forEach((Ctor) => {
|
||||
this.root.children![Ctor.id] = new Ctor(this)
|
||||
})
|
||||
}
|
||||
|
@ -2864,7 +2864,7 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShapeType) {
|
||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']) {
|
||||
const shapes = this.sortedShapesArray
|
||||
|
||||
for (let i = shapes.length - 1; i >= 0; i--) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { canolicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives'
|
||||
import { TLFrameShape, TLShape, TLShapeId, TLShapeType } from '@tldraw/tlschema'
|
||||
import { TLFrameShape, TLShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import { last } from '@tldraw/utils'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defaultEmptyAs } from '../../../utils/string'
|
||||
|
@ -148,7 +148,7 @@ export class TLFrameUtil extends TLBoxUtil<TLFrameShape> {
|
|||
)
|
||||
}
|
||||
|
||||
override canReceiveNewChildrenOfType = (_type: TLShapeType) => {
|
||||
override canReceiveNewChildrenOfType = (_type: TLShape['type']) => {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
TLHandle,
|
||||
TLShape,
|
||||
TLShapePartial,
|
||||
TLShapeType,
|
||||
TLUnknownShape,
|
||||
Vec2dModel,
|
||||
} from '@tldraw/tlschema'
|
||||
|
@ -308,7 +307,7 @@ export abstract class TLShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param type - The shape type.
|
||||
* @public
|
||||
*/
|
||||
canReceiveNewChildrenOfType(type: TLShapeType) {
|
||||
canReceiveNewChildrenOfType(type: TLShape['type']) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TLShapeType, TLStyleType } from '@tldraw/tlschema'
|
||||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../StateNode'
|
||||
import { Idle } from './children/Idle'
|
||||
import { Pointing } from './children/Pointing'
|
||||
|
@ -8,7 +8,7 @@ export class TLArrowTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
shapeType: TLShapeType = 'arrow'
|
||||
shapeType = 'arrow'
|
||||
|
||||
styles = [
|
||||
'color',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createShapeId, TLArrowShape, TLShapeType } from '@tldraw/tlschema'
|
||||
import { createShapeId, TLArrowShape } from '@tldraw/tlschema'
|
||||
import { TLArrowUtil } from '../../../shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
@ -7,8 +7,6 @@ import { TLArrowTool } from '../TLArrowTool'
|
|||
export class Pointing extends StateNode {
|
||||
static override id = 'pointing'
|
||||
|
||||
shapeType = '' as TLShapeType
|
||||
|
||||
shape?: TLArrowShape
|
||||
|
||||
preciseTimeout = -1
|
||||
|
@ -33,7 +31,7 @@ export class Pointing extends StateNode {
|
|||
|
||||
this.didTimeout = false
|
||||
|
||||
this.shapeType = (this.parent as TLArrowTool).shapeType
|
||||
const shapeType = (this.parent as TLArrowTool).shapeType
|
||||
|
||||
this.app.mark('creating')
|
||||
|
||||
|
@ -42,7 +40,7 @@ export class Pointing extends StateNode {
|
|||
this.app.createShapes([
|
||||
{
|
||||
id,
|
||||
type: this.shapeType,
|
||||
type: shapeType,
|
||||
x: currentPagePoint.x,
|
||||
y: currentPagePoint.y,
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TLShapeType, TLStyleType } from '@tldraw/tlschema'
|
||||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../StateNode'
|
||||
|
||||
import { Idle } from './children/Idle'
|
||||
|
@ -9,7 +9,7 @@ export class TLLineTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
shapeType: TLShapeType = 'line'
|
||||
shapeType = 'line'
|
||||
|
||||
styles = ['color', 'opacity', 'dash', 'size', 'spline'] as TLStyleType[]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getIndexAbove, sortByIndex } from '@tldraw/indices'
|
||||
import { Matrix2d, Vec2d } from '@tldraw/primitives'
|
||||
import { TLHandle, TLLineShape, TLShapeId, TLShapeType, createShapeId } from '@tldraw/tlschema'
|
||||
import { TLHandle, TLLineShape, TLShapeId, createShapeId } from '@tldraw/tlschema'
|
||||
import { last, structuredClone } from '@tldraw/utils'
|
||||
import { TLEventHandlers, TLInterruptEvent } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
@ -9,8 +9,6 @@ import { TLLineTool } from '../TLLineTool'
|
|||
export class Pointing extends StateNode {
|
||||
static override id = 'pointing'
|
||||
|
||||
shapeType = '' as TLShapeType
|
||||
|
||||
shape = {} as TLLineShape
|
||||
|
||||
markPointId = ''
|
||||
|
@ -19,7 +17,6 @@ export class Pointing extends StateNode {
|
|||
const { inputs } = this.app
|
||||
const { currentPagePoint } = inputs
|
||||
|
||||
this.shapeType = (this.parent as TLLineTool).shapeType
|
||||
this.markPointId = this.app.mark('creating')
|
||||
|
||||
let shapeExists = false
|
||||
|
@ -85,7 +82,7 @@ export class Pointing extends StateNode {
|
|||
this.app.createShapes([
|
||||
{
|
||||
id,
|
||||
type: this.shapeType,
|
||||
type: (this.parent as TLLineTool).shapeType,
|
||||
x: currentPagePoint.x,
|
||||
y: currentPagePoint.y,
|
||||
},
|
||||
|
|
|
@ -1,63 +1,19 @@
|
|||
import {
|
||||
CLIENT_FIXUP_SCRIPT,
|
||||
TLAsset,
|
||||
TLCamera,
|
||||
TLDOCUMENT_ID,
|
||||
TLDocument,
|
||||
TLDefaultShape,
|
||||
TLInstance,
|
||||
TLInstanceId,
|
||||
TLInstancePageState,
|
||||
TLInstancePresence,
|
||||
TLPage,
|
||||
TLRecord,
|
||||
TLShape,
|
||||
TLStore,
|
||||
TLStoreProps,
|
||||
TLUnknownShape,
|
||||
TLUser,
|
||||
TLUserDocument,
|
||||
TLUserId,
|
||||
TLUserPresence,
|
||||
arrowShapeTypeMigrations,
|
||||
arrowShapeTypeValidator,
|
||||
bookmarkShapeTypeMigrations,
|
||||
bookmarkShapeTypeValidator,
|
||||
createIntegrityChecker,
|
||||
defaultDerivePresenceState,
|
||||
drawShapeTypeMigrations,
|
||||
drawShapeTypeValidator,
|
||||
embedShapeTypeMigrations,
|
||||
embedShapeTypeValidator,
|
||||
frameShapeTypeMigrations,
|
||||
frameShapeTypeValidator,
|
||||
geoShapeTypeMigrations,
|
||||
geoShapeTypeValidator,
|
||||
groupShapeTypeMigrations,
|
||||
groupShapeTypeValidator,
|
||||
imageShapeTypeMigrations,
|
||||
imageShapeTypeValidator,
|
||||
lineShapeTypeMigrations,
|
||||
lineShapeTypeValidator,
|
||||
noteShapeTypeMigrations,
|
||||
noteShapeTypeValidator,
|
||||
onValidationFailure,
|
||||
rootShapeTypeMigrations,
|
||||
storeMigrations,
|
||||
textShapeTypeMigrations,
|
||||
textShapeTypeValidator,
|
||||
videoShapeTypeMigrations,
|
||||
videoShapeTypeValidator,
|
||||
createTLSchema,
|
||||
} from '@tldraw/tlschema'
|
||||
import {
|
||||
Migrations,
|
||||
RecordType,
|
||||
Store,
|
||||
StoreSchema,
|
||||
StoreSnapshot,
|
||||
createRecordType,
|
||||
defineMigrations,
|
||||
} from '@tldraw/tlstore'
|
||||
import { T } from '@tldraw/tlvalidate'
|
||||
import { Migrations, RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
|
||||
import { Signal } from 'signia'
|
||||
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
|
@ -74,51 +30,12 @@ import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
|||
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
||||
|
||||
/** @public */
|
||||
export type ValidatorsForShapes<T extends TLUnknownShape> = Record<
|
||||
T['type'],
|
||||
{ validate: (record: T) => T }
|
||||
>
|
||||
// Secret shape types that don't have a shape util yet
|
||||
type ShapeTypesNotImplemented = 'icon'
|
||||
|
||||
/** @public */
|
||||
export type MigrationsForShapes<T extends TLUnknownShape> = Record<T['type'], Migrations>
|
||||
|
||||
type CustomShapeInfo<T extends TLUnknownShape> = {
|
||||
util: TLShapeUtilConstructor<any>
|
||||
validator?: { validate: (record: T) => T }
|
||||
migrations?: Migrations
|
||||
}
|
||||
|
||||
type UtilsForShapes<T extends TLUnknownShape> = Record<T['type'], TLShapeUtilConstructor<any>>
|
||||
|
||||
type TldrawEditorConfigOptions<T extends TLUnknownShape = TLShape> = {
|
||||
tools?: readonly StateNodeConstructor[]
|
||||
shapes?: { [K in T['type']]: CustomShapeInfo<T> }
|
||||
/** @internal */
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class TldrawEditorConfig {
|
||||
static readonly default = new TldrawEditorConfig({})
|
||||
|
||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>
|
||||
readonly TLShape: RecordType<TLShape, 'type' | 'props' | 'index' | 'parentId'>
|
||||
readonly tools: readonly StateNodeConstructor[]
|
||||
|
||||
// Custom shape utils
|
||||
readonly shapeUtils: UtilsForShapes<TLShape>
|
||||
// Validators for shape subtypes
|
||||
readonly shapeValidators: Record<TLShape['type'], T.Validator<any>>
|
||||
// Migrations for shape subtypes
|
||||
readonly shapeMigrations: MigrationsForShapes<TLShape>
|
||||
|
||||
constructor(opts: TldrawEditorConfigOptions) {
|
||||
const { shapes = [], tools = [], derivePresenceState } = opts
|
||||
|
||||
this.tools = tools
|
||||
|
||||
this.shapeUtils = {
|
||||
const DEFAULT_SHAPE_UTILS: {
|
||||
[K in Exclude<TLDefaultShape['type'], ShapeTypesNotImplemented>]: TLShapeUtilConstructor<any>
|
||||
} = {
|
||||
arrow: TLArrowUtil,
|
||||
bookmark: TLBookmarkUtil,
|
||||
draw: TLDrawUtil,
|
||||
|
@ -131,78 +48,51 @@ export class TldrawEditorConfig {
|
|||
note: TLNoteUtil,
|
||||
text: TLTextUtil,
|
||||
video: TLVideoUtil,
|
||||
}
|
||||
}
|
||||
|
||||
this.shapeMigrations = {
|
||||
arrow: arrowShapeTypeMigrations,
|
||||
bookmark: bookmarkShapeTypeMigrations,
|
||||
draw: drawShapeTypeMigrations,
|
||||
embed: embedShapeTypeMigrations,
|
||||
frame: frameShapeTypeMigrations,
|
||||
geo: geoShapeTypeMigrations,
|
||||
group: groupShapeTypeMigrations,
|
||||
image: imageShapeTypeMigrations,
|
||||
line: lineShapeTypeMigrations,
|
||||
note: noteShapeTypeMigrations,
|
||||
text: textShapeTypeMigrations,
|
||||
video: videoShapeTypeMigrations,
|
||||
}
|
||||
|
||||
this.shapeValidators = {
|
||||
arrow: arrowShapeTypeValidator,
|
||||
bookmark: bookmarkShapeTypeValidator,
|
||||
draw: drawShapeTypeValidator,
|
||||
embed: embedShapeTypeValidator,
|
||||
frame: frameShapeTypeValidator,
|
||||
geo: geoShapeTypeValidator,
|
||||
group: groupShapeTypeValidator,
|
||||
image: imageShapeTypeValidator,
|
||||
line: lineShapeTypeValidator,
|
||||
note: noteShapeTypeValidator,
|
||||
text: textShapeTypeValidator,
|
||||
video: videoShapeTypeValidator,
|
||||
}
|
||||
|
||||
// Add custom shapes
|
||||
for (const [type, shape] of Object.entries(shapes)) {
|
||||
this.shapeUtils[type] = shape.util
|
||||
this.shapeMigrations[type] = shape.migrations ?? defineMigrations({})
|
||||
this.shapeValidators[type] = (shape.validator ?? T.any) as T.Validator<any>
|
||||
}
|
||||
|
||||
const shapeRecord = createRecordType<TLShape>('shape', {
|
||||
migrations: defineMigrations({
|
||||
currentVersion: rootShapeTypeMigrations.currentVersion,
|
||||
firstVersion: rootShapeTypeMigrations.firstVersion,
|
||||
migrators: rootShapeTypeMigrations.migrators,
|
||||
subTypeKey: 'type',
|
||||
subTypeMigrations: this.shapeMigrations,
|
||||
}),
|
||||
validator: T.model('shape', T.union('type', { ...this.shapeValidators })),
|
||||
scope: 'document',
|
||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
|
||||
|
||||
this.storeSchema = StoreSchema.create<TLRecord, TLStoreProps>(
|
||||
/** @public */
|
||||
export type TldrawEditorConfigOptions = {
|
||||
tools?: readonly StateNodeConstructor[]
|
||||
shapes?: Record<
|
||||
string,
|
||||
{
|
||||
asset: TLAsset,
|
||||
camera: TLCamera,
|
||||
document: TLDocument,
|
||||
instance: TLInstance,
|
||||
instance_page_state: TLInstancePageState,
|
||||
page: TLPage,
|
||||
shape: shapeRecord,
|
||||
user: TLUser,
|
||||
user_document: TLUserDocument,
|
||||
user_presence: TLUserPresence,
|
||||
instance_presence: TLInstancePresence,
|
||||
},
|
||||
{
|
||||
snapshotMigrations: storeMigrations,
|
||||
onValidationFailure,
|
||||
createIntegrityChecker: createIntegrityChecker,
|
||||
derivePresenceState: derivePresenceState ?? defaultDerivePresenceState,
|
||||
util: TLShapeUtilConstructor<any>
|
||||
validator?: { validate: <T>(record: T) => T }
|
||||
migrations?: Migrations
|
||||
}
|
||||
)
|
||||
>
|
||||
/** @internal */
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class TldrawEditorConfig {
|
||||
// Custom tools
|
||||
readonly tools: readonly StateNodeConstructor[]
|
||||
|
||||
// Custom shape utils
|
||||
readonly shapeUtils: Record<TLShape['type'], TLShapeUtilConstructor<any>>
|
||||
|
||||
// The record used for TLShape incorporating any custom shapes
|
||||
readonly TLShape: RecordType<TLShape, 'type' | 'props' | 'index' | 'parentId'>
|
||||
|
||||
// The schema used for the store incorporating any custom shapes
|
||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>
|
||||
|
||||
constructor(opts = {} as TldrawEditorConfigOptions) {
|
||||
const { shapes = {}, tools = [], derivePresenceState } = opts
|
||||
|
||||
this.tools = tools
|
||||
|
||||
this.shapeUtils = {
|
||||
...DEFAULT_SHAPE_UTILS,
|
||||
...Object.fromEntries(Object.entries(shapes).map(([k, v]) => [k, v.util])),
|
||||
}
|
||||
|
||||
this.storeSchema = createTLSchema({
|
||||
customShapes: shapes,
|
||||
derivePresenceState: derivePresenceState,
|
||||
})
|
||||
|
||||
this.TLShape = this.storeSchema.types.shape as RecordType<
|
||||
TLShape,
|
||||
|
|
7
packages/editor/src/lib/test/TestApp.test.ts
Normal file
7
packages/editor/src/lib/test/TestApp.test.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { TestApp } from './TestApp'
|
||||
|
||||
it('loads the test app', () => {
|
||||
expect(() => {
|
||||
new TestApp()
|
||||
}).not.toThrow()
|
||||
})
|
|
@ -55,11 +55,13 @@ export const TEST_INSTANCE_ID = TLInstance.createCustomId('testInstance1')
|
|||
export const TEST_USER_ID = TLUser.createCustomId('testUser1')
|
||||
|
||||
export class TestApp extends App {
|
||||
constructor(options = {} as Partial<AppOptions>) {
|
||||
constructor(options = {} as Partial<Omit<AppOptions, 'store'>>) {
|
||||
const elm = document.createElement('div')
|
||||
elm.tabIndex = 0
|
||||
const config = options.config ?? new TldrawEditorConfig()
|
||||
super({
|
||||
store: (options.config ?? TldrawEditorConfig.default).createStore({
|
||||
config,
|
||||
store: config.createStore({
|
||||
userId: TEST_USER_ID,
|
||||
instanceId: TEST_INSTANCE_ID,
|
||||
}),
|
||||
|
|
|
@ -21,14 +21,17 @@ afterEach(() => {
|
|||
|
||||
describe('<Tldraw />', () => {
|
||||
it('Accepts fresh versions of store and calls `onMount` for each one', async () => {
|
||||
const initialStore = TldrawEditorConfig.default.createStore({
|
||||
const config = new TldrawEditorConfig()
|
||||
|
||||
const initialStore = config.createStore({
|
||||
instanceId: TLInstance.createCustomId('test'),
|
||||
userId: TLUser.createCustomId('test'),
|
||||
})
|
||||
|
||||
const onMount = jest.fn()
|
||||
|
||||
const rendered = render(
|
||||
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
||||
<TldrawEditor config={config} store={initialStore} onMount={onMount} autoFocus>
|
||||
<div data-testid="canvas-1" />
|
||||
</TldrawEditor>
|
||||
)
|
||||
|
@ -40,7 +43,7 @@ describe('<Tldraw />', () => {
|
|||
|
||||
// re-render with the same store:
|
||||
rendered.rerender(
|
||||
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
||||
<TldrawEditor config={config} store={initialStore} onMount={onMount} autoFocus>
|
||||
<div data-testid="canvas-2" />
|
||||
</TldrawEditor>
|
||||
)
|
||||
|
@ -49,12 +52,12 @@ describe('<Tldraw />', () => {
|
|||
expect(onMount).toHaveBeenCalledTimes(1)
|
||||
|
||||
// re-render with a new store:
|
||||
const newStore = TldrawEditorConfig.default.createStore({
|
||||
const newStore = config.createStore({
|
||||
instanceId: TLInstance.createCustomId('test'),
|
||||
userId: TLUser.createCustomId('test'),
|
||||
})
|
||||
rendered.rerender(
|
||||
<TldrawEditor store={newStore} onMount={onMount} autoFocus>
|
||||
<TldrawEditor config={config} store={newStore} onMount={onMount} autoFocus>
|
||||
<div data-testid="canvas-3" />
|
||||
</TldrawEditor>
|
||||
)
|
||||
|
|
|
@ -1076,19 +1076,6 @@ export interface LegacyTldrawDocument {
|
|||
|
||||
/* ------------------ Translations ------------------ */
|
||||
|
||||
// const v1ShapeTypesToV2ShapeTypes: Record<TDShapeType, TLShapeType> = {
|
||||
// [TDShapeType.Rectangle]: 'geo',
|
||||
// [TDShapeType.Ellipse]: 'geo',
|
||||
// [TDShapeType.Text]: 'text',
|
||||
// [TDShapeType.Image]: 'image',
|
||||
// [TDShapeType.Video]: 'video',
|
||||
// [TDShapeType.Group]: 'group',
|
||||
// [TDShapeType.Arrow]: 'arrow',
|
||||
// [TDShapeType.Sticky]: 'note',
|
||||
// [TDShapeType.Draw]: 'draw',
|
||||
// [TDShapeType.Triangle]: 'geo',
|
||||
// }
|
||||
|
||||
const v1ColorsToV2Colors: Record<ColorStyle, TLColorType> = {
|
||||
[ColorStyle.White]: 'black',
|
||||
[ColorStyle.Black]: 'black',
|
||||
|
|
|
@ -208,7 +208,7 @@ export async function parseAndLoadDocument(
|
|||
forceDarkMode?: boolean
|
||||
) {
|
||||
const parseFileResult = parseTldrawJsonFile({
|
||||
config: TldrawEditorConfig.default,
|
||||
config: new TldrawEditorConfig(),
|
||||
json: document,
|
||||
instanceId: app.instanceId,
|
||||
userId: app.userId,
|
||||
|
|
|
@ -17,14 +17,14 @@ function serialize(file: TldrawFile): string {
|
|||
|
||||
describe('parseTldrawJsonFile', () => {
|
||||
it('returns an error if the file is not json', () => {
|
||||
const result = parseTldrawJsonFile(TldrawEditorConfig.default, 'not json')
|
||||
const result = parseTldrawJsonFile(new TldrawEditorConfig(), 'not json')
|
||||
assert(!result.ok)
|
||||
expect(result.error.type).toBe('notATldrawFile')
|
||||
})
|
||||
|
||||
it('returns an error if the file doesnt look like a tldraw file', () => {
|
||||
const result = parseTldrawJsonFile(
|
||||
TldrawEditorConfig.default,
|
||||
new TldrawEditorConfig(),
|
||||
JSON.stringify({ not: 'a tldraw file' })
|
||||
)
|
||||
assert(!result.ok)
|
||||
|
@ -33,10 +33,10 @@ describe('parseTldrawJsonFile', () => {
|
|||
|
||||
it('returns an error if the file version is too old', () => {
|
||||
const result = parseTldrawJsonFile(
|
||||
TldrawEditorConfig.default,
|
||||
new TldrawEditorConfig(),
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 0,
|
||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
records: [],
|
||||
})
|
||||
)
|
||||
|
@ -46,10 +46,10 @@ describe('parseTldrawJsonFile', () => {
|
|||
|
||||
it('returns an error if the file version is too new', () => {
|
||||
const result = parseTldrawJsonFile(
|
||||
TldrawEditorConfig.default,
|
||||
new TldrawEditorConfig(),
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 100,
|
||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
records: [],
|
||||
})
|
||||
)
|
||||
|
@ -58,10 +58,10 @@ describe('parseTldrawJsonFile', () => {
|
|||
})
|
||||
|
||||
it('returns an error if migrations fail', () => {
|
||||
const serializedSchema = TldrawEditorConfig.default.storeSchema.serialize()
|
||||
const serializedSchema = new TldrawEditorConfig().storeSchema.serialize()
|
||||
serializedSchema.storeVersion = 100
|
||||
const result = parseTldrawJsonFile(
|
||||
TldrawEditorConfig.default,
|
||||
new TldrawEditorConfig(),
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: serializedSchema,
|
||||
|
@ -72,10 +72,10 @@ describe('parseTldrawJsonFile', () => {
|
|||
assert(result.error.type === 'migrationFailed')
|
||||
expect(result.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld)
|
||||
|
||||
const serializedSchema2 = TldrawEditorConfig.default.storeSchema.serialize()
|
||||
const serializedSchema2 = new TldrawEditorConfig().storeSchema.serialize()
|
||||
serializedSchema2.recordVersions.shape.version = 100
|
||||
const result2 = parseTldrawJsonFile(
|
||||
TldrawEditorConfig.default,
|
||||
new TldrawEditorConfig(),
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: serializedSchema2,
|
||||
|
@ -90,10 +90,10 @@ describe('parseTldrawJsonFile', () => {
|
|||
|
||||
it('returns an error if a record is invalid', () => {
|
||||
const result = parseTldrawJsonFile(
|
||||
TldrawEditorConfig.default,
|
||||
new TldrawEditorConfig(),
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
records: [
|
||||
{
|
||||
typeName: 'shape',
|
||||
|
@ -113,10 +113,10 @@ describe('parseTldrawJsonFile', () => {
|
|||
|
||||
it('returns a store if the file is valid', () => {
|
||||
const result = parseTldrawJsonFile(
|
||||
TldrawEditorConfig.default,
|
||||
new TldrawEditorConfig(),
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
records: [],
|
||||
})
|
||||
)
|
||||
|
|
|
@ -8,9 +8,10 @@ import { TldrawEditorProps } from '@tldraw/editor';
|
|||
import { TldrawUiContextProviderProps } from '@tldraw/ui';
|
||||
|
||||
// @public (undocumented)
|
||||
export function Tldraw(props: Omit<TldrawEditorProps, 'store'> & TldrawUiContextProviderProps & {
|
||||
export function Tldraw(props: Omit<TldrawEditorProps, 'config' | 'store'> & TldrawUiContextProviderProps & {
|
||||
persistenceKey?: string;
|
||||
hideUi?: boolean;
|
||||
config?: TldrawEditorProps['config'];
|
||||
}): JSX.Element;
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Canvas, TldrawEditor, TldrawEditorProps } from '@tldraw/editor'
|
||||
import { Canvas, TldrawEditor, TldrawEditorConfig, TldrawEditorProps } from '@tldraw/editor'
|
||||
import {
|
||||
DEFAULT_DOCUMENT_NAME,
|
||||
TAB_ID,
|
||||
|
@ -6,18 +6,33 @@ import {
|
|||
useLocalSyncClient,
|
||||
} from '@tldraw/tlsync-client'
|
||||
import { ContextMenu, TldrawUi, TldrawUiContextProviderProps } from '@tldraw/ui'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
/** @public */
|
||||
export function Tldraw(
|
||||
props: Omit<TldrawEditorProps, 'store'> &
|
||||
props: Omit<TldrawEditorProps, 'store' | 'config'> &
|
||||
TldrawUiContextProviderProps & {
|
||||
/** The key under which to persist this editor's data to local storage. */
|
||||
persistenceKey?: string
|
||||
/** Whether to hide the user interface and only display the canvas. */
|
||||
hideUi?: boolean
|
||||
/** A custom configuration for this Tldraw editor */
|
||||
config?: TldrawEditorProps['config']
|
||||
}
|
||||
) {
|
||||
const { children, persistenceKey = DEFAULT_DOCUMENT_NAME, instanceId = TAB_ID, ...rest } = props
|
||||
const {
|
||||
config,
|
||||
children,
|
||||
persistenceKey = DEFAULT_DOCUMENT_NAME,
|
||||
instanceId = TAB_ID,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
const [_config, _setConfig] = useState(() => config ?? new TldrawEditorConfig())
|
||||
|
||||
useEffect(() => {
|
||||
_setConfig(config ?? new TldrawEditorConfig())
|
||||
}, [config])
|
||||
|
||||
const userData = getUserData()
|
||||
|
||||
|
@ -25,13 +40,19 @@ export function Tldraw(
|
|||
|
||||
const syncedStore = useLocalSyncClient({
|
||||
instanceId,
|
||||
userId: userId,
|
||||
userId,
|
||||
config: _config,
|
||||
universalPersistenceKey: persistenceKey,
|
||||
config: props.config,
|
||||
})
|
||||
|
||||
return (
|
||||
<TldrawEditor {...rest} instanceId={instanceId} userId={userId} store={syncedStore}>
|
||||
<TldrawEditor
|
||||
{...rest}
|
||||
instanceId={instanceId}
|
||||
userId={userId}
|
||||
store={syncedStore}
|
||||
config={_config}
|
||||
>
|
||||
<TldrawUi {...rest}>
|
||||
<ContextMenu>
|
||||
<Canvas />
|
||||
|
|
|
@ -103,6 +103,12 @@ export function createShapeValidator<Type extends string, Props extends object>(
|
|||
props: Props;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export function createTLSchema<T extends TLUnknownShape>(opts?: {
|
||||
customShapes?: { [K in T["type"]]: CustomShapeInfo<T>; } | undefined;
|
||||
derivePresenceState?: ((store: TLStore) => Signal<null | TLInstancePresence>) | undefined;
|
||||
}): StoreSchema<TLRecord, TLStoreProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const cursorTypeValidator: T.Validator<string>;
|
||||
|
||||
|
@ -722,6 +728,9 @@ export interface TLDashStyle extends TLBaseStyle {
|
|||
// @public (undocumented)
|
||||
export type TLDashType = SetValue<typeof TL_DASH_TYPES>;
|
||||
|
||||
// @public
|
||||
export type TLDefaultShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEmbedShape | TLFrameShape | TLGeoShape | TLGroupShape | TLIconShape | TLImageShape | TLLineShape | TLNoteShape | TLTextShape | TLVideoShape;
|
||||
|
||||
// @public
|
||||
export interface TLDocument extends BaseRecord<'document'> {
|
||||
// (undocumented)
|
||||
|
@ -1137,7 +1146,7 @@ export type TLScribble = {
|
|||
};
|
||||
|
||||
// @public
|
||||
export type TLShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEmbedShape | TLFrameShape | TLGeoShape | TLGroupShape | TLIconShape | TLImageShape | TLLineShape | TLNoteShape | TLTextShape | TLUnknownShape | TLVideoShape;
|
||||
export type TLShape = TLDefaultShape | TLUnknownShape;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLShapeId = ID<TLBaseShape<any, any>>;
|
||||
|
@ -1155,9 +1164,6 @@ export type TLShapeProp = keyof TLShapeProps;
|
|||
// @public (undocumented)
|
||||
export type TLShapeProps = SmooshedUnionObject<TLShape['props']>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLShapeType = TLShape['type'];
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLSizeStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
|
@ -1252,7 +1258,7 @@ export type TLTextShapeProps = {
|
|||
// @public (undocumented)
|
||||
export type TLUiColorType = SetValue<typeof TL_UI_COLOR_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type TLUnknownShape = TLBaseShape<string, object>;
|
||||
|
||||
// @public
|
||||
|
|
134
packages/tlschema/src/createTLSchema.ts
Normal file
134
packages/tlschema/src/createTLSchema.ts
Normal file
|
@ -0,0 +1,134 @@
|
|||
import { Migrations, StoreSchema, createRecordType, defineMigrations } from '@tldraw/tlstore'
|
||||
import { T } from '@tldraw/tlvalidate'
|
||||
import { Signal } from 'signia'
|
||||
import { TLRecord } from './TLRecord'
|
||||
import { TLStore, TLStoreProps, createIntegrityChecker, onValidationFailure } from './TLStore'
|
||||
import { defaultDerivePresenceState } from './defaultDerivePresenceState'
|
||||
import { TLAsset } from './records/TLAsset'
|
||||
import { TLCamera } from './records/TLCamera'
|
||||
import { TLDocument } from './records/TLDocument'
|
||||
import { TLInstance } from './records/TLInstance'
|
||||
import { TLInstancePageState } from './records/TLInstancePageState'
|
||||
import { TLInstancePresence } from './records/TLInstancePresence'
|
||||
import { TLPage } from './records/TLPage'
|
||||
import { TLShape, TLUnknownShape, rootShapeTypeMigrations } from './records/TLShape'
|
||||
import { TLUser } from './records/TLUser'
|
||||
import { TLUserDocument } from './records/TLUserDocument'
|
||||
import { TLUserPresence } from './records/TLUserPresence'
|
||||
import { storeMigrations } from './schema'
|
||||
import { arrowShapeTypeMigrations, arrowShapeTypeValidator } from './shapes/TLArrowShape'
|
||||
import { bookmarkShapeTypeMigrations, bookmarkShapeTypeValidator } from './shapes/TLBookmarkShape'
|
||||
import { drawShapeTypeMigrations, drawShapeTypeValidator } from './shapes/TLDrawShape'
|
||||
import { embedShapeTypeMigrations, embedShapeTypeValidator } from './shapes/TLEmbedShape'
|
||||
import { frameShapeTypeMigrations, frameShapeTypeValidator } from './shapes/TLFrameShape'
|
||||
import { geoShapeTypeMigrations, geoShapeTypeValidator } from './shapes/TLGeoShape'
|
||||
import { groupShapeTypeMigrations, groupShapeTypeValidator } from './shapes/TLGroupShape'
|
||||
import { imageShapeTypeMigrations, imageShapeTypeValidator } from './shapes/TLImageShape'
|
||||
import { lineShapeTypeMigrations, lineShapeTypeValidator } from './shapes/TLLineShape'
|
||||
import { noteShapeTypeMigrations, noteShapeTypeValidator } from './shapes/TLNoteShape'
|
||||
import { textShapeTypeMigrations, textShapeTypeValidator } from './shapes/TLTextShape'
|
||||
import { videoShapeTypeMigrations, videoShapeTypeValidator } from './shapes/TLVideoShape'
|
||||
|
||||
type DefaultShapeInfo<T extends TLShape> = {
|
||||
validator: T.Validator<T>
|
||||
migrations: Migrations
|
||||
}
|
||||
|
||||
const DEFAULT_SHAPES: { [K in TLShape['type']]: DefaultShapeInfo<Extract<TLShape, { type: K }>> } =
|
||||
{
|
||||
arrow: { migrations: arrowShapeTypeMigrations, validator: arrowShapeTypeValidator },
|
||||
bookmark: { migrations: bookmarkShapeTypeMigrations, validator: bookmarkShapeTypeValidator },
|
||||
draw: { migrations: drawShapeTypeMigrations, validator: drawShapeTypeValidator },
|
||||
embed: { migrations: embedShapeTypeMigrations, validator: embedShapeTypeValidator },
|
||||
frame: { migrations: frameShapeTypeMigrations, validator: frameShapeTypeValidator },
|
||||
geo: { migrations: geoShapeTypeMigrations, validator: geoShapeTypeValidator },
|
||||
group: { migrations: groupShapeTypeMigrations, validator: groupShapeTypeValidator },
|
||||
image: { migrations: imageShapeTypeMigrations, validator: imageShapeTypeValidator },
|
||||
line: { migrations: lineShapeTypeMigrations, validator: lineShapeTypeValidator },
|
||||
note: { migrations: noteShapeTypeMigrations, validator: noteShapeTypeValidator },
|
||||
text: { migrations: textShapeTypeMigrations, validator: textShapeTypeValidator },
|
||||
video: { migrations: videoShapeTypeMigrations, validator: videoShapeTypeValidator },
|
||||
}
|
||||
|
||||
type CustomShapeInfo<T extends TLUnknownShape> = {
|
||||
validator?: { validate: (record: T) => T }
|
||||
migrations?: Migrations
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a store schema for a tldraw store that includes all the default shapes together with any custom shapes.
|
||||
* @public */
|
||||
export function createTLSchema<T extends TLUnknownShape>(
|
||||
opts = {} as {
|
||||
customShapes?: { [K in T['type']]: CustomShapeInfo<T> }
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
}
|
||||
) {
|
||||
const { customShapes = {}, derivePresenceState } = opts
|
||||
|
||||
const defaultShapeSubTypeEntries = Object.entries(DEFAULT_SHAPES) as [
|
||||
TLShape['type'],
|
||||
DefaultShapeInfo<TLShape>
|
||||
][]
|
||||
|
||||
const customShapeSubTypeEntries = Object.entries(customShapes) as [
|
||||
T['type'],
|
||||
CustomShapeInfo<T>
|
||||
][]
|
||||
|
||||
// Create a shape record that incorporates the defeault shapes and any custom shapes
|
||||
// into its subtype migrations and validators, so that we can migrate any new custom
|
||||
// subtypes. Note that migrations AND validators for custom shapes are optional. If
|
||||
// not provided, we use an empty migrations set and/or an "any" validator.
|
||||
|
||||
const shapeSubTypeMigrationsWithCustomSubTypeMigrations = {
|
||||
...Object.fromEntries(defaultShapeSubTypeEntries.map(([k, v]) => [k, v.migrations])),
|
||||
...Object.fromEntries(
|
||||
customShapeSubTypeEntries.map(([k, v]) => [k, v.migrations ?? defineMigrations({})])
|
||||
),
|
||||
}
|
||||
|
||||
const validatorWithCustomShapeValidators = T.model(
|
||||
'shape',
|
||||
T.union('type', {
|
||||
...Object.fromEntries(defaultShapeSubTypeEntries.map(([k, v]) => [k, v.validator])),
|
||||
...Object.fromEntries(
|
||||
customShapeSubTypeEntries.map(([k, v]) => [k, (v.validator as T.Validator<any>) ?? T.any])
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
const shapeRecord = createRecordType<TLShape>('shape', {
|
||||
migrations: defineMigrations({
|
||||
currentVersion: rootShapeTypeMigrations.currentVersion,
|
||||
firstVersion: rootShapeTypeMigrations.firstVersion,
|
||||
migrators: rootShapeTypeMigrations.migrators,
|
||||
subTypeKey: 'type',
|
||||
subTypeMigrations: shapeSubTypeMigrationsWithCustomSubTypeMigrations,
|
||||
}),
|
||||
validator: validatorWithCustomShapeValidators,
|
||||
scope: 'document',
|
||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
|
||||
|
||||
return StoreSchema.create<TLRecord, TLStoreProps>(
|
||||
{
|
||||
asset: TLAsset,
|
||||
camera: TLCamera,
|
||||
document: TLDocument,
|
||||
instance: TLInstance,
|
||||
instance_page_state: TLInstancePageState,
|
||||
page: TLPage,
|
||||
shape: shapeRecord,
|
||||
user: TLUser,
|
||||
user_document: TLUserDocument,
|
||||
user_presence: TLUserPresence,
|
||||
instance_presence: TLInstancePresence,
|
||||
},
|
||||
{
|
||||
snapshotMigrations: storeMigrations,
|
||||
onValidationFailure,
|
||||
createIntegrityChecker: createIntegrityChecker,
|
||||
derivePresenceState: derivePresenceState ?? defaultDerivePresenceState,
|
||||
}
|
||||
)
|
||||
}
|
|
@ -24,6 +24,7 @@ export {
|
|||
type TLVideoAsset,
|
||||
} from './assets/TLVideoAsset'
|
||||
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
|
||||
export { createTLSchema } from './createTLSchema'
|
||||
export { defaultDerivePresenceState } from './defaultDerivePresenceState'
|
||||
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
|
||||
export { type Box2dModel, type Vec2dModel } from './geometry-types'
|
||||
|
@ -58,6 +59,7 @@ export {
|
|||
isShape,
|
||||
isShapeId,
|
||||
rootShapeTypeMigrations,
|
||||
type TLDefaultShape,
|
||||
type TLNullableShapeProps,
|
||||
type TLParentId,
|
||||
type TLShape,
|
||||
|
@ -65,7 +67,6 @@ export {
|
|||
type TLShapePartial,
|
||||
type TLShapeProp,
|
||||
type TLShapeProps,
|
||||
type TLShapeType,
|
||||
type TLUnknownShape,
|
||||
} from './records/TLShape'
|
||||
export { TLUser, userTypeValidator, type TLUserId } from './records/TLUser'
|
||||
|
|
|
@ -17,15 +17,11 @@ import { TLVideoShape } from '../shapes/TLVideoShape'
|
|||
import { SmooshedUnionObject } from '../util-types'
|
||||
import { TLPageId } from './TLPage'
|
||||
|
||||
/** @public */
|
||||
export type TLUnknownShape = TLBaseShape<string, object>
|
||||
|
||||
/**
|
||||
* TLShape
|
||||
* The default set of shapes that are available in the editor.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TLShape =
|
||||
* @public */
|
||||
export type TLDefaultShape =
|
||||
| TLArrowShape
|
||||
| TLBookmarkShape
|
||||
| TLDrawShape
|
||||
|
@ -38,11 +34,21 @@ export type TLShape =
|
|||
| TLNoteShape
|
||||
| TLTextShape
|
||||
| TLVideoShape
|
||||
| TLUnknownShape
|
||||
| TLIconShape
|
||||
|
||||
/** @public */
|
||||
export type TLShapeType = TLShape['type']
|
||||
/**
|
||||
* A type for a shape that is available in the editor but whose type is
|
||||
* unknown—either one of the editor's default shapes or else a custom shape.
|
||||
*
|
||||
* @public */
|
||||
export type TLUnknownShape = TLBaseShape<string, object>
|
||||
|
||||
/**
|
||||
* The set of all shapes that are available in the editor, including unknown shapes.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TLShape = TLDefaultShape | TLUnknownShape
|
||||
|
||||
/** @public */
|
||||
export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
||||
|
|
|
@ -101,7 +101,7 @@ export function useLocalSyncClient({ universalPersistenceKey, instanceId, userId
|
|||
universalPersistenceKey: string;
|
||||
instanceId: TLInstanceId;
|
||||
userId: TLUserId;
|
||||
config?: TldrawEditorConfig;
|
||||
config: TldrawEditorConfig;
|
||||
}): SyncedStore;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
|
|
@ -34,7 +34,7 @@ function testClient(
|
|||
userId: TLUserId = TLUser.createCustomId('test'),
|
||||
channel = new BroadcastChannelMock('test')
|
||||
) {
|
||||
const store = TldrawEditorConfig.default.createStore({
|
||||
const store = new TldrawEditorConfig().createStore({
|
||||
userId,
|
||||
instanceId,
|
||||
})
|
||||
|
|
|
@ -14,12 +14,12 @@ export function useLocalSyncClient({
|
|||
universalPersistenceKey,
|
||||
instanceId,
|
||||
userId,
|
||||
config = TldrawEditorConfig.default,
|
||||
config,
|
||||
}: {
|
||||
universalPersistenceKey: string
|
||||
instanceId: TLInstanceId
|
||||
userId: TLUserId
|
||||
config?: TldrawEditorConfig
|
||||
config: TldrawEditorConfig
|
||||
}): SyncedStore {
|
||||
const [state, setState] = useState<{ id: string; syncedStore: SyncedStore } | null>(null)
|
||||
|
||||
|
|
Loading…
Reference in a new issue