[refactor] User-facing APIs (#1478)
This PR updates our user-facing APIs for the Tldraw and TldrawEditor components, as well as the Editor (App). It mainly incorporates surface changes from #1450 without any changes to validators or migrators, incorporating feedback / discussion with @SomeHats and @ds300. Here we: - remove the TldrawEditorConfig - bring back a loose version of shape definitions - make a separation between "core" shapes and "default" shapes - do not allow custom shapes, migrators or validators to overwrite core shapes - but _do_ allow new shapes ## `<Tldraw>` component In this PR, the `Tldraw` component wraps both the `TldrawEditor` component and our `TldrawUi` component. It accepts a union of props for both components. Previously, this component also added local syncing via a `useLocalSyncClient` hook call, however that has been pushed down to the `TldrawEditor` component. ## `<TldrawEditor>` component The `TldrawEditor` component now more neatly wraps up the different ways that the editor can be configured. ## The store prop (`TldrawEditorProps.store`) There are three main ways for the `TldrawEditor` component to be run: 1. with an externally defined store 2. with an externally defined syncing store (local or remote) 3. with an internally defined store 4. with an internally defined locally syncing store The `store` prop allows for these configurations. If the `store` prop is defined, it may be defined either as a `TLStore` or as a `SyncedStore`. If the store is a `TLStore`, then the Editor will assume that the store is ready to go; if it is defined as a SyncedStore, then the component will display the loading / error screens as needed, or the final editor once the store's status is "synced". When the store is left undefined, then the `TldrawEditor` will create its own internal store using the optional `instanceId`, `initialData`, or `shapes` props to define the store / store schema. If the `persistenceKey` prop is left undefined, then the store will not be synced. If the `persistenceKey` is defined, then the store will be synced locally. In the future, we may also here accept the API key / roomId / etc for creating a remotely synced store. The `SyncedStore` type has been expanded to also include types used for remote syncing, e.g. with `ConnectionStatus`. ## Tools By default, the App has two "baked-in" tools: the select tool and the zoom tool. These cannot (for now) be replaced or removed. The default tools are used by default, but may be replaced by other tools if provided. ## Shapes By default, the App has a set of "core" shapes: - group - embed - bookmark - image - video - text That cannot by overwritten because they're created by the app at different moments, such as when double clicking on the canvas or via a copy and paste event. In follow up PRs, we'll split these out so that users can replace parts of the code where these shapes are created. ### Change Type - [x] `major` — Breaking Change ### Test Plan - [x] Unit Tests
This commit is contained in:
parent
d6085e4ea6
commit
0c4174c0b8
87 changed files with 1430 additions and 1563 deletions
|
@ -15,4 +15,4 @@ keywords:
|
|||
|
||||
Coming soon.
|
||||
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw) for an example of how to use the `@tldraw/tlsync-client` library to persist and sync between tabs.
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw) for an example of how to use persistence with the `@tldraw/tldraw` or `@tldraw/editor` libraries.
|
||||
|
|
|
@ -64,7 +64,6 @@
|
|||
"@tldraw/tldraw": "workspace:*",
|
||||
"@tldraw/tlschema": "workspace:*",
|
||||
"@tldraw/tlstore": "workspace:*",
|
||||
"@tldraw/tlsync-client": "workspace:*",
|
||||
"@tldraw/tlvalidate": "workspace:*",
|
||||
"@tldraw/ui": "workspace:*",
|
||||
"lazyrepo": "0.0.0-alpha.26",
|
||||
|
|
|
@ -33,7 +33,7 @@ export async function cleanup({ page }: PlaywrightTestArgs) {
|
|||
}
|
||||
|
||||
export async function setupPage(page: PlaywrightTestArgs['page']) {
|
||||
await page.goto('http://localhost:5420/e2e')
|
||||
await page.goto('http://localhost:5420/end-to-end')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
await page.evaluate(() => (app.enableAnimations = false))
|
||||
}
|
||||
|
|
73
apps/examples/e2e/tests/test-routes.spec.ts
Normal file
73
apps/examples/e2e/tests/test-routes.spec.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import test from '@playwright/test'
|
||||
|
||||
test.describe('Routes', () => {
|
||||
test('end-to-end', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/end-to-end')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('basic', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('api', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/api')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('hide-ui', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/custom-config')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('custom-config', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/custom-config')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('custom-ui', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/custom-ui')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('exploded', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/exploded')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('scroll', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/scroll')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('multiple', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/multiple')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('error-boundary', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/error-boundary')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('user-presence', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/user-presence')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('ui-events', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/ui-events')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('store-events', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/store-events')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
|
||||
test('persistence', async ({ page }) => {
|
||||
await page.goto('http://localhost:5420/persistence')
|
||||
await page.waitForSelector('.tl-canvas')
|
||||
})
|
||||
})
|
|
@ -1,37 +1,21 @@
|
|||
import {
|
||||
Canvas,
|
||||
ContextMenu,
|
||||
TAB_ID,
|
||||
TldrawEditor,
|
||||
TldrawEditorConfig,
|
||||
TldrawUi,
|
||||
} from '@tldraw/tldraw'
|
||||
import { Canvas, ContextMenu, TAB_ID, TldrawEditor, TldrawUi, createTLStore } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
import { throttle } from '@tldraw/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLayoutEffect, useState } from 'react'
|
||||
|
||||
const PERSISTENCE_KEY = 'example-3'
|
||||
const config = new TldrawEditorConfig()
|
||||
const instanceId = TAB_ID
|
||||
const store = config.createStore({ instanceId })
|
||||
|
||||
export default function PersistenceExample() {
|
||||
const [state, setState] = useState<
|
||||
| {
|
||||
name: 'loading'
|
||||
}
|
||||
| {
|
||||
name: 'ready'
|
||||
}
|
||||
| {
|
||||
name: 'error'
|
||||
error: string
|
||||
}
|
||||
>({ name: 'loading', error: undefined })
|
||||
const [store] = useState(() => createTLStore({ instanceId: TAB_ID }))
|
||||
const [loadingStore, setLoadingStore] = useState<
|
||||
{ status: 'loading' } | { status: 'ready' } | { status: 'error'; error: string }
|
||||
>({
|
||||
status: 'loading',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setState({ name: 'loading' })
|
||||
useLayoutEffect(() => {
|
||||
setLoadingStore({ status: 'loading' })
|
||||
|
||||
// Get persisted data from local storage
|
||||
const persistedSnapshot = localStorage.getItem(PERSISTENCE_KEY)
|
||||
|
@ -40,29 +24,28 @@ export default function PersistenceExample() {
|
|||
try {
|
||||
const snapshot = JSON.parse(persistedSnapshot)
|
||||
store.loadSnapshot(snapshot)
|
||||
setState({ name: 'ready' })
|
||||
} catch (e: any) {
|
||||
setState({ name: 'error', error: e.message }) // Something went wrong
|
||||
setLoadingStore({ status: 'ready' })
|
||||
} catch (error: any) {
|
||||
setLoadingStore({ status: 'error', error: error.message }) // Something went wrong
|
||||
}
|
||||
} else {
|
||||
setState({ name: 'ready' }) // Nothing persisted, continue with the empty store
|
||||
setLoadingStore({ status: 'ready' }) // Nothing persisted, continue with the empty store
|
||||
}
|
||||
|
||||
const persist = throttle(() => {
|
||||
// Each time the store changes, persist the store snapshot
|
||||
const snapshot = store.getSnapshot()
|
||||
localStorage.setItem(PERSISTENCE_KEY, JSON.stringify(snapshot))
|
||||
}, 1000)
|
||||
|
||||
// Each time the store changes, run the (debounced) persist function
|
||||
const cleanupFn = store.listen(persist)
|
||||
const cleanupFn = store.listen(
|
||||
throttle(() => {
|
||||
const snapshot = store.getSnapshot()
|
||||
localStorage.setItem(PERSISTENCE_KEY, JSON.stringify(snapshot))
|
||||
}, 500)
|
||||
)
|
||||
|
||||
return () => {
|
||||
cleanupFn()
|
||||
}
|
||||
}, [])
|
||||
}, [store])
|
||||
|
||||
if (state.name === 'loading') {
|
||||
if (loadingStore.status === 'loading') {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<h2>Loading...</h2>
|
||||
|
@ -70,18 +53,18 @@ export default function PersistenceExample() {
|
|||
)
|
||||
}
|
||||
|
||||
if (state.name === 'error') {
|
||||
if (loadingStore.status === 'error') {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<h2>Error!</h2>
|
||||
<p>{state.error}</p>
|
||||
<p>{loadingStore.error}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<TldrawEditor instanceId={instanceId} store={store} config={config} autoFocus>
|
||||
<TldrawEditor store={store} autoFocus>
|
||||
<TldrawUi>
|
||||
<ContextMenu>
|
||||
<Canvas />
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useEffect } from 'react'
|
|||
// component and all shapes, tools, and UI components use this instance to
|
||||
// send events, observe changes, and perform actions.
|
||||
|
||||
export default function Example() {
|
||||
export default function APIExample() {
|
||||
const handleMount = (app: App) => {
|
||||
// Create a shape id
|
||||
const id = app.createShapeId('hello')
|
||||
|
|
10
apps/examples/src/3-custom-config/CardShape.ts
Normal file
10
apps/examples/src/3-custom-config/CardShape.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { TLBaseShape, TLOpacityType } from '@tldraw/tldraw'
|
||||
|
||||
export type CardShape = TLBaseShape<
|
||||
'card',
|
||||
{
|
||||
opacity: TLOpacityType // necessary for all shapes at the moment, others can be whatever you want!
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
>
|
13
apps/examples/src/3-custom-config/CardTool.ts
Normal file
13
apps/examples/src/3-custom-config/CardTool.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Tool
|
||||
// ----
|
||||
// Because the card tool can be just a rectangle, we can extend the
|
||||
|
||||
import { TLBoxTool } from '@tldraw/tldraw'
|
||||
|
||||
// TLBoxTool class. This gives us a lot of functionality for free.
|
||||
export class CardTool extends TLBoxTool {
|
||||
static override id = 'card'
|
||||
static override initial = 'idle'
|
||||
|
||||
override shapeType = 'card'
|
||||
}
|
46
apps/examples/src/3-custom-config/CardUtil.tsx
Normal file
46
apps/examples/src/3-custom-config/CardUtil.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { HTMLContainer, TLBoxUtil } from '@tldraw/tldraw'
|
||||
import { CardShape } from './CardShape'
|
||||
|
||||
export class CardUtil extends TLBoxUtil<CardShape> {
|
||||
// Id — the shape util's id
|
||||
static override type = 'card' as const
|
||||
|
||||
// Flags — there are a LOT of other flags!
|
||||
override isAspectRatioLocked = (_shape: CardShape) => false
|
||||
override canResize = (_shape: CardShape) => true
|
||||
override canBind = (_shape: CardShape) => true
|
||||
|
||||
// Default props — used for shapes created with the tool
|
||||
override defaultProps(): CardShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 300,
|
||||
h: 300,
|
||||
}
|
||||
}
|
||||
|
||||
// Render method — the React component that will be rendered for the shape
|
||||
render(shape: CardShape) {
|
||||
const bounds = this.bounds(shape)
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
id={shape.id}
|
||||
style={{
|
||||
border: '1px solid black',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'all',
|
||||
}}
|
||||
>
|
||||
{bounds.w.toFixed()}x{bounds.h.toFixed()}
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
// Indicator — used when hovering over a shape or when it's selected; must return only SVG elements here
|
||||
indicator(shape: CardShape) {
|
||||
return <rect width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
}
|
|
@ -1,120 +1,25 @@
|
|||
import {
|
||||
HTMLContainer,
|
||||
MenuGroup,
|
||||
menuItem,
|
||||
TLBaseShape,
|
||||
TLBoxTool,
|
||||
TLBoxUtil,
|
||||
Tldraw,
|
||||
TldrawEditorConfig,
|
||||
TLOpacityType,
|
||||
toolbarItem,
|
||||
} from '@tldraw/tldraw'
|
||||
import { MenuGroup, Tldraw, menuItem, toolbarItem } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
import { CardTool } from './CardTool'
|
||||
import { CardUtil } from './CardUtil'
|
||||
|
||||
// Let's make a custom shape called a Card.
|
||||
const shapes = { card: { util: CardUtil } }
|
||||
const tools = [CardTool]
|
||||
|
||||
// Shape Type
|
||||
// ----------
|
||||
// The shape type defines the card's type (`card`) and its props.
|
||||
// Every shape needs an opacity prop (for now), but other than that
|
||||
// you can add whatever you want, so long as it's JSON serializable.
|
||||
type CardShape = TLBaseShape<
|
||||
'card',
|
||||
{
|
||||
w: number
|
||||
h: number
|
||||
opacity: TLOpacityType
|
||||
}
|
||||
>
|
||||
|
||||
// Shape Util
|
||||
// ----------
|
||||
// The CardUtil class is used by the app to answer questions about a
|
||||
// shape of the 'card' type. For example, what is the default props
|
||||
// for this shape? What should we render for it, or for its indicator?
|
||||
class CardUtil extends TLBoxUtil<CardShape> {
|
||||
static override type = 'card' as const
|
||||
|
||||
// There are a LOT of other things we could add here, like these flags
|
||||
override isAspectRatioLocked = (_shape: CardShape) => false
|
||||
override canResize = (_shape: CardShape) => true
|
||||
override canBind = (_shape: CardShape) => true
|
||||
|
||||
override defaultProps(): CardShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 300,
|
||||
h: 300,
|
||||
}
|
||||
}
|
||||
|
||||
// This is the component that will be rendered for the shape.
|
||||
// Try changing the contents of the HTMLContainer to see what happens.
|
||||
render(shape: CardShape) {
|
||||
// You can access class methods from here
|
||||
const bounds = this.bounds(shape)
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
id={shape.id}
|
||||
style={{
|
||||
border: '1px solid black',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'all',
|
||||
}}
|
||||
>
|
||||
{/* Anything you want can go here—it's a regular React component */}
|
||||
{bounds.w.toFixed()}x{bounds.h.toFixed()}
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
// The indicator is used when hovering over a shape or when it's selected.
|
||||
// This can only be SVG path data; generally you want the outline of the
|
||||
// component you're rendering.
|
||||
indicator(shape: CardShape) {
|
||||
return <rect width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
}
|
||||
|
||||
// Tool
|
||||
// ----
|
||||
// Because the card tool can be just a rectangle, we can extend the
|
||||
// TLBoxTool class. This gives us a lot of functionality for free.
|
||||
export class CardTool extends TLBoxTool {
|
||||
static override id = 'card'
|
||||
static override initial = 'idle'
|
||||
override shapeType = 'card'
|
||||
}
|
||||
|
||||
// Finally, collect the custom tools and shapes into a config object
|
||||
const customTldrawConfig = new TldrawEditorConfig({
|
||||
tools: [CardTool],
|
||||
shapes: {
|
||||
card: {
|
||||
util: CardUtil,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// ... and we can make our custom shape example!
|
||||
export default function Example() {
|
||||
export default function CustomConfigExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
persistenceKey="custom-config"
|
||||
config={customTldrawConfig}
|
||||
autoFocus
|
||||
tools={tools}
|
||||
shapes={shapes}
|
||||
overrides={{
|
||||
// In order for our custom tool to show up in the UI...
|
||||
// We need to add it to the tools list. This "toolItem"
|
||||
// has information about its icon, label, keyboard shortcut,
|
||||
// and what to do when it's selected.
|
||||
tools(app, tools) {
|
||||
// In order for our custom tool to show up in the UI...
|
||||
// We need to add it to the tools list. This "toolItem"
|
||||
// has information about its icon, label, keyboard shortcut,
|
||||
// and what to do when it's selected.
|
||||
tools.card = {
|
||||
id: 'card',
|
||||
icon: 'color',
|
||||
|
@ -127,13 +32,13 @@ export default function Example() {
|
|||
}
|
||||
return tools
|
||||
},
|
||||
toolbar(app, toolbar, { tools }) {
|
||||
toolbar(_app, toolbar, { tools }) {
|
||||
// The toolbar is an array of items. We can add it to the
|
||||
// end of the array or splice it in, then return the array.
|
||||
toolbar.splice(4, 0, toolbarItem(tools.card))
|
||||
return toolbar
|
||||
},
|
||||
keyboardShortcutsMenu(app, keyboardShortcutsMenu, { tools }) {
|
||||
keyboardShortcutsMenu(_app, keyboardShortcutsMenu, { tools }) {
|
||||
// Same for the keyboard shortcuts menu, but this menu contains
|
||||
// both items and groups. We want to find the "Tools" group and
|
||||
// add it to that before returning the array.
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { Canvas, TldrawEditor, TldrawEditorConfig, useApp } from '@tldraw/tldraw'
|
||||
import { Canvas, TldrawEditor, 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() {
|
||||
export default function CustomUiExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<TldrawEditor config={config} autoFocus>
|
||||
<TldrawEditor autoFocus>
|
||||
<Canvas />
|
||||
<CustomUi />
|
||||
</TldrawEditor>
|
||||
|
|
|
@ -1,30 +1,11 @@
|
|||
import {
|
||||
Canvas,
|
||||
ContextMenu,
|
||||
InstanceRecordType,
|
||||
TldrawEditor,
|
||||
TldrawEditorConfig,
|
||||
TldrawUi,
|
||||
useLocalSyncClient,
|
||||
} from '@tldraw/tldraw'
|
||||
import { Canvas, ContextMenu, TldrawEditor, TldrawUi } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
|
||||
const instanceId = InstanceRecordType.createCustomId('example')
|
||||
|
||||
// for custom config, see 3-custom-config
|
||||
const config = new TldrawEditorConfig()
|
||||
|
||||
export default function Example() {
|
||||
const syncedStore = useLocalSyncClient({
|
||||
config,
|
||||
instanceId,
|
||||
universalPersistenceKey: 'exploded-example',
|
||||
})
|
||||
|
||||
export default function ExplodedExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<TldrawEditor instanceId={instanceId} store={syncedStore} config={config} autoFocus>
|
||||
<TldrawEditor autoFocus persistenceKey="exploded-example">
|
||||
<TldrawUi>
|
||||
<ContextMenu>
|
||||
<Canvas />
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Tldraw } from '@tldraw/tldraw'
|
|||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
|
||||
export default function Example() {
|
||||
export default function MultipleExample() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import { createShapeId, TLBaseShape, TLBoxUtil, Tldraw, TldrawEditorConfig } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
|
||||
export default function ErrorBoundaryExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
components={{
|
||||
// disable app-level error boundaries:
|
||||
ErrorFallback: null,
|
||||
// use a custom error fallback for shapes:
|
||||
ShapeErrorFallback: ({ error }) => <div>Shape error! {String(error)}</div>,
|
||||
}}
|
||||
// below, we define a custom shape that always throws an error so we can see our new error boundary in action
|
||||
config={customConfigWithErrorShape}
|
||||
onMount={(app) => {
|
||||
// when the app starts, create our error shape so we can see
|
||||
// what it looks like:
|
||||
app.createShapes([
|
||||
{
|
||||
type: 'error',
|
||||
id: createShapeId(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: { message: 'Something has gone wrong' },
|
||||
},
|
||||
])
|
||||
|
||||
// center the camera on the error shape
|
||||
app.zoomToFit()
|
||||
app.resetZoom()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// do make it easy to see our custom shape error fallback, let's create a new
|
||||
// shape type that always throws an error. See CustomConfigExample for more info
|
||||
// on creating custom shapes.
|
||||
type ErrorShape = TLBaseShape<'error', { w: number; h: number; message: string }>
|
||||
|
||||
class ErrorUtil extends TLBoxUtil<ErrorShape> {
|
||||
override type = 'error' as const
|
||||
|
||||
defaultProps() {
|
||||
return { message: 'Error!', w: 100, h: 100 }
|
||||
}
|
||||
render(shape: ErrorShape) {
|
||||
throw new Error(shape.props.message)
|
||||
}
|
||||
indicator() {
|
||||
throw new Error(`Error shape indicator!`)
|
||||
}
|
||||
}
|
||||
|
||||
const customConfigWithErrorShape = new TldrawEditorConfig({
|
||||
shapes: {
|
||||
error: {
|
||||
util: ErrorUtil,
|
||||
},
|
||||
},
|
||||
})
|
40
apps/examples/src/8-error-boundary/ErrorBoundaryExample.tsx
Normal file
40
apps/examples/src/8-error-boundary/ErrorBoundaryExample.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { createShapeId, Tldraw } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
import { ErrorUtil } from './ErrorUtil'
|
||||
|
||||
const shapes = {
|
||||
error: {
|
||||
util: ErrorUtil, // a custom shape that will always error
|
||||
},
|
||||
}
|
||||
|
||||
export default function ErrorBoundaryExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
shapes={shapes}
|
||||
components={{
|
||||
ErrorFallback: null, // disable app-level error boundaries
|
||||
ShapeErrorFallback: ({ error }) => <div>Shape error! {String(error)}</div>, // use a custom error fallback for shapes
|
||||
}}
|
||||
onMount={(app) => {
|
||||
// When the app starts, create our error shape so we can see.
|
||||
app.createShapes([
|
||||
{
|
||||
type: 'error',
|
||||
id: createShapeId(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: { message: 'Something has gone wrong' },
|
||||
},
|
||||
])
|
||||
|
||||
// Center the camera on the error shape
|
||||
app.zoomToFit()
|
||||
app.resetZoom()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
3
apps/examples/src/8-error-boundary/ErrorShape.ts
Normal file
3
apps/examples/src/8-error-boundary/ErrorShape.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { TLBaseShape } from '@tldraw/tldraw'
|
||||
|
||||
export type ErrorShape = TLBaseShape<'error', { w: number; h: number; message: string }>
|
17
apps/examples/src/8-error-boundary/ErrorUtil.ts
Normal file
17
apps/examples/src/8-error-boundary/ErrorUtil.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { TLBoxUtil } from '@tldraw/tldraw'
|
||||
import { ErrorShape } from './ErrorShape'
|
||||
|
||||
export class ErrorUtil extends TLBoxUtil<ErrorShape> {
|
||||
static override type = 'error'
|
||||
override type = 'error' as const
|
||||
|
||||
defaultProps() {
|
||||
return { message: 'Error!', w: 100, h: 100 }
|
||||
}
|
||||
render(shape: ErrorShape) {
|
||||
throw new Error(shape.props.message)
|
||||
}
|
||||
indicator() {
|
||||
throw new Error(`Error shape indicator!`)
|
||||
}
|
||||
}
|
|
@ -2,9 +2,8 @@ import { Tldraw } from '@tldraw/tldraw'
|
|||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
|
||||
export default function ForEndToEndTests() {
|
||||
export default function EndToEnd() {
|
||||
;(window as any).__tldraw_editor_events = []
|
||||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
|
@ -12,7 +12,7 @@ import ExampleBasic from './1-basic/BasicExample'
|
|||
import CustomComponentsExample from './10-custom-components/CustomComponentsExample'
|
||||
import UserPresenceExample from './11-user-presence/UserPresenceExample'
|
||||
import UiEventsExample from './12-ui-events/UiEventsExample'
|
||||
import StoreEventsExample from './13-store/StoreEventsExample'
|
||||
import StoreEventsExample from './13-store-events/StoreEventsExample'
|
||||
import PersistenceExample from './14-persistence/PersistenceExample'
|
||||
import ExampleApi from './2-api/APIExample'
|
||||
import CustomConfigExample from './3-custom-config/CustomConfigExample'
|
||||
|
@ -20,9 +20,11 @@ import CustomUiExample from './4-custom-ui/CustomUiExample'
|
|||
import ExplodedExample from './5-exploded/ExplodedExample'
|
||||
import ExampleScroll from './6-scroll/ScrollExample'
|
||||
import ExampleMultiple from './7-multiple/MultipleExample'
|
||||
import ErrorBoundaryExample from './8-error-boundaries/ErrorBoundaryExample'
|
||||
import ErrorBoundaryExample from './8-error-boundary/ErrorBoundaryExample'
|
||||
import HideUiExample from './9-hide-ui/HideUiExample'
|
||||
import ForEndToEndTests from './end-to-end/ForEndToEndTests'
|
||||
import EndToEnd from './end-to-end/end-to-end'
|
||||
|
||||
// This example is only used for end to end tests
|
||||
|
||||
// we use secret internal `setDefaultAssetUrls` functions to set these at the
|
||||
// top-level so assets don't need to be passed down in every single example.
|
||||
|
@ -34,6 +36,7 @@ type Example = {
|
|||
path: string
|
||||
element: JSX.Element
|
||||
}
|
||||
|
||||
export const allExamples: Example[] = [
|
||||
{
|
||||
path: '/',
|
||||
|
@ -52,7 +55,7 @@ export const allExamples: Example[] = [
|
|||
element: <ExampleApi />,
|
||||
},
|
||||
{
|
||||
path: '/custom',
|
||||
path: '/custom-config',
|
||||
element: <CustomConfigExample />,
|
||||
},
|
||||
{
|
||||
|
@ -92,8 +95,8 @@ export const allExamples: Example[] = [
|
|||
element: <PersistenceExample />,
|
||||
},
|
||||
{
|
||||
path: '/e2e',
|
||||
element: <ForEndToEndTests />,
|
||||
path: '/end-to-end',
|
||||
element: <EndToEnd />,
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
"@tldraw/editor": "workspace:*",
|
||||
"@tldraw/file-format": "workspace:*",
|
||||
"@tldraw/tldraw": "workspace:*",
|
||||
"@tldraw/tlsync-client": "workspace:*",
|
||||
"@tldraw/ui": "workspace:*",
|
||||
"@tldraw/utils": "workspace:*",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SyncedStore, TLInstanceId, useApp } from '@tldraw/editor'
|
||||
import { useApp } from '@tldraw/editor'
|
||||
import { parseAndLoadDocument, serializeTldrawJson } from '@tldraw/file-format'
|
||||
import { useDefaultHelpers } from '@tldraw/ui'
|
||||
import { debounce } from '@tldraw/utils'
|
||||
|
@ -9,13 +9,7 @@ import { vscode } from './utils/vscode'
|
|||
// @ts-ignore
|
||||
import type { VscodeMessage } from '../../messages'
|
||||
|
||||
export const ChangeResponder = ({
|
||||
syncedStore,
|
||||
instanceId,
|
||||
}: {
|
||||
syncedStore: SyncedStore
|
||||
instanceId: TLInstanceId
|
||||
}) => {
|
||||
export const ChangeResponder = () => {
|
||||
const app = useApp()
|
||||
const { addToast, clearToasts, msg } = useDefaultHelpers()
|
||||
|
||||
|
@ -44,19 +38,17 @@ export const ChangeResponder = ({
|
|||
clearToasts()
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
}, [app, instanceId, msg, addToast, clearToasts])
|
||||
}, [app, msg, addToast, clearToasts])
|
||||
|
||||
React.useEffect(() => {
|
||||
// When the history changes, send the new file contents to VSCode
|
||||
const handleChange = debounce(async () => {
|
||||
if (syncedStore.store) {
|
||||
vscode.postMessage({
|
||||
type: 'vscode:editor-updated',
|
||||
data: {
|
||||
fileContents: await serializeTldrawJson(syncedStore.store),
|
||||
},
|
||||
})
|
||||
}
|
||||
vscode.postMessage({
|
||||
type: 'vscode:editor-updated',
|
||||
data: {
|
||||
fileContents: await serializeTldrawJson(app.store),
|
||||
},
|
||||
})
|
||||
}, 250)
|
||||
|
||||
vscode.postMessage({
|
||||
|
@ -69,7 +61,7 @@ export const ChangeResponder = ({
|
|||
handleChange()
|
||||
app.off('change-history', handleChange)
|
||||
}
|
||||
}, [app, syncedStore, instanceId])
|
||||
}, [app])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TLInstanceId, useApp } from '@tldraw/editor'
|
||||
import { useApp } from '@tldraw/editor'
|
||||
import { parseAndLoadDocument } from '@tldraw/file-format'
|
||||
import { useDefaultHelpers } from '@tldraw/ui'
|
||||
import React from 'react'
|
||||
|
@ -6,10 +6,8 @@ import { vscode } from './utils/vscode'
|
|||
|
||||
export function FileOpen({
|
||||
fileContents,
|
||||
instanceId,
|
||||
forceDarkMode,
|
||||
}: {
|
||||
instanceId: TLInstanceId
|
||||
fileContents: string
|
||||
forceDarkMode: boolean
|
||||
}) {
|
||||
|
@ -42,7 +40,7 @@ export function FileOpen({
|
|||
return () => {
|
||||
clearToasts()
|
||||
}
|
||||
}, [fileContents, app, instanceId, addToast, msg, clearToasts, forceDarkMode, isFileLoaded])
|
||||
}, [fileContents, app, addToast, msg, clearToasts, forceDarkMode, isFileLoaded])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -2,19 +2,18 @@ import {
|
|||
App,
|
||||
Canvas,
|
||||
ErrorBoundary,
|
||||
setRuntimeOverrides,
|
||||
TAB_ID,
|
||||
TldrawEditor,
|
||||
TldrawEditorConfig,
|
||||
setRuntimeOverrides,
|
||||
} from '@tldraw/editor'
|
||||
import { linksUiOverrides } from './utils/links'
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import '@tldraw/editor/editor.css'
|
||||
import { TAB_ID, useLocalSyncClient } from '@tldraw/tlsync-client'
|
||||
import { ContextMenu, MenuSchema, TldrawUi } from '@tldraw/ui'
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import { getAssetUrlsByImport } from '@tldraw/assets/imports'
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import '@tldraw/ui/ui.css'
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import { getAssetUrlsByImport } from '@tldraw/assets/imports'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { VscodeMessage } from '../../messages'
|
||||
import '../public/index.css'
|
||||
|
@ -24,10 +23,6 @@ import { FullPageMessage } from './FullPageMessage'
|
|||
import { onCreateBookmarkFromUrl } from './utils/bookmarks'
|
||||
import { vscode } from './utils/vscode'
|
||||
|
||||
const config = new TldrawEditorConfig()
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
setRuntimeOverrides({
|
||||
openWindow: (url, target) => {
|
||||
vscode.postMessage({
|
||||
|
@ -97,7 +92,6 @@ export const TldrawWrapper = () => {
|
|||
fileContents: message.data.fileContents,
|
||||
uri: message.data.uri,
|
||||
isDarkMode: message.data.isDarkMode,
|
||||
config,
|
||||
})
|
||||
// We only want to listen for this message once
|
||||
window.removeEventListener('message', handleMessage)
|
||||
|
@ -127,32 +121,23 @@ export type TLDrawInnerProps = {
|
|||
fileContents: string
|
||||
uri: string
|
||||
isDarkMode: boolean
|
||||
config: TldrawEditorConfig
|
||||
}
|
||||
|
||||
function TldrawInner({ uri, config, assetSrc, isDarkMode, fileContents }: TLDrawInnerProps) {
|
||||
const instanceId = TAB_ID
|
||||
const syncedStore = useLocalSyncClient({
|
||||
universalPersistenceKey: uri,
|
||||
instanceId,
|
||||
config,
|
||||
})
|
||||
|
||||
function TldrawInner({ uri, assetSrc, isDarkMode, fileContents }: TLDrawInnerProps) {
|
||||
const assetUrls = useMemo(() => getAssetUrlsByImport({ baseUrl: assetSrc }), [assetSrc])
|
||||
|
||||
return (
|
||||
<TldrawEditor
|
||||
config={config}
|
||||
assetUrls={assetUrls}
|
||||
instanceId={TAB_ID}
|
||||
store={syncedStore}
|
||||
persistenceKey={uri}
|
||||
onCreateBookmarkFromUrl={onCreateBookmarkFromUrl}
|
||||
autoFocus
|
||||
>
|
||||
{/* <DarkModeHandler themeKind={themeKind} /> */}
|
||||
<TldrawUi assetUrls={assetUrls} overrides={[menuOverrides, linksUiOverrides]}>
|
||||
<FileOpen instanceId={instanceId} fileContents={fileContents} forceDarkMode={isDarkMode} />
|
||||
<ChangeResponder syncedStore={syncedStore} instanceId={instanceId} />
|
||||
<FileOpen fileContents={fileContents} forceDarkMode={isDarkMode} />
|
||||
<ChangeResponder />
|
||||
<ContextMenu>
|
||||
<Canvas />
|
||||
</ContextMenu>
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
{ "path": "../../../packages/file-format" },
|
||||
{ "path": "../../../packages/ui" },
|
||||
{ "path": "../../../packages/editor" },
|
||||
{ "path": "../../../packages/tlsync-client" },
|
||||
{ "path": "../../../packages/utils" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -124,7 +124,6 @@
|
|||
"scripts": {
|
||||
"dev": "tsx scripts/dev.ts",
|
||||
"build": "cd ../editor && yarn build && cd ../extension && tsx scripts/build.ts",
|
||||
"web": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=.",
|
||||
"package": "yarn build && tsx scripts/package.ts",
|
||||
"publish": "vsce publish",
|
||||
"lint": "yarn run -T tsx ../../../scripts/lint.ts",
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { TldrawEditorConfig } from '@tldraw/editor'
|
||||
import { createTLSchema } from '@tldraw/editor'
|
||||
import { TldrawFile } from '@tldraw/file-format'
|
||||
import * as vscode from 'vscode'
|
||||
|
||||
export const defaultFileContents: TldrawFile = {
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
schema: createTLSchema().serialize(),
|
||||
records: [],
|
||||
}
|
||||
|
||||
export const fileContentWithErrors: TldrawFile = {
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
schema: createTLSchema().serialize(),
|
||||
records: [{ typeName: 'shape', id: null } as any],
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,8 @@ import { Matrix2d } from '@tldraw/primitives';
|
|||
import { Matrix2dModel } from '@tldraw/primitives';
|
||||
import { Migrations } from '@tldraw/tlstore';
|
||||
import { Polyline2d } from '@tldraw/primitives';
|
||||
import * as React_2 from 'react';
|
||||
import { default as React_3 } from 'react';
|
||||
import { RecordType } from '@tldraw/tlstore';
|
||||
import { default as React_2 } from 'react';
|
||||
import * as React_3 from 'react';
|
||||
import { RotateCorner } from '@tldraw/primitives';
|
||||
import { SelectionCorner } from '@tldraw/primitives';
|
||||
import { SelectionEdge } from '@tldraw/primitives';
|
||||
|
@ -39,7 +38,6 @@ import { SelectionHandle } from '@tldraw/primitives';
|
|||
import { SerializedSchema } from '@tldraw/tlstore';
|
||||
import { Signal } from 'signia';
|
||||
import { sortByIndex } from '@tldraw/indices';
|
||||
import { StoreSchema } from '@tldraw/tlstore';
|
||||
import { StoreSnapshot } from '@tldraw/tlstore';
|
||||
import { StrokePoint } from '@tldraw/primitives';
|
||||
import { TLAlignType } from '@tldraw/tlschema';
|
||||
|
@ -57,7 +55,6 @@ import { TLColorType } from '@tldraw/tlschema';
|
|||
import { TLCursor } from '@tldraw/tlschema';
|
||||
import { TLDocument } from '@tldraw/tlschema';
|
||||
import { TLDrawShape } from '@tldraw/tlschema';
|
||||
import { TLDrawShapeSegment } from '@tldraw/tlschema';
|
||||
import { TLEmbedShape } from '@tldraw/tlschema';
|
||||
import { TLFontType } from '@tldraw/tlschema';
|
||||
import { TLFrameShape } from '@tldraw/tlschema';
|
||||
|
@ -88,7 +85,6 @@ import { TLShapeProps } from '@tldraw/tlschema';
|
|||
import { TLSizeStyle } from '@tldraw/tlschema';
|
||||
import { TLSizeType } from '@tldraw/tlschema';
|
||||
import { TLStore } from '@tldraw/tlschema';
|
||||
import { TLStoreProps } from '@tldraw/tlschema';
|
||||
import { TLStyleCollections } from '@tldraw/tlschema';
|
||||
import { TLStyleType } from '@tldraw/tlschema';
|
||||
import { TLTextShape } from '@tldraw/tlschema';
|
||||
|
@ -125,7 +121,7 @@ export type AnimationOptions = Partial<{
|
|||
|
||||
// @public (undocumented)
|
||||
export class App extends EventEmitter<TLEventMap> {
|
||||
constructor({ config, store, getContainer }: AppOptions);
|
||||
constructor({ store, user, tools, shapes, getContainer, }: AppOptions);
|
||||
addOpenMenu: (id: string) => this;
|
||||
alignShapes(operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top', ids?: TLShapeId[]): this;
|
||||
get allShapesCommonBounds(): Box2d | null;
|
||||
|
@ -167,7 +163,6 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
// @internal
|
||||
protected _clickManager: ClickManager;
|
||||
complete(): this;
|
||||
readonly config: TldrawEditorConfig;
|
||||
// @internal (undocumented)
|
||||
crash(error: unknown): void;
|
||||
// @internal
|
||||
|
@ -551,9 +546,11 @@ export function applyRotationToSnapshotShapes({ delta, app, snapshot, stage, }:
|
|||
|
||||
// @public (undocumented)
|
||||
export interface AppOptions {
|
||||
config: TldrawEditorConfig;
|
||||
getContainer: () => HTMLElement;
|
||||
shapes?: Record<string, ShapeInfo>;
|
||||
store: TLStore;
|
||||
tools?: StateNodeConstructor[];
|
||||
user?: TLUser;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -569,8 +566,8 @@ export const BOUND_ARROW_OFFSET = 10;
|
|||
export function buildFromV1Document(app: App, document: LegacyTldrawDocument): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Canvas: React_2.MemoExoticComponent<({ onDropOverride, }: {
|
||||
onDropOverride?: ((defaultOnDrop: (e: React_2.DragEvent<Element>) => Promise<void>) => (e: React_2.DragEvent<Element>) => Promise<void>) | undefined;
|
||||
export const Canvas: React_3.MemoExoticComponent<({ onDropOverride, }: {
|
||||
onDropOverride?: ((defaultOnDrop: (e: React_3.DragEvent<Element>) => Promise<void>) => (e: React_3.DragEvent<Element>) => Promise<void>) | undefined;
|
||||
}) => JSX.Element>;
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -613,6 +610,9 @@ export function createEmbedShapeAtPoint(app: App, url: string, point: Vec2dModel
|
|||
// @public (undocumented)
|
||||
export function createShapesFromFiles(app: App, files: File[], position: VecLike, _ignoreParent?: boolean): Promise<void>;
|
||||
|
||||
// @public
|
||||
export function createTLStore(opts?: StoreOptions): TLStore;
|
||||
|
||||
// @public (undocumented)
|
||||
export function dataTransferItemAsString(item: DataTransferItem): Promise<string>;
|
||||
|
||||
|
@ -658,6 +658,12 @@ export function defaultEmptyAs(str: string, dflt: string): string;
|
|||
// @internal (undocumented)
|
||||
export const DefaultErrorFallback: TLErrorFallback;
|
||||
|
||||
// @public (undocumented)
|
||||
export const defaultShapes: Record<string, ShapeInfo>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const defaultTools: StateNodeConstructor[];
|
||||
|
||||
// @internal (undocumented)
|
||||
export const DOUBLE_CLICK_DURATION = 450;
|
||||
|
||||
|
@ -685,7 +691,7 @@ export type EmbedResult = {
|
|||
} | undefined;
|
||||
|
||||
// @public (undocumented)
|
||||
export class ErrorBoundary extends React_2.Component<React_2.PropsWithRef<React_2.PropsWithChildren<ErrorBoundaryProps>>, ErrorBoundaryState> {
|
||||
export class ErrorBoundary extends React_3.Component<React_3.PropsWithRef<React_3.PropsWithChildren<ErrorBoundaryProps>>, ErrorBoundaryState> {
|
||||
// (undocumented)
|
||||
componentDidCatch(error: unknown): void;
|
||||
// (undocumented)
|
||||
|
@ -693,7 +699,7 @@ export class ErrorBoundary extends React_2.Component<React_2.PropsWithRef<React_
|
|||
error: Error;
|
||||
};
|
||||
// (undocumented)
|
||||
render(): React_2.ReactNode;
|
||||
render(): React_3.ReactNode;
|
||||
// (undocumented)
|
||||
state: ErrorBoundaryState;
|
||||
}
|
||||
|
@ -701,9 +707,9 @@ export class ErrorBoundary extends React_2.Component<React_2.PropsWithRef<React_
|
|||
// @public (undocumented)
|
||||
export interface ErrorBoundaryProps {
|
||||
// (undocumented)
|
||||
children: React_2.ReactNode;
|
||||
children: React_3.ReactNode;
|
||||
// (undocumented)
|
||||
fallback: (error: unknown) => React_2.ReactNode;
|
||||
fallback: (error: unknown) => React_3.ReactNode;
|
||||
// (undocumented)
|
||||
onError?: ((error: unknown) => void) | null;
|
||||
}
|
||||
|
@ -713,16 +719,6 @@ export function ErrorScreen({ children }: {
|
|||
children: any;
|
||||
}): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ErrorSyncedStore {
|
||||
// (undocumented)
|
||||
readonly error: Error;
|
||||
// (undocumented)
|
||||
readonly status: 'error';
|
||||
// (undocumented)
|
||||
readonly store?: undefined;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const EVENT_NAME_MAP: Record<Exclude<TLEventName, TLPinchEventName>, keyof TLEventHandlers>;
|
||||
|
||||
|
@ -842,6 +838,9 @@ export function getSvgPathFromStrokePoints(points: StrokePoint[], closed?: boole
|
|||
// @public (undocumented)
|
||||
export function getTextBoundingBox(text: SVGTextElement): DOMRect;
|
||||
|
||||
// @public (undocumented)
|
||||
export function getUserPreferences(): TLUserPreferences;
|
||||
|
||||
// @public (undocumented)
|
||||
export const getValidHttpURLList: (url: string) => string[] | undefined;
|
||||
|
||||
|
@ -864,6 +863,11 @@ export const GRID_STEPS: {
|
|||
// @internal (undocumented)
|
||||
export const HAND_TOOL_FRICTION = 0.09;
|
||||
|
||||
// @public
|
||||
export function hardReset({ shouldReload }?: {
|
||||
shouldReload?: boolean | undefined;
|
||||
}): Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function hardResetApp(): void;
|
||||
|
||||
|
@ -874,7 +878,7 @@ export const HASH_PATERN_ZOOM_NAMES: Record<string, string>;
|
|||
export function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type HTMLContainerProps = React_2.HTMLAttributes<HTMLDivElement>;
|
||||
export type HTMLContainerProps = React_3.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ICON_SIZES: Record<TLSizeType, number>;
|
||||
|
@ -882,16 +886,6 @@ export const ICON_SIZES: Record<TLSizeType, number>;
|
|||
// @public (undocumented)
|
||||
export const INDENT = " ";
|
||||
|
||||
// @public (undocumented)
|
||||
export interface InitializingSyncedStore {
|
||||
// (undocumented)
|
||||
readonly error?: undefined;
|
||||
// (undocumented)
|
||||
readonly status: 'loading';
|
||||
// (undocumented)
|
||||
readonly store?: undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function isAnimated(buffer: ArrayBuffer): boolean;
|
||||
|
||||
|
@ -1392,27 +1386,17 @@ export function openWindow(url: string, target?: string): void;
|
|||
|
||||
// @internal (undocumented)
|
||||
export function OptionalErrorBoundary({ children, fallback, ...props }: Omit<ErrorBoundaryProps, 'fallback'> & {
|
||||
fallback: ((error: unknown) => React_2.ReactNode) | null;
|
||||
fallback: ((error: unknown) => React_3.ReactNode) | null;
|
||||
}): JSX.Element;
|
||||
|
||||
// @public
|
||||
export function preventDefault(event: Event | React_3.BaseSyntheticEvent): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ReadySyncedStore {
|
||||
// (undocumented)
|
||||
readonly error?: undefined;
|
||||
// (undocumented)
|
||||
readonly status: 'synced';
|
||||
// (undocumented)
|
||||
readonly store: TLStore;
|
||||
}
|
||||
export function preventDefault(event: Event | React_2.BaseSyntheticEvent): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function refreshPage(): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function releasePointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent<Element>): void;
|
||||
export function releasePointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent<Element>): void;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const REMOVE_SYMBOL: unique symbol;
|
||||
|
@ -1455,7 +1439,7 @@ export const runtime: {
|
|||
export function setDefaultEditorAssetUrls(assetUrls: EditorAssetUrls): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function setPointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent<Element>): void;
|
||||
export function setPointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent<Element>): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function setPropsForNextShape(previousProps: TLInstancePropsForNextShape, newProps: Partial<TLShapeProps>): TLInstancePropsForNextShape;
|
||||
|
@ -1463,6 +1447,9 @@ export function setPropsForNextShape(previousProps: TLInstancePropsForNextShape,
|
|||
// @public (undocumented)
|
||||
export function setRuntimeOverrides(input: Partial<typeof runtime>): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function setUserPreferences(user: TLUserPreferences): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function snapToGrid(n: number, gridSize: number): number;
|
||||
|
||||
|
@ -1559,6 +1546,30 @@ export interface StateNodeConstructor {
|
|||
styles?: TLStyleType[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type StoreWithStatus = {
|
||||
readonly status: 'error';
|
||||
readonly store?: undefined;
|
||||
readonly error: Error;
|
||||
} | {
|
||||
readonly status: 'loading';
|
||||
readonly store?: undefined;
|
||||
readonly error?: undefined;
|
||||
} | {
|
||||
readonly status: 'not-synced';
|
||||
readonly store: TLStore;
|
||||
readonly error?: undefined;
|
||||
} | {
|
||||
readonly status: 'synced-local';
|
||||
readonly store: TLStore;
|
||||
readonly error?: undefined;
|
||||
} | {
|
||||
readonly status: 'synced-remote';
|
||||
readonly connectionStatus: 'offline' | 'online';
|
||||
readonly store: TLStore;
|
||||
readonly error?: undefined;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const STYLES: TLStyleCollections;
|
||||
|
||||
|
@ -1569,10 +1580,10 @@ export const SVG_PADDING = 32;
|
|||
export function SVGContainer({ children, className, ...rest }: SVGContainerProps): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SVGContainerProps = React_2.HTMLAttributes<SVGElement>;
|
||||
export type SVGContainerProps = React_3.HTMLAttributes<SVGElement>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SyncedStore = ErrorSyncedStore | InitializingSyncedStore | ReadySyncedStore;
|
||||
export const TAB_ID: TLInstanceId;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TEXT_PROPS: {
|
||||
|
@ -1696,7 +1707,7 @@ export type TLBoxLike = TLBaseShape<string, {
|
|||
// @public (undocumented)
|
||||
export abstract class TLBoxTool extends StateNode {
|
||||
// (undocumented)
|
||||
static children: () => (typeof Idle_4 | typeof Pointing_3)[];
|
||||
static children: () => (typeof Idle_4 | typeof Pointing_2)[];
|
||||
// (undocumented)
|
||||
static id: string;
|
||||
// (undocumented)
|
||||
|
@ -1793,51 +1804,31 @@ export type TLCompleteEventInfo = {
|
|||
export type TLCopyType = 'jpeg' | 'json' | 'png' | 'svg';
|
||||
|
||||
// @public (undocumented)
|
||||
export function TldrawEditor(props: TldrawEditorProps): JSX.Element;
|
||||
export const TldrawEditor: React_2.NamedExoticComponent<TldrawEditorProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class TldrawEditorConfig {
|
||||
constructor(opts?: TldrawEditorConfigOptions);
|
||||
// (undocumented)
|
||||
createStore(config: {
|
||||
initialData?: StoreSnapshot<TLRecord>;
|
||||
instanceId: TLInstanceId;
|
||||
}): TLStore;
|
||||
// (undocumented)
|
||||
readonly derivePresenceState: (store: TLStore) => Signal<null | TLInstancePresence>;
|
||||
// (undocumented)
|
||||
readonly setUserPreferences: (userPreferences: TLUserPreferences) => void;
|
||||
// (undocumented)
|
||||
readonly shapeUtils: Record<TLShape['type'], TLShapeUtilConstructor<any>>;
|
||||
// (undocumented)
|
||||
readonly storeSchema: StoreSchema<TLRecord, TLStoreProps>;
|
||||
// (undocumented)
|
||||
readonly TLShape: RecordType<TLShape, 'index' | 'parentId' | 'props' | 'type'>;
|
||||
// (undocumented)
|
||||
readonly tools: readonly StateNodeConstructor[];
|
||||
// (undocumented)
|
||||
readonly userPreferences: Signal<TLUserPreferences>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TldrawEditorProps {
|
||||
export type TldrawEditorProps = {
|
||||
children?: any;
|
||||
shapes?: Record<string, ShapeInfo>;
|
||||
tools?: StateNodeConstructor[];
|
||||
assetUrls?: EditorAssetUrls;
|
||||
autoFocus?: boolean;
|
||||
// (undocumented)
|
||||
children?: any;
|
||||
components?: Partial<TLEditorComponents>;
|
||||
config: TldrawEditorConfig;
|
||||
instanceId?: TLInstanceId;
|
||||
isDarkMode?: boolean;
|
||||
onMount?: (app: App) => void;
|
||||
onCreateAssetFromFile?: (file: File) => Promise<TLAsset>;
|
||||
onCreateBookmarkFromUrl?: (url: string) => Promise<{
|
||||
image: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}>;
|
||||
onMount?: (app: App) => void;
|
||||
store?: SyncedStore | TLStore;
|
||||
}
|
||||
} & ({
|
||||
store: StoreWithStatus | TLStore;
|
||||
} | {
|
||||
store?: undefined;
|
||||
initialData?: StoreSnapshot<TLRecord>;
|
||||
instanceId?: TLInstanceId;
|
||||
persistenceKey?: string;
|
||||
});
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLDrawUtil extends TLShapeUtil<TLDrawShape> {
|
||||
|
@ -2209,6 +2200,8 @@ export class TLGroupUtil extends TLShapeUtil<TLGroupShape> {
|
|||
render(shape: TLGroupShape): JSX.Element | null;
|
||||
// (undocumented)
|
||||
static type: string;
|
||||
// (undocumented)
|
||||
type: "group";
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -2570,6 +2563,8 @@ export abstract class TLShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
export interface TLShapeUtilConstructor<T extends TLUnknownShape, ShapeUtil extends TLShapeUtil<T> = TLShapeUtil<T>> {
|
||||
// (undocumented)
|
||||
new (app: App, type: T['type']): ShapeUtil;
|
||||
// (undocumented)
|
||||
type: T['type'];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -2663,6 +2658,22 @@ export class TLTextUtil extends TLShapeUtil<TLTextShape> {
|
|||
// @public (undocumented)
|
||||
export type TLTickEvent = (elapsed: number) => void;
|
||||
|
||||
// @public
|
||||
export interface TLUserPreferences {
|
||||
// (undocumented)
|
||||
animationSpeed: number;
|
||||
// (undocumented)
|
||||
color: string;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
isDarkMode: boolean;
|
||||
// (undocumented)
|
||||
locale: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class TLVideoUtil extends TLBoxUtil<TLVideoShape> {
|
||||
// (undocumented)
|
||||
|
@ -2715,6 +2726,11 @@ export const useApp: () => App;
|
|||
// @public (undocumented)
|
||||
export function useContainer(): HTMLDivElement;
|
||||
|
||||
// @internal (undocumented)
|
||||
export function useLocalStore(opts?: {
|
||||
persistenceKey?: string | undefined;
|
||||
} & StoreOptions): StoreWithStatus;
|
||||
|
||||
// @internal (undocumented)
|
||||
export function usePeerIds(): string[];
|
||||
|
||||
|
@ -2733,6 +2749,9 @@ export const USER_COLORS: readonly ["#FF802B", "#EC5E41", "#F2555A", "#F04F88",
|
|||
// @public (undocumented)
|
||||
export function useReactor(name: string, reactFn: () => void, deps?: any[] | undefined): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function useTLStore(opts: StoreOptions): TLStore;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10;
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
"crc": "^4.3.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"idb": "^7.1.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.uniq": "^4.5.0",
|
||||
|
@ -79,7 +80,7 @@
|
|||
"@types/wicg-file-system-access": "^2020.9.5",
|
||||
"benchmark": "^2.1.4",
|
||||
"fake-indexeddb": "^4.0.0",
|
||||
"jest-canvas-mock": "^2.4.0",
|
||||
"jest-canvas-mock": "^2.5.1",
|
||||
"jest-environment-jsdom": "^29.4.3",
|
||||
"lazyrepo": "0.0.0-alpha.26",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
|
@ -103,6 +104,7 @@
|
|||
},
|
||||
"setupFiles": [
|
||||
"raf/polyfill",
|
||||
"jest-canvas-mock",
|
||||
"<rootDir>/setupTests.js"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
|
|
|
@ -127,13 +127,14 @@ export {
|
|||
export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer'
|
||||
export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer'
|
||||
export {
|
||||
type ErrorSyncedStore,
|
||||
type InitializingSyncedStore,
|
||||
type ReadySyncedStore,
|
||||
type SyncedStore,
|
||||
} from './lib/config/SyncedStore'
|
||||
export { USER_COLORS } from './lib/config/TLUserPreferences'
|
||||
export { TldrawEditorConfig } from './lib/config/TldrawEditorConfig'
|
||||
USER_COLORS,
|
||||
getUserPreferences,
|
||||
setUserPreferences,
|
||||
type TLUserPreferences,
|
||||
} from './lib/config/TLUserPreferences'
|
||||
export { createTLStore } from './lib/config/createTLStore'
|
||||
export { defaultShapes } from './lib/config/defaultShapes'
|
||||
export { defaultTools } from './lib/config/defaultTools'
|
||||
export {
|
||||
ANIMATION_MEDIUM_MS,
|
||||
ANIMATION_SHORT_MS,
|
||||
|
@ -176,10 +177,12 @@ export { normalizeWheel } from './lib/hooks/shared'
|
|||
export { useApp } from './lib/hooks/useApp'
|
||||
export { useContainer } from './lib/hooks/useContainer'
|
||||
export type { TLEditorComponents } from './lib/hooks/useEditorComponents'
|
||||
export { useLocalStore } from './lib/hooks/useLocalStore'
|
||||
export { usePeerIds } from './lib/hooks/usePeerIds'
|
||||
export { usePresence } from './lib/hooks/usePresence'
|
||||
export { useQuickReactor } from './lib/hooks/useQuickReactor'
|
||||
export { useReactor } from './lib/hooks/useReactor'
|
||||
export { useTLStore } from './lib/hooks/useTLStore'
|
||||
export { WeakMapCache } from './lib/utils/WeakMapCache'
|
||||
export {
|
||||
ACCEPTED_ASSET_TYPE,
|
||||
|
@ -256,4 +259,7 @@ export {
|
|||
defaultEmptyAs,
|
||||
} from './lib/utils/string'
|
||||
export { getPointerInfo, getSvgPathFromStroke, getSvgPathFromStrokePoints } from './lib/utils/svg'
|
||||
export { type StoreWithStatus } from './lib/utils/sync/StoreWithStatus'
|
||||
export { hardReset } from './lib/utils/sync/hardReset'
|
||||
export { TAB_ID } from './lib/utils/sync/persistence-constants'
|
||||
export { openWindow } from './lib/utils/window-open'
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { InstanceRecordType, TLAsset, TLInstanceId, TLStore } from '@tldraw/tlschema'
|
||||
import { Store } from '@tldraw/tlstore'
|
||||
import { TLAsset, TLInstanceId, TLRecord, TLStore } from '@tldraw/tlschema'
|
||||
import { Store, StoreSnapshot } from '@tldraw/tlstore'
|
||||
import { annotateError } from '@tldraw/utils'
|
||||
import React, { useCallback, useMemo, useSyncExternalStore } from 'react'
|
||||
import React, { memo, useCallback, useLayoutEffect, useState, useSyncExternalStore } from 'react'
|
||||
import { App } from './app/App'
|
||||
import { StateNodeConstructor } from './app/statechart/StateNode'
|
||||
import { EditorAssetUrls, defaultEditorAssetUrls } from './assetUrls'
|
||||
import { OptionalErrorBoundary } from './components/ErrorBoundary'
|
||||
|
||||
import { SyncedStore } from './config/SyncedStore'
|
||||
import { TldrawEditorConfig } from './config/TldrawEditorConfig'
|
||||
|
||||
import { DefaultErrorFallback } from './components/DefaultErrorFallback'
|
||||
import { OptionalErrorBoundary } from './components/ErrorBoundary'
|
||||
import { ShapeInfo } from './config/createTLStore'
|
||||
import { AppContext } from './hooks/useApp'
|
||||
import { ContainerProvider, useContainer } from './hooks/useContainer'
|
||||
import { useCursor } from './hooks/useCursor'
|
||||
|
@ -21,21 +19,38 @@ import {
|
|||
} from './hooks/useEditorComponents'
|
||||
import { useEvent } from './hooks/useEvent'
|
||||
import { useForceUpdate } from './hooks/useForceUpdate'
|
||||
import { useLocalStore } from './hooks/useLocalStore'
|
||||
import { usePreloadAssets } from './hooks/usePreloadAssets'
|
||||
import { useSafariFocusOutFix } from './hooks/useSafariFocusOutFix'
|
||||
import { useZoomCss } from './hooks/useZoomCss'
|
||||
import { StoreWithStatus } from './utils/sync/StoreWithStatus'
|
||||
import { TAB_ID } from './utils/sync/persistence-constants'
|
||||
|
||||
/** @public */
|
||||
export interface TldrawEditorProps {
|
||||
export type 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
|
||||
/**
|
||||
* Called when the app has mounted.
|
||||
* An array of shape utils to use in the editor.
|
||||
*/
|
||||
shapes?: Record<string, ShapeInfo>
|
||||
/**
|
||||
* An array of tools to use in the editor.
|
||||
*/
|
||||
tools?: StateNodeConstructor[]
|
||||
/**
|
||||
* Urls for where to find fonts and other assets.
|
||||
*/
|
||||
assetUrls?: EditorAssetUrls
|
||||
/**
|
||||
* Whether to automatically focus the editor when it mounts.
|
||||
*/
|
||||
autoFocus?: boolean
|
||||
/**
|
||||
* Overrides for the tldraw user interface components.
|
||||
*/
|
||||
components?: Partial<TLEditorComponents>
|
||||
/**
|
||||
* Called when the editor has mounted.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
|
@ -49,7 +64,7 @@ export interface TldrawEditorProps {
|
|||
*/
|
||||
onMount?: (app: App) => void
|
||||
/**
|
||||
* Called when the app generates a new asset from a file, such as when an image is dropped into
|
||||
* Called when the editor generates a new asset from a file, such as when an image is dropped into
|
||||
* the canvas.
|
||||
*
|
||||
* @example
|
||||
|
@ -81,22 +96,31 @@ export interface TldrawEditorProps {
|
|||
onCreateBookmarkFromUrl?: (
|
||||
url: string
|
||||
) => Promise<{ image: string; title: string; description: string }>
|
||||
|
||||
/**
|
||||
* The Store instance to use for keeping the app's data. This may be prepopulated, e.g. by loading
|
||||
* from a server or database.
|
||||
*/
|
||||
store?: TLStore | SyncedStore
|
||||
/**
|
||||
* The id of the app instance (e.g. a browser tab if the app will have only one tldraw app per
|
||||
* tab). If not given, one will be generated.
|
||||
*/
|
||||
instanceId?: TLInstanceId
|
||||
/** Asset URLs */
|
||||
assetUrls?: EditorAssetUrls
|
||||
/** Whether to automatically focus the editor when it mounts. */
|
||||
autoFocus?: boolean
|
||||
}
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
* The Store instance to use for keeping the editor's data. This may be prepopulated, e.g. by loading
|
||||
* from a server or database.
|
||||
*/
|
||||
store: TLStore | StoreWithStatus
|
||||
}
|
||||
| {
|
||||
store?: undefined
|
||||
/**
|
||||
* The editor's initial data.
|
||||
*/
|
||||
initialData?: StoreSnapshot<TLRecord>
|
||||
/**
|
||||
* The id of the editor instance (e.g. a browser tab if the editor will have only one tldraw app per
|
||||
* tab). If not given, one will be generated.
|
||||
*/
|
||||
instanceId?: TLInstanceId
|
||||
/**
|
||||
* The id under which to sync and persist the editor's data.
|
||||
*/
|
||||
persistenceKey?: string
|
||||
}
|
||||
)
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -105,12 +129,15 @@ declare global {
|
|||
}
|
||||
|
||||
/** @public */
|
||||
export function TldrawEditor(props: TldrawEditorProps) {
|
||||
export const TldrawEditor = memo(function TldrawEditor(props: TldrawEditorProps) {
|
||||
const [container, setContainer] = React.useState<HTMLDivElement | null>(null)
|
||||
const { components, ...rest } = props
|
||||
|
||||
const ErrorFallback =
|
||||
components?.ErrorFallback === undefined ? DefaultErrorFallback : components?.ErrorFallback
|
||||
props.components?.ErrorFallback === undefined
|
||||
? DefaultErrorFallback
|
||||
: props.components?.ErrorFallback
|
||||
|
||||
const { store, ...rest } = props
|
||||
|
||||
return (
|
||||
<div ref={setContainer} draggable={false} className="tl-container tl-theme__light" tabIndex={0}>
|
||||
|
@ -120,51 +147,68 @@ export function TldrawEditor(props: TldrawEditorProps) {
|
|||
>
|
||||
{container && (
|
||||
<ContainerProvider container={container}>
|
||||
<EditorComponentsProvider overrides={components}>
|
||||
<TldrawEditorBeforeLoading {...rest} />
|
||||
<EditorComponentsProvider overrides={props.components}>
|
||||
{store ? (
|
||||
store instanceof Store ? (
|
||||
// Store is ready to go, whether externally synced or not
|
||||
<TldrawEditorWithReadyStore {...rest} store={store} />
|
||||
) : (
|
||||
// Store is a synced store, so handle syncing stages internally
|
||||
<TldrawEditorWithLoadingStore {...rest} store={store} />
|
||||
)
|
||||
) : (
|
||||
// We have no store (it's undefined) so create one and possibly sync it
|
||||
<TldrawEditorWithOwnStore {...rest} store={store} />
|
||||
)}
|
||||
</EditorComponentsProvider>
|
||||
</ContainerProvider>
|
||||
)}
|
||||
</OptionalErrorBoundary>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
function TldrawEditorWithOwnStore(props: TldrawEditorProps & { store: undefined }) {
|
||||
const { initialData, instanceId = TAB_ID, shapes, persistenceKey } = props
|
||||
|
||||
const syncedStore = useLocalStore({
|
||||
customShapes: shapes,
|
||||
instanceId,
|
||||
initialData,
|
||||
persistenceKey,
|
||||
})
|
||||
|
||||
return <TldrawEditorWithLoadingStore {...props} store={syncedStore} />
|
||||
}
|
||||
|
||||
function TldrawEditorBeforeLoading({ config, instanceId, store, ...props }: TldrawEditorProps) {
|
||||
const TldrawEditorWithLoadingStore = memo(function TldrawEditorBeforeLoading({
|
||||
store,
|
||||
assetUrls,
|
||||
...rest
|
||||
}: TldrawEditorProps & { store: StoreWithStatus }) {
|
||||
const { done: preloadingComplete, error: preloadingError } = usePreloadAssets(
|
||||
props.assetUrls ?? defaultEditorAssetUrls
|
||||
assetUrls ?? defaultEditorAssetUrls
|
||||
)
|
||||
|
||||
const _store = useMemo<TLStore | SyncedStore>(() => {
|
||||
return (
|
||||
store ??
|
||||
config.createStore({
|
||||
instanceId: instanceId ?? InstanceRecordType.createId(),
|
||||
})
|
||||
)
|
||||
}, [store, config, instanceId])
|
||||
|
||||
let loadedStore: TLStore | SyncedStore
|
||||
if (!(_store instanceof Store)) {
|
||||
if (_store.error) {
|
||||
switch (store.status) {
|
||||
case '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) {
|
||||
case 'loading': {
|
||||
return <LoadingScreen>Connecting...</LoadingScreen>
|
||||
}
|
||||
|
||||
loadedStore = _store.store
|
||||
} else {
|
||||
loadedStore = _store
|
||||
}
|
||||
|
||||
if (instanceId && loadedStore.props.instanceId !== instanceId) {
|
||||
console.error(
|
||||
`The store's instanceId (${loadedStore.props.instanceId}) does not match the instanceId prop (${instanceId}). This may cause unexpected behavior.`
|
||||
)
|
||||
case 'not-synced': {
|
||||
break
|
||||
}
|
||||
case 'synced-local': {
|
||||
break
|
||||
}
|
||||
case 'synced-remote': {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (preloadingError) {
|
||||
|
@ -175,57 +219,56 @@ function TldrawEditorBeforeLoading({ config, instanceId, store, ...props }: Tldr
|
|||
return <LoadingScreen>Loading assets...</LoadingScreen>
|
||||
}
|
||||
|
||||
return <TldrawEditorAfterLoading {...props} store={loadedStore} config={config} />
|
||||
}
|
||||
return <TldrawEditorWithReadyStore {...rest} store={store.store} />
|
||||
})
|
||||
|
||||
function TldrawEditorAfterLoading({
|
||||
function TldrawEditorWithReadyStore({
|
||||
onMount,
|
||||
config,
|
||||
children,
|
||||
onCreateAssetFromFile,
|
||||
onCreateBookmarkFromUrl,
|
||||
store,
|
||||
tools,
|
||||
shapes,
|
||||
autoFocus,
|
||||
}: Omit<TldrawEditorProps, 'store' | 'config' | 'instanceId' | 'userId'> & {
|
||||
config: TldrawEditorConfig
|
||||
}: TldrawEditorProps & {
|
||||
store: TLStore
|
||||
}) {
|
||||
const container = useContainer()
|
||||
|
||||
const [app, setApp] = React.useState<App | null>(null)
|
||||
const { ErrorFallback } = useEditorComponents()
|
||||
const container = useContainer()
|
||||
const [app, setApp] = useState<App | null>(null)
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
const app = new App({
|
||||
store,
|
||||
config,
|
||||
shapes,
|
||||
tools,
|
||||
getContainer: () => container,
|
||||
})
|
||||
setApp(app)
|
||||
|
||||
if (autoFocus) {
|
||||
app.focus()
|
||||
}
|
||||
;(window as any).app = app
|
||||
setApp(app)
|
||||
return () => {
|
||||
app.dispose()
|
||||
setApp((prevApp) => (prevApp === app ? null : prevApp))
|
||||
}
|
||||
}, [container, config, store, autoFocus])
|
||||
}, [container, shapes, tools, store])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (app) {
|
||||
// Overwrite the default onCreateAssetFromFile handler.
|
||||
if (onCreateAssetFromFile) {
|
||||
app.onCreateAssetFromFile = onCreateAssetFromFile
|
||||
}
|
||||
if (!app) return
|
||||
|
||||
if (onCreateBookmarkFromUrl) {
|
||||
app.onCreateBookmarkFromUrl = onCreateBookmarkFromUrl
|
||||
}
|
||||
// Overwrite the default onCreateAssetFromFile handler.
|
||||
if (onCreateAssetFromFile) {
|
||||
app.onCreateAssetFromFile = onCreateAssetFromFile
|
||||
}
|
||||
|
||||
if (onCreateBookmarkFromUrl) {
|
||||
app.onCreateBookmarkFromUrl = onCreateBookmarkFromUrl
|
||||
}
|
||||
}, [app, onCreateAssetFromFile, onCreateBookmarkFromUrl])
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (app && autoFocus) app.focus()
|
||||
}, [app, autoFocus])
|
||||
|
||||
const onMountEvent = useEvent((app: App) => {
|
||||
onMount?.(app)
|
||||
app.emit('mount')
|
||||
|
@ -233,10 +276,7 @@ function TldrawEditorAfterLoading({
|
|||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
if (app) {
|
||||
// Run onMount
|
||||
onMountEvent(app)
|
||||
}
|
||||
if (app) onMountEvent(app)
|
||||
}, [app, onMountEvent])
|
||||
|
||||
const crashingError = useSyncExternalStore(
|
||||
|
|
|
@ -64,7 +64,7 @@ import {
|
|||
isShape,
|
||||
isShapeId,
|
||||
} from '@tldraw/tlschema'
|
||||
import { ComputedCache, HistoryEntry, UnknownRecord } from '@tldraw/tlstore'
|
||||
import { ComputedCache, HistoryEntry, RecordType, UnknownRecord } from '@tldraw/tlstore'
|
||||
import {
|
||||
annotateError,
|
||||
compact,
|
||||
|
@ -77,7 +77,10 @@ import {
|
|||
import { EventEmitter } from 'eventemitter3'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { EMPTY_ARRAY, atom, computed, transact } from 'signia'
|
||||
import { TldrawEditorConfig } from '../config/TldrawEditorConfig'
|
||||
import { ShapeInfo } from '../config/createTLStore'
|
||||
import { TLUser, createTLUser } from '../config/createTLUser'
|
||||
import { coreShapes, defaultShapes } from '../config/defaultShapes'
|
||||
import { defaultTools } from '../config/defaultTools'
|
||||
import {
|
||||
ANIMATION_MEDIUM_MS,
|
||||
BLACKLISTED_PROPS,
|
||||
|
@ -132,7 +135,7 @@ import { TLResizeMode, TLShapeUtil } from './shapeutils/TLShapeUtil'
|
|||
import { TLTextUtil } from './shapeutils/TLTextUtil/TLTextUtil'
|
||||
import { TLExportColors } from './shapeutils/shared/TLExportColors'
|
||||
import { RootState } from './statechart/RootState'
|
||||
import { StateNode } from './statechart/StateNode'
|
||||
import { StateNode, StateNodeConstructor } from './statechart/StateNode'
|
||||
import { TLClipboardModel } from './types/clipboard-types'
|
||||
import { TLEventMap } from './types/emit-types'
|
||||
import { TLEventInfo, TLPinchEventInfo, TLPointerEventInfo } from './types/event-types'
|
||||
|
@ -161,8 +164,18 @@ export interface AppOptions {
|
|||
* from a server or database.
|
||||
*/
|
||||
store: TLStore
|
||||
/** A configuration defining major customizations to the app, such as custom shapes and new tools */
|
||||
config: TldrawEditorConfig
|
||||
/**
|
||||
* An array of shapes to use in the app. These will be used to create and manage shapes in the app.
|
||||
*/
|
||||
shapes?: Record<string, ShapeInfo>
|
||||
/**
|
||||
* An array of tools to use in the app. These will be used to handle events and manage user interactions in the app.
|
||||
*/
|
||||
tools?: StateNodeConstructor[]
|
||||
/**
|
||||
* A user defined externally to replace the default user.
|
||||
*/
|
||||
user?: TLUser
|
||||
/**
|
||||
* Should return a containing html element which has all the styles applied to the app. If not
|
||||
* given, the body element will be used.
|
||||
|
@ -177,28 +190,54 @@ export function isShapeWithHandles(shape: TLShape) {
|
|||
|
||||
/** @public */
|
||||
export class App extends EventEmitter<TLEventMap> {
|
||||
constructor({ config, store, getContainer }: AppOptions) {
|
||||
constructor({
|
||||
store,
|
||||
user,
|
||||
tools = defaultTools,
|
||||
shapes = defaultShapes,
|
||||
getContainer,
|
||||
}: AppOptions) {
|
||||
super()
|
||||
|
||||
this.config = config
|
||||
|
||||
if (store.schema !== this.config.storeSchema) {
|
||||
throw new Error('Store schema does not match schema given to App')
|
||||
}
|
||||
|
||||
this.store = store
|
||||
|
||||
this.user = new UserPreferencesManager(this)
|
||||
this.user = new UserPreferencesManager(user ?? createTLUser())
|
||||
|
||||
this.getContainer = getContainer ?? (() => document.body)
|
||||
|
||||
this.textMeasure = new TextManager(this)
|
||||
|
||||
// Set the shape utils
|
||||
this.shapeUtils = Object.fromEntries(
|
||||
Object.entries(this.config.shapeUtils).map(([type, Util]) => [type, new Util(this, type)])
|
||||
this.root = new RootState(this)
|
||||
|
||||
// Shapes.
|
||||
// Accept shapes from constructor parameters which may not conflict with the root note's core tools.
|
||||
const shapeUtils = Object.fromEntries(
|
||||
Object.values(coreShapes).map(({ util: Util }) => [Util.type, new Util(this, Util.type)])
|
||||
)
|
||||
|
||||
for (const [type, { util: Util }] of Object.entries(shapes)) {
|
||||
if (shapeUtils[type]) {
|
||||
throw Error(`May not overwrite core shape of type "${type}".`)
|
||||
}
|
||||
if (type !== Util.type) {
|
||||
throw Error(`Shape util's type "${Util.type}" does not match provided type "${type}".`)
|
||||
}
|
||||
shapeUtils[type] = new Util(this, Util.type)
|
||||
}
|
||||
this.shapeUtils = shapeUtils
|
||||
|
||||
// Tools.
|
||||
// Accept tools from constructor parameters which may not conflict with the root note's default or
|
||||
// "baked in" tools, select and zoom.
|
||||
const uniqueTools = Object.fromEntries(tools.map((Ctor) => [Ctor.id, Ctor]))
|
||||
for (const [id, Ctor] of Object.entries(uniqueTools)) {
|
||||
if (this.root.children?.[id]) {
|
||||
throw Error(`Can't override tool with id "${id}"`)
|
||||
}
|
||||
|
||||
this.root.children![id] = new Ctor(this)
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && 'navigator' in window) {
|
||||
this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
||||
this.isIos = !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPhone/i)
|
||||
|
@ -212,13 +251,6 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
// Set styles
|
||||
this.colors = new Map(App.styles.color.map((c) => [c.id, `var(--palette-${c.id})`]))
|
||||
|
||||
this.root = new RootState(this)
|
||||
if (this.root.children) {
|
||||
this.config.tools.forEach((Ctor) => {
|
||||
this.root.children![Ctor.id] = new Ctor(this)
|
||||
})
|
||||
}
|
||||
|
||||
this.store.onBeforeDelete = (record) => {
|
||||
if (record.typeName === 'shape') {
|
||||
this._shapeWillBeDeleted(record)
|
||||
|
@ -310,13 +342,6 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
readonly store: TLStore
|
||||
|
||||
/**
|
||||
* The editor's config
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
readonly config: TldrawEditorConfig
|
||||
|
||||
/**
|
||||
* The root state of the statechart.
|
||||
*
|
||||
|
@ -4699,7 +4724,12 @@ export class App extends EventEmitter<TLEventMap> {
|
|||
|
||||
// When we create the shape, take in the partial (the props coming into the
|
||||
// function) and merge it with the default props.
|
||||
let shapeRecordToCreate = this.config.TLShape.create({
|
||||
let shapeRecordToCreate = (
|
||||
this.store.schema.types.shape as RecordType<
|
||||
TLShape,
|
||||
'type' | 'props' | 'index' | 'parentId'
|
||||
>
|
||||
).create({
|
||||
...partial,
|
||||
index,
|
||||
parentId: partial.parentId ?? focusLayerId,
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
import { TLUserPreferences } from '../../config/TLUserPreferences'
|
||||
import { App } from '../App'
|
||||
import { TLUser } from '../../config/createTLUser'
|
||||
|
||||
export class UserPreferencesManager {
|
||||
constructor(private readonly editor: App) {}
|
||||
constructor(private readonly user: TLUser) {}
|
||||
|
||||
updateUserPreferences = (userPreferences: Partial<TLUserPreferences>) => {
|
||||
this.editor.config.setUserPreferences({
|
||||
...this.editor.config.userPreferences.value,
|
||||
this.user.setUserPreferences({
|
||||
...this.user.userPreferences.value,
|
||||
...userPreferences,
|
||||
})
|
||||
}
|
||||
|
||||
get isDarkMode() {
|
||||
return this.editor.config.userPreferences.value.isDarkMode
|
||||
return this.user.userPreferences.value.isDarkMode
|
||||
}
|
||||
|
||||
get animationSpeed() {
|
||||
return this.editor.config.userPreferences.value.animationSpeed
|
||||
return this.user.userPreferences.value.animationSpeed
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.editor.config.userPreferences.value.id
|
||||
return this.user.userPreferences.value.id
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.editor.config.userPreferences.value.name
|
||||
return this.user.userPreferences.value.name
|
||||
}
|
||||
|
||||
get locale() {
|
||||
return this.editor.config.userPreferences.value.locale
|
||||
return this.user.userPreferences.value.locale
|
||||
}
|
||||
|
||||
get color() {
|
||||
return this.editor.config.userPreferences.value.color
|
||||
return this.user.userPreferences.value.color
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import { DashedOutlineBox } from '../shared/DashedOutlineBox'
|
|||
export class TLGroupUtil extends TLShapeUtil<TLGroupShape> {
|
||||
static override type = 'group'
|
||||
|
||||
type = 'group' as const
|
||||
|
||||
hideSelectionBoundsBg = () => false
|
||||
hideSelectionBoundsFg = () => true
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ export interface TLShapeUtilConstructor<
|
|||
ShapeUtil extends TLShapeUtil<T> = TLShapeUtil<T>
|
||||
> {
|
||||
new (app: App, type: T['type']): ShapeUtil
|
||||
type: T['type']
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -1,37 +1,12 @@
|
|||
import { TLEventHandlers } from '../types/event-types'
|
||||
import { StateNode } from './StateNode'
|
||||
import { TLArrowTool } from './TLArrowTool/TLArrowTool'
|
||||
import { TLDrawTool } from './TLDrawTool/TLDrawTool'
|
||||
import { TLEraserTool } from './TLEraserTool/TLEraserTool'
|
||||
import { TLFrameTool } from './TLFrameTool/TLFrameTool'
|
||||
import { TLGeoTool } from './TLGeoTool/TLGeoTool'
|
||||
import { TLHandTool } from './TLHandTool/TLHandTool'
|
||||
import { TLHighlightTool } from './TLHighlightTool/TLHighlightTool'
|
||||
import { TLLaserTool } from './TLLaserTool/TLLaserTool'
|
||||
import { TLLineTool } from './TLLineTool/TLLineTool'
|
||||
import { TLNoteTool } from './TLNoteTool/TLNoteTool'
|
||||
import { TLSelectTool } from './TLSelectTool/TLSelectTool'
|
||||
import { TLTextTool } from './TLTextTool/TLTextTool'
|
||||
import { TLZoomTool } from './TLZoomTool/TLZoomTool'
|
||||
|
||||
export class RootState extends StateNode {
|
||||
static override id = 'root'
|
||||
static initial = 'select'
|
||||
static children = () => [
|
||||
TLSelectTool,
|
||||
TLHandTool,
|
||||
TLEraserTool,
|
||||
TLDrawTool,
|
||||
TLHighlightTool,
|
||||
TLTextTool,
|
||||
TLLineTool,
|
||||
TLArrowTool,
|
||||
TLGeoTool,
|
||||
TLNoteTool,
|
||||
TLFrameTool,
|
||||
TLZoomTool,
|
||||
TLLaserTool,
|
||||
]
|
||||
static children = () => [TLSelectTool, TLZoomTool]
|
||||
|
||||
onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
||||
switch (info.code) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Lasering } from './children/Lasering'
|
|||
|
||||
export class TLLaserTool extends StateNode {
|
||||
static override id = 'laser'
|
||||
|
||||
static initial = 'idle'
|
||||
static children = () => [Idle, Lasering]
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { TLStore } from '@tldraw/tlschema'
|
||||
|
||||
/** @public */
|
||||
export interface ReadySyncedStore {
|
||||
readonly status: 'synced'
|
||||
readonly store: TLStore
|
||||
readonly error?: undefined
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ErrorSyncedStore {
|
||||
readonly status: 'error'
|
||||
readonly store?: undefined
|
||||
readonly error: Error
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface InitializingSyncedStore {
|
||||
readonly status: 'loading'
|
||||
readonly store?: undefined
|
||||
readonly error?: undefined
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type SyncedStore = ReadySyncedStore | ErrorSyncedStore | InitializingSyncedStore
|
|
@ -146,6 +146,7 @@ function storeUserPreferences() {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function setUserPreferences(user: TLUserPreferences) {
|
||||
userTypeValidator.validate(user)
|
||||
globalUserPreferences.set(user)
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
import {
|
||||
CLIENT_FIXUP_SCRIPT,
|
||||
InstanceRecordType,
|
||||
TLDOCUMENT_ID,
|
||||
TLDefaultShape,
|
||||
TLInstanceId,
|
||||
TLInstancePresence,
|
||||
TLRecord,
|
||||
TLShape,
|
||||
TLStore,
|
||||
TLStoreProps,
|
||||
createTLSchema,
|
||||
} from '@tldraw/tlschema'
|
||||
import { Migrations, RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
|
||||
import { Signal, computed } from 'signia'
|
||||
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
import { TLDrawUtil } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
import { TLEmbedUtil } from '../app/shapeutils/TLEmbedUtil/TLEmbedUtil'
|
||||
import { TLFrameUtil } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGeoUtil } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TLGroupUtil } from '../app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLHighlightUtil } from '../app/shapeutils/TLHighlightUtil/TLHighlightUtil'
|
||||
import { TLImageUtil } from '../app/shapeutils/TLImageUtil/TLImageUtil'
|
||||
import { TLLineUtil } from '../app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
import { TLNoteUtil } from '../app/shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
import { TLShapeUtilConstructor } from '../app/shapeutils/TLShapeUtil'
|
||||
import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
||||
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
||||
import { TLUserPreferences, getUserPreferences, setUserPreferences } from './TLUserPreferences'
|
||||
|
||||
// Secret shape types that don't have a shape util yet
|
||||
type ShapeTypesNotImplemented = 'icon'
|
||||
|
||||
const DEFAULT_SHAPE_UTILS: {
|
||||
[K in Exclude<TLDefaultShape['type'], ShapeTypesNotImplemented>]: TLShapeUtilConstructor<any>
|
||||
} = {
|
||||
arrow: TLArrowUtil,
|
||||
bookmark: TLBookmarkUtil,
|
||||
draw: TLDrawUtil,
|
||||
embed: TLEmbedUtil,
|
||||
frame: TLFrameUtil,
|
||||
geo: TLGeoUtil,
|
||||
group: TLGroupUtil,
|
||||
image: TLImageUtil,
|
||||
line: TLLineUtil,
|
||||
note: TLNoteUtil,
|
||||
text: TLTextUtil,
|
||||
video: TLVideoUtil,
|
||||
highlight: TLHighlightUtil,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type TldrawEditorConfigOptions = {
|
||||
tools?: readonly StateNodeConstructor[]
|
||||
shapes?: Record<
|
||||
string,
|
||||
{
|
||||
util: TLShapeUtilConstructor<any>
|
||||
validator?: { validate: <T>(record: T) => T }
|
||||
migrations?: Migrations
|
||||
}
|
||||
>
|
||||
/** @internal */
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
userPreferences?: Signal<TLUserPreferences>
|
||||
setUserPreferences?: (userPreferences: TLUserPreferences) => void
|
||||
}
|
||||
|
||||
/** @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>
|
||||
readonly derivePresenceState: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
readonly userPreferences: Signal<TLUserPreferences>
|
||||
readonly setUserPreferences: (userPreferences: TLUserPreferences) => void
|
||||
|
||||
constructor(opts = {} as TldrawEditorConfigOptions) {
|
||||
const { shapes = {}, tools = [], derivePresenceState } = opts
|
||||
|
||||
this.tools = tools
|
||||
this.derivePresenceState = derivePresenceState ?? (() => computed('presence', () => null))
|
||||
this.userPreferences =
|
||||
opts.userPreferences ?? computed('userPreferences', () => getUserPreferences())
|
||||
this.setUserPreferences = opts.setUserPreferences ?? setUserPreferences
|
||||
|
||||
this.shapeUtils = {
|
||||
...DEFAULT_SHAPE_UTILS,
|
||||
...Object.fromEntries(Object.entries(shapes).map(([k, v]) => [k, v.util])),
|
||||
}
|
||||
|
||||
this.storeSchema = createTLSchema({
|
||||
customShapes: shapes,
|
||||
})
|
||||
|
||||
this.TLShape = this.storeSchema.types.shape as RecordType<
|
||||
TLShape,
|
||||
'type' | 'props' | 'index' | 'parentId'
|
||||
>
|
||||
}
|
||||
|
||||
createStore(config: {
|
||||
/** The store's initial data. */
|
||||
initialData?: StoreSnapshot<TLRecord>
|
||||
instanceId: TLInstanceId
|
||||
}): TLStore {
|
||||
let initialData = config.initialData
|
||||
if (initialData) {
|
||||
initialData = CLIENT_FIXUP_SCRIPT(initialData)
|
||||
}
|
||||
|
||||
return new Store<TLRecord, TLStoreProps>({
|
||||
schema: this.storeSchema,
|
||||
initialData,
|
||||
props: {
|
||||
instanceId: config?.instanceId ?? InstanceRecordType.createId(),
|
||||
documentId: TLDOCUMENT_ID,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
43
packages/editor/src/lib/config/createTLStore.ts
Normal file
43
packages/editor/src/lib/config/createTLStore.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import {
|
||||
InstanceRecordType,
|
||||
TLDOCUMENT_ID,
|
||||
TLInstanceId,
|
||||
TLRecord,
|
||||
TLStore,
|
||||
createTLSchema,
|
||||
} from '@tldraw/tlschema'
|
||||
import { Migrations, Store, StoreSnapshot } from '@tldraw/tlstore'
|
||||
import { TLShapeUtilConstructor } from '../app/shapeutils/TLShapeUtil'
|
||||
|
||||
/** @public */
|
||||
export type ShapeInfo = {
|
||||
util: TLShapeUtilConstructor<any>
|
||||
migrations?: Migrations
|
||||
validator?: { validate: (record: any) => any }
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type StoreOptions = {
|
||||
customShapes?: Record<string, ShapeInfo>
|
||||
instanceId?: TLInstanceId
|
||||
initialData?: StoreSnapshot<TLRecord>
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for creating a TLStore. Custom shapes cannot override default shapes.
|
||||
*
|
||||
* @param opts - Options for creating the store.
|
||||
*
|
||||
* @public */
|
||||
export function createTLStore(opts = {} as StoreOptions): TLStore {
|
||||
const { customShapes = {}, instanceId = InstanceRecordType.createId(), initialData } = opts
|
||||
|
||||
return new Store({
|
||||
schema: createTLSchema({ customShapes }),
|
||||
initialData,
|
||||
props: {
|
||||
instanceId,
|
||||
documentId: TLDOCUMENT_ID,
|
||||
},
|
||||
})
|
||||
}
|
27
packages/editor/src/lib/config/createTLUser.ts
Normal file
27
packages/editor/src/lib/config/createTLUser.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { TLInstancePresence, TLStore } from '@tldraw/tlschema'
|
||||
import { Signal, computed } from 'signia'
|
||||
import { TLUserPreferences, getUserPreferences, setUserPreferences } from './TLUserPreferences'
|
||||
|
||||
/** @public */
|
||||
export interface TLUser {
|
||||
readonly derivePresenceState: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
readonly userPreferences: Signal<TLUserPreferences>
|
||||
readonly setUserPreferences: (userPreferences: TLUserPreferences) => void
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createTLUser(
|
||||
opts = {} as {
|
||||
/** @internal */
|
||||
derivePresenceState?: (store: TLStore) => Signal<TLInstancePresence | null>
|
||||
userPreferences?: Signal<TLUserPreferences>
|
||||
setUserPreferences?: (userPreferences: TLUserPreferences) => void
|
||||
}
|
||||
): TLUser {
|
||||
return {
|
||||
derivePresenceState: opts.derivePresenceState ?? (() => computed('presence', () => null)),
|
||||
userPreferences:
|
||||
opts.userPreferences ?? computed('userPreferences', () => getUserPreferences()),
|
||||
setUserPreferences: opts.setUserPreferences ?? setUserPreferences,
|
||||
}
|
||||
}
|
121
packages/editor/src/lib/config/defaultShapes.ts
Normal file
121
packages/editor/src/lib/config/defaultShapes.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import {
|
||||
arrowShapeTypeMigrations,
|
||||
arrowShapeTypeValidator,
|
||||
bookmarkShapeTypeMigrations,
|
||||
bookmarkShapeTypeValidator,
|
||||
drawShapeTypeMigrations,
|
||||
drawShapeTypeValidator,
|
||||
embedShapeTypeMigrations,
|
||||
embedShapeTypeValidator,
|
||||
frameShapeTypeMigrations,
|
||||
frameShapeTypeValidator,
|
||||
geoShapeTypeMigrations,
|
||||
geoShapeTypeValidator,
|
||||
groupShapeTypeMigrations,
|
||||
groupShapeTypeValidator,
|
||||
highlightShapeTypeMigrations,
|
||||
highlightShapeTypeValidator,
|
||||
imageShapeTypeMigrations,
|
||||
imageShapeTypeValidator,
|
||||
lineShapeTypeMigrations,
|
||||
lineShapeTypeValidator,
|
||||
noteShapeTypeMigrations,
|
||||
noteShapeTypeValidator,
|
||||
textShapeTypeMigrations,
|
||||
textShapeTypeValidator,
|
||||
videoShapeTypeMigrations,
|
||||
videoShapeTypeValidator,
|
||||
} from '@tldraw/tlschema'
|
||||
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
|
||||
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
|
||||
import { TLDrawUtil } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
|
||||
import { TLEmbedUtil } from '../app/shapeutils/TLEmbedUtil/TLEmbedUtil'
|
||||
import { TLFrameUtil } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
|
||||
import { TLGeoUtil } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
|
||||
import { TLGroupUtil } from '../app/shapeutils/TLGroupUtil/TLGroupUtil'
|
||||
import { TLHighlightUtil } from '../app/shapeutils/TLHighlightUtil/TLHighlightUtil'
|
||||
import { TLImageUtil } from '../app/shapeutils/TLImageUtil/TLImageUtil'
|
||||
import { TLLineUtil } from '../app/shapeutils/TLLineUtil/TLLineUtil'
|
||||
import { TLNoteUtil } from '../app/shapeutils/TLNoteUtil/TLNoteUtil'
|
||||
import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
|
||||
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
|
||||
import { ShapeInfo } from './createTLStore'
|
||||
|
||||
/** @public */
|
||||
export const coreShapes: Record<string, ShapeInfo> = {
|
||||
// created by grouping interactions, probably the corest core shape that we have
|
||||
group: {
|
||||
util: TLGroupUtil,
|
||||
validator: groupShapeTypeValidator,
|
||||
migrations: groupShapeTypeMigrations,
|
||||
},
|
||||
// created by embed menu / url drop
|
||||
embed: {
|
||||
util: TLEmbedUtil,
|
||||
validator: embedShapeTypeValidator,
|
||||
migrations: embedShapeTypeMigrations,
|
||||
},
|
||||
// created by copy and paste / url drop
|
||||
bookmark: {
|
||||
util: TLBookmarkUtil,
|
||||
validator: bookmarkShapeTypeValidator,
|
||||
migrations: bookmarkShapeTypeMigrations,
|
||||
},
|
||||
// created by copy and paste / file drop
|
||||
image: {
|
||||
util: TLImageUtil,
|
||||
validator: imageShapeTypeValidator,
|
||||
migrations: imageShapeTypeMigrations,
|
||||
},
|
||||
// created by copy and paste / file drop
|
||||
video: {
|
||||
util: TLVideoUtil,
|
||||
validator: videoShapeTypeValidator,
|
||||
migrations: videoShapeTypeMigrations,
|
||||
},
|
||||
// created by copy and paste
|
||||
text: {
|
||||
util: TLTextUtil,
|
||||
validator: textShapeTypeValidator,
|
||||
migrations: textShapeTypeMigrations,
|
||||
},
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const defaultShapes: Record<string, ShapeInfo> = {
|
||||
draw: {
|
||||
util: TLDrawUtil,
|
||||
validator: drawShapeTypeValidator,
|
||||
migrations: drawShapeTypeMigrations,
|
||||
},
|
||||
geo: {
|
||||
util: TLGeoUtil,
|
||||
validator: geoShapeTypeValidator,
|
||||
migrations: geoShapeTypeMigrations,
|
||||
},
|
||||
line: {
|
||||
util: TLLineUtil,
|
||||
validator: lineShapeTypeValidator,
|
||||
migrations: lineShapeTypeMigrations,
|
||||
},
|
||||
note: {
|
||||
util: TLNoteUtil,
|
||||
validator: noteShapeTypeValidator,
|
||||
migrations: noteShapeTypeMigrations,
|
||||
},
|
||||
frame: {
|
||||
util: TLFrameUtil,
|
||||
validator: frameShapeTypeValidator,
|
||||
migrations: frameShapeTypeMigrations,
|
||||
},
|
||||
arrow: {
|
||||
util: TLArrowUtil,
|
||||
validator: arrowShapeTypeValidator,
|
||||
migrations: arrowShapeTypeMigrations,
|
||||
},
|
||||
highlight: {
|
||||
util: TLHighlightUtil,
|
||||
validator: highlightShapeTypeValidator,
|
||||
migrations: highlightShapeTypeMigrations,
|
||||
},
|
||||
}
|
27
packages/editor/src/lib/config/defaultTools.ts
Normal file
27
packages/editor/src/lib/config/defaultTools.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { StateNodeConstructor } from '../app/statechart/StateNode'
|
||||
import { TLArrowTool } from '../app/statechart/TLArrowTool/TLArrowTool'
|
||||
import { TLDrawTool } from '../app/statechart/TLDrawTool/TLDrawTool'
|
||||
import { TLEraserTool } from '../app/statechart/TLEraserTool/TLEraserTool'
|
||||
import { TLFrameTool } from '../app/statechart/TLFrameTool/TLFrameTool'
|
||||
import { TLGeoTool } from '../app/statechart/TLGeoTool/TLGeoTool'
|
||||
import { TLHandTool } from '../app/statechart/TLHandTool/TLHandTool'
|
||||
import { TLHighlightTool } from '../app/statechart/TLHighlightTool/TLHighlightTool'
|
||||
import { TLLaserTool } from '../app/statechart/TLLaserTool/TLLaserTool'
|
||||
import { TLLineTool } from '../app/statechart/TLLineTool/TLLineTool'
|
||||
import { TLNoteTool } from '../app/statechart/TLNoteTool/TLNoteTool'
|
||||
import { TLTextTool } from '../app/statechart/TLTextTool/TLTextTool'
|
||||
|
||||
/** @public */
|
||||
export const defaultTools: StateNodeConstructor[] = [
|
||||
TLHandTool,
|
||||
TLEraserTool,
|
||||
TLLaserTool,
|
||||
TLDrawTool,
|
||||
TLTextTool,
|
||||
TLLineTool,
|
||||
TLArrowTool,
|
||||
TLGeoTool,
|
||||
TLNoteTool,
|
||||
TLFrameTool,
|
||||
TLHighlightTool,
|
||||
]
|
|
@ -4,12 +4,14 @@ import { useApp } from './useApp'
|
|||
export function useCoarsePointer() {
|
||||
const app = useApp()
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia('(pointer: coarse)')
|
||||
const handler = () => {
|
||||
app.isCoarsePointer = mql.matches
|
||||
if (window.matchMedia) {
|
||||
const mql = window.matchMedia('(pointer: coarse)')
|
||||
const handler = () => {
|
||||
app.isCoarsePointer = mql.matches
|
||||
}
|
||||
handler()
|
||||
mql.addEventListener('change', handler)
|
||||
return () => mql.removeEventListener('change', handler)
|
||||
}
|
||||
handler()
|
||||
mql.addEventListener('change', handler)
|
||||
return () => mql.removeEventListener('change', handler)
|
||||
}, [app])
|
||||
}
|
||||
|
|
59
packages/editor/src/lib/hooks/useLocalStore.ts
Normal file
59
packages/editor/src/lib/hooks/useLocalStore.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { StoreOptions } from '../config/createTLStore'
|
||||
import { uniqueId } from '../utils/data'
|
||||
import { StoreWithStatus } from '../utils/sync/StoreWithStatus'
|
||||
import { TLLocalSyncClient } from '../utils/sync/TLLocalSyncClient'
|
||||
import { useTLStore } from './useTLStore'
|
||||
|
||||
/** @internal */
|
||||
export function useLocalStore(
|
||||
opts = {} as { persistenceKey?: string } & StoreOptions
|
||||
): StoreWithStatus {
|
||||
const { persistenceKey, ...rest } = opts
|
||||
|
||||
const [state, setState] = useState<{ id: string; storeWithStatus: StoreWithStatus } | null>(null)
|
||||
const store = useTLStore(rest)
|
||||
|
||||
useEffect(() => {
|
||||
const id = uniqueId()
|
||||
|
||||
if (!persistenceKey) {
|
||||
setState({
|
||||
id,
|
||||
storeWithStatus: { status: 'not-synced', store },
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setState({
|
||||
id,
|
||||
storeWithStatus: { status: 'loading' },
|
||||
})
|
||||
|
||||
const setStoreWithStatus = (storeWithStatus: StoreWithStatus) => {
|
||||
setState((prev) => {
|
||||
if (prev?.id === id) {
|
||||
return { id, storeWithStatus }
|
||||
}
|
||||
return prev
|
||||
})
|
||||
}
|
||||
|
||||
const client = new TLLocalSyncClient(store, {
|
||||
universalPersistenceKey: persistenceKey,
|
||||
onLoad() {
|
||||
setStoreWithStatus({ store, status: 'synced-local' })
|
||||
},
|
||||
onLoadError(err: any) {
|
||||
setStoreWithStatus({ status: 'error', error: err })
|
||||
},
|
||||
})
|
||||
|
||||
return () => {
|
||||
setState((prevState) => (prevState?.id === id ? null : prevState))
|
||||
client.close()
|
||||
}
|
||||
}, [persistenceKey, store])
|
||||
|
||||
return state?.storeWithStatus ?? { status: 'loading' }
|
||||
}
|
|
@ -14,7 +14,7 @@ const generateImage = (dpr: number, currentZoom: number, darkMode: boolean) => {
|
|||
canvasEl.height = size
|
||||
|
||||
const ctx = canvasEl.getContext('2d')
|
||||
if (!ctx) throw new Error('No canvas')
|
||||
if (!ctx) return
|
||||
|
||||
ctx.fillStyle = darkMode ? '#212529' : '#f8f9fa'
|
||||
ctx.fillRect(0, 0, size, size)
|
||||
|
@ -53,7 +53,9 @@ const canvasBlob = (size: [number, number], fn: (ctx: CanvasRenderingContext2D)
|
|||
const canvas = document.createElement('canvas')
|
||||
canvas.width = size[0]
|
||||
canvas.height = size[1]
|
||||
fn(canvas.getContext('2d')!)
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return ''
|
||||
fn(ctx)
|
||||
return canvas.toDataURL()
|
||||
}
|
||||
type PatternDef = { zoom: number; url: string; darkMode: boolean }
|
||||
|
|
10
packages/editor/src/lib/hooks/usePrevious.ts
Normal file
10
packages/editor/src/lib/hooks/usePrevious.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
|
||||
/** @internal */
|
||||
export function usePrevious<T>(value: T) {
|
||||
const ref = useRef(value)
|
||||
useEffect(() => {
|
||||
ref.current = value
|
||||
})
|
||||
return ref.current
|
||||
}
|
19
packages/editor/src/lib/hooks/useTLStore.ts
Normal file
19
packages/editor/src/lib/hooks/useTLStore.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { useState } from 'react'
|
||||
import { StoreOptions, createTLStore } from '../config/createTLStore'
|
||||
import { usePrevious } from './usePrevious'
|
||||
|
||||
/** @public */
|
||||
export function useTLStore(opts: StoreOptions) {
|
||||
const [store, setStore] = useState(() => createTLStore(opts))
|
||||
const previousOpts = usePrevious(opts)
|
||||
if (
|
||||
previousOpts.customShapes !== opts.customShapes ||
|
||||
previousOpts.initialData !== opts.initialData ||
|
||||
previousOpts.instanceId !== opts.instanceId
|
||||
) {
|
||||
const newStore = createTLStore(opts)
|
||||
setStore(newStore)
|
||||
return newStore
|
||||
}
|
||||
return store
|
||||
}
|
|
@ -26,7 +26,9 @@ import {
|
|||
TLWheelEventInfo,
|
||||
} from '../app/types/event-types'
|
||||
import { RequiredKeys } from '../app/types/misc-types'
|
||||
import { TldrawEditorConfig } from '../config/TldrawEditorConfig'
|
||||
import { createTLStore } from '../config/createTLStore'
|
||||
import { defaultShapes } from '../config/defaultShapes'
|
||||
import { defaultTools } from '../config/defaultTools'
|
||||
import { shapesFromJsx } from './jsx'
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
@ -56,12 +58,14 @@ export const TEST_INSTANCE_ID = InstanceRecordType.createCustomId('testInstance1
|
|||
export class TestApp extends App {
|
||||
constructor(options = {} as Partial<Omit<AppOptions, 'store'>>) {
|
||||
const elm = document.createElement('div')
|
||||
const { shapes = {}, tools = [] } = options
|
||||
elm.tabIndex = 0
|
||||
const config = options.config ?? new TldrawEditorConfig()
|
||||
super({
|
||||
config,
|
||||
store: config.createStore({
|
||||
shapes: { ...defaultShapes, ...shapes },
|
||||
tools: [...defaultTools, ...tools],
|
||||
store: createTLStore({
|
||||
instanceId: TEST_INSTANCE_ID,
|
||||
customShapes: shapes,
|
||||
}),
|
||||
getContainer: () => elm,
|
||||
...options,
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { render, screen } from '@testing-library/react'
|
||||
import { InstanceRecordType } from '@tldraw/tlschema'
|
||||
import { act, render, screen } from '@testing-library/react'
|
||||
import { InstanceRecordType, TLBaseShape, TLOpacityType } from '@tldraw/tlschema'
|
||||
import { TldrawEditor } from '../TldrawEditor'
|
||||
import { TldrawEditorConfig } from '../config/TldrawEditorConfig'
|
||||
import { App } from '../app/App'
|
||||
import { TLBoxUtil } from '../app/shapeutils/TLBoxUtil'
|
||||
import { TLBoxTool } from '../app/statechart/TLBoxTool/TLBoxTool'
|
||||
import { Canvas } from '../components/Canvas'
|
||||
import { HTMLContainer } from '../components/HTMLContainer'
|
||||
import { createTLStore } from '../config/createTLStore'
|
||||
|
||||
let originalFetch: typeof window.fetch
|
||||
beforeEach(() => {
|
||||
|
@ -9,7 +14,6 @@ beforeEach(() => {
|
|||
if (args[0] === '/icons/icon/icon-names.json') {
|
||||
return Promise.resolve({ json: () => Promise.resolve([]) } as Response)
|
||||
}
|
||||
|
||||
return originalFetch(...args)
|
||||
})
|
||||
})
|
||||
|
@ -19,43 +23,75 @@ afterEach(() => {
|
|||
window.fetch = originalFetch
|
||||
})
|
||||
|
||||
describe('<Tldraw />', () => {
|
||||
it('Accepts fresh versions of store and calls `onMount` for each one', async () => {
|
||||
const config = new TldrawEditorConfig()
|
||||
describe('<TldrawEditor />', () => {
|
||||
it('Renders without crashing', async () => {
|
||||
await act(async () => (
|
||||
<TldrawEditor autoFocus>
|
||||
<div data-testid="canvas-1" />
|
||||
</TldrawEditor>
|
||||
))
|
||||
})
|
||||
|
||||
const initialStore = config.createStore({
|
||||
it('Creates its own store', async () => {
|
||||
let store: any
|
||||
render(
|
||||
await act(async () => (
|
||||
<TldrawEditor onMount={(app) => (store = app.store)} autoFocus>
|
||||
<div data-testid="canvas-1" />
|
||||
</TldrawEditor>
|
||||
))
|
||||
)
|
||||
await screen.findByTestId('canvas-1')
|
||||
expect(store).toBeTruthy()
|
||||
})
|
||||
|
||||
it('Renders with an external store', async () => {
|
||||
const store = createTLStore()
|
||||
render(
|
||||
await act(async () => (
|
||||
<TldrawEditor
|
||||
store={store}
|
||||
onMount={(app) => {
|
||||
expect(app.store).toBe(store)
|
||||
}}
|
||||
autoFocus
|
||||
>
|
||||
<div data-testid="canvas-1" />
|
||||
</TldrawEditor>
|
||||
))
|
||||
)
|
||||
await screen.findByTestId('canvas-1')
|
||||
})
|
||||
|
||||
it('Accepts fresh versions of store and calls `onMount` for each one', async () => {
|
||||
const initialStore = createTLStore({
|
||||
instanceId: InstanceRecordType.createCustomId('test'),
|
||||
})
|
||||
|
||||
const onMount = jest.fn()
|
||||
|
||||
const rendered = render(
|
||||
<TldrawEditor config={config} store={initialStore} onMount={onMount} autoFocus>
|
||||
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
||||
<div data-testid="canvas-1" />
|
||||
</TldrawEditor>
|
||||
)
|
||||
await screen.findByTestId('canvas-1')
|
||||
expect(onMount).toHaveBeenCalledTimes(1)
|
||||
const initialApp = onMount.mock.lastCall[0]
|
||||
jest.spyOn(initialApp, 'dispose')
|
||||
expect(initialApp.store).toBe(initialStore)
|
||||
|
||||
// re-render with the same store:
|
||||
rendered.rerender(
|
||||
<TldrawEditor config={config} store={initialStore} onMount={onMount} autoFocus>
|
||||
<TldrawEditor store={initialStore} onMount={onMount} autoFocus>
|
||||
<div data-testid="canvas-2" />
|
||||
</TldrawEditor>
|
||||
)
|
||||
await screen.findByTestId('canvas-2')
|
||||
// not called again:
|
||||
expect(onMount).toHaveBeenCalledTimes(1)
|
||||
|
||||
// re-render with a new store:
|
||||
const newStore = config.createStore({
|
||||
const newStore = createTLStore({
|
||||
instanceId: InstanceRecordType.createCustomId('test'),
|
||||
})
|
||||
rendered.rerender(
|
||||
<TldrawEditor config={config} store={newStore} onMount={onMount} autoFocus>
|
||||
<TldrawEditor store={newStore} onMount={onMount} autoFocus>
|
||||
<div data-testid="canvas-3" />
|
||||
</TldrawEditor>
|
||||
)
|
||||
|
@ -64,4 +100,188 @@ describe('<Tldraw />', () => {
|
|||
expect(onMount).toHaveBeenCalledTimes(2)
|
||||
expect(onMount.mock.lastCall[0].store).toBe(newStore)
|
||||
})
|
||||
|
||||
it('Renders the canvas and shapes', async () => {
|
||||
let app = {} as App
|
||||
render(
|
||||
await act(async () => (
|
||||
<TldrawEditor
|
||||
autoFocus
|
||||
onMount={(editorApp) => {
|
||||
app = editorApp
|
||||
}}
|
||||
>
|
||||
<Canvas />
|
||||
<div data-testid="canvas-1" />
|
||||
</TldrawEditor>
|
||||
))
|
||||
)
|
||||
await screen.findByTestId('canvas-1')
|
||||
|
||||
expect(app).toBeTruthy()
|
||||
await act(async () => {
|
||||
app.updateInstanceState({ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } }, true, true)
|
||||
})
|
||||
|
||||
const id = app.createShapeId()
|
||||
|
||||
await act(async () => {
|
||||
app.createShapes([
|
||||
{
|
||||
id,
|
||||
type: 'geo',
|
||||
props: { w: 100, h: 100 },
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
// Does the shape exist?
|
||||
expect(app.getShapeById(id)).toMatchObject({
|
||||
id,
|
||||
type: 'geo',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: { geo: 'rectangle', w: 100, h: 100, opacity: '1' },
|
||||
})
|
||||
|
||||
// Is the shape's component rendering?
|
||||
expect(document.querySelectorAll('.tl-shape')).toHaveLength(1)
|
||||
|
||||
expect(document.querySelectorAll('.tl-shape-indicator')).toHaveLength(0)
|
||||
|
||||
// Select the shape
|
||||
await act(async () => app.select(id))
|
||||
|
||||
// Is the shape's component rendering?
|
||||
expect(document.querySelectorAll('.tl-shape-indicator')).toHaveLength(1)
|
||||
|
||||
// Select the eraser tool...
|
||||
await act(async () => app.setSelectedTool('eraser'))
|
||||
|
||||
// Is the editor's current tool correct?
|
||||
expect(app.currentToolId).toBe('eraser')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Custom shapes', () => {
|
||||
type CardShape = TLBaseShape<
|
||||
'card',
|
||||
{
|
||||
w: number
|
||||
h: number
|
||||
opacity: TLOpacityType
|
||||
}
|
||||
>
|
||||
|
||||
class CardUtil extends TLBoxUtil<CardShape> {
|
||||
static override type = 'card' as const
|
||||
|
||||
override isAspectRatioLocked = (_shape: CardShape) => false
|
||||
override canResize = (_shape: CardShape) => true
|
||||
override canBind = (_shape: CardShape) => true
|
||||
|
||||
override defaultProps(): CardShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 300,
|
||||
h: 300,
|
||||
}
|
||||
}
|
||||
|
||||
render(shape: CardShape) {
|
||||
const bounds = this.bounds(shape)
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
id={shape.id}
|
||||
data-testid="card-shape"
|
||||
style={{
|
||||
border: '1px solid black',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'all',
|
||||
}}
|
||||
>
|
||||
{bounds.w.toFixed()}x{bounds.h.toFixed()}
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: CardShape) {
|
||||
return <rect data-testid="card-indicator" width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
}
|
||||
|
||||
class CardTool extends TLBoxTool {
|
||||
static override id = 'card'
|
||||
static override initial = 'idle'
|
||||
override shapeType = 'card'
|
||||
}
|
||||
|
||||
const tools = [CardTool]
|
||||
const shapes = { card: { util: CardUtil } }
|
||||
|
||||
it('Uses custom shapes', async () => {
|
||||
let app = {} as App
|
||||
render(
|
||||
await act(async () => (
|
||||
<TldrawEditor
|
||||
shapes={shapes}
|
||||
tools={tools}
|
||||
autoFocus
|
||||
onMount={(editorApp) => {
|
||||
app = editorApp
|
||||
}}
|
||||
>
|
||||
<Canvas />
|
||||
<div data-testid="canvas-1" />
|
||||
</TldrawEditor>
|
||||
))
|
||||
)
|
||||
await screen.findByTestId('canvas-1')
|
||||
|
||||
expect(app).toBeTruthy()
|
||||
await act(async () => {
|
||||
app.updateInstanceState({ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } }, true, true)
|
||||
})
|
||||
|
||||
expect(app.shapeUtils.card).toBeTruthy()
|
||||
|
||||
const id = app.createShapeId()
|
||||
|
||||
await act(async () => {
|
||||
app.createShapes([
|
||||
{
|
||||
id,
|
||||
type: 'card',
|
||||
props: { w: 100, h: 100 },
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
// Does the shape exist?
|
||||
expect(app.getShapeById(id)).toMatchObject({
|
||||
id,
|
||||
type: 'card',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: { w: 100, h: 100, opacity: '1' },
|
||||
})
|
||||
|
||||
// Is the shape's component rendering?
|
||||
expect(await screen.findByTestId('card-shape')).toBeTruthy()
|
||||
|
||||
// Select the shape
|
||||
await act(async () => app.select(id))
|
||||
|
||||
// Is the shape's component rendering?
|
||||
expect(await screen.findByTestId('card-indicator')).toBeTruthy()
|
||||
|
||||
// Select the tool...
|
||||
await act(async () => app.setSelectedTool('card'))
|
||||
|
||||
// Is the editor's current tool correct?
|
||||
expect(app.currentToolId).toBe('card')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Box2d, Vec2d, VecLike } from '@tldraw/primitives'
|
|||
import { TLShapeId, TLShapePartial, Vec2dModel, createCustomShapeId } from '@tldraw/tlschema'
|
||||
import { GapsSnapLine, PointsSnapLine, SnapLine } from '../../app/managers/SnapManager'
|
||||
import { TLShapeUtil } from '../../app/shapeutils/TLShapeUtil'
|
||||
import { TldrawEditorConfig } from '../../config/TldrawEditorConfig'
|
||||
import { TestApp } from '../TestApp'
|
||||
|
||||
import { defaultShapes } from '../../config/defaultShapes'
|
||||
import { getSnapLines } from '../testutils/getSnapLines'
|
||||
|
||||
type __TopLeftSnapOnlyShape = any
|
||||
|
@ -40,14 +40,6 @@ class __TopLeftSnapOnlyShapeUtil extends TLShapeUtil<__TopLeftSnapOnlyShape> {
|
|||
}
|
||||
}
|
||||
|
||||
const configWithCustomShape = new TldrawEditorConfig({
|
||||
shapes: {
|
||||
__test_top_left_snap_only: {
|
||||
util: __TopLeftSnapOnlyShapeUtil,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
let app: TestApp
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -759,8 +751,12 @@ describe('custom snapping points', () => {
|
|||
beforeEach(() => {
|
||||
app?.dispose()
|
||||
app = new TestApp({
|
||||
config: configWithCustomShape,
|
||||
|
||||
shapes: {
|
||||
...defaultShapes,
|
||||
__test_top_left_snap_only: {
|
||||
util: __TopLeftSnapOnlyShapeUtil,
|
||||
},
|
||||
},
|
||||
// x───────┐
|
||||
// │ T │
|
||||
// │ │
|
||||
|
|
30
packages/editor/src/lib/utils/sync/StoreWithStatus.ts
Normal file
30
packages/editor/src/lib/utils/sync/StoreWithStatus.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { TLStore } from '@tldraw/tlschema'
|
||||
|
||||
/** @public */
|
||||
export type StoreWithStatus =
|
||||
| {
|
||||
readonly status: 'not-synced'
|
||||
readonly store: TLStore
|
||||
readonly error?: undefined
|
||||
}
|
||||
| {
|
||||
readonly status: 'error'
|
||||
readonly store?: undefined
|
||||
readonly error: Error
|
||||
}
|
||||
| {
|
||||
readonly status: 'loading'
|
||||
readonly store?: undefined
|
||||
readonly error?: undefined
|
||||
}
|
||||
| {
|
||||
readonly status: 'synced-local'
|
||||
readonly store: TLStore
|
||||
readonly error?: undefined
|
||||
}
|
||||
| {
|
||||
readonly status: 'synced-remote'
|
||||
readonly connectionStatus: 'online' | 'offline'
|
||||
readonly store: TLStore
|
||||
readonly error?: undefined
|
||||
}
|
|
@ -1,12 +1,8 @@
|
|||
import {
|
||||
InstanceRecordType,
|
||||
PageRecordType,
|
||||
TldrawEditorConfig,
|
||||
TLInstanceId,
|
||||
} from '@tldraw/editor'
|
||||
import { InstanceRecordType, PageRecordType, TLInstanceId } from '@tldraw/tlschema'
|
||||
import { promiseWithResolve } from '@tldraw/utils'
|
||||
import * as idb from './indexedDb'
|
||||
import { createTLStore } from '../../config/createTLStore'
|
||||
import { TLLocalSyncClient } from './TLLocalSyncClient'
|
||||
import * as idb from './indexedDb'
|
||||
|
||||
jest.mock('./indexedDb', () => ({
|
||||
...jest.requireActual('./indexedDb'),
|
||||
|
@ -31,7 +27,7 @@ function testClient(
|
|||
instanceId: TLInstanceId = InstanceRecordType.createCustomId('test'),
|
||||
channel = new BroadcastChannelMock('test')
|
||||
) {
|
||||
const store = new TldrawEditorConfig().createStore({
|
||||
const store = createTLStore({
|
||||
instanceId,
|
||||
})
|
||||
const onLoad = jest.fn(() => {
|
|
@ -1,4 +1,4 @@
|
|||
import { TLInstanceId, TLRecord, TLStore } from '@tldraw/editor'
|
||||
import { TLInstanceId, TLRecord, TLStore } from '@tldraw/tlschema'
|
||||
import { RecordsDiff, SerializedSchema, compareSchemas, squashRecordDiffs } from '@tldraw/tlstore'
|
||||
import { assert, hasOwnProperty } from '@tldraw/utils'
|
||||
import { transact } from 'signia'
|
|
@ -1,4 +1,4 @@
|
|||
import { TLRecord, TLStoreSchema } from '@tldraw/editor'
|
||||
import { TLRecord, TLStoreSchema } from '@tldraw/tlschema'
|
||||
import { RecordsDiff, SerializedSchema, StoreSnapshot } from '@tldraw/tlstore'
|
||||
import { IDBPDatabase, openDB } from 'idb'
|
||||
import { STORE_PREFIX, addDbName, getAllIndexDbNames } from './persistence-constants'
|
|
@ -1,4 +1,5 @@
|
|||
import { InstanceRecordType, TLInstanceId, uniqueId } from '@tldraw/editor'
|
||||
import { InstanceRecordType, TLInstanceId } from '@tldraw/tlschema'
|
||||
import { uniqueId } from '../data'
|
||||
|
||||
const tabIdKey = 'TLDRAW_TAB_ID_v2' as const
|
||||
|
|
@ -8,7 +8,6 @@ import { App } from '@tldraw/editor';
|
|||
import { MigrationFailureReason } from '@tldraw/tlstore';
|
||||
import { Result } from '@tldraw/utils';
|
||||
import { SerializedSchema } from '@tldraw/tlstore';
|
||||
import { TldrawEditorConfig } from '@tldraw/editor';
|
||||
import { TLInstanceId } from '@tldraw/editor';
|
||||
import { TLStore } from '@tldraw/editor';
|
||||
import { TLTranslationKey } from '@tldraw/ui';
|
||||
|
@ -22,8 +21,8 @@ export function isV1File(data: any): boolean;
|
|||
export function parseAndLoadDocument(app: App, document: string, msg: (id: TLTranslationKey) => string, addToast: ToastsContextType['addToast'], onV1FileLoad?: () => void, forceDarkMode?: boolean): Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function parseTldrawJsonFile({ config, json, instanceId, }: {
|
||||
config: TldrawEditorConfig;
|
||||
export function parseTldrawJsonFile({ json, instanceId, store, }: {
|
||||
store: TLStore;
|
||||
json: string;
|
||||
instanceId: TLInstanceId;
|
||||
}): Result<TLStore, TldrawFileParseError>;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {
|
||||
App,
|
||||
buildFromV1Document,
|
||||
createTLStore,
|
||||
fileToBase64,
|
||||
TLAsset,
|
||||
TldrawEditorConfig,
|
||||
TLInstanceId,
|
||||
TLRecord,
|
||||
TLStore,
|
||||
|
@ -81,11 +81,11 @@ export type TldrawFileParseError =
|
|||
|
||||
/** @public */
|
||||
export function parseTldrawJsonFile({
|
||||
config,
|
||||
json,
|
||||
instanceId,
|
||||
store,
|
||||
}: {
|
||||
config: TldrawEditorConfig
|
||||
store: TLStore
|
||||
json: string
|
||||
instanceId: TLInstanceId
|
||||
}): Result<TLStore, TldrawFileParseError> {
|
||||
|
@ -123,7 +123,7 @@ export function parseTldrawJsonFile({
|
|||
let migrationResult: MigrationResult<StoreSnapshot<TLRecord>>
|
||||
try {
|
||||
const storeSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r as TLRecord]))
|
||||
migrationResult = config.storeSchema.migrateStoreSnapshot(storeSnapshot, data.schema)
|
||||
migrationResult = store.schema.migrateStoreSnapshot(storeSnapshot, data.schema)
|
||||
} catch (e) {
|
||||
// junk data in the migration
|
||||
return Result.err({ type: 'invalidRecords', cause: e })
|
||||
|
@ -137,7 +137,12 @@ export function parseTldrawJsonFile({
|
|||
// we should be able to validate them. if any of the records at this stage
|
||||
// are invalid, we don't open the file
|
||||
try {
|
||||
return Result.ok(config.createStore({ initialData: migrationResult.value, instanceId }))
|
||||
return Result.ok(
|
||||
createTLStore({
|
||||
initialData: migrationResult.value,
|
||||
instanceId,
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
// junk data in the records (they're not validated yet!) could cause the
|
||||
// migrations to crash. We treat any throw from a migration as an
|
||||
|
@ -205,7 +210,7 @@ export async function parseAndLoadDocument(
|
|||
forceDarkMode?: boolean
|
||||
) {
|
||||
const parseFileResult = parseTldrawJsonFile({
|
||||
config: new TldrawEditorConfig(),
|
||||
store: createTLStore(),
|
||||
json: document,
|
||||
instanceId: app.instanceId,
|
||||
})
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { createCustomShapeId, InstanceRecordType, TldrawEditorConfig } from '@tldraw/editor'
|
||||
import { createCustomShapeId, createTLStore, InstanceRecordType, TLStore } from '@tldraw/editor'
|
||||
import { MigrationFailureReason, UnknownRecord } from '@tldraw/tlstore'
|
||||
import { assert } from '@tldraw/utils'
|
||||
import { parseTldrawJsonFile as _parseTldrawJsonFile, TldrawFile } from '../lib/file'
|
||||
|
||||
const parseTldrawJsonFile = (config: TldrawEditorConfig, json: string) =>
|
||||
const parseTldrawJsonFile = (store: TLStore, json: string) =>
|
||||
_parseTldrawJsonFile({
|
||||
config,
|
||||
store,
|
||||
json,
|
||||
instanceId: InstanceRecordType.createCustomId('instance'),
|
||||
})
|
||||
|
@ -16,26 +16,26 @@ function serialize(file: TldrawFile): string {
|
|||
|
||||
describe('parseTldrawJsonFile', () => {
|
||||
it('returns an error if the file is not json', () => {
|
||||
const result = parseTldrawJsonFile(new TldrawEditorConfig(), 'not json')
|
||||
const store = createTLStore()
|
||||
const result = parseTldrawJsonFile(store, 'not json')
|
||||
assert(!result.ok)
|
||||
expect(result.error.type).toBe('notATldrawFile')
|
||||
})
|
||||
|
||||
it("returns an error if the file doesn't look like a tldraw file", () => {
|
||||
const result = parseTldrawJsonFile(
|
||||
new TldrawEditorConfig(),
|
||||
JSON.stringify({ not: 'a tldraw file' })
|
||||
)
|
||||
const store = createTLStore()
|
||||
const result = parseTldrawJsonFile(store, JSON.stringify({ not: 'a tldraw file' }))
|
||||
assert(!result.ok)
|
||||
expect(result.error.type).toBe('notATldrawFile')
|
||||
})
|
||||
|
||||
it('returns an error if the file version is too old', () => {
|
||||
const store = createTLStore()
|
||||
const result = parseTldrawJsonFile(
|
||||
new TldrawEditorConfig(),
|
||||
store,
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 0,
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
schema: store.schema.serialize(),
|
||||
records: [],
|
||||
})
|
||||
)
|
||||
|
@ -44,11 +44,12 @@ describe('parseTldrawJsonFile', () => {
|
|||
})
|
||||
|
||||
it('returns an error if the file version is too new', () => {
|
||||
const store = createTLStore()
|
||||
const result = parseTldrawJsonFile(
|
||||
new TldrawEditorConfig(),
|
||||
store,
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 100,
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
schema: store.schema.serialize(),
|
||||
records: [],
|
||||
})
|
||||
)
|
||||
|
@ -57,10 +58,11 @@ describe('parseTldrawJsonFile', () => {
|
|||
})
|
||||
|
||||
it('returns an error if migrations fail', () => {
|
||||
const serializedSchema = new TldrawEditorConfig().storeSchema.serialize()
|
||||
const store = createTLStore()
|
||||
const serializedSchema = store.schema.serialize()
|
||||
serializedSchema.storeVersion = 100
|
||||
const result = parseTldrawJsonFile(
|
||||
new TldrawEditorConfig(),
|
||||
store,
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: serializedSchema,
|
||||
|
@ -71,10 +73,11 @@ describe('parseTldrawJsonFile', () => {
|
|||
assert(result.error.type === 'migrationFailed')
|
||||
expect(result.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld)
|
||||
|
||||
const serializedSchema2 = new TldrawEditorConfig().storeSchema.serialize()
|
||||
const store2 = createTLStore()
|
||||
const serializedSchema2 = store2.schema.serialize()
|
||||
serializedSchema2.recordVersions.shape.version = 100
|
||||
const result2 = parseTldrawJsonFile(
|
||||
new TldrawEditorConfig(),
|
||||
store2,
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: serializedSchema2,
|
||||
|
@ -88,11 +91,12 @@ describe('parseTldrawJsonFile', () => {
|
|||
})
|
||||
|
||||
it('returns an error if a record is invalid', () => {
|
||||
const store = createTLStore()
|
||||
const result = parseTldrawJsonFile(
|
||||
new TldrawEditorConfig(),
|
||||
store,
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
schema: store.schema.serialize(),
|
||||
records: [
|
||||
{
|
||||
typeName: 'shape',
|
||||
|
@ -103,19 +107,21 @@ describe('parseTldrawJsonFile', () => {
|
|||
],
|
||||
})
|
||||
)
|
||||
|
||||
assert(!result.ok)
|
||||
assert(result.error.type === 'invalidRecords')
|
||||
expect(result.error.cause).toMatchInlineSnapshot(
|
||||
`[ValidationError: At shape(id = shape:shape, type = geo).rotation: Expected number, got undefined]`
|
||||
`[ValidationError: At shape(id = shape:shape, type = geo).x: Expected number, got undefined]`
|
||||
)
|
||||
})
|
||||
|
||||
it('returns a store if the file is valid', () => {
|
||||
const store = createTLStore()
|
||||
const result = parseTldrawJsonFile(
|
||||
new TldrawEditorConfig(),
|
||||
store,
|
||||
serialize({
|
||||
tldrawFileFormatVersion: 1,
|
||||
schema: new TldrawEditorConfig().storeSchema.serialize(),
|
||||
schema: store.schema.serialize(),
|
||||
records: [],
|
||||
})
|
||||
)
|
||||
|
|
|
@ -5,18 +5,13 @@
|
|||
```ts
|
||||
|
||||
import { TldrawEditorProps } from '@tldraw/editor';
|
||||
import { TldrawUiContextProviderProps } from '@tldraw/ui';
|
||||
import { TldrawUiProps } from '@tldraw/ui';
|
||||
|
||||
// @public (undocumented)
|
||||
export function Tldraw(props: Omit<TldrawEditorProps, 'config' | 'store'> & TldrawUiContextProviderProps & {
|
||||
persistenceKey?: string;
|
||||
hideUi?: boolean;
|
||||
config?: TldrawEditorProps['config'];
|
||||
}): JSX.Element;
|
||||
export function Tldraw(props: TldrawEditorProps & TldrawUiProps): JSX.Element;
|
||||
|
||||
|
||||
export * from "@tldraw/editor";
|
||||
export * from "@tldraw/tlsync-client";
|
||||
export * from "@tldraw/ui";
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
"dependencies": {
|
||||
"@tldraw/editor": "workspace:*",
|
||||
"@tldraw/polyfills": "workspace:*",
|
||||
"@tldraw/tlsync-client": "workspace:*",
|
||||
"@tldraw/ui": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -4,7 +4,5 @@ import '@tldraw/polyfills'
|
|||
// eslint-disable-next-line local/no-export-star
|
||||
export * from '@tldraw/editor'
|
||||
// eslint-disable-next-line local/no-export-star
|
||||
export * from '@tldraw/tlsync-client'
|
||||
// eslint-disable-next-line local/no-export-star
|
||||
export * from '@tldraw/ui'
|
||||
export { Tldraw } from './lib/Tldraw'
|
||||
|
|
|
@ -1,38 +1,12 @@
|
|||
import { Canvas, TldrawEditor, TldrawEditorConfig, TldrawEditorProps } from '@tldraw/editor'
|
||||
import { DEFAULT_DOCUMENT_NAME, TAB_ID, useLocalSyncClient } from '@tldraw/tlsync-client'
|
||||
import { ContextMenu, TldrawUi, TldrawUiContextProviderProps } from '@tldraw/ui'
|
||||
import { useMemo } from 'react'
|
||||
import { Canvas, TldrawEditor, TldrawEditorProps } from '@tldraw/editor'
|
||||
import { ContextMenu, TldrawUi, TldrawUiProps } from '@tldraw/ui'
|
||||
|
||||
/** @public */
|
||||
export function Tldraw(
|
||||
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 {
|
||||
config,
|
||||
children,
|
||||
persistenceKey = DEFAULT_DOCUMENT_NAME,
|
||||
instanceId = TAB_ID,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
const _config = useMemo(() => config ?? new TldrawEditorConfig(), [config])
|
||||
|
||||
const syncedStore = useLocalSyncClient({
|
||||
instanceId,
|
||||
config: _config,
|
||||
universalPersistenceKey: persistenceKey,
|
||||
})
|
||||
export function Tldraw(props: TldrawEditorProps & TldrawUiProps) {
|
||||
const { children, ...rest } = props
|
||||
|
||||
return (
|
||||
<TldrawEditor {...rest} instanceId={instanceId} store={syncedStore} config={_config}>
|
||||
<TldrawEditor {...rest}>
|
||||
<TldrawUi {...rest}>
|
||||
<ContextMenu>
|
||||
<Canvas />
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
"noImplicitReturns": false,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"references": [{ "path": "../editor" }, { "path": "../tlsync-client" }, { "path": "../ui" }]
|
||||
"references": [{ "path": "../editor" }, { "path": "../ui" }]
|
||||
}
|
||||
|
|
|
@ -118,8 +118,8 @@ export function createShapeValidator<Type extends string, Props extends object>(
|
|||
}>;
|
||||
|
||||
// @public
|
||||
export function createTLSchema<T extends TLUnknownShape>(opts?: {
|
||||
customShapes?: { [K in T["type"]]: CustomShapeInfo<T>; } | undefined;
|
||||
export function createTLSchema(opts?: {
|
||||
customShapes: Record<string, SchemaShapeInfo>;
|
||||
}): StoreSchema<TLRecord, TLStoreProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -376,7 +376,7 @@ export const groupShapeTypeValidator: T.Validator<TLGroupShape>;
|
|||
export const handleTypeValidator: T.Validator<TLHandle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const highlightShapeMigrations: Migrations;
|
||||
export const highlightShapeTypeMigrations: Migrations;
|
||||
|
||||
// @public (undocumented)
|
||||
export const highlightShapeTypeValidator: T.Validator<TLHighlightShape>;
|
||||
|
@ -477,6 +477,14 @@ export const pointerTypeValidator: T.Validator<TLPointer>;
|
|||
// @internal (undocumented)
|
||||
export const rootShapeTypeMigrations: Migrations;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SchemaShapeInfo = {
|
||||
migrations?: Migrations;
|
||||
validator?: {
|
||||
validate: (record: any) => any;
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const scribbleTypeValidator: T.Validator<TLScribble>;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { InstancePageStateRecordType } from './records/TLInstancePageState'
|
|||
import { InstancePresenceRecordType } from './records/TLInstancePresence'
|
||||
import { PageRecordType } from './records/TLPage'
|
||||
import { PointerRecordType } from './records/TLPointer'
|
||||
import { TLShape, TLUnknownShape, rootShapeTypeMigrations } from './records/TLShape'
|
||||
import { TLShape, rootShapeTypeMigrations } from './records/TLShape'
|
||||
import { UserDocumentRecordType } from './records/TLUserDocument'
|
||||
import { storeMigrations } from './schema'
|
||||
import { arrowShapeTypeMigrations, arrowShapeTypeValidator } from './shapes/TLArrowShape'
|
||||
|
@ -20,92 +20,122 @@ import { embedShapeTypeMigrations, embedShapeTypeValidator } from './shapes/TLEm
|
|||
import { frameShapeTypeMigrations, frameShapeTypeValidator } from './shapes/TLFrameShape'
|
||||
import { geoShapeTypeMigrations, geoShapeTypeValidator } from './shapes/TLGeoShape'
|
||||
import { groupShapeTypeMigrations, groupShapeTypeValidator } from './shapes/TLGroupShape'
|
||||
import { highlightShapeMigrations, highlightShapeTypeValidator } from './shapes/TLHighlightShape'
|
||||
import {
|
||||
highlightShapeTypeMigrations,
|
||||
highlightShapeTypeValidator,
|
||||
} from './shapes/TLHighlightShape'
|
||||
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
|
||||
/** @public */
|
||||
export type SchemaShapeInfo = {
|
||||
migrations?: Migrations
|
||||
validator?: { validate: (record: any) => any }
|
||||
}
|
||||
|
||||
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 },
|
||||
highlight: { migrations: highlightShapeMigrations, validator: highlightShapeTypeValidator },
|
||||
}
|
||||
const coreShapes: Record<string, SchemaShapeInfo> = {
|
||||
group: {
|
||||
migrations: groupShapeTypeMigrations,
|
||||
validator: groupShapeTypeValidator,
|
||||
},
|
||||
bookmark: {
|
||||
migrations: bookmarkShapeTypeMigrations,
|
||||
validator: bookmarkShapeTypeValidator,
|
||||
},
|
||||
embed: {
|
||||
migrations: embedShapeTypeMigrations,
|
||||
validator: embedShapeTypeValidator,
|
||||
},
|
||||
image: {
|
||||
migrations: imageShapeTypeMigrations,
|
||||
validator: imageShapeTypeValidator,
|
||||
},
|
||||
text: {
|
||||
migrations: textShapeTypeMigrations,
|
||||
validator: textShapeTypeValidator,
|
||||
},
|
||||
video: {
|
||||
migrations: videoShapeTypeMigrations,
|
||||
validator: videoShapeTypeValidator,
|
||||
},
|
||||
}
|
||||
|
||||
type CustomShapeInfo<T extends TLUnknownShape> = {
|
||||
validator?: { validate: (record: T) => T }
|
||||
migrations?: Migrations
|
||||
const defaultShapes: Record<string, SchemaShapeInfo> = {
|
||||
arrow: {
|
||||
migrations: arrowShapeTypeMigrations,
|
||||
validator: arrowShapeTypeValidator,
|
||||
},
|
||||
draw: {
|
||||
migrations: drawShapeTypeMigrations,
|
||||
validator: drawShapeTypeValidator,
|
||||
},
|
||||
frame: {
|
||||
migrations: frameShapeTypeMigrations,
|
||||
validator: frameShapeTypeValidator,
|
||||
},
|
||||
geo: {
|
||||
migrations: geoShapeTypeMigrations,
|
||||
validator: geoShapeTypeValidator,
|
||||
},
|
||||
line: {
|
||||
migrations: lineShapeTypeMigrations,
|
||||
validator: lineShapeTypeValidator,
|
||||
},
|
||||
note: {
|
||||
migrations: noteShapeTypeMigrations,
|
||||
validator: noteShapeTypeValidator,
|
||||
},
|
||||
highlight: {
|
||||
migrations: highlightShapeTypeMigrations,
|
||||
validator: highlightShapeTypeValidator,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>(
|
||||
* Create a TLSchema with custom shapes. Custom shapes cannot override default shapes.
|
||||
*
|
||||
* @param opts - Options
|
||||
*
|
||||
* @public */
|
||||
export function createTLSchema(
|
||||
opts = {} as {
|
||||
customShapes?: { [K in T['type']]: CustomShapeInfo<T> }
|
||||
customShapes: Record<string, SchemaShapeInfo>
|
||||
}
|
||||
) {
|
||||
const { customShapes = {} } = opts
|
||||
const { customShapes } = 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 default 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({})])
|
||||
),
|
||||
for (const key in customShapes) {
|
||||
if (key in coreShapes) {
|
||||
throw Error(`Can't override default shape ${key}!`)
|
||||
}
|
||||
}
|
||||
|
||||
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 allShapeEntries = Object.entries({ ...coreShapes, ...defaultShapes, ...customShapes })
|
||||
|
||||
const shapeRecord = createRecordType<TLShape>('shape', {
|
||||
const ShapeRecordType = createRecordType<TLShape>('shape', {
|
||||
migrations: defineMigrations({
|
||||
currentVersion: rootShapeTypeMigrations.currentVersion,
|
||||
firstVersion: rootShapeTypeMigrations.firstVersion,
|
||||
migrators: rootShapeTypeMigrations.migrators,
|
||||
subTypeKey: 'type',
|
||||
subTypeMigrations: shapeSubTypeMigrationsWithCustomSubTypeMigrations,
|
||||
subTypeMigrations: {
|
||||
...Object.fromEntries(
|
||||
allShapeEntries.map(([k, v]) => [k, v.migrations ?? defineMigrations({})])
|
||||
),
|
||||
},
|
||||
}),
|
||||
validator: validatorWithCustomShapeValidators,
|
||||
scope: 'document',
|
||||
validator: T.model(
|
||||
'shape',
|
||||
T.union('type', {
|
||||
...Object.fromEntries(
|
||||
allShapeEntries.map(([k, v]) => [k, (v.validator as T.Validator<any>) ?? T.any])
|
||||
),
|
||||
})
|
||||
),
|
||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
|
||||
|
||||
return StoreSchema.create<TLRecord, TLStoreProps>(
|
||||
|
@ -116,7 +146,7 @@ export function createTLSchema<T extends TLUnknownShape>(
|
|||
instance: InstanceRecordType,
|
||||
instance_page_state: InstancePageStateRecordType,
|
||||
page: PageRecordType,
|
||||
shape: shapeRecord,
|
||||
shape: ShapeRecordType,
|
||||
user_document: UserDocumentRecordType,
|
||||
instance_presence: InstancePresenceRecordType,
|
||||
pointer: PointerRecordType,
|
||||
|
|
|
@ -24,7 +24,7 @@ export {
|
|||
} from './assets/TLVideoAsset'
|
||||
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
|
||||
export { createPresenceStateDerivation } from './createPresenceStateDerivation'
|
||||
export { createTLSchema } from './createTLSchema'
|
||||
export { createTLSchema, type SchemaShapeInfo } from './createTLSchema'
|
||||
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
|
||||
export { type Box2dModel, type Vec2dModel } from './geometry-types'
|
||||
export {
|
||||
|
@ -157,7 +157,7 @@ export {
|
|||
type TLGroupShapeProps,
|
||||
} from './shapes/TLGroupShape'
|
||||
export {
|
||||
highlightShapeMigrations,
|
||||
highlightShapeTypeMigrations,
|
||||
highlightShapeTypeValidator,
|
||||
type TLHighlightShape,
|
||||
type TLHighlightShapeProps,
|
||||
|
|
|
@ -18,7 +18,6 @@ export type TLHighlightShapeProps = {
|
|||
/** @public */
|
||||
export type TLHighlightShape = TLBaseShape<'highlight', TLHighlightShapeProps>
|
||||
|
||||
// --- VALIDATION ---
|
||||
/** @public */
|
||||
export const highlightShapeTypeValidator: T.Validator<TLHighlightShape> = createShapeValidator(
|
||||
'highlight',
|
||||
|
@ -32,6 +31,5 @@ export const highlightShapeTypeValidator: T.Validator<TLHighlightShape> = create
|
|||
})
|
||||
)
|
||||
|
||||
// --- MIGRATIONS ---
|
||||
/** @public */
|
||||
export const highlightShapeMigrations = defineMigrations({})
|
||||
export const highlightShapeTypeMigrations = defineMigrations({})
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
# v2.0.0-alpha.12 (Mon Apr 03 2023)
|
||||
|
||||
#### 🐛 Bug Fix
|
||||
|
||||
- Make sure all types and build stuff get run in CI [#1548](https://github.com/tldraw/tldraw-lite/pull/1548) ([@SomeHats](https://github.com/SomeHats))
|
||||
- add pre-commit api report generation [#1517](https://github.com/tldraw/tldraw-lite/pull/1517) ([@SomeHats](https://github.com/SomeHats))
|
||||
- [chore] restore api extractor [#1500](https://github.com/tldraw/tldraw-lite/pull/1500) ([@steveruizok](https://github.com/steveruizok))
|
||||
- Remove initial data parameter as it is not being used. [#1480](https://github.com/tldraw/tldraw-lite/pull/1480) ([@MitjaBezensek](https://github.com/MitjaBezensek))
|
||||
- David/publish good [#1488](https://github.com/tldraw/tldraw-lite/pull/1488) ([@ds300](https://github.com/ds300))
|
||||
- [chore] alpha 10 [#1486](https://github.com/tldraw/tldraw-lite/pull/1486) ([@ds300](https://github.com/ds300))
|
||||
- [chore] package build improvements [#1484](https://github.com/tldraw/tldraw-lite/pull/1484) ([@ds300](https://github.com/ds300))
|
||||
- [chore] bump for alpha 8 [#1485](https://github.com/tldraw/tldraw-lite/pull/1485) ([@steveruizok](https://github.com/steveruizok))
|
||||
- stop using broken-af turbo for publishing [#1476](https://github.com/tldraw/tldraw-lite/pull/1476) ([@ds300](https://github.com/ds300))
|
||||
- [chore] add canary release script [#1423](https://github.com/tldraw/tldraw-lite/pull/1423) ([@ds300](https://github.com/ds300) [@steveruizok](https://github.com/steveruizok))
|
||||
- [chore] upgrade yarn [#1430](https://github.com/tldraw/tldraw-lite/pull/1430) ([@ds300](https://github.com/ds300))
|
||||
- [update] docs [#1448](https://github.com/tldraw/tldraw-lite/pull/1448) ([@steveruizok](https://github.com/steveruizok))
|
||||
- [fix] dev version number for tldraw/tldraw [#1434](https://github.com/tldraw/tldraw-lite/pull/1434) ([@steveruizok](https://github.com/steveruizok))
|
||||
- repo cleanup [#1426](https://github.com/tldraw/tldraw-lite/pull/1426) ([@steveruizok](https://github.com/steveruizok))
|
||||
- Vscode extension [#1253](https://github.com/tldraw/tldraw-lite/pull/1253) ([@steveruizok](https://github.com/steveruizok) [@MitjaBezensek](https://github.com/MitjaBezensek) [@orangemug](https://github.com/orangemug))
|
||||
- Run all the tests. Fix linting for tests. [#1389](https://github.com/tldraw/tldraw-lite/pull/1389) ([@MitjaBezensek](https://github.com/MitjaBezensek))
|
||||
- add beta-redirect app [#1415](https://github.com/tldraw/tldraw-lite/pull/1415) ([@SomeHats](https://github.com/SomeHats))
|
||||
|
||||
#### Authors: 5
|
||||
|
||||
- alex ([@SomeHats](https://github.com/SomeHats))
|
||||
- David Sheldrick ([@ds300](https://github.com/ds300))
|
||||
- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
|
||||
- Orange Mug ([@orangemug](https://github.com/orangemug))
|
||||
- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
|
||||
|
||||
---
|
||||
|
||||
# @tldraw/tlsync-client
|
||||
|
||||
## 2.0.0-alpha.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fix some package build scripting
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.11
|
||||
- @tldraw/tlstore@2.0.0-alpha.11
|
||||
|
||||
## 2.0.0-alpha.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4b4399b6e: redeploy with yarn to prevent package version issues
|
||||
- Updated dependencies [4b4399b6e]
|
||||
- @tldraw/tlstore@2.0.0-alpha.10
|
||||
- @tldraw/editor@2.0.0-alpha.10
|
||||
|
||||
## 2.0.0-alpha.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Release day!
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.9
|
||||
- @tldraw/tlstore@2.0.0-alpha.9
|
||||
|
||||
## 2.0.0-alpha.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 23dd81cfe: Make signia a peer dependency
|
||||
- Updated dependencies [23dd81cfe]
|
||||
- @tldraw/editor@2.0.0-alpha.8
|
||||
- @tldraw/tlstore@2.0.0-alpha.8
|
||||
- @tldraw/tlsync@2.0.0-alpha.8
|
||||
|
||||
## 2.0.0-alpha.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Bug fixes.
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.7
|
||||
- @tldraw/tlstore@2.0.0-alpha.7
|
||||
- @tldraw/tlsync@2.0.0-alpha.7
|
||||
|
||||
## 2.0.0-alpha.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add licenses.
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.6
|
||||
- @tldraw/tlstore@2.0.0-alpha.6
|
||||
- @tldraw/tlsync@2.0.0-alpha.6
|
||||
|
||||
## 2.0.0-alpha.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add CSS files to tldraw/tldraw.
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.5
|
||||
- @tldraw/tlstore@2.0.0-alpha.5
|
||||
- @tldraw/tlsync@2.0.0-alpha.5
|
||||
|
||||
## 2.0.0-alpha.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add children to tldraw/tldraw
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.4
|
||||
- @tldraw/tlstore@2.0.0-alpha.4
|
||||
- @tldraw/tlsync@2.0.0-alpha.4
|
||||
|
||||
## 2.0.0-alpha.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Change permissions.
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.3
|
||||
- @tldraw/tlstore@2.0.0-alpha.3
|
||||
- @tldraw/tlsync@2.0.0-alpha.3
|
||||
|
||||
## 2.0.0-alpha.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add tldraw, editor
|
||||
- Updated dependencies
|
||||
- @tldraw/editor@2.0.0-alpha.2
|
||||
- @tldraw/tlstore@2.0.0-alpha.2
|
||||
- @tldraw/tlsync@2.0.0-alpha.2
|
||||
|
||||
## 0.1.0-alpha.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix stale reactors.
|
||||
- Updated dependencies
|
||||
- @tldraw/tldraw-beta@0.1.0-alpha.11
|
||||
- @tldraw/tlstore@0.1.0-alpha.11
|
||||
- @tldraw/tlsync@0.1.0-alpha.11
|
||||
|
||||
## 0.1.0-alpha.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix type export bug.
|
||||
- Updated dependencies
|
||||
- @tldraw/tldraw-beta@0.1.0-alpha.10
|
||||
- @tldraw/tlstore@0.1.0-alpha.10
|
||||
- @tldraw/tlsync@0.1.0-alpha.10
|
||||
|
||||
## 0.1.0-alpha.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix import bugs.
|
||||
- Updated dependencies
|
||||
- @tldraw/tldraw-beta@0.1.0-alpha.9
|
||||
- @tldraw/tlstore@0.1.0-alpha.9
|
||||
- @tldraw/tlsync@0.1.0-alpha.9
|
||||
|
||||
## 0.1.0-alpha.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Changes validation requirements, exports validation helpers.
|
||||
- Updated dependencies
|
||||
- @tldraw/tldraw-beta@0.1.0-alpha.8
|
||||
- @tldraw/tlstore@0.1.0-alpha.8
|
||||
- @tldraw/tlsync@0.1.0-alpha.8
|
||||
|
||||
## 0.1.0-alpha.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- - Pre-pre-release update
|
||||
- Updated dependencies
|
||||
- @tldraw/tldraw-beta@0.1.0-alpha.7
|
||||
- @tldraw/tlstore@0.1.0-alpha.7
|
||||
- @tldraw/tlsync@0.1.0-alpha.7
|
||||
|
||||
## 0.0.2-alpha.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix error with HMR
|
||||
- Updated dependencies
|
||||
- @tldraw/tldraw-beta@0.0.2-alpha.1
|
||||
- @tldraw/tlstore@0.0.2-alpha.1
|
||||
- @tldraw/tlsync@0.0.2-alpha.1
|
||||
|
||||
## 0.0.2-alpha.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Initial release
|
||||
- Updated dependencies
|
||||
- @tldraw/tldraw-beta@0.0.2-alpha.0
|
||||
- @tldraw/tlstore@0.0.2-alpha.0
|
||||
- @tldraw/tlsync@0.0.2-alpha.0
|
|
@ -1,190 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2023 tldraw GB Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,5 +0,0 @@
|
|||
# @tldraw/tlsync-client
|
||||
|
||||
## License
|
||||
|
||||
The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com).
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"extends": "../../config/api-extractor.json"
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
## API Report File for "@tldraw/tlsync-client"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { SyncedStore } from '@tldraw/editor';
|
||||
import { TldrawEditorConfig } from '@tldraw/editor';
|
||||
import { TLInstanceId } from '@tldraw/editor';
|
||||
|
||||
// @public (undocumented)
|
||||
export const DEFAULT_DOCUMENT_NAME: any;
|
||||
|
||||
// @public
|
||||
export function hardReset({ shouldReload }?: {
|
||||
shouldReload?: boolean | undefined;
|
||||
}): Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const STORE_PREFIX = "TLDRAW_DOCUMENT_v2";
|
||||
|
||||
// @public (undocumented)
|
||||
export const TAB_ID: TLInstanceId;
|
||||
|
||||
// @public
|
||||
export function useLocalSyncClient({ universalPersistenceKey, instanceId, config, }: {
|
||||
universalPersistenceKey: string;
|
||||
instanceId: TLInstanceId;
|
||||
config: TldrawEditorConfig;
|
||||
}): SyncedStore;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
|
@ -1,75 +0,0 @@
|
|||
{
|
||||
"name": "@tldraw/tlsync-client",
|
||||
"description": "A tiny little drawing app (multiplayer sync).",
|
||||
"version": "2.0.0-alpha.12",
|
||||
"packageManager": "yarn@3.5.0",
|
||||
"author": {
|
||||
"name": "tldraw GB Ltd.",
|
||||
"email": "hello@tldraw.com"
|
||||
},
|
||||
"homepage": "https://tldraw.dev",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tldraw/tldraw"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tldraw/tldraw/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"tldraw",
|
||||
"drawing",
|
||||
"app",
|
||||
"development",
|
||||
"whiteboard",
|
||||
"canvas",
|
||||
"infinite"
|
||||
],
|
||||
"/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./.tsbuild/index.d.ts",
|
||||
"/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here",
|
||||
"files": [],
|
||||
"scripts": {
|
||||
"test": "lazy inherit",
|
||||
"test-coverage": "lazy inherit",
|
||||
"build": "yarn run -T tsx ../../scripts/build-package.ts",
|
||||
"build-api": "yarn run -T tsx ../../scripts/build-api.ts",
|
||||
"prepack": "yarn run -T tsx ../../scripts/prepack.ts",
|
||||
"postpack": "../../scripts/postpack.sh",
|
||||
"pack-tarball": "yarn pack",
|
||||
"lint": "yarn run -T tsx ../../scripts/lint.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"lazyrepo": "0.0.0-alpha.26",
|
||||
"ws": "^8.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"react": "*"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "config/jest/node",
|
||||
"testEnvironment": "jsdom",
|
||||
"setupFiles": [
|
||||
"./setupJest.js"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^~(.*)": "<rootDir>/src/$1"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(nanoid|escape-string-regexp)/)"
|
||||
]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"signia": "*",
|
||||
"signia-react": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tldraw/editor": "workspace:*",
|
||||
"@tldraw/tlstore": "workspace:*",
|
||||
"@tldraw/utils": "workspace:*",
|
||||
"idb": "^7.1.0"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
window.crypto = {
|
||||
// required by nanoid
|
||||
// if we need more of the crypto apis, just add a proper mock here
|
||||
getRandomValues: function (array) {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
array[i] = Math.floor(Math.random() * 256)
|
||||
}
|
||||
return array
|
||||
},
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export { hardReset } from './lib/hardReset'
|
||||
export { useLocalSyncClient } from './lib/hooks/useLocalSyncClient'
|
||||
export { DEFAULT_DOCUMENT_NAME, STORE_PREFIX, TAB_ID } from './lib/persistence-constants'
|
|
@ -1,56 +0,0 @@
|
|||
import { SyncedStore, TldrawEditorConfig, TLInstanceId, uniqueId } from '@tldraw/editor'
|
||||
import { useEffect, useState } from 'react'
|
||||
import '../hardReset'
|
||||
import { TLLocalSyncClient } from '../TLLocalSyncClient'
|
||||
|
||||
/**
|
||||
* Use a client that persists to indexedDB and syncs to other stores with the same instance id, e.g. other tabs running the same instance of tldraw.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useLocalSyncClient({
|
||||
universalPersistenceKey,
|
||||
instanceId,
|
||||
config,
|
||||
}: {
|
||||
universalPersistenceKey: string
|
||||
instanceId: TLInstanceId
|
||||
config: TldrawEditorConfig
|
||||
}): SyncedStore {
|
||||
const [state, setState] = useState<{ id: string; syncedStore: SyncedStore } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const id = uniqueId()
|
||||
setState({
|
||||
id,
|
||||
syncedStore: { status: 'loading' },
|
||||
})
|
||||
const setSyncedStore = (syncedStore: SyncedStore) => {
|
||||
setState((prev) => {
|
||||
if (prev?.id === id) {
|
||||
return { id, syncedStore }
|
||||
}
|
||||
return prev
|
||||
})
|
||||
}
|
||||
|
||||
const store = config.createStore({ instanceId })
|
||||
|
||||
const client = new TLLocalSyncClient(store, {
|
||||
universalPersistenceKey,
|
||||
onLoad() {
|
||||
setSyncedStore({ status: 'synced', store })
|
||||
},
|
||||
onLoadError(err) {
|
||||
setSyncedStore({ status: 'error', error: err })
|
||||
},
|
||||
})
|
||||
|
||||
return () => {
|
||||
setState((prevState) => (prevState?.id === id ? null : prevState))
|
||||
client.close()
|
||||
}
|
||||
}, [instanceId, universalPersistenceKey, config])
|
||||
|
||||
return state?.syncedStore ?? { status: 'loading' }
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"include": ["src", "start.ts"],
|
||||
"exclude": ["node_modules", "dist", "docs", ".tsbuild*"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./.tsbuild",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"references": [{ "path": "../tlstore" }, { "path": "../editor" }]
|
||||
}
|
|
@ -621,10 +621,10 @@ export interface TLDialog {
|
|||
|
||||
// @public (undocumented)
|
||||
export const TldrawUi: React_2.NamedExoticComponent<{
|
||||
shareZone?: ReactNode;
|
||||
renderDebugMenuItems?: (() => React_2.ReactNode) | undefined;
|
||||
children?: ReactNode;
|
||||
hideUi?: boolean | undefined;
|
||||
shareZone?: ReactNode;
|
||||
renderDebugMenuItems?: (() => React_2.ReactNode) | undefined;
|
||||
} & TldrawUiContextProviderProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -667,6 +667,14 @@ export interface TldrawUiOverrides {
|
|||
translations?: TranslationProviderProps['overrides'];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TldrawUiProps = {
|
||||
children?: ReactNode;
|
||||
hideUi?: boolean;
|
||||
shareZone?: ReactNode;
|
||||
renderDebugMenuItems?: () => React_2.ReactNode;
|
||||
} & TldrawUiContextProviderProps;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLListedTranslation = {
|
||||
readonly locale: string;
|
||||
|
|
|
@ -55,7 +55,6 @@
|
|||
"@tldraw/editor": "workspace:*",
|
||||
"@tldraw/primitives": "workspace:*",
|
||||
"@tldraw/tlschema": "workspace:*",
|
||||
"@tldraw/tlsync-client": "workspace:*",
|
||||
"@tldraw/utils": "workspace:*",
|
||||
"browser-fs-access": "^0.31.0",
|
||||
"classnames": "^2.3.2",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as Dialog from './lib/components/primitives/Dialog'
|
||||
import * as DropdownMenu from './lib/components/primitives/DropdownMenu'
|
||||
|
||||
export { TldrawUi, TldrawUiContent } from './lib/TldrawUi'
|
||||
export { TldrawUi, TldrawUiContent, type TldrawUiProps } from './lib/TldrawUi'
|
||||
export {
|
||||
TldrawUiContextProvider,
|
||||
type TldrawUiContextProviderProps,
|
||||
|
|
|
@ -24,6 +24,17 @@ import { useNativeClipboardEvents } from './hooks/useClipboardEvents'
|
|||
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
|
||||
import { useTranslation } from './hooks/useTranslation/useTranslation'
|
||||
|
||||
/** @public */
|
||||
export type TldrawUiProps = {
|
||||
children?: ReactNode
|
||||
/** Whether to hide the interface and only display the canvas. */
|
||||
hideUi?: boolean
|
||||
/** A component to use for the share zone (will be deprecated) */
|
||||
shareZone?: ReactNode
|
||||
/** Additional items to add to the debug menu (will be deprecated)*/
|
||||
renderDebugMenuItems?: () => React.ReactNode
|
||||
} & TldrawUiContextProviderProps
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -33,13 +44,7 @@ export const TldrawUi = React.memo(function TldrawUi({
|
|||
children,
|
||||
hideUi,
|
||||
...rest
|
||||
}: {
|
||||
shareZone?: ReactNode
|
||||
renderDebugMenuItems?: () => React.ReactNode
|
||||
children?: ReactNode
|
||||
/** Whether to hide the interface and only display the canvas. */
|
||||
hideUi?: boolean
|
||||
} & TldrawUiContextProviderProps) {
|
||||
}: TldrawUiProps) {
|
||||
return (
|
||||
<TldrawUiContextProvider {...rest}>
|
||||
<TldrawUiInner
|
||||
|
@ -64,12 +69,6 @@ const TldrawUiInner = React.memo(function TldrawUiInner({
|
|||
hideUi,
|
||||
...rest
|
||||
}: TldrawUiContentProps & { children: ReactNode }) {
|
||||
// const isLoaded = usePreloadIcons()
|
||||
|
||||
// if (!isLoaded) {
|
||||
// return <LoadingScreen>Loading assets...</LoadingScreen>
|
||||
// }
|
||||
|
||||
// The hideUi prop should prevent the UI from mounting.
|
||||
// If we ever need want the UI to mount and preserve state, then
|
||||
// we should change this behavior and hide the UI via CSS instead.
|
||||
|
|
|
@ -8,10 +8,5 @@
|
|||
"noImplicitReturns": false,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../editor" },
|
||||
{ "path": "../primitives" },
|
||||
{ "path": "../tlsync-client" },
|
||||
{ "path": "../utils" }
|
||||
]
|
||||
"references": [{ "path": "../editor" }, { "path": "../primitives" }, { "path": "../utils" }]
|
||||
}
|
||||
|
|
|
@ -4297,7 +4297,6 @@ __metadata:
|
|||
"@tldraw/tldraw": "workspace:*"
|
||||
"@tldraw/tlschema": "workspace:*"
|
||||
"@tldraw/tlstore": "workspace:*"
|
||||
"@tldraw/tlsync-client": "workspace:*"
|
||||
"@tldraw/tlvalidate": "workspace:*"
|
||||
"@tldraw/ui": "workspace:*"
|
||||
"@tldraw/utils": "workspace:*"
|
||||
|
@ -4351,8 +4350,9 @@ __metadata:
|
|||
escape-string-regexp: ^5.0.0
|
||||
eventemitter3: ^4.0.7
|
||||
fake-indexeddb: ^4.0.0
|
||||
idb: ^7.1.1
|
||||
is-plain-object: ^5.0.0
|
||||
jest-canvas-mock: ^2.4.0
|
||||
jest-canvas-mock: ^2.5.1
|
||||
jest-environment-jsdom: ^29.4.3
|
||||
lazyrepo: 0.0.0-alpha.26
|
||||
lodash.throttle: ^4.1.1
|
||||
|
@ -4477,7 +4477,6 @@ __metadata:
|
|||
"@testing-library/react": ^14.0.0
|
||||
"@tldraw/editor": "workspace:*"
|
||||
"@tldraw/polyfills": "workspace:*"
|
||||
"@tldraw/tlsync-client": "workspace:*"
|
||||
"@tldraw/ui": "workspace:*"
|
||||
chokidar-cli: ^3.0.0
|
||||
jest-canvas-mock: ^2.4.0
|
||||
|
@ -4521,28 +4520,6 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@tldraw/tlsync-client@workspace:*, @tldraw/tlsync-client@workspace:packages/tlsync-client":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@tldraw/tlsync-client@workspace:packages/tlsync-client"
|
||||
dependencies:
|
||||
"@tldraw/editor": "workspace:*"
|
||||
"@tldraw/tlstore": "workspace:*"
|
||||
"@tldraw/utils": "workspace:*"
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
idb: ^7.1.0
|
||||
lazyrepo: 0.0.0-alpha.26
|
||||
react: "*"
|
||||
ws: ^8.10.0
|
||||
peerDependencies:
|
||||
signia: "*"
|
||||
signia-react: "*"
|
||||
dependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@tldraw/tlvalidate@workspace:*, @tldraw/tlvalidate@workspace:packages/tlvalidate":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@tldraw/tlvalidate@workspace:packages/tlvalidate"
|
||||
|
@ -4570,7 +4547,6 @@ __metadata:
|
|||
"@tldraw/editor": "workspace:*"
|
||||
"@tldraw/primitives": "workspace:*"
|
||||
"@tldraw/tlschema": "workspace:*"
|
||||
"@tldraw/tlsync-client": "workspace:*"
|
||||
"@tldraw/utils": "workspace:*"
|
||||
"@types/lz-string": ^1.3.34
|
||||
browser-fs-access: ^0.31.0
|
||||
|
@ -4606,7 +4582,6 @@ __metadata:
|
|||
"@tldraw/editor": "workspace:*"
|
||||
"@tldraw/file-format": "workspace:*"
|
||||
"@tldraw/tldraw": "workspace:*"
|
||||
"@tldraw/tlsync-client": "workspace:*"
|
||||
"@tldraw/ui": "workspace:*"
|
||||
"@tldraw/utils": "workspace:*"
|
||||
"@types/fs-extra": ^11.0.1
|
||||
|
@ -5149,15 +5124,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-dom@npm:*, @types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.0.6":
|
||||
version: 18.0.11
|
||||
resolution: "@types/react-dom@npm:18.0.11"
|
||||
dependencies:
|
||||
"@types/react": "*"
|
||||
checksum: 579691e4d5ec09688087568037c35edf8cfb1ab3e07f6c60029280733ee7b5c06d66df6fcc90786702c93ac8cb13bc7ff16c79ddfc75d082938fbaa36e1cdbf4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-dom@npm:<18.0.0":
|
||||
version: 17.0.19
|
||||
resolution: "@types/react-dom@npm:17.0.19"
|
||||
|
@ -5167,6 +5133,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.0.6":
|
||||
version: 18.0.11
|
||||
resolution: "@types/react-dom@npm:18.0.11"
|
||||
dependencies:
|
||||
"@types/react": "*"
|
||||
checksum: 579691e4d5ec09688087568037c35edf8cfb1ab3e07f6c60029280733ee7b5c06d66df6fcc90786702c93ac8cb13bc7ff16c79ddfc75d082938fbaa36e1cdbf4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-router-dom@npm:^5.1.8":
|
||||
version: 5.3.3
|
||||
resolution: "@types/react-router-dom@npm:5.3.3"
|
||||
|
@ -10588,7 +10563,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"idb@npm:^7.1.0, idb@npm:^7.1.1":
|
||||
"idb@npm:^7.1.1":
|
||||
version: 7.1.1
|
||||
resolution: "idb@npm:7.1.1"
|
||||
checksum: 1973c28d53c784b177bdef9f527ec89ec239ec7cf5fcbd987dae75a16c03f5b7dfcc8c6d3285716fd0309dd57739805390bd9f98ce23b1b7d8849a3b52de8d56
|
||||
|
@ -11316,6 +11291,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jest-canvas-mock@npm:^2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "jest-canvas-mock@npm:2.5.1"
|
||||
dependencies:
|
||||
cssfontparser: ^1.2.1
|
||||
moo-color: ^1.0.2
|
||||
checksum: b8ff56c1b7b7feb6d33b7914dbfac21f19a5a33db0bc092f0426e500e80e67df1286bf817eb780e378b648c9130d7b8ca20cd46e45520657996273a948a7c198
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jest-changed-files@npm:^28.1.3":
|
||||
version: 28.1.3
|
||||
resolution: "jest-changed-files@npm:28.1.3"
|
||||
|
@ -15392,7 +15377,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:*, react@npm:18.2.0, react@npm:^18.2.0":
|
||||
"react@npm:18.2.0, react@npm:^18.2.0":
|
||||
version: 18.2.0
|
||||
resolution: "react@npm:18.2.0"
|
||||
dependencies:
|
||||
|
@ -18357,9 +18342,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"which-module@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "which-module@npm:2.0.1"
|
||||
checksum: 1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be
|
||||
version: 2.0.0
|
||||
resolution: "which-module@npm:2.0.0"
|
||||
checksum: 809f7fd3dfcb2cdbe0180b60d68100c88785084f8f9492b0998c051d7a8efe56784492609d3f09ac161635b78ea29219eb1418a98c15ce87d085bce905705c9c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -18476,7 +18461,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.10.0, ws@npm:^8.11.0, ws@npm:^8.2.3":
|
||||
"ws@npm:^8.11.0, ws@npm:^8.2.3":
|
||||
version: 8.13.0
|
||||
resolution: "ws@npm:8.13.0"
|
||||
peerDependencies:
|
||||
|
|
Loading…
Reference in a new issue