tldraw/apps/docs/content/docs/shapes.mdx
David Sheldrick 4f70a4f4e8
New migrations again (#3220)
Describe what your pull request does. If appropriate, add GIFs or images
showing the before and after.

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `galaxy brain` — Architectural changes



### Test Plan

1. Add a step-by-step description of how to test your PR here.
2.

- [ ] Unit Tests
- [ ] End to end tests

### Release Notes

#### BREAKING CHANGES

- The `Migrations` type is now called `LegacyMigrations`.
- The serialized schema format (e.g. returned by
`StoreSchema.serialize()` and `Store.getSnapshot()`) has changed. You
don't need to do anything about it unless you were reading data directly
from the schema for some reason. In which case it'd be best to avoid
that in the future! We have no plans to change the schema format again
(this time was traumatic enough) but you never know.
- `compareRecordVersions` and the `RecordVersion` type have both
disappeared. There is no replacement. These were public by mistake
anyway, so hopefully nobody had been using it.
- `compareSchemas` is a bit less useful now. Our migrations system has
become a little fuzzy to allow for simpler UX when adding/removing
custom extensions and 3rd party dependencies, and as a result we can no
longer compare serialized schemas in any rigorous manner. You can rely
on this function to return `0` if the schemas are the same. Otherwise it
will return `-1` if the schema on the right _seems_ to be newer than the
schema on the left, but it cannot guarantee that in situations where
migration sequences have been removed over time (e.g. if you remove one
of the builtin tldraw shapes).

Generally speaking, the best way to check schema compatibility now is to
call `store.schema.getMigrationsSince(persistedSchema)`. This will throw
an error if there is no upgrade path from the `persistedSchema` to the
current version.

- `defineMigrations` has been deprecated and will be removed in a future
release. For upgrade instructions see
https://tldraw.dev/docs/persistence#Updating-legacy-shape-migrations-defineMigrations

- `migrate` has been removed. Nobody should have been using this but if
you were you'll need to find an alternative. For migrating tldraw data,
you should stick to using `schema.migrateStoreSnapshot` and, if you are
building a nuanced sync engine that supports some amount of backwards
compatibility, also feel free to use `schema.migratePersistedRecord`.
- the `Migration` type has changed. If you need the old one for some
reason it has been renamed to `LegacyMigration`. It will be removed in a
future release.
- the `Migrations` type has been renamed to `LegacyMigrations` and will
be removed in a future release.
- the `SerializedSchema` type has been augmented. If you need the old
version specifically you can use `SerializedSchemaV1`

---------

Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
2024-04-15 12:53:42 +00:00

244 lines
8.2 KiB
Text

---
title: Shapes
status: published
author: steveruizok
date: 3/22/2023
order: 2
keywords:
- custom
- shapes
- shapeutils
- utils
---
In tldraw, a shape is something that can exist on the page, like an arrow, an image, or some text.
This article is about shapes: what they are, how they work, and how to create your own shapes. If you'd prefer to see an example, see the tldraw repository's [examples app](https://github.com/tldraw/tldraw/tree/main/apps/examples) for examples of how to create custom shapes in tldraw.
## Types of shape
We make a distinction between three types of shapes: "core", "default", and "custom".
### Core shapes
The editor's core shapes are shapes that are built in and always present. At the moment the only core shape is the [group shape](/reference/tlschema/TLGroupShape).
### Default shapes
The default shapes are all of the shapes that are included by default in the [Tldraw](?) component, such as the [TLArrowShape](?) or [TLDrawShape](?). They are exported from the `tldraw` library as [defaultShapeUtils](?).
### Custom shapes
Custom shapes are shapes that were created by you or someone you love. Find more information about custom shapes [below](#Custom-shapes-1).
## The shape object
Shapes are just records (JSON objects) that sit in the [store](/docs/editor#Store). For example, here's a shape record for a rectangle geo shape:
```ts
{
"parentId": "page:somePage",
"id": "shape:someId",
"typeName": "shape"
"type": "geo",
"x": 106,
"y": 294,
"rotation": 0,
"index": "a28",
"opacity": 1,
"isLocked": false,
"props": {
"w": 200,
"h": 200,
"geo": "rectangle",
"color": "black",
"labelColor": "black",
"fill": "none",
"dash": "draw",
"size": "m",
"font": "draw",
"text": "diagram",
"align": "middle",
"verticalAlign": "middle",
"growY": 0,
"url": ""
},
"meta": {},
}
```
### Base properties
Every shape contains some base information. These include the shape's type, position, rotation, opacity, and more. You can find the full list of base properties [here](/reference/tlschema/TLBaseShape).
### Props
Every shape also contains some shape-specific information, called `props`. Each type of shape can have different props. For example, the `props` of a text shape are much different than the props of an arrow shape.
### Meta
Meta information is information that is not used by tldraw but is instead used by your application. For example, you might want to store the name of the user who created a shape, or the date that the shape was created. You can find more information about meta information [below](#Meta-information).
## The `ShapeUtil` class
While tldraw's shapes themselves are simple JSON objects, we use [ShapeUtil](?) classes to answer questions about shapes. For example, when the editor needs to render a text shape, it will find the [TextShapeUtil](?) and call its [ShapeUtil#component](?) method, passing in the text shape object as an argument.
---
## Custom shapes
You can create your own custom shapes. In the examples below, we will create a custom "card" shape. It'll be a simple rectangle with some text inside.
> For an example of how to create custom shapes, see our [custom shapes example](/examples/shapes/tools/custom-shape).
### Shape type
In tldraw's data model, each shape is represented by a JSON object. Let's first create a type that describes what this object will look like.
```ts
import { TLBaseShape } from 'tldraw'
type CardShape = TLBaseShape<'card', { w: number; h: number }>
```
With the [TLBaseShape](?) helper, we define the shape's `type` property (`card`) and the shape's `props` property (`{ w: number, h: number }`). The type can be any string but the props must be a regular [JSON-serializable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) JavaScript object.
The [TLBaseShape](?) helper adds the other base properties of a shape, such as `x`, `y`, `rotation`, and `opacity`.
### Shape Util
While tldraw's shapes themselves are simple JSON objects, we use [ShapeUtil](?) classes to answer questions about shapes.
Let's create a [ShapeUtil](?) class for the shape.
```tsx
import { HTMLContainer, ShapeUtil } from 'tldraw'
class CardShapeUtil extends ShapeUtil<CardShape> {
static override type = 'card' as const
getDefaultProps(): CardShape['props'] {
return {
w: 100,
h: 100,
}
}
getGeometry(shape: ICardShape) {
return new Rectangle2d({
width: shape.props.w,
height: shape.props.h,
isFilled: true,
})
}
component(shape: CardShape) {
return <HTMLContainer>Hello</HTMLContainer>
}
indicator(shape: CardShape) {
return <rect width={shape.props.w} height={shape.props.h} />
}
}
```
This is a minimal [ShapeUtil](?). We've given it a static property `type` that matches the type of our shape, we've provided implementations for the abstract methods [ShapeUtil#getDefaultProps](?), [ShapeUtil#getBounds](?), [ShapeUtil#component](?), and [ShapeUtil#indicator](?).
We still have work to do on the `CardShapeUtil` class, but we'll come back to it later. For now, let's put the shape onto the canvas by passing it to the [Tldraw](?) component.
### The `shapeUtils` prop
We pass an array of our shape utils into the [Tldraw](?) component's `shapeUtils` prop.
```tsx
const MyCustomShapes = [MyCardShape]
export default function () {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw shapeUtils={MyCustomShapes} />
</div>
)
}
```
We can create one of our custom card shapes using the [Editor](?) API. We'll do this by setting the `onMount` prop of the [Tldraw](?) component.
```tsx
export default function () {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw
shapeUtils={MyCustomShapes}
onMount={(editor) => {
editor.createShapes([{ type: 'card' }])
}}
/>
</div>
)
}
```
Once the page refreshes, we should now have our custom shape on the canvas.
### Meta information
Shapes also have a `meta` property (see [TLBaseShape#meta](?)) that you can fill with your own data. This should feel like a bit of a hack, however it's intended to be an escape hatch for applications where you want to use tldraw's existing shapes but also want to attach a bit of extra data to the shape.
Note that tldraw's regular shape definitions have an unknown object for the shape's `meta` property. To type your shape's meta, use a union like this:
```ts
type MyShapeWithMeta = TLGeoShape & { meta: { createdBy: string } }
const shape = editor.getShape<MyShapeWithMeta>(myGeoShape.id)
```
You can update a shape's `meta` property in the same way you would update its props, using [Editor#updateShapes](?).
```ts
editor.updateShapes<MyShapeWithMeta>([
{
id: myGeoShape.id,
type: 'geo',
meta: {
createdBy: 'Steve',
},
},
])
```
Like [TLBaseShape#props](?), the data in a [TLBaseShape#meta](?) object must be JSON serializable.
In addition to setting meta properties this way, you can also set the default meta data for shapes using the Editor's [Editor#getInitialMetaForShape](?) method.
```tsx
editor.getInitialMetaForShape = (shape: TLShape) => {
if (shape.type === 'text') {
return { createdBy: currentUser.id, lastModified: Date.now() }
} else {
return { createdBy: currentUser.id }
}
}
```
Whenever new shapes are created using the [Editor#createShapes](?) method, the shape's meta property will be set using the [Editor#getInitialMetaForShape](?) method. By default this method returns an empty object.
### Using starter shapes
You can use "starter" shape utils like [BaseBoxShapeUtil](?) to get regular rectangular shape behavior.
### Flags
You can use flags like [ShapeUtil#hideRotateHandle](?) to hide different parts of the UI when the shape is selected, or else to control different behaviors of the shape.
### Interaction
You can turn on `pointer-events` to allow users to interact inside of the shape.
### Editing
You can make shapes "editable" to help decide when they're interactive or not.
### Migrations
You can add migrations for your shape props by adding a `migrations` property to your shape's util class. See [the persistence docs](/docs/persistence#Shape-props-migrations) for more information.