import { BaseBoxShapeUtil, HTMLContainer, T, TLBaseShape, TLOnResizeHandler, TLStoreSnapshot, 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 { 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 ( ) } indicator(shape: IMyShape) { return } override onResize: TLOnResizeHandler = (shape, info) => { return resizeBox(shape, info) } } const customShapeUtils = [MigratedShapeUtil] export default function ShapeWithMigrationsExample() { return (
) } /* 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. */