kopia lustrzana https://github.com/Tldraw/Tldraw
Shape with Migrations (#3078)
Adds an example of how to add migrations for a custom shape. closes tld-2246 - [x] `documentation` — Changes to the documentation only[^2] ### Release Notes - Adds a shape with migrations example --------- Co-authored-by: Steve Ruiz <steveruizok@gmail.com>pull/2924/head^2
rodzic
40c20e5585
commit
eb80cf787b
|
@ -2,12 +2,11 @@
|
|||
title: Bounds Snapping Shape
|
||||
component: ./BoundsSnappingShape.tsx
|
||||
category: shapes/tools
|
||||
priority: 2
|
||||
priority: 3
|
||||
---
|
||||
|
||||
Custom shapes with special bounds snapping behaviour.
|
||||
|
||||
---
|
||||
|
||||
|
||||
This example shows how to create a shape with custom snapping geometry. When shapes are moved around in snap mode, they will snap to the bounds of other shapes by default. However a shape can return custom snapping geometry to snap to instead. This example creates a playing card shape. The cards are designed to snap together so that the top-left icon remains visible when stacked, similar to a hand of cards in a game.The most relevant code for this customisation is in playing-card-util.tsx.
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Shape with migrations
|
||||
component: ./ShapeWithMigrationsExample.tsx
|
||||
category: shapes/tools
|
||||
priority: 3
|
||||
---
|
||||
|
||||
Migrate your shapes and their data between versions
|
||||
|
||||
---
|
||||
|
||||
Sometimes you'll want to update the way a shape works in your application. When this happens there can be a risk of errors and bugs. For example, users with an old version of a shape in their documents might encounter errors when the editor tries to access a property that doesn't exist. This example shows how you can use our migrations system to preserve your users' data between versions. It uses a snapshot to load a document with a shape that is missing a "color" prop, and uses the migrations method of the shape util to update it.
|
|
@ -0,0 +1,173 @@
|
|||
import {
|
||||
BaseBoxShapeUtil,
|
||||
HTMLContainer,
|
||||
T,
|
||||
TLBaseShape,
|
||||
TLOnResizeHandler,
|
||||
Tldraw,
|
||||
resizeBox,
|
||||
} from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import snapshot from './snapshot.json'
|
||||
|
||||
// There's a guide at the bottom of this file!
|
||||
|
||||
export type IMyShape = TLBaseShape<
|
||||
'myshape',
|
||||
{
|
||||
w: number
|
||||
h: number
|
||||
color: string
|
||||
}
|
||||
>
|
||||
|
||||
export class MigratedShapeUtil extends BaseBoxShapeUtil<IMyShape> {
|
||||
static override type = 'myshape' as const
|
||||
|
||||
static override props = {
|
||||
w: T.number,
|
||||
h: T.number,
|
||||
color: T.string,
|
||||
}
|
||||
|
||||
// [1]
|
||||
static override migrations = {
|
||||
firstVersion: 0,
|
||||
currentVersion: 1,
|
||||
migrators: {
|
||||
1: {
|
||||
up(shape: IMyShape) {
|
||||
return {
|
||||
...shape,
|
||||
props: {
|
||||
...shape.props,
|
||||
color: 'lightblue',
|
||||
},
|
||||
}
|
||||
},
|
||||
down(shape: IMyShape) {
|
||||
const { color: _, ...propsWithoutColor } = shape.props
|
||||
return {
|
||||
...shape,
|
||||
props: propsWithoutColor,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
getDefaultProps(): IMyShape['props'] {
|
||||
return {
|
||||
w: 300,
|
||||
h: 300,
|
||||
color: 'lightblue',
|
||||
}
|
||||
}
|
||||
|
||||
component(shape: IMyShape) {
|
||||
return (
|
||||
<HTMLContainer
|
||||
id={shape.id}
|
||||
style={{
|
||||
backgroundColor: shape.props.color,
|
||||
boxShadow: '0 0 10px rgba(0,0,0,0.5)',
|
||||
}}
|
||||
></HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: IMyShape) {
|
||||
return <rect width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
|
||||
override onResize: TLOnResizeHandler<IMyShape> = (shape, info) => {
|
||||
return resizeBox(shape, info)
|
||||
}
|
||||
}
|
||||
|
||||
const customShapeUtils = [MigratedShapeUtil]
|
||||
|
||||
export default function ShapeWithMigrationsExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
// Pass in the array of custom shape classes
|
||||
shapeUtils={customShapeUtils}
|
||||
// Use a snapshot to load an old version of the shape
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Introduction:
|
||||
|
||||
Sometimes you'll want to update the way a shape works in your application without
|
||||
breaking older versions of the shape that a user may have stored or persisted in
|
||||
memory.
|
||||
|
||||
This example shows how you can use our migrations system to upgrade (or downgrade)
|
||||
user's data between different versions. Most of the code above is general "custom
|
||||
shape" code—see our custom shape example for more details.
|
||||
|
||||
[1]
|
||||
To define migrations, we can override the migrations property of our shape util. Each migration
|
||||
had two parts: an `up` migration and `down` migration. In this case, the `up` migration adds
|
||||
the `color` prop to the shape, and the `down` migration removes it.
|
||||
|
||||
In some cases (mainly in multiplayer sessions) a peer or server may need to take a later
|
||||
version of a shape and migrate it down to an older version—in this case, it would run the
|
||||
down migrations in order to get it to the needed version.
|
||||
|
||||
How it works:
|
||||
|
||||
Each time the editor's store creates a snapshot (`editor.store.createSnapshot`), it
|
||||
serializes all of the records (the snapshot's `store`) as well as versions of each
|
||||
record that it contains (the snapshot's `scena`). When the editor loads a snapshot,
|
||||
it compares its current schema with the snapshot's schema to determine which migrations
|
||||
to apply to each record.
|
||||
|
||||
In this example, we have a snapshot (snapshot.json) that we created in version 0,
|
||||
however our shape now has a 'color' prop that was added in version 1.
|
||||
|
||||
The snapshot looks something like this:
|
||||
|
||||
```json{
|
||||
{
|
||||
"store": {
|
||||
"shape:BqG5uIAa9ig2-ukfnxwBX": {
|
||||
...,
|
||||
"props": {
|
||||
"w": 300,
|
||||
"h": 300
|
||||
},
|
||||
},
|
||||
"schema": {
|
||||
...,
|
||||
"recordVersions": {
|
||||
...,
|
||||
"shape": {
|
||||
"version": 3,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {
|
||||
...,
|
||||
"myshape": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the shape in the snapshot doesn't have a 'color' prop.
|
||||
|
||||
Note also that the schema's version for this shape is 0.
|
||||
|
||||
When the editor loads the snapshot, it will compare the serialzied schema's version with
|
||||
its current schema's version for the shape, which is 1 as defined in our shape's migrations.
|
||||
Since the serialized version is older than its current version, it will use our migration
|
||||
to bring it up to date: it will run the migration's `up` function, which will add the 'color'
|
||||
prop to the shape.
|
||||
*/
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": "",
|
||||
"meta": {},
|
||||
"id": "document:document",
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page:page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
},
|
||||
"shape:BqG5uIAa9ig2-ukfnxwBX": {
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"rotation": 0,
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"meta": {},
|
||||
"id": "shape:BqG5uIAa9ig2-ukfnxwBX",
|
||||
"type": "myshape",
|
||||
"parentId": "page:page",
|
||||
"index": "a1",
|
||||
"props": {
|
||||
"w": 300,
|
||||
"h": 300
|
||||
},
|
||||
"typeName": "shape"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"schemaVersion": 1,
|
||||
"storeVersion": 4,
|
||||
"recordVersions": {
|
||||
"asset": {
|
||||
"version": 1,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {
|
||||
"image": 3,
|
||||
"video": 3,
|
||||
"bookmark": 1
|
||||
}
|
||||
},
|
||||
"camera": {
|
||||
"version": 1
|
||||
},
|
||||
"document": {
|
||||
"version": 2
|
||||
},
|
||||
"instance": {
|
||||
"version": 24
|
||||
},
|
||||
"instance_page_state": {
|
||||
"version": 5
|
||||
},
|
||||
"page": {
|
||||
"version": 1
|
||||
},
|
||||
"shape": {
|
||||
"version": 3,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {
|
||||
"group": 0,
|
||||
"text": 1,
|
||||
"bookmark": 2,
|
||||
"draw": 1,
|
||||
"geo": 8,
|
||||
"note": 5,
|
||||
"line": 4,
|
||||
"frame": 0,
|
||||
"arrow": 3,
|
||||
"highlight": 0,
|
||||
"embed": 4,
|
||||
"image": 3,
|
||||
"video": 2,
|
||||
"myshape": 0
|
||||
}
|
||||
},
|
||||
"instance_presence": {
|
||||
"version": 5
|
||||
},
|
||||
"pointer": {
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue