[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 '@tldraw/tldraw/editor.css'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { track } from 'signia-react'
|
import { track } from 'signia-react'
|
||||||
import './custom-ui.css'
|
import './custom-ui.css'
|
||||||
|
|
||||||
|
const config = new TldrawEditorConfig()
|
||||||
|
|
||||||
export default function Example() {
|
export default function Example() {
|
||||||
return (
|
return (
|
||||||
<div className="tldraw__editor">
|
<div className="tldraw__editor">
|
||||||
<TldrawEditor autoFocus>
|
<TldrawEditor config={config} autoFocus>
|
||||||
<Canvas />
|
<Canvas />
|
||||||
<CustomUi />
|
<CustomUi />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
getUserData,
|
getUserData,
|
||||||
TldrawEditor,
|
TldrawEditor,
|
||||||
|
TldrawEditorConfig,
|
||||||
TldrawUi,
|
TldrawUi,
|
||||||
TLInstance,
|
TLInstance,
|
||||||
useLocalSyncClient,
|
useLocalSyncClient,
|
||||||
|
@ -12,10 +13,13 @@ import '@tldraw/tldraw/ui.css'
|
||||||
|
|
||||||
const instanceId = TLInstance.createCustomId('example')
|
const instanceId = TLInstance.createCustomId('example')
|
||||||
|
|
||||||
|
const config = new TldrawEditorConfig()
|
||||||
|
|
||||||
export default function Example() {
|
export default function Example() {
|
||||||
const userData = getUserData()
|
const userData = getUserData()
|
||||||
|
|
||||||
const syncedStore = useLocalSyncClient({
|
const syncedStore = useLocalSyncClient({
|
||||||
|
config,
|
||||||
instanceId,
|
instanceId,
|
||||||
userId: userData.id,
|
userId: userData.id,
|
||||||
universalPersistenceKey: 'exploded-example',
|
universalPersistenceKey: 'exploded-example',
|
||||||
|
@ -24,7 +28,13 @@ export default function Example() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tldraw__editor">
|
<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>
|
<TldrawUi>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<Canvas />
|
<Canvas />
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
ErrorBoundary,
|
ErrorBoundary,
|
||||||
setRuntimeOverrides,
|
setRuntimeOverrides,
|
||||||
TldrawEditor,
|
TldrawEditor,
|
||||||
|
TldrawEditorConfig,
|
||||||
TLUserId,
|
TLUserId,
|
||||||
} from '@tldraw/editor'
|
} from '@tldraw/editor'
|
||||||
import { linksUiOverrides } from './utils/links'
|
import { linksUiOverrides } from './utils/links'
|
||||||
|
@ -24,6 +25,8 @@ import { FullPageMessage } from './FullPageMessage'
|
||||||
import { onCreateBookmarkFromUrl } from './utils/bookmarks'
|
import { onCreateBookmarkFromUrl } from './utils/bookmarks'
|
||||||
import { vscode } from './utils/vscode'
|
import { vscode } from './utils/vscode'
|
||||||
|
|
||||||
|
const config = new TldrawEditorConfig()
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
||||||
setRuntimeOverrides({
|
setRuntimeOverrides({
|
||||||
|
@ -96,6 +99,7 @@ export const TldrawWrapper = () => {
|
||||||
uri: message.data.uri,
|
uri: message.data.uri,
|
||||||
userId: message.data.userId as TLUserId,
|
userId: message.data.userId as TLUserId,
|
||||||
isDarkMode: message.data.isDarkMode,
|
isDarkMode: message.data.isDarkMode,
|
||||||
|
config,
|
||||||
})
|
})
|
||||||
// We only want to listen for this message once
|
// We only want to listen for this message once
|
||||||
window.removeEventListener('message', handleMessage)
|
window.removeEventListener('message', handleMessage)
|
||||||
|
@ -126,20 +130,30 @@ export type TLDrawInnerProps = {
|
||||||
uri: string
|
uri: string
|
||||||
userId: TLUserId
|
userId: TLUserId
|
||||||
isDarkMode: boolean
|
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 instanceId = TAB_ID
|
||||||
const syncedStore = useLocalSyncClient({
|
const syncedStore = useLocalSyncClient({
|
||||||
universalPersistenceKey: uri,
|
universalPersistenceKey: uri,
|
||||||
instanceId,
|
instanceId,
|
||||||
userId,
|
userId,
|
||||||
|
config,
|
||||||
})
|
})
|
||||||
|
|
||||||
const assetUrls = useMemo(() => getAssetUrlsByImport({ baseUrl: assetSrc }), [assetSrc])
|
const assetUrls = useMemo(() => getAssetUrlsByImport({ baseUrl: assetSrc }), [assetSrc])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TldrawEditor
|
<TldrawEditor
|
||||||
|
config={config}
|
||||||
assetUrls={assetUrls}
|
assetUrls={assetUrls}
|
||||||
instanceId={TAB_ID}
|
instanceId={TAB_ID}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
|
|
|
@ -4,13 +4,13 @@ import * as vscode from 'vscode'
|
||||||
|
|
||||||
export const defaultFileContents: TldrawFile = {
|
export const defaultFileContents: TldrawFile = {
|
||||||
tldrawFileFormatVersion: 1,
|
tldrawFileFormatVersion: 1,
|
||||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||||
records: [],
|
records: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fileContentWithErrors: TldrawFile = {
|
export const fileContentWithErrors: TldrawFile = {
|
||||||
tldrawFileFormatVersion: 1,
|
tldrawFileFormatVersion: 1,
|
||||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||||
records: [{ typeName: 'shape', id: null } as any],
|
records: [{ typeName: 'shape', id: null } as any],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,16 @@ export async function pointWithinActiveArea(x: number, y: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function waitForReady() {
|
export async function waitForReady() {
|
||||||
await browser.waitUntil(() => {
|
await Promise.any([
|
||||||
return browser.execute(() => {
|
new Promise<boolean>((r) => {
|
||||||
return window.tldrawReady
|
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
|
// Make sure the window is focused... maybe
|
||||||
await ui.canvas.click(100, 100)
|
await ui.canvas.click(100, 100)
|
||||||
|
|
|
@ -44,7 +44,6 @@ import { sortByIndex } from '@tldraw/indices';
|
||||||
import { StoreSchema } from '@tldraw/tlstore';
|
import { StoreSchema } from '@tldraw/tlstore';
|
||||||
import { StoreSnapshot } from '@tldraw/tlstore';
|
import { StoreSnapshot } from '@tldraw/tlstore';
|
||||||
import { StrokePoint } from '@tldraw/primitives';
|
import { StrokePoint } from '@tldraw/primitives';
|
||||||
import { T } from '@tldraw/tlvalidate';
|
|
||||||
import { TLAlignType } from '@tldraw/tlschema';
|
import { TLAlignType } from '@tldraw/tlschema';
|
||||||
import { TLArrowheadType } from '@tldraw/tlschema';
|
import { TLArrowheadType } from '@tldraw/tlschema';
|
||||||
import { TLArrowShape } from '@tldraw/tlschema';
|
import { TLArrowShape } from '@tldraw/tlschema';
|
||||||
|
@ -87,7 +86,6 @@ import { TLShapeId } from '@tldraw/tlschema';
|
||||||
import { TLShapePartial } from '@tldraw/tlschema';
|
import { TLShapePartial } from '@tldraw/tlschema';
|
||||||
import { TLShapeProp } from '@tldraw/tlschema';
|
import { TLShapeProp } from '@tldraw/tlschema';
|
||||||
import { TLShapeProps } from '@tldraw/tlschema';
|
import { TLShapeProps } from '@tldraw/tlschema';
|
||||||
import { TLShapeType } from '@tldraw/tlschema';
|
|
||||||
import { TLSizeStyle } from '@tldraw/tlschema';
|
import { TLSizeStyle } from '@tldraw/tlschema';
|
||||||
import { TLSizeType } from '@tldraw/tlschema';
|
import { TLSizeType } from '@tldraw/tlschema';
|
||||||
import { TLStore } from '@tldraw/tlschema';
|
import { TLStore } from '@tldraw/tlschema';
|
||||||
|
@ -274,7 +272,7 @@ export class App extends EventEmitter<TLEventMap> {
|
||||||
getPageTransform(shape: TLShape): Matrix2d | undefined;
|
getPageTransform(shape: TLShape): Matrix2d | undefined;
|
||||||
getPageTransformById(id: TLShapeId): Matrix2d | undefined;
|
getPageTransformById(id: TLShapeId): Matrix2d | undefined;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShapeType): TLPageId | TLShapeId;
|
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']): TLPageId | TLShapeId;
|
||||||
getParentPageId(shape?: TLShape): TLPageId | undefined;
|
getParentPageId(shape?: TLShape): TLPageId | undefined;
|
||||||
getParentShape(shape?: TLShape): TLShape | undefined;
|
getParentShape(shape?: TLShape): TLShape | undefined;
|
||||||
getParentsMappedToChildren(ids: TLShapeId[]): Map<TLParentId, Set<TLShape>>;
|
getParentsMappedToChildren(ids: TLShapeId[]): Map<TLParentId, Set<TLShape>>;
|
||||||
|
@ -556,7 +554,7 @@ export function applyRotationToSnapshotShapes({ delta, app, snapshot, stage, }:
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface AppOptions {
|
export interface AppOptions {
|
||||||
config?: TldrawEditorConfig;
|
config: TldrawEditorConfig;
|
||||||
getContainer: () => HTMLElement;
|
getContainer: () => HTMLElement;
|
||||||
store: TLStore;
|
store: TLStore;
|
||||||
}
|
}
|
||||||
|
@ -1793,7 +1791,7 @@ export function TldrawEditor(props: TldrawEditorProps): JSX.Element;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export class TldrawEditorConfig {
|
export class TldrawEditorConfig {
|
||||||
constructor(opts: TldrawEditorConfigOptions);
|
constructor(opts?: TldrawEditorConfigOptions);
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
createStore(config: {
|
createStore(config: {
|
||||||
initialData?: StoreSnapshot<TLRecord>;
|
initialData?: StoreSnapshot<TLRecord>;
|
||||||
|
@ -1801,13 +1799,7 @@ export class TldrawEditorConfig {
|
||||||
instanceId: TLInstanceId;
|
instanceId: TLInstanceId;
|
||||||
}): TLStore;
|
}): TLStore;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
static readonly default: TldrawEditorConfig;
|
readonly shapeUtils: Record<TLShape['type'], TLShapeUtilConstructor<any>>;
|
||||||
// (undocumented)
|
|
||||||
readonly shapeMigrations: MigrationsForShapes<TLShape>;
|
|
||||||
// (undocumented)
|
|
||||||
readonly shapeUtils: UtilsForShapes<TLShape>;
|
|
||||||
// (undocumented)
|
|
||||||
readonly shapeValidators: Record<TLShape['type'], T.Validator<any>>;
|
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>;
|
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -1823,7 +1815,7 @@ export interface TldrawEditorProps {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
children?: any;
|
children?: any;
|
||||||
components?: Partial<TLEditorComponents>;
|
components?: Partial<TLEditorComponents>;
|
||||||
config?: TldrawEditorConfig;
|
config: TldrawEditorConfig;
|
||||||
instanceId?: TLInstanceId;
|
instanceId?: TLInstanceId;
|
||||||
isDarkMode?: boolean;
|
isDarkMode?: boolean;
|
||||||
onCreateAssetFromFile?: (file: File) => Promise<TLAsset>;
|
onCreateAssetFromFile?: (file: File) => Promise<TLAsset>;
|
||||||
|
@ -2044,7 +2036,7 @@ export class TLFrameUtil extends TLBoxUtil<TLFrameShape> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
canEdit: () => boolean;
|
canEdit: () => boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
canReceiveNewChildrenOfType: (_type: TLShapeType) => boolean;
|
canReceiveNewChildrenOfType: (_type: TLShape['type']) => boolean;
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
defaultProps(): TLFrameShape['props'];
|
defaultProps(): TLFrameShape['props'];
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -2456,7 +2448,7 @@ export abstract class TLShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
||||||
canCrop: TLShapeUtilFlag<T>;
|
canCrop: TLShapeUtilFlag<T>;
|
||||||
canDropShapes(shape: T, shapes: TLShape[]): boolean;
|
canDropShapes(shape: T, shapes: TLShape[]): boolean;
|
||||||
canEdit: TLShapeUtilFlag<T>;
|
canEdit: TLShapeUtilFlag<T>;
|
||||||
canReceiveNewChildrenOfType(type: TLShapeType): boolean;
|
canReceiveNewChildrenOfType(type: TLShape['type']): boolean;
|
||||||
canResize: TLShapeUtilFlag<T>;
|
canResize: TLShapeUtilFlag<T>;
|
||||||
canScroll: TLShapeUtilFlag<T>;
|
canScroll: TLShapeUtilFlag<T>;
|
||||||
canUnmount: TLShapeUtilFlag<T>;
|
canUnmount: TLShapeUtilFlag<T>;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { TLAsset, TLInstance, TLInstanceId, TLStore, TLUser, TLUserId } from '@tldraw/tlschema'
|
import { TLAsset, TLInstance, TLInstanceId, TLStore, TLUser, TLUserId } from '@tldraw/tlschema'
|
||||||
import { Store } from '@tldraw/tlstore'
|
import { Store } from '@tldraw/tlstore'
|
||||||
import { annotateError } from '@tldraw/utils'
|
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 { App } from './app/App'
|
||||||
import { EditorAssetUrls, defaultEditorAssetUrls } from './assetUrls'
|
import { EditorAssetUrls, defaultEditorAssetUrls } from './assetUrls'
|
||||||
import { OptionalErrorBoundary } from './components/ErrorBoundary'
|
import { OptionalErrorBoundary } from './components/ErrorBoundary'
|
||||||
|
@ -28,12 +28,12 @@ import { useZoomCss } from './hooks/useZoomCss'
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface TldrawEditorProps {
|
export interface TldrawEditorProps {
|
||||||
children?: any
|
children?: any
|
||||||
|
/** A configuration defining major customizations to the app, such as custom shapes and new tools */
|
||||||
|
config: TldrawEditorConfig
|
||||||
/** Overrides for the tldraw components */
|
/** Overrides for the tldraw components */
|
||||||
components?: Partial<TLEditorComponents>
|
components?: Partial<TLEditorComponents>
|
||||||
/** Whether to display the dark mode. */
|
/** Whether to display the dark mode. */
|
||||||
isDarkMode?: boolean
|
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.
|
* Called when the app has mounted.
|
||||||
*
|
*
|
||||||
|
@ -133,7 +133,7 @@ export function TldrawEditor(props: TldrawEditorProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function TldrawEditorBeforeLoading({
|
function TldrawEditorBeforeLoading({
|
||||||
config = TldrawEditorConfig.default,
|
config,
|
||||||
userId,
|
userId,
|
||||||
instanceId,
|
instanceId,
|
||||||
store,
|
store,
|
||||||
|
@ -143,26 +143,43 @@ function TldrawEditorBeforeLoading({
|
||||||
props.assetUrls ?? defaultEditorAssetUrls
|
props.assetUrls ?? defaultEditorAssetUrls
|
||||||
)
|
)
|
||||||
|
|
||||||
store ??= config.createStore({
|
const [_store, _setStore] = useState<TLStore | SyncedStore>(() => {
|
||||||
userId: userId ?? TLUser.createId(),
|
return (
|
||||||
instanceId: instanceId ?? TLInstance.createId(),
|
store ??
|
||||||
|
config.createStore({
|
||||||
|
userId: userId ?? TLUser.createId(),
|
||||||
|
instanceId: instanceId ?? TLInstance.createId(),
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
let loadedStore
|
useEffect(() => {
|
||||||
if (!(store instanceof Store)) {
|
_setStore(() => {
|
||||||
if (store.error) {
|
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.
|
// for error handling, we fall back to the default error boundary.
|
||||||
// if users want to handle this error differently, they can render
|
// if users want to handle this error differently, they can render
|
||||||
// their own error screen before the TldrawEditor component
|
// their own error screen before the TldrawEditor component
|
||||||
throw store.error
|
throw _store.error
|
||||||
}
|
}
|
||||||
if (!store.store) {
|
if (!_store.store) {
|
||||||
return <LoadingScreen>Connecting...</LoadingScreen>
|
return <LoadingScreen>Connecting...</LoadingScreen>
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedStore = store.store
|
loadedStore = _store.store
|
||||||
} else {
|
} else {
|
||||||
loadedStore = store
|
loadedStore = _store
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instanceId && loadedStore.props.instanceId !== instanceId) {
|
if (instanceId && loadedStore.props.instanceId !== instanceId) {
|
||||||
|
@ -209,8 +226,8 @@ function TldrawEditorAfterLoading({
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
const app = new App({
|
const app = new App({
|
||||||
store,
|
store,
|
||||||
getContainer: () => container,
|
|
||||||
config,
|
config,
|
||||||
|
getContainer: () => container,
|
||||||
})
|
})
|
||||||
setApp(app)
|
setApp(app)
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ import {
|
||||||
TLShapeId,
|
TLShapeId,
|
||||||
TLShapePartial,
|
TLShapePartial,
|
||||||
TLShapeProp,
|
TLShapeProp,
|
||||||
TLShapeType,
|
|
||||||
TLSizeStyle,
|
TLSizeStyle,
|
||||||
TLStore,
|
TLStore,
|
||||||
TLUnknownShape,
|
TLUnknownShape,
|
||||||
|
@ -160,7 +159,7 @@ export interface AppOptions {
|
||||||
*/
|
*/
|
||||||
store: TLStore
|
store: TLStore
|
||||||
/** A configuration defining major customizations to the app, such as custom shapes and new tools */
|
/** 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
|
* Should return a containing html element which has all the styles applied to the app. If not
|
||||||
* given, the body element will be used.
|
* given, the body element will be used.
|
||||||
|
@ -175,14 +174,15 @@ export function isShapeWithHandles(shape: TLShape) {
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class App extends EventEmitter<TLEventMap> {
|
export class App extends EventEmitter<TLEventMap> {
|
||||||
constructor({ config = TldrawEditorConfig.default, store, getContainer }: AppOptions) {
|
constructor({ config, store, getContainer }: AppOptions) {
|
||||||
super()
|
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')
|
throw new Error('Store schema does not match schema given to App')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.config = config
|
|
||||||
this.store = store
|
this.store = store
|
||||||
|
|
||||||
this.getContainer = getContainer ?? (() => document.body)
|
this.getContainer = getContainer ?? (() => document.body)
|
||||||
|
@ -191,7 +191,7 @@ export class App extends EventEmitter<TLEventMap> {
|
||||||
|
|
||||||
// Set the shape utils
|
// Set the shape utils
|
||||||
this.shapeUtils = Object.fromEntries(
|
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) {
|
if (typeof window !== 'undefined' && 'navigator' in window) {
|
||||||
|
@ -209,7 +209,7 @@ export class App extends EventEmitter<TLEventMap> {
|
||||||
|
|
||||||
this.root = new RootState(this)
|
this.root = new RootState(this)
|
||||||
if (this.root.children) {
|
if (this.root.children) {
|
||||||
config.tools.forEach((Ctor) => {
|
this.config.tools.forEach((Ctor) => {
|
||||||
this.root.children![Ctor.id] = new Ctor(this)
|
this.root.children![Ctor.id] = new Ctor(this)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2864,7 +2864,7 @@ export class App extends EventEmitter<TLEventMap> {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShapeType) {
|
getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']) {
|
||||||
const shapes = this.sortedShapesArray
|
const shapes = this.sortedShapesArray
|
||||||
|
|
||||||
for (let i = shapes.length - 1; i >= 0; i--) {
|
for (let i = shapes.length - 1; i >= 0; i--) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { canolicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives'
|
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 { last } from '@tldraw/utils'
|
||||||
import { SVGContainer } from '../../../components/SVGContainer'
|
import { SVGContainer } from '../../../components/SVGContainer'
|
||||||
import { defaultEmptyAs } from '../../../utils/string'
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
TLHandle,
|
TLHandle,
|
||||||
TLShape,
|
TLShape,
|
||||||
TLShapePartial,
|
TLShapePartial,
|
||||||
TLShapeType,
|
|
||||||
TLUnknownShape,
|
TLUnknownShape,
|
||||||
Vec2dModel,
|
Vec2dModel,
|
||||||
} from '@tldraw/tlschema'
|
} from '@tldraw/tlschema'
|
||||||
|
@ -308,7 +307,7 @@ export abstract class TLShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
||||||
* @param type - The shape type.
|
* @param type - The shape type.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
canReceiveNewChildrenOfType(type: TLShapeType) {
|
canReceiveNewChildrenOfType(type: TLShape['type']) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { TLShapeType, TLStyleType } from '@tldraw/tlschema'
|
import { TLStyleType } from '@tldraw/tlschema'
|
||||||
import { StateNode } from '../StateNode'
|
import { StateNode } from '../StateNode'
|
||||||
import { Idle } from './children/Idle'
|
import { Idle } from './children/Idle'
|
||||||
import { Pointing } from './children/Pointing'
|
import { Pointing } from './children/Pointing'
|
||||||
|
@ -8,7 +8,7 @@ export class TLArrowTool extends StateNode {
|
||||||
static initial = 'idle'
|
static initial = 'idle'
|
||||||
static children = () => [Idle, Pointing]
|
static children = () => [Idle, Pointing]
|
||||||
|
|
||||||
shapeType: TLShapeType = 'arrow'
|
shapeType = 'arrow'
|
||||||
|
|
||||||
styles = [
|
styles = [
|
||||||
'color',
|
'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 { TLArrowUtil } from '../../../shapeutils/TLArrowUtil/TLArrowUtil'
|
||||||
import { TLEventHandlers } from '../../../types/event-types'
|
import { TLEventHandlers } from '../../../types/event-types'
|
||||||
import { StateNode } from '../../StateNode'
|
import { StateNode } from '../../StateNode'
|
||||||
|
@ -7,8 +7,6 @@ import { TLArrowTool } from '../TLArrowTool'
|
||||||
export class Pointing extends StateNode {
|
export class Pointing extends StateNode {
|
||||||
static override id = 'pointing'
|
static override id = 'pointing'
|
||||||
|
|
||||||
shapeType = '' as TLShapeType
|
|
||||||
|
|
||||||
shape?: TLArrowShape
|
shape?: TLArrowShape
|
||||||
|
|
||||||
preciseTimeout = -1
|
preciseTimeout = -1
|
||||||
|
@ -33,7 +31,7 @@ export class Pointing extends StateNode {
|
||||||
|
|
||||||
this.didTimeout = false
|
this.didTimeout = false
|
||||||
|
|
||||||
this.shapeType = (this.parent as TLArrowTool).shapeType
|
const shapeType = (this.parent as TLArrowTool).shapeType
|
||||||
|
|
||||||
this.app.mark('creating')
|
this.app.mark('creating')
|
||||||
|
|
||||||
|
@ -42,7 +40,7 @@ export class Pointing extends StateNode {
|
||||||
this.app.createShapes([
|
this.app.createShapes([
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type: this.shapeType,
|
type: shapeType,
|
||||||
x: currentPagePoint.x,
|
x: currentPagePoint.x,
|
||||||
y: currentPagePoint.y,
|
y: currentPagePoint.y,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { TLShapeType, TLStyleType } from '@tldraw/tlschema'
|
import { TLStyleType } from '@tldraw/tlschema'
|
||||||
import { StateNode } from '../StateNode'
|
import { StateNode } from '../StateNode'
|
||||||
|
|
||||||
import { Idle } from './children/Idle'
|
import { Idle } from './children/Idle'
|
||||||
|
@ -9,7 +9,7 @@ export class TLLineTool extends StateNode {
|
||||||
static initial = 'idle'
|
static initial = 'idle'
|
||||||
static children = () => [Idle, Pointing]
|
static children = () => [Idle, Pointing]
|
||||||
|
|
||||||
shapeType: TLShapeType = 'line'
|
shapeType = 'line'
|
||||||
|
|
||||||
styles = ['color', 'opacity', 'dash', 'size', 'spline'] as TLStyleType[]
|
styles = ['color', 'opacity', 'dash', 'size', 'spline'] as TLStyleType[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getIndexAbove, sortByIndex } from '@tldraw/indices'
|
import { getIndexAbove, sortByIndex } from '@tldraw/indices'
|
||||||
import { Matrix2d, Vec2d } from '@tldraw/primitives'
|
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 { last, structuredClone } from '@tldraw/utils'
|
||||||
import { TLEventHandlers, TLInterruptEvent } from '../../../types/event-types'
|
import { TLEventHandlers, TLInterruptEvent } from '../../../types/event-types'
|
||||||
import { StateNode } from '../../StateNode'
|
import { StateNode } from '../../StateNode'
|
||||||
|
@ -9,8 +9,6 @@ import { TLLineTool } from '../TLLineTool'
|
||||||
export class Pointing extends StateNode {
|
export class Pointing extends StateNode {
|
||||||
static override id = 'pointing'
|
static override id = 'pointing'
|
||||||
|
|
||||||
shapeType = '' as TLShapeType
|
|
||||||
|
|
||||||
shape = {} as TLLineShape
|
shape = {} as TLLineShape
|
||||||
|
|
||||||
markPointId = ''
|
markPointId = ''
|
||||||
|
@ -19,7 +17,6 @@ export class Pointing extends StateNode {
|
||||||
const { inputs } = this.app
|
const { inputs } = this.app
|
||||||
const { currentPagePoint } = inputs
|
const { currentPagePoint } = inputs
|
||||||
|
|
||||||
this.shapeType = (this.parent as TLLineTool).shapeType
|
|
||||||
this.markPointId = this.app.mark('creating')
|
this.markPointId = this.app.mark('creating')
|
||||||
|
|
||||||
let shapeExists = false
|
let shapeExists = false
|
||||||
|
@ -85,7 +82,7 @@ export class Pointing extends StateNode {
|
||||||
this.app.createShapes([
|
this.app.createShapes([
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
type: this.shapeType,
|
type: (this.parent as TLLineTool).shapeType,
|
||||||
x: currentPagePoint.x,
|
x: currentPagePoint.x,
|
||||||
y: currentPagePoint.y,
|
y: currentPagePoint.y,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,63 +1,19 @@
|
||||||
import {
|
import {
|
||||||
CLIENT_FIXUP_SCRIPT,
|
CLIENT_FIXUP_SCRIPT,
|
||||||
TLAsset,
|
|
||||||
TLCamera,
|
|
||||||
TLDOCUMENT_ID,
|
TLDOCUMENT_ID,
|
||||||
TLDocument,
|
TLDefaultShape,
|
||||||
TLInstance,
|
TLInstance,
|
||||||
TLInstanceId,
|
TLInstanceId,
|
||||||
TLInstancePageState,
|
|
||||||
TLInstancePresence,
|
TLInstancePresence,
|
||||||
TLPage,
|
|
||||||
TLRecord,
|
TLRecord,
|
||||||
TLShape,
|
TLShape,
|
||||||
TLStore,
|
TLStore,
|
||||||
TLStoreProps,
|
TLStoreProps,
|
||||||
TLUnknownShape,
|
|
||||||
TLUser,
|
TLUser,
|
||||||
TLUserDocument,
|
|
||||||
TLUserId,
|
TLUserId,
|
||||||
TLUserPresence,
|
createTLSchema,
|
||||||
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,
|
|
||||||
} from '@tldraw/tlschema'
|
} from '@tldraw/tlschema'
|
||||||
import {
|
import { Migrations, RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
|
||||||
Migrations,
|
|
||||||
RecordType,
|
|
||||||
Store,
|
|
||||||
StoreSchema,
|
|
||||||
StoreSnapshot,
|
|
||||||
createRecordType,
|
|
||||||
defineMigrations,
|
|
||||||
} from '@tldraw/tlstore'
|
|
||||||
import { T } from '@tldraw/tlvalidate'
|
|
||||||
import { Signal } from 'signia'
|
import { Signal } from 'signia'
|
||||||
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||||
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||||
|
@ -74,135 +30,69 @@ import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
||||||
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||||
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
||||||
|
|
||||||
/** @public */
|
// Secret shape types that don't have a shape util yet
|
||||||
export type ValidatorsForShapes<T extends TLUnknownShape> = Record<
|
type ShapeTypesNotImplemented = 'icon'
|
||||||
T['type'],
|
|
||||||
{ validate: (record: T) => T }
|
|
||||||
>
|
|
||||||
|
|
||||||
/** @public */
|
const DEFAULT_SHAPE_UTILS: {
|
||||||
export type MigrationsForShapes<T extends TLUnknownShape> = Record<T['type'], Migrations>
|
[K in Exclude<TLDefaultShape['type'], ShapeTypesNotImplemented>]: TLShapeUtilConstructor<any>
|
||||||
|
} = {
|
||||||
type CustomShapeInfo<T extends TLUnknownShape> = {
|
arrow: TLArrowUtil,
|
||||||
util: TLShapeUtilConstructor<any>
|
bookmark: TLBookmarkUtil,
|
||||||
validator?: { validate: (record: T) => T }
|
draw: TLDrawUtil,
|
||||||
migrations?: Migrations
|
embed: TLEmbedUtil,
|
||||||
|
frame: TLFrameUtil,
|
||||||
|
geo: TLGeoUtil,
|
||||||
|
group: TLGroupUtil,
|
||||||
|
image: TLImageUtil,
|
||||||
|
line: TLLineUtil,
|
||||||
|
note: TLNoteUtil,
|
||||||
|
text: TLTextUtil,
|
||||||
|
video: TLVideoUtil,
|
||||||
}
|
}
|
||||||
|
|
||||||
type UtilsForShapes<T extends TLUnknownShape> = Record<T['type'], TLShapeUtilConstructor<any>>
|
/** @public */
|
||||||
|
export type TldrawEditorConfigOptions = {
|
||||||
type TldrawEditorConfigOptions<T extends TLUnknownShape = TLShape> = {
|
|
||||||
tools?: readonly StateNodeConstructor[]
|
tools?: readonly StateNodeConstructor[]
|
||||||
shapes?: { [K in T['type']]: CustomShapeInfo<T> }
|
shapes?: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
util: TLShapeUtilConstructor<any>
|
||||||
|
validator?: { validate: <T>(record: T) => T }
|
||||||
|
migrations?: Migrations
|
||||||
|
}
|
||||||
|
>
|
||||||
/** @internal */
|
/** @internal */
|
||||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class TldrawEditorConfig {
|
export class TldrawEditorConfig {
|
||||||
static readonly default = new TldrawEditorConfig({})
|
// Custom tools
|
||||||
|
|
||||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>
|
|
||||||
readonly TLShape: RecordType<TLShape, 'type' | 'props' | 'index' | 'parentId'>
|
|
||||||
readonly tools: readonly StateNodeConstructor[]
|
readonly tools: readonly StateNodeConstructor[]
|
||||||
|
|
||||||
// Custom shape utils
|
// Custom shape utils
|
||||||
readonly shapeUtils: UtilsForShapes<TLShape>
|
readonly shapeUtils: Record<TLShape['type'], TLShapeUtilConstructor<any>>
|
||||||
// Validators for shape subtypes
|
|
||||||
readonly shapeValidators: Record<TLShape['type'], T.Validator<any>>
|
|
||||||
// Migrations for shape subtypes
|
|
||||||
readonly shapeMigrations: MigrationsForShapes<TLShape>
|
|
||||||
|
|
||||||
constructor(opts: TldrawEditorConfigOptions) {
|
// The record used for TLShape incorporating any custom shapes
|
||||||
const { shapes = [], tools = [], derivePresenceState } = opts
|
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.tools = tools
|
||||||
|
|
||||||
this.shapeUtils = {
|
this.shapeUtils = {
|
||||||
arrow: TLArrowUtil,
|
...DEFAULT_SHAPE_UTILS,
|
||||||
bookmark: TLBookmarkUtil,
|
...Object.fromEntries(Object.entries(shapes).map(([k, v]) => [k, v.util])),
|
||||||
draw: TLDrawUtil,
|
|
||||||
embed: TLEmbedUtil,
|
|
||||||
frame: TLFrameUtil,
|
|
||||||
geo: TLGeoUtil,
|
|
||||||
group: TLGroupUtil,
|
|
||||||
image: TLImageUtil,
|
|
||||||
line: TLLineUtil,
|
|
||||||
note: TLNoteUtil,
|
|
||||||
text: TLTextUtil,
|
|
||||||
video: TLVideoUtil,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shapeMigrations = {
|
this.storeSchema = createTLSchema({
|
||||||
arrow: arrowShapeTypeMigrations,
|
customShapes: shapes,
|
||||||
bookmark: bookmarkShapeTypeMigrations,
|
derivePresenceState: derivePresenceState,
|
||||||
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>(
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
this.TLShape = this.storeSchema.types.shape as RecordType<
|
this.TLShape = this.storeSchema.types.shape as RecordType<
|
||||||
TLShape,
|
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 const TEST_USER_ID = TLUser.createCustomId('testUser1')
|
||||||
|
|
||||||
export class TestApp extends App {
|
export class TestApp extends App {
|
||||||
constructor(options = {} as Partial<AppOptions>) {
|
constructor(options = {} as Partial<Omit<AppOptions, 'store'>>) {
|
||||||
const elm = document.createElement('div')
|
const elm = document.createElement('div')
|
||||||
elm.tabIndex = 0
|
elm.tabIndex = 0
|
||||||
|
const config = options.config ?? new TldrawEditorConfig()
|
||||||
super({
|
super({
|
||||||
store: (options.config ?? TldrawEditorConfig.default).createStore({
|
config,
|
||||||
|
store: config.createStore({
|
||||||
userId: TEST_USER_ID,
|
userId: TEST_USER_ID,
|
||||||
instanceId: TEST_INSTANCE_ID,
|
instanceId: TEST_INSTANCE_ID,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -21,14 +21,17 @@ afterEach(() => {
|
||||||
|
|
||||||
describe('<Tldraw />', () => {
|
describe('<Tldraw />', () => {
|
||||||
it('Accepts fresh versions of store and calls `onMount` for each one', async () => {
|
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'),
|
instanceId: TLInstance.createCustomId('test'),
|
||||||
userId: TLUser.createCustomId('test'),
|
userId: TLUser.createCustomId('test'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const onMount = jest.fn()
|
const onMount = jest.fn()
|
||||||
|
|
||||||
const rendered = render(
|
const rendered = render(
|
||||||
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
<TldrawEditor config={config} store={initialStore} onMount={onMount} autoFocus>
|
||||||
<div data-testid="canvas-1" />
|
<div data-testid="canvas-1" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
)
|
)
|
||||||
|
@ -40,7 +43,7 @@ describe('<Tldraw />', () => {
|
||||||
|
|
||||||
// re-render with the same store:
|
// re-render with the same store:
|
||||||
rendered.rerender(
|
rendered.rerender(
|
||||||
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
<TldrawEditor config={config} store={initialStore} onMount={onMount} autoFocus>
|
||||||
<div data-testid="canvas-2" />
|
<div data-testid="canvas-2" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
)
|
)
|
||||||
|
@ -49,12 +52,12 @@ describe('<Tldraw />', () => {
|
||||||
expect(onMount).toHaveBeenCalledTimes(1)
|
expect(onMount).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
// re-render with a new store:
|
// re-render with a new store:
|
||||||
const newStore = TldrawEditorConfig.default.createStore({
|
const newStore = config.createStore({
|
||||||
instanceId: TLInstance.createCustomId('test'),
|
instanceId: TLInstance.createCustomId('test'),
|
||||||
userId: TLUser.createCustomId('test'),
|
userId: TLUser.createCustomId('test'),
|
||||||
})
|
})
|
||||||
rendered.rerender(
|
rendered.rerender(
|
||||||
<TldrawEditor store={newStore} onMount={onMount} autoFocus>
|
<TldrawEditor config={config} store={newStore} onMount={onMount} autoFocus>
|
||||||
<div data-testid="canvas-3" />
|
<div data-testid="canvas-3" />
|
||||||
</TldrawEditor>
|
</TldrawEditor>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1076,19 +1076,6 @@ export interface LegacyTldrawDocument {
|
||||||
|
|
||||||
/* ------------------ Translations ------------------ */
|
/* ------------------ 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> = {
|
const v1ColorsToV2Colors: Record<ColorStyle, TLColorType> = {
|
||||||
[ColorStyle.White]: 'black',
|
[ColorStyle.White]: 'black',
|
||||||
[ColorStyle.Black]: 'black',
|
[ColorStyle.Black]: 'black',
|
||||||
|
|
|
@ -208,7 +208,7 @@ export async function parseAndLoadDocument(
|
||||||
forceDarkMode?: boolean
|
forceDarkMode?: boolean
|
||||||
) {
|
) {
|
||||||
const parseFileResult = parseTldrawJsonFile({
|
const parseFileResult = parseTldrawJsonFile({
|
||||||
config: TldrawEditorConfig.default,
|
config: new TldrawEditorConfig(),
|
||||||
json: document,
|
json: document,
|
||||||
instanceId: app.instanceId,
|
instanceId: app.instanceId,
|
||||||
userId: app.userId,
|
userId: app.userId,
|
||||||
|
|
|
@ -17,14 +17,14 @@ function serialize(file: TldrawFile): string {
|
||||||
|
|
||||||
describe('parseTldrawJsonFile', () => {
|
describe('parseTldrawJsonFile', () => {
|
||||||
it('returns an error if the file is not json', () => {
|
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)
|
assert(!result.ok)
|
||||||
expect(result.error.type).toBe('notATldrawFile')
|
expect(result.error.type).toBe('notATldrawFile')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an error if the file doesnt look like a tldraw file', () => {
|
it('returns an error if the file doesnt look like a tldraw file', () => {
|
||||||
const result = parseTldrawJsonFile(
|
const result = parseTldrawJsonFile(
|
||||||
TldrawEditorConfig.default,
|
new TldrawEditorConfig(),
|
||||||
JSON.stringify({ not: 'a tldraw file' })
|
JSON.stringify({ not: 'a tldraw file' })
|
||||||
)
|
)
|
||||||
assert(!result.ok)
|
assert(!result.ok)
|
||||||
|
@ -33,10 +33,10 @@ describe('parseTldrawJsonFile', () => {
|
||||||
|
|
||||||
it('returns an error if the file version is too old', () => {
|
it('returns an error if the file version is too old', () => {
|
||||||
const result = parseTldrawJsonFile(
|
const result = parseTldrawJsonFile(
|
||||||
TldrawEditorConfig.default,
|
new TldrawEditorConfig(),
|
||||||
serialize({
|
serialize({
|
||||||
tldrawFileFormatVersion: 0,
|
tldrawFileFormatVersion: 0,
|
||||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||||
records: [],
|
records: [],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -46,10 +46,10 @@ describe('parseTldrawJsonFile', () => {
|
||||||
|
|
||||||
it('returns an error if the file version is too new', () => {
|
it('returns an error if the file version is too new', () => {
|
||||||
const result = parseTldrawJsonFile(
|
const result = parseTldrawJsonFile(
|
||||||
TldrawEditorConfig.default,
|
new TldrawEditorConfig(),
|
||||||
serialize({
|
serialize({
|
||||||
tldrawFileFormatVersion: 100,
|
tldrawFileFormatVersion: 100,
|
||||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||||
records: [],
|
records: [],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -58,10 +58,10 @@ describe('parseTldrawJsonFile', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an error if migrations fail', () => {
|
it('returns an error if migrations fail', () => {
|
||||||
const serializedSchema = TldrawEditorConfig.default.storeSchema.serialize()
|
const serializedSchema = new TldrawEditorConfig().storeSchema.serialize()
|
||||||
serializedSchema.storeVersion = 100
|
serializedSchema.storeVersion = 100
|
||||||
const result = parseTldrawJsonFile(
|
const result = parseTldrawJsonFile(
|
||||||
TldrawEditorConfig.default,
|
new TldrawEditorConfig(),
|
||||||
serialize({
|
serialize({
|
||||||
tldrawFileFormatVersion: 1,
|
tldrawFileFormatVersion: 1,
|
||||||
schema: serializedSchema,
|
schema: serializedSchema,
|
||||||
|
@ -72,10 +72,10 @@ describe('parseTldrawJsonFile', () => {
|
||||||
assert(result.error.type === 'migrationFailed')
|
assert(result.error.type === 'migrationFailed')
|
||||||
expect(result.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld)
|
expect(result.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld)
|
||||||
|
|
||||||
const serializedSchema2 = TldrawEditorConfig.default.storeSchema.serialize()
|
const serializedSchema2 = new TldrawEditorConfig().storeSchema.serialize()
|
||||||
serializedSchema2.recordVersions.shape.version = 100
|
serializedSchema2.recordVersions.shape.version = 100
|
||||||
const result2 = parseTldrawJsonFile(
|
const result2 = parseTldrawJsonFile(
|
||||||
TldrawEditorConfig.default,
|
new TldrawEditorConfig(),
|
||||||
serialize({
|
serialize({
|
||||||
tldrawFileFormatVersion: 1,
|
tldrawFileFormatVersion: 1,
|
||||||
schema: serializedSchema2,
|
schema: serializedSchema2,
|
||||||
|
@ -90,10 +90,10 @@ describe('parseTldrawJsonFile', () => {
|
||||||
|
|
||||||
it('returns an error if a record is invalid', () => {
|
it('returns an error if a record is invalid', () => {
|
||||||
const result = parseTldrawJsonFile(
|
const result = parseTldrawJsonFile(
|
||||||
TldrawEditorConfig.default,
|
new TldrawEditorConfig(),
|
||||||
serialize({
|
serialize({
|
||||||
tldrawFileFormatVersion: 1,
|
tldrawFileFormatVersion: 1,
|
||||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||||
records: [
|
records: [
|
||||||
{
|
{
|
||||||
typeName: 'shape',
|
typeName: 'shape',
|
||||||
|
@ -113,10 +113,10 @@ describe('parseTldrawJsonFile', () => {
|
||||||
|
|
||||||
it('returns a store if the file is valid', () => {
|
it('returns a store if the file is valid', () => {
|
||||||
const result = parseTldrawJsonFile(
|
const result = parseTldrawJsonFile(
|
||||||
TldrawEditorConfig.default,
|
new TldrawEditorConfig(),
|
||||||
serialize({
|
serialize({
|
||||||
tldrawFileFormatVersion: 1,
|
tldrawFileFormatVersion: 1,
|
||||||
schema: TldrawEditorConfig.default.storeSchema.serialize(),
|
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||||
records: [],
|
records: [],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,9 +8,10 @@ import { TldrawEditorProps } from '@tldraw/editor';
|
||||||
import { TldrawUiContextProviderProps } from '@tldraw/ui';
|
import { TldrawUiContextProviderProps } from '@tldraw/ui';
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function Tldraw(props: Omit<TldrawEditorProps, 'store'> & TldrawUiContextProviderProps & {
|
export function Tldraw(props: Omit<TldrawEditorProps, 'config' | 'store'> & TldrawUiContextProviderProps & {
|
||||||
persistenceKey?: string;
|
persistenceKey?: string;
|
||||||
hideUi?: boolean;
|
hideUi?: boolean;
|
||||||
|
config?: TldrawEditorProps['config'];
|
||||||
}): JSX.Element;
|
}): JSX.Element;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Canvas, TldrawEditor, TldrawEditorProps } from '@tldraw/editor'
|
import { Canvas, TldrawEditor, TldrawEditorConfig, TldrawEditorProps } from '@tldraw/editor'
|
||||||
import {
|
import {
|
||||||
DEFAULT_DOCUMENT_NAME,
|
DEFAULT_DOCUMENT_NAME,
|
||||||
TAB_ID,
|
TAB_ID,
|
||||||
|
@ -6,18 +6,33 @@ import {
|
||||||
useLocalSyncClient,
|
useLocalSyncClient,
|
||||||
} from '@tldraw/tlsync-client'
|
} from '@tldraw/tlsync-client'
|
||||||
import { ContextMenu, TldrawUi, TldrawUiContextProviderProps } from '@tldraw/ui'
|
import { ContextMenu, TldrawUi, TldrawUiContextProviderProps } from '@tldraw/ui'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export function Tldraw(
|
export function Tldraw(
|
||||||
props: Omit<TldrawEditorProps, 'store'> &
|
props: Omit<TldrawEditorProps, 'store' | 'config'> &
|
||||||
TldrawUiContextProviderProps & {
|
TldrawUiContextProviderProps & {
|
||||||
/** The key under which to persist this editor's data to local storage. */
|
/** The key under which to persist this editor's data to local storage. */
|
||||||
persistenceKey?: string
|
persistenceKey?: string
|
||||||
/** Whether to hide the user interface and only display the canvas. */
|
/** Whether to hide the user interface and only display the canvas. */
|
||||||
hideUi?: boolean
|
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()
|
const userData = getUserData()
|
||||||
|
|
||||||
|
@ -25,13 +40,19 @@ export function Tldraw(
|
||||||
|
|
||||||
const syncedStore = useLocalSyncClient({
|
const syncedStore = useLocalSyncClient({
|
||||||
instanceId,
|
instanceId,
|
||||||
userId: userId,
|
userId,
|
||||||
|
config: _config,
|
||||||
universalPersistenceKey: persistenceKey,
|
universalPersistenceKey: persistenceKey,
|
||||||
config: props.config,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TldrawEditor {...rest} instanceId={instanceId} userId={userId} store={syncedStore}>
|
<TldrawEditor
|
||||||
|
{...rest}
|
||||||
|
instanceId={instanceId}
|
||||||
|
userId={userId}
|
||||||
|
store={syncedStore}
|
||||||
|
config={_config}
|
||||||
|
>
|
||||||
<TldrawUi {...rest}>
|
<TldrawUi {...rest}>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<Canvas />
|
<Canvas />
|
||||||
|
|
|
@ -103,6 +103,12 @@ export function createShapeValidator<Type extends string, Props extends object>(
|
||||||
props: Props;
|
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)
|
// @public (undocumented)
|
||||||
export const cursorTypeValidator: T.Validator<string>;
|
export const cursorTypeValidator: T.Validator<string>;
|
||||||
|
|
||||||
|
@ -722,6 +728,9 @@ export interface TLDashStyle extends TLBaseStyle {
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLDashType = SetValue<typeof TL_DASH_TYPES>;
|
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
|
// @public
|
||||||
export interface TLDocument extends BaseRecord<'document'> {
|
export interface TLDocument extends BaseRecord<'document'> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -1137,7 +1146,7 @@ export type TLScribble = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public
|
// @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)
|
// @public (undocumented)
|
||||||
export type TLShapeId = ID<TLBaseShape<any, any>>;
|
export type TLShapeId = ID<TLBaseShape<any, any>>;
|
||||||
|
@ -1155,9 +1164,6 @@ export type TLShapeProp = keyof TLShapeProps;
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLShapeProps = SmooshedUnionObject<TLShape['props']>;
|
export type TLShapeProps = SmooshedUnionObject<TLShape['props']>;
|
||||||
|
|
||||||
// @public (undocumented)
|
|
||||||
export type TLShapeType = TLShape['type'];
|
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export interface TLSizeStyle extends TLBaseStyle {
|
export interface TLSizeStyle extends TLBaseStyle {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
|
@ -1252,7 +1258,7 @@ export type TLTextShapeProps = {
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export type TLUiColorType = SetValue<typeof TL_UI_COLOR_TYPES>;
|
export type TLUiColorType = SetValue<typeof TL_UI_COLOR_TYPES>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public
|
||||||
export type TLUnknownShape = TLBaseShape<string, object>;
|
export type TLUnknownShape = TLBaseShape<string, object>;
|
||||||
|
|
||||||
// @public
|
// @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,
|
type TLVideoAsset,
|
||||||
} from './assets/TLVideoAsset'
|
} from './assets/TLVideoAsset'
|
||||||
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
|
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
|
||||||
|
export { createTLSchema } from './createTLSchema'
|
||||||
export { defaultDerivePresenceState } from './defaultDerivePresenceState'
|
export { defaultDerivePresenceState } from './defaultDerivePresenceState'
|
||||||
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
|
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
|
||||||
export { type Box2dModel, type Vec2dModel } from './geometry-types'
|
export { type Box2dModel, type Vec2dModel } from './geometry-types'
|
||||||
|
@ -58,6 +59,7 @@ export {
|
||||||
isShape,
|
isShape,
|
||||||
isShapeId,
|
isShapeId,
|
||||||
rootShapeTypeMigrations,
|
rootShapeTypeMigrations,
|
||||||
|
type TLDefaultShape,
|
||||||
type TLNullableShapeProps,
|
type TLNullableShapeProps,
|
||||||
type TLParentId,
|
type TLParentId,
|
||||||
type TLShape,
|
type TLShape,
|
||||||
|
@ -65,7 +67,6 @@ export {
|
||||||
type TLShapePartial,
|
type TLShapePartial,
|
||||||
type TLShapeProp,
|
type TLShapeProp,
|
||||||
type TLShapeProps,
|
type TLShapeProps,
|
||||||
type TLShapeType,
|
|
||||||
type TLUnknownShape,
|
type TLUnknownShape,
|
||||||
} from './records/TLShape'
|
} from './records/TLShape'
|
||||||
export { TLUser, userTypeValidator, type TLUserId } from './records/TLUser'
|
export { TLUser, userTypeValidator, type TLUserId } from './records/TLUser'
|
||||||
|
|
|
@ -17,15 +17,11 @@ import { TLVideoShape } from '../shapes/TLVideoShape'
|
||||||
import { SmooshedUnionObject } from '../util-types'
|
import { SmooshedUnionObject } from '../util-types'
|
||||||
import { TLPageId } from './TLPage'
|
import { TLPageId } from './TLPage'
|
||||||
|
|
||||||
/** @public */
|
|
||||||
export type TLUnknownShape = TLBaseShape<string, object>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TLShape
|
* The default set of shapes that are available in the editor.
|
||||||
*
|
*
|
||||||
* @public
|
* @public */
|
||||||
*/
|
export type TLDefaultShape =
|
||||||
export type TLShape =
|
|
||||||
| TLArrowShape
|
| TLArrowShape
|
||||||
| TLBookmarkShape
|
| TLBookmarkShape
|
||||||
| TLDrawShape
|
| TLDrawShape
|
||||||
|
@ -38,11 +34,21 @@ export type TLShape =
|
||||||
| TLNoteShape
|
| TLNoteShape
|
||||||
| TLTextShape
|
| TLTextShape
|
||||||
| TLVideoShape
|
| TLVideoShape
|
||||||
| TLUnknownShape
|
|
||||||
| TLIconShape
|
| 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 */
|
/** @public */
|
||||||
export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
||||||
|
|
|
@ -101,7 +101,7 @@ export function useLocalSyncClient({ universalPersistenceKey, instanceId, userId
|
||||||
universalPersistenceKey: string;
|
universalPersistenceKey: string;
|
||||||
instanceId: TLInstanceId;
|
instanceId: TLInstanceId;
|
||||||
userId: TLUserId;
|
userId: TLUserId;
|
||||||
config?: TldrawEditorConfig;
|
config: TldrawEditorConfig;
|
||||||
}): SyncedStore;
|
}): SyncedStore;
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
|
@ -34,7 +34,7 @@ function testClient(
|
||||||
userId: TLUserId = TLUser.createCustomId('test'),
|
userId: TLUserId = TLUser.createCustomId('test'),
|
||||||
channel = new BroadcastChannelMock('test')
|
channel = new BroadcastChannelMock('test')
|
||||||
) {
|
) {
|
||||||
const store = TldrawEditorConfig.default.createStore({
|
const store = new TldrawEditorConfig().createStore({
|
||||||
userId,
|
userId,
|
||||||
instanceId,
|
instanceId,
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,12 +14,12 @@ export function useLocalSyncClient({
|
||||||
universalPersistenceKey,
|
universalPersistenceKey,
|
||||||
instanceId,
|
instanceId,
|
||||||
userId,
|
userId,
|
||||||
config = TldrawEditorConfig.default,
|
config,
|
||||||
}: {
|
}: {
|
||||||
universalPersistenceKey: string
|
universalPersistenceKey: string
|
||||||
instanceId: TLInstanceId
|
instanceId: TLInstanceId
|
||||||
userId: TLUserId
|
userId: TLUserId
|
||||||
config?: TldrawEditorConfig
|
config: TldrawEditorConfig
|
||||||
}): SyncedStore {
|
}): SyncedStore {
|
||||||
const [state, setState] = useState<{ id: string; syncedStore: SyncedStore } | null>(null)
|
const [state, setState] = useState<{ id: string; syncedStore: SyncedStore } | null>(null)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue