[improvement] examples (#264)

* Example project, fix bugs in readonly mode

* Adds ui options
This commit is contained in:
Steve Ruiz 2021-11-11 11:37:57 +00:00 committed by GitHub
parent 2441ca0c90
commit fa38c0ef0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1566 additions and 731 deletions

180
README.md
View file

@ -98,29 +98,51 @@ Internally, the `TLDraw` component's user interface uses this API to make change
The `TLDraw` React component is the [tldraw](https://tldraw.com) editor exported as a standalone component. You can control the editor through props, or through the `TLDrawState`'s imperative API. **All props are optional.**
| Prop | Type | Description |
| --------------- | ---------------- | -------------------------------------------------------------------------------------------- |
| `id` | `string` | An id under which to persist the component's state. |
| `document` | `TLDrawDocument` | An initial [`TLDrawDocument`](#tldrawdocument) object. |
| `currentPageId` | `string` | A current page id, referencing the `TLDrawDocument` object provided via the `document` prop. |
| `onMount` | `Function` | Called when the editor first mounts, receiving the current `TLDrawState`. |
| `onPatch` | `Function` | Called when the state is updated via a patch. |
| `onCommand` | `Function` | Called when the state is updated via a command. |
| `onPersist` | `Function` | Called when the state is persisted after an action. |
| `onChange` | `Function` | Called when the `TLDrawState` updates for any reason. |
| `onUndo` | `Function` | Called when the `TLDrawState` updates after an undo. |
| `onRedo` | `Function` | Called when the `TLDrawState` updates after a redo. |
| `onUserChange` | `Function` | Called when the user's "presence" information changes. |
| `autofocus` | `boolean` | Whether the editor should immediately receive focus. Defaults to true. |
| `showMenu` | `boolean` | Whether to show the menu. |
| `showPages` | `boolean` | Whether to show the pages menu. |
| `showStyles` | `boolean` | Whether to show the styles menu. |
| `showTools` | `boolean` | Whether to show the tools. |
| `showUI` | `boolean` | Whether to show any UI other than the canvas. |
| Prop | Type | Description |
| ----------------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
| `id` | `string` | An id under which to persist the component's state. |
| `document` | `TLDrawDocument` | An initial [`TLDrawDocument`](#tldrawdocument) object. |
| `currentPageId` | `string` | A current page id, referencing the `TLDrawDocument` object provided via the `document` prop. |
| `autofocus` | `boolean` | Whether the editor should immediately receive focus. Defaults to true. |
| `showMenu` | `boolean` | Whether to show the menu. |
| `showPages` | `boolean` | Whether to show the pages menu. |
| `showStyles` | `boolean` | Whether to show the styles menu. |
| `showTools` | `boolean` | Whether to show the tools. |
| `showUI` | `boolean` | Whether to show any UI other than the canvas. |
| `onMount` | `Function` | Called when the editor first mounts, receiving the current `TLDrawState`. |
| `onPatch` | `Function` | Called when the state is updated via a patch. |
| `onCommand` | `Function` | Called when the state is updated via a command. |
| `onPersist` | `Function` | Called when the state is persisted after an action. |
| `onChange` | `Function` | Called when the `TLDrawState` updates for any reason. |
| `onUserChange` | `Function` | Called when the user's "presence" information changes. |
| `onUndo` | `Function` | Called when the `TLDrawState` updates after an undo. |
| `onRedo` | `Function` | Called when the `TLDrawState` updates after a redo. |
| `onSignIn` | `Function` | Called when the user selects Sign In from the menu. |
| `onSignOut` | `Function` | Called when the user selects Sign Out from the menu. |
| `onNewProject` | `Function` | Called when the user when the user creates a new project through the menu or through a keyboard shortcut. |
| `onSaveProject` | `Function` | Called when the user saves a project through the menu or through a keyboard shortcut. |
| `onSaveProjectAs` | `Function` | Called when the user saves a project as a new project through the menu or through a keyboard shortcut. |
| `onOpenProject` | `Function` | Called when the user opens new project through the menu or through a keyboard shortcut. |
> **Note**: For help with the file-related callbacks, see `useFileSystem`.
### `useFileSystem`
You can use the `useFileSystem` hook to get prepared callbacks for `onNewProject`, `onOpenProject`, `onSaveProject`, and `onSaveProjectAs`. These callbacks allow a user to save files via the [FileSystem](https://developer.mozilla.org/en-US/docs/Web/API/FileSystem) API.
```ts
import { TLDraw, useFileSystem } from '@tldraw/tldraw'
function App() {
const fileSystemEvents = useFileSystem()
return <TLDraw {...fileSystemEvents} />
}
```
### `TLDrawDocument`
A `TLDrawDocument` is an object with three properties:
You can initialize or control the `<TLDraw>` component via its `document` property. A `TLDrawDocument` is an object with three properties:
- `id` - A unique ID for this document
- `pages` - A table of `TLDrawPage` objects
@ -130,7 +152,7 @@ A `TLDrawDocument` is an object with three properties:
```ts
import { TLDrawDocument, TLDrawState } from '@tldraw/tldraw'
const tldocument: TLDrawDocument = {
const myDocument: TLDrawDocument = {
id: 'doc',
version: TLDrawState.version,
pages: {
@ -152,9 +174,13 @@ const tldocument: TLDrawDocument = {
},
},
}
function App() {
return <TLDraw document={myDocument} />
}
```
**Tip:** TLDraw is built on [@tldraw/core](https://github.com/tldraw/core). The pages and pageStates in TLDraw are just objects containing `TLPage` and `TLPageState` objects from the core library. For more about these types, check out the [@tldraw/core](https://github.com/tldraw/core) documentation.
**Tip:** TLDraw is built on [@tldraw/core](https://github.com/tldraw/core). The pages and pageStates in TLDraw are objects containing `TLPage` and `TLPageState` objects from the core library. For more about these types, check out the [@tldraw/core](https://github.com/tldraw/core) documentation.
**Important:** In the `pages` object, each `TLPage` object must be keyed under its `id` property. Likewise, each `TLPageState` object must be keyed under its `id`. In addition, each `TLPageState` object must have an `id` that matches its corresponding page.
@ -179,7 +205,7 @@ Your `TLPage` objects may include shapes: objects that fit one of the `TLDrawSha
| `isGenerated` | `boolean` | (optional) True if the shape is generated. |
| `isAspectRatioLocked` | `boolean` | (optional) True if the shape's aspect ratio is locked. |
> **Important:** In order for re-ordering to work correctly, a shape's `childIndex` values _must_ start from 1, not 0. The page or parent shape's "bottom-most" child should have a `childIndex` of 1.
> **Important:** In order for re-ordering to work, a shape's `childIndex` values _must_ start from 1, not 0. The page or parent shape's "bottom-most" child should have a `childIndex` of 1.
The `ShapeStyle` object is a common style API for all shapes.
@ -268,71 +294,81 @@ function App() {
}
```
The `TLDrawState` API is too large to document here. To view documentation for the API, build the documentation by running `yarn docs` from the root folder and open the file at `/packages/tldraw/docs/classes/TLDrawState.html` in your browser.
To view the full documentation of the `TLDrawState` API, generate the project's documentation by running `yarn docs` from the root folder, then open the file at:
```
/packages/tldraw/docs/classes/TLDrawState.html
```
Here are some useful methods:
| Method | Description |
| ----------------- | ----------- |
| `loadDocument` | |
| `select` | |
| `selectAll` | |
| `selectNone` | |
| `delete` | |
| `deleteAll` | |
| `deletePage` | |
| `changePage` | |
| `cut` | |
| `copy` | |
| `paste` | |
| `copyJson` | |
| `copySvg` | |
| `undo` | |
| `redo` | |
| `zoomIn` | |
| `zoomOut` | |
| `zoomToContent` | |
| `zoomToSelection` | |
| `zoomToFit` | |
| `zoomTo` | |
| `resetZoom` | |
| `setCamera` | |
| `resetCamera` | |
| `align` | |
| `distribute` | |
| `stretch` | |
| `nudge` | |
| `duplicate` | |
| `flipHorizontal` | |
| `flipVertical` | |
| `rotate` | |
| `style` | |
| `group` | |
| `ungroup` | |
| `createShapes` | |
| `updateShapes` | |
| `updateDocument` | |
| `updateUsers` | |
| `removeUser` | |
| `setSetting` | |
| `selectTool` | |
| `cancel` | |
- `loadDocument`
- `select`
- `selectAll`
- `selectNone`
- `delete`
- `deleteAll`
- `deletePage`
- `changePage`
- `cut`
- `copy`
- `paste`
- `copyJson`
- `copySvg`
- `undo`
- `redo`
- `zoomIn`
- `zoomOut`
- `zoomToContent`
- `zoomToSelection`
- `zoomToFit`
- `zoomTo`
- `resetZoom`
- `setCamera`
- `resetCamera`
- `align`
- `distribute`
- `stretch`
- `nudge`
- `duplicate`
- `flipHorizontal`
- `flipVertical`
- `rotate`
- `style`
- `group`
- `ungroup`
- `createShapes`
- `updateShapes`
- `updateDocument`
- `updateUsers`
- `removeUser`
- `setSetting`
- `selectTool`
- `cancel`
Check the generated docs, source or the TypeScript types for more on these and other methods.
## Local Development
From the root folder:
- Run `yarn` to install dependencies.
- Run `yarn start` to start the development server for the package and for the example.
- Open `localhost:5420` to view the example project.
**Note:** The multiplayer examples and endpoints currently require an API key from [Liveblocks](https://liveblocks.io/), however the storage services that are used in TLDraw are currently in alpha and (as of November 2021) not accessible to the general public. You won't be able to authenticate and run these parts of the project.
Other scripts:
- Run `yarn test` to execute unit tests via [Jest](https://jestjs.io).
- Run `yarn docs` to build the docs via [ts-doc](https://typedoc.org/).
## Example
See the `example` folder.
See the `example` folder for examples of how to use the `<TLDraw/>` component.
## Community
@ -346,7 +382,9 @@ Want to connect with other devs? Visit the [Discord channel](https://discord.gg/
### License
This project is licensed under MIT. If you're using the library in a commercial product, please consider [becoming a sponsor](https://github.com/sponsors/steveruizok?frequency=recurring&sponsor=steveruizok).
This project is licensed under MIT.
If you're using the library in a commercial product, please consider [becoming a sponsor](https://github.com/sponsors/steveruizok?frequency=recurring&sponsor=steveruizok).
## Author

View file

@ -1,19 +1,13 @@
/* eslint-disable */
import fs from 'fs'
import path from 'path'
import esbuild from 'esbuild'
import dotenv from 'dotenv'
import { createRequire } from 'module'
const pkg = createRequire(import.meta.url)('../package.json')
async function main() {
if (fs.existsSync('./dist')) {
fs.rmSync('./dist', { recursive: true }, (e) => {
if (e) {
throw e
}
})
}
try {
esbuild.buildSync({
entryPoints: ['./src/index.tsx'],
@ -27,15 +21,17 @@ async function main() {
tsconfig: './tsconfig.json',
define: {
'process.env.NODE_ENV': '"production"',
'process.env.LIVEBLOCKS_PUBLIC_API_KEY': `"${process.env.LIVEBLOCKS_PUBLIC_API_KEY}"`,
},
metafile: false,
sourcemap: false,
})
fs.copyFile('./src/index.html', './dist/index.html', (err) => {
if (err) throw err
})
fs.readdirSync('./src/public').forEach((file) =>
fs.copyFile(path.join('./src/public', file), path.join('./dist', file), (err) => {
if (err) throw err
})
)
console.log(`${pkg.name}: Build completed.`)
} catch (e) {
console.log(`× ${pkg.name}: Build failed due to an error.`)

View file

@ -1,18 +1,27 @@
/* eslint-disable no-undef */
import fs from 'fs'
import path from 'path'
import esbuildServe from 'esbuild-serve'
import dotenv from 'dotenv'
dotenv.config()
async function main() {
if (!fs.existsSync('./dist')) {
fs.mkdirSync('./dist')
if (fs.existsSync('./dist')) {
fs.rmSync('./dist', { recursive: true }, (e) => {
if (e) {
throw e
}
})
}
fs.copyFile('./src/index.html', './dist/index.html', (err) => {
if (err) throw err
})
fs.mkdirSync('./dist')
fs.readdirSync('./src/public').forEach((file) =>
fs.copyFile(path.join('./src/public', file), path.join('./dist', file), (err) => {
if (err) throw err
})
)
try {
await esbuildServe(

30
example/src/api.tsx Normal file
View file

@ -0,0 +1,30 @@
import * as React from 'react'
import { TLDraw, TLDrawState, TLDrawShapeType, ColorStyle } from '@tldraw/tldraw'
export default function Api(): JSX.Element {
const rTLDrawState = React.useRef<TLDrawState>()
const handleMount = React.useCallback((state: TLDrawState) => {
rTLDrawState.current = state
state
.createShapes({
id: 'rect1',
type: TLDrawShapeType.Rectangle,
point: [100, 100],
size: [200, 200],
})
.selectAll()
.nudge([1, 1], true)
.duplicate()
.select('rect1')
.style({ color: ColorStyle.Blue })
.selectNone()
}, [])
return (
<div className="tldraw">
<TLDraw onMount={handleMount} />
</div>
)
}

View file

@ -2,29 +2,54 @@ import * as React from 'react'
import { Switch, Route, Link } from 'react-router-dom'
import Basic from './basic'
import ReadOnly from './readonly'
import Controlled from './controlled'
import Imperative from './imperative'
import PropsControl from './props-control'
import ApiControl from './api-control'
import LoadingFiles from './loading-files'
import Embedded from './embedded'
import NoSizeEmbedded from './no-size-embedded'
import { Multiplayer } from './multiplayer'
import ChangingId from './changing-id'
import Persisted from './persisted'
import Develop from './develop'
import Api from './api'
import FileSystem from './file-system'
import UIOptions from './ui-options'
import { Multiplayer } from './multiplayer'
import './styles.css'
export default function App(): JSX.Element {
return (
<main>
<img className="hero" src="./card-repo.png" />
<Switch>
<Route path="/basic">
<Develop />
</Route>
<Route path="/basic">
<Basic />
</Route>
<Route path="/ui-options">
<UIOptions />
</Route>
<Route path="/persisted">
<Persisted />
</Route>
<Route path="/loading-files">
<LoadingFiles />
</Route>
<Route path="/file-system">
<FileSystem />
</Route>
<Route path="/api">
<Api />
</Route>
<Route path="/readonly">
<ReadOnly />
</Route>
<Route path="/controlled">
<Controlled />
<PropsControl />
</Route>
<Route path="/imperative">
<Imperative />
<ApiControl />
</Route>
<Route path="/changing-id">
<ChangingId />
@ -39,30 +64,52 @@ export default function App(): JSX.Element {
<Multiplayer />
</Route>
<Route path="/">
<ul>
<ul className="links">
<li>
<Link to="/basic">basic</Link>
<Link to="/basic">Develop</Link>
</li>
<hr />
<li>
<Link to="/basic">Basic</Link>
</li>
<li>
<Link to="/readonly">readonly</Link>
<Link to="/ui-options">UI Options</Link>
</li>
<li>
<Link to="/controlled">controlled</Link>
<Link to="/persisted">Persisting State with an ID</Link>
</li>
<li>
<Link to="/imperative">imperative</Link>
<Link to="/file-system">Using the File System</Link>
</li>
<li>
<Link to="/changing-id">changing id</Link>
<Link to="/readonly">Readonly Mode</Link>
</li>
<li>
<Link to="/embedded">embedded</Link>
<Link to="/loading-files">Loading Files</Link>
</li>
<li>
<Link to="/no-size-embedded">embedded (no size)</Link>
<Link to="/file-system">Using the File System</Link>
</li>
<li>
<Link to="/multiplayer">multiplayer</Link>
<Link to="/controlled">Controlled via Props</Link>
</li>
<li>
<Link to="/api">Using the TLDrawState API</Link>
</li>
<li>
<Link to="/imperative">Controlled via TLDrawState API</Link>
</li>
<li>
<Link to="/changing-id">Changing ID</Link>
</li>
<li>
<Link to="/embedded">Embedded</Link>
</li>
<li>
<Link to="/no-size-embedded">Embedded (without explicit size)</Link>
</li>
<li>
<Link to="/multiplayer">Multiplayer</Link>
</li>
</ul>
</Route>

View file

@ -1,561 +0,0 @@
{
"id": "doc",
"pages": {
"page": {
"id": "page",
"name": "Page 1",
"childIndex": 1,
"shapes": {
"58343232-2843-48ff-214c-f801ba274f24": {
"id": "58343232-2843-48ff-214c-f801ba274f24",
"type": "rectangle",
"name": "Rectangle",
"parentId": "page",
"childIndex": 1,
"point": [
653.82,
158.58
],
"size": [
146.33,
213.43
],
"rotation": 0,
"style": {
"color": "Green",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"a755e702-548c-4ae3-0f62-307ced48d060": {
"id": "a755e702-548c-4ae3-0f62-307ced48d060",
"type": "ellipse",
"name": "Ellipse",
"parentId": "page",
"childIndex": 2,
"point": [
316.64,
164.11
],
"radius": [
73.7000000000001,
74.86500000000002
],
"rotation": 0,
"style": {
"color": "Blue",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"2ac5ddca-0fdd-4744-3c5b-dc63d9804993": {
"id": "2ac5ddca-0fdd-4744-3c5b-dc63d9804993",
"type": "rectangle",
"name": "Rectangle",
"parentId": "page",
"childIndex": 2,
"point": [
495.88,
466.31
],
"size": [
149.87,
134.37
],
"rotation": 0,
"style": {
"color": "Red",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"8f7c6768-8124-427f-0795-5b11b8e55cb3": {
"id": "8f7c6768-8124-427f-0795-5b11b8e55cb3",
"type": "arrow",
"name": "Arrow",
"parentId": "page",
"childIndex": 3,
"point": [
663.96,
388.01
],
"rotation": 0,
"bend": 0,
"handles": {
"start": {
"id": "start",
"index": 0,
"point": [
0,
66.018
],
"canBind": true,
"bindingId": "37f1f650-7368-41df-2489-b980efab5b7d"
},
"end": {
"id": "end",
"index": 1,
"point": [
37.531,
0
],
"canBind": true,
"bindingId": "284b2bbb-92eb-4f2b-255d-4b5a78e8a991"
},
"bend": {
"id": "bend",
"index": 2,
"point": [
18.765,
33.009
]
}
},
"decorations": {
"end": "Arrow"
},
"style": {
"color": "Black",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"10e6dade-7fab-42f8-0fc3-2091f8b5cb1f": {
"id": "10e6dade-7fab-42f8-0fc3-2091f8b5cb1f",
"type": "arrow",
"name": "Arrow",
"parentId": "page",
"childIndex": 4,
"point": [
479.98,
235.6
],
"rotation": 0,
"bend": 0,
"handles": {
"start": {
"id": "start",
"index": 0,
"point": [
157.84,
18.972
],
"canBind": true,
"bindingId": "500844a2-1dc9-4f58-0837-1a0caf446604"
},
"end": {
"id": "end",
"index": 1,
"point": [
0,
0
],
"canBind": true,
"bindingId": "a9c71649-7729-47c5-0e3c-37455ecd837c"
},
"bend": {
"id": "bend",
"index": 2,
"point": [
78.921,
9.4859
]
}
},
"decorations": {
"end": "Arrow"
},
"style": {
"color": "Black",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"0ef85012-7e75-4fdf-119f-18e615609335": {
"id": "0ef85012-7e75-4fdf-119f-18e615609335",
"type": "arrow",
"name": "Arrow",
"parentId": "page",
"childIndex": 5,
"point": [
414.98,
326.35
],
"rotation": 0,
"bend": 0,
"handles": {
"start": {
"id": "start",
"index": 0,
"point": [
0,
0
],
"canBind": true,
"bindingId": "aea8ac9a-0527-47ea-1d50-43862270759a"
},
"end": {
"id": "end",
"index": 1,
"point": [
74.457,
123.96
],
"canBind": true,
"bindingId": "be4cee5e-484d-4908-1079-a93aba785bd1"
},
"bend": {
"id": "bend",
"index": 2,
"point": [
37.228,
61.982
]
}
},
"decorations": {
"end": "Arrow"
},
"style": {
"color": "Black",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
}
},
"bindings": {
"284b2bbb-92eb-4f2b-255d-4b5a78e8a991": {
"id": "284b2bbb-92eb-4f2b-255d-4b5a78e8a991",
"type": "arrow",
"fromId": "8f7c6768-8124-427f-0795-5b11b8e55cb3",
"handleId": "end",
"toId": "58343232-2843-48ff-214c-f801ba274f24",
"point": [
0.67161,
0.5518
],
"distance": 16
},
"37f1f650-7368-41df-2489-b980efab5b7d": {
"id": "37f1f650-7368-41df-2489-b980efab5b7d",
"type": "arrow",
"fromId": "8f7c6768-8124-427f-0795-5b11b8e55cb3",
"handleId": "start",
"toId": "2ac5ddca-0fdd-4744-3c5b-dc63d9804993",
"point": [
0.68144,
0.58126
],
"distance": 18.210000000000036
},
"a9c71649-7729-47c5-0e3c-37455ecd837c": {
"id": "a9c71649-7729-47c5-0e3c-37455ecd837c",
"type": "arrow",
"fromId": "10e6dade-7fab-42f8-0fc3-2091f8b5cb1f",
"handleId": "end",
"toId": "a755e702-548c-4ae3-0f62-307ced48d060",
"point": [
0.84807,
0.4752
],
"distance": 16
},
"500844a2-1dc9-4f58-0837-1a0caf446604": {
"id": "500844a2-1dc9-4f58-0837-1a0caf446604",
"type": "arrow",
"fromId": "10e6dade-7fab-42f8-0fc3-2091f8b5cb1f",
"handleId": "start",
"toId": "58343232-2843-48ff-214c-f801ba274f24",
"point": [
0.5,
0.5
],
"distance": 16
},
"be4cee5e-484d-4908-1079-a93aba785bd1": {
"id": "be4cee5e-484d-4908-1079-a93aba785bd1",
"type": "arrow",
"fromId": "0ef85012-7e75-4fdf-119f-18e615609335",
"handleId": "end",
"toId": "2ac5ddca-0fdd-4744-3c5b-dc63d9804993",
"point": [
0.38339,
0.55439
],
"distance": 16
},
"aea8ac9a-0527-47ea-1d50-43862270759a": {
"id": "aea8ac9a-0527-47ea-1d50-43862270759a",
"type": "arrow",
"fromId": "0ef85012-7e75-4fdf-119f-18e615609335",
"handleId": "start",
"toId": "a755e702-548c-4ae3-0f62-307ced48d060",
"point": [
0.57016,
0.83242
],
"distance": 16
}
}
},
"page1": {
"id": "page1",
"shapes": {
"fa7a50a0-00d3-432c-3e97-585a9d97b6c6": {
"id": "fa7a50a0-00d3-432c-3e97-585a9d97b6c6",
"type": "rectangle",
"name": "Rectangle",
"parentId": "page1",
"childIndex": 1,
"point": [
1757.9,
912.27
],
"size": [
92.549,
78.049
],
"rotation": 0,
"radius": 0,
"style": {
"color": "Indigo",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"76a961f3-9cad-4a44-3790-74d8e46c918b": {
"id": "76a961f3-9cad-4a44-3790-74d8e46c918b",
"type": "rectangle",
"name": "Rectangle",
"parentId": "page1",
"childIndex": 2,
"point": [
2054.6,
956.4
],
"size": [
53.205,
136.52
],
"rotation": 0,
"radius": 0,
"style": {
"color": "Red",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"5e9a929d-6fa4-4176-3f60-9a0d206a986b": {
"id": "5e9a929d-6fa4-4176-3f60-9a0d206a986b",
"type": "ellipse",
"name": "Ellipse",
"parentId": "page1",
"childIndex": 3,
"point": [
1821.3,
1044.1
],
"radius": [
34.65772824556598,
39.066377761684635
],
"rotation": 0,
"style": {
"color": "Green",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"d6e6f6a8-8472-4a05-352e-3749bc75f487": {
"id": "d6e6f6a8-8472-4a05-352e-3749bc75f487",
"type": "arrow",
"name": "Arrow",
"parentId": "page1",
"childIndex": 4,
"point": [
1873.779,
968.915
],
"rotation": 0,
"bend": 0,
"handles": {
"start": {
"id": "start",
"index": 0,
"point": [
71.321,
20.225
],
"canBind": true
},
"end": {
"id": "end",
"index": 1,
"point": [
-8.5265e-14,
2.1316e-14
],
"canBind": true
},
"bend": {
"id": "bend",
"index": 2,
"point": [
35.661,
10.112
]
}
},
"decorations": {
"end": "Arrow"
},
"style": {
"color": "Black",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"ede2b8f7-2ce0-4b79-2770-a5d5b174f7d4": {
"id": "ede2b8f7-2ce0-4b79-2770-a5d5b174f7d4",
"type": "arrow",
"name": "Arrow",
"parentId": "page1",
"childIndex": 5,
"point": [
1781.2,
1003.8
],
"rotation": 0,
"bend": 0,
"handles": {
"start": {
"id": "start",
"index": 0,
"point": [
0,
0
],
"canBind": true
},
"end": {
"id": "end",
"index": 1,
"point": [
25.925,
43.226
],
"canBind": true
},
"bend": {
"id": "bend",
"index": 2,
"point": [
12.963,
21.613
]
}
},
"decorations": {
"end": "Arrow"
},
"style": {
"color": "Black",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
},
"8edca8a7-0c6d-414b-0ff0-e00c32733b46": {
"id": "8edca8a7-0c6d-414b-0ff0-e00c32733b46",
"type": "arrow",
"name": "Arrow",
"parentId": "page1",
"childIndex": 6,
"point": [
1912.9,
1065.7669999999998
],
"rotation": 0,
"bend": 0,
"handles": {
"start": {
"id": "start",
"index": 0,
"point": [
0,
12.333
],
"canBind": true
},
"end": {
"id": "end",
"index": 1,
"point": [
36.712,
8.3489e-14
],
"canBind": true
},
"bend": {
"id": "bend",
"index": 2,
"point": [
18.356,
6.1665
]
}
},
"decorations": {
"end": "Arrow"
},
"style": {
"color": "Black",
"size": "Medium",
"isFilled": true,
"dash": "Draw"
}
}
},
"bindings": {},
"name": "Page 2"
}
},
"pageStates": {
"page": {
"id": "page",
"selectedIds": [],
"camera": {
"point": [
-257,
-61
],
"zoom": 1
},
"editingId": null
},
"page1": {
"id": "page1",
"selectedIds": [
"76a961f3-9cad-4a44-3790-74d8e46c918b"
],
"camera": {
"point": [
-1620.9073790458274,
-841.1990943476605
],
"zoom": 1.5319298689588086
},
"currentParentId": "page1"
}
}
}

View file

@ -1,6 +1,10 @@
import * as React from 'react'
import Editor from './components/editor'
import { TLDraw } from '@tldraw/tldraw'
export default function Basic(): JSX.Element {
return <Editor />
return (
<div className="tldraw">
<TLDraw />
</div>
)
}

View file

@ -1,38 +0,0 @@
import * as React from 'react'
import { TLDraw, TLDrawProps, TLDrawState, useFileSystem } from '@tldraw/tldraw'
export default function Editor(props: TLDrawProps): JSX.Element {
const rTLDrawState = React.useRef<TLDrawState>()
const fileSystemEvents = useFileSystem()
const handleMount = React.useCallback((state: TLDrawState) => {
rTLDrawState.current = state
props.onMount?.(state)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.state = state
}, [])
const onSignIn = React.useCallback((state: TLDrawState) => {
// Sign in?
}, [])
const onSignOut = React.useCallback((state: TLDrawState) => {
// Sign out?
}, [])
return (
<div className="tldraw">
<TLDraw
id="tldraw1"
{...props}
onMount={handleMount}
onSignIn={onSignIn}
onSignOut={onSignOut}
{...fileSystemEvents}
autofocus
/>
</div>
)
}

41
example/src/develop.tsx Normal file
View file

@ -0,0 +1,41 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'react'
import { TLDraw, TLDrawState, useFileSystem } from '@tldraw/tldraw'
declare const window: Window & { state: TLDrawState }
export default function Develop(): JSX.Element {
const rTLDrawState = React.useRef<TLDrawState>()
const fileSystemEvents = useFileSystem()
const handleMount = React.useCallback((state: TLDrawState) => {
window.state = state
rTLDrawState.current = state
}, [])
const handleSignOut = React.useCallback(() => {
// noop
}, [])
const handleSignIn = React.useCallback(() => {
// noop
}, [])
const handlePersist = React.useCallback(() => {
// noop
}, [])
return (
<div className="tldraw">
<TLDraw
id="develop"
{...fileSystemEvents}
onMount={handleMount}
onSignIn={handleSignIn}
onSignOut={handleSignOut}
onPersist={handlePersist}
/>
</div>
)
}

View file

@ -24,7 +24,7 @@ export default function Embedded(): JSX.Element {
overflow: 'hidden',
}}
>
<TLDraw id="small6" />
<TLDraw id="embedded" />
</div>
</div>
)

View file

@ -0,0 +1,14 @@
import * as React from 'react'
import { TLDraw, useFileSystem } from '@tldraw/tldraw'
export default function FileSystem(): JSX.Element {
const fileSystemEvents = useFileSystem()
// Use the Menu > File to create, open, and save .tldr files.
return (
<div className="tldraw">
<TLDraw {...fileSystemEvents} />
</div>
)
}

View file

@ -0,0 +1,17 @@
import { TLDraw, TLDrawFile } from '@tldraw/tldraw'
import * as React from 'react'
export default function LoadingFiles(): JSX.Element {
const [file, setFile] = React.useState<TLDrawFile>()
React.useEffect(() => {
async function loadFile(): Promise<void> {
const file = await fetch('Example.tldr').then((response) => response.json())
setFile(file)
}
loadFile()
}, [])
return <TLDraw document={file?.document} />
}

View file

@ -1,3 +0,0 @@
export function Cursors() {
return <div>hi</div>
}

View file

@ -1,11 +1,6 @@
import { TLDraw } from '@tldraw/tldraw'
import * as React from 'react'
/**
* This is bugged until Radix gives me an option to control
* where menus are portaled.
*/
export default function NoSizeEmbedded(): JSX.Element {
return <TLDraw />
}

10
example/src/persisted.tsx Normal file
View file

@ -0,0 +1,10 @@
import * as React from 'react'
import { TLDraw } from '@tldraw/tldraw'
export default function Persisted(): JSX.Element {
return (
<div className="tldraw">
<TLDraw id="tldraw-persisted-id" />
</div>
)
}

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -1,6 +1,21 @@
import { TLDraw, TLDrawFile } from '@tldraw/tldraw'
import * as React from 'react'
import Editor from './components/editor'
export default function Basic(): JSX.Element {
return <Editor readOnly />
export default function ReadOnly(): JSX.Element {
const [file, setFile] = React.useState<TLDrawFile>()
React.useEffect(() => {
async function loadFile(): Promise<void> {
const file = await fetch('Example.tldr').then((response) => response.json())
setFile(file)
}
loadFile()
}, [])
return (
<div className="tldraw">
<TLDraw readOnly document={file?.document} />
</div>
)
}

View file

@ -7,6 +7,8 @@ body {
overscroll-behavior: none;
margin: 0px;
padding: 0px;
font-size: 1em;
font-family: Arial, Helvetica, sans-serif;
}
.tldraw {
@ -18,3 +20,29 @@ body {
width: 100%;
height: 100%;
}
.hero {
width: 100%;
max-width: 720px;
}
.links {
display: grid;
width: fit-content;
list-style: none;
padding: 1em;
margin: 0px;
}
.links a {
padding: 0.5em 0.7em;
color: black;
text-decoration: none;
font-size: 1.2em;
display: block;
border-radius: 8px;
}
.links a:hover {
background-color: rgba(144, 144, 144, 0.1);
}

View file

@ -0,0 +1,17 @@
import * as React from 'react'
import { TLDraw } from '@tldraw/tldraw'
export default function UIOptions(): JSX.Element {
return (
<div className="tldraw">
<TLDraw
showUI={true}
showMenu={true}
showPages={true}
showStyles={true}
showTools={true}
showZoom={true}
/>
</div>
)
}

View file

@ -475,6 +475,7 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
* @param bounds
*/
updateBounds = (bounds: TLBounds) => {
if (this.readOnly) return
this.bounds = { ...bounds }
if (this.session) {
this.session.updateViewport(this.viewport)
@ -487,6 +488,8 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
* @param id [string]
*/
setEditingId = (id?: string) => {
if (this.readOnly) return
this.editingStartTime = Date.now()
this.patchState(
{
@ -640,7 +643,7 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
* @param tool The tool to select, or "select".
*/
selectTool = (type: ToolType): this => {
if (this.session) return this
if (this.readOnly || this.session) return this
const tool = this.tools[type]
@ -1145,6 +1148,7 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
* @param pageId (optional) The new page's id.
*/
createPage = (id?: string): this => {
if (this.readOnly) return this
return this.setState(
Commands.createPage(this.state, [-this.bounds.width / 2, -this.bounds.height / 2], id)
)
@ -1164,6 +1168,7 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
* @param name The page's new name
*/
renamePage = (pageId: string, name: string): this => {
if (this.readOnly) return this
return this.setState(Commands.renamePage(this.state, pageId, name))
}

View file

@ -1,8 +1,8 @@
import * as React from 'react'
import { TLDrawState } from '@tldraw/tldraw'
export function useFileSystem(state: TLDrawState) {
const promptSaveBeforeChange = React.useCallback(async () => {
export function useFileSystem() {
const promptSaveBeforeChange = React.useCallback(async (state: TLDrawState) => {
if (state.isDirty) {
if (state.fileSystemHandle) {
if (window.confirm('Do you want to save changes to your current project?')) {
@ -14,25 +14,31 @@ export function useFileSystem(state: TLDrawState) {
}
}
}
}, [state])
}, [])
const onNewProject = React.useCallback(async () => {
await promptSaveBeforeChange()
state.newProject()
}, [state, promptSaveBeforeChange])
const onNewProject = React.useCallback(
async (state: TLDrawState) => {
await promptSaveBeforeChange(state)
state.newProject()
},
[promptSaveBeforeChange]
)
const onSaveProject = React.useCallback(() => {
const onSaveProject = React.useCallback((state: TLDrawState) => {
state.saveProject()
}, [state])
}, [])
const onSaveProjectAs = React.useCallback(() => {
const onSaveProjectAs = React.useCallback((state: TLDrawState) => {
state.saveProjectAs()
}, [state])
}, [])
const onOpenProject = React.useCallback(async () => {
await promptSaveBeforeChange()
state.openProject()
}, [state, promptSaveBeforeChange])
const onOpenProject = React.useCallback(
async (state: TLDrawState) => {
await promptSaveBeforeChange(state)
state.openProject()
},
[promptSaveBeforeChange]
)
return {
onNewProject,