UNPKG

@tldraw/tlschema

Version:

tldraw infinite canvas SDK (schema).

272 lines (251 loc) • 6.88 kB
import { createMigrationSequence } from '@tldraw/store' import { T } from '@tldraw/validate' import { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText' import { VecModel, vecModelValidator } from '../misc/geometry-types' import { createBindingId } from '../records/TLBinding' import { TLShapeId, createShapePropsMigrationIds } from '../records/TLShape' import { RecordProps, TLPropsMigration, createPropsMigration } from '../recordsWithProps' import { StyleProp } from '../styles/StyleProp' import { DefaultColorStyle, DefaultLabelColorStyle, TLDefaultColorStyle, } from '../styles/TLColorStyle' import { DefaultDashStyle, TLDefaultDashStyle } from '../styles/TLDashStyle' import { DefaultFillStyle, TLDefaultFillStyle } from '../styles/TLFillStyle' import { DefaultFontStyle, TLDefaultFontStyle } from '../styles/TLFontStyle' import { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle' import { TLBaseShape } from './TLBaseShape' const arrowKinds = ['arc', 'elbow'] as const /** @public */ export const ArrowShapeKindStyle = StyleProp.defineEnum('tldraw:arrowKind', { defaultValue: 'arc', values: arrowKinds, }) /** @public */ export type TLArrowShapeKind = T.TypeOf<typeof ArrowShapeKindStyle> const arrowheadTypes = [ 'arrow', 'triangle', 'square', 'dot', 'pipe', 'diamond', 'inverted', 'bar', 'none', ] as const /** @public */ export const ArrowShapeArrowheadStartStyle = StyleProp.defineEnum('tldraw:arrowheadStart', { defaultValue: 'none', values: arrowheadTypes, }) /** @public */ export const ArrowShapeArrowheadEndStyle = StyleProp.defineEnum('tldraw:arrowheadEnd', { defaultValue: 'arrow', values: arrowheadTypes, }) /** @public */ export type TLArrowShapeArrowheadStyle = T.TypeOf<typeof ArrowShapeArrowheadStartStyle> /** @public */ export interface TLArrowShapeProps { kind: TLArrowShapeKind labelColor: TLDefaultColorStyle color: TLDefaultColorStyle fill: TLDefaultFillStyle dash: TLDefaultDashStyle size: TLDefaultSizeStyle arrowheadStart: TLArrowShapeArrowheadStyle arrowheadEnd: TLArrowShapeArrowheadStyle font: TLDefaultFontStyle start: VecModel end: VecModel bend: number richText: TLRichText labelPosition: number scale: number elbowMidPoint: number } /** @public */ export type TLArrowShape = TLBaseShape<'arrow', TLArrowShapeProps> /** @public */ export const arrowShapeProps: RecordProps<TLArrowShape> = { kind: ArrowShapeKindStyle, labelColor: DefaultLabelColorStyle, color: DefaultColorStyle, fill: DefaultFillStyle, dash: DefaultDashStyle, size: DefaultSizeStyle, arrowheadStart: ArrowShapeArrowheadStartStyle, arrowheadEnd: ArrowShapeArrowheadEndStyle, font: DefaultFontStyle, start: vecModelValidator, end: vecModelValidator, bend: T.number, richText: richTextValidator, labelPosition: T.number, scale: T.nonZeroNumber, elbowMidPoint: T.number, } /** @public */ export const arrowShapeVersions = createShapePropsMigrationIds('arrow', { AddLabelColor: 1, AddIsPrecise: 2, AddLabelPosition: 3, ExtractBindings: 4, AddScale: 5, AddElbow: 6, AddRichText: 7, }) function propsMigration(migration: TLPropsMigration) { return createPropsMigration<TLArrowShape>('shape', 'arrow', migration) } /** @public */ export const arrowShapeMigrations = createMigrationSequence({ sequenceId: 'com.tldraw.shape.arrow', retroactive: false, sequence: [ propsMigration({ id: arrowShapeVersions.AddLabelColor, up: (props) => { props.labelColor = 'black' }, down: 'retired', }), propsMigration({ id: arrowShapeVersions.AddIsPrecise, up: ({ start, end }) => { if (start.type === 'binding') { start.isPrecise = !(start.normalizedAnchor.x === 0.5 && start.normalizedAnchor.y === 0.5) } if (end.type === 'binding') { end.isPrecise = !(end.normalizedAnchor.x === 0.5 && end.normalizedAnchor.y === 0.5) } }, down: ({ start, end }) => { if (start.type === 'binding') { if (!start.isPrecise) { start.normalizedAnchor = { x: 0.5, y: 0.5 } } delete start.isPrecise } if (end.type === 'binding') { if (!end.isPrecise) { end.normalizedAnchor = { x: 0.5, y: 0.5 } } delete end.isPrecise } }, }), propsMigration({ id: arrowShapeVersions.AddLabelPosition, up: (props) => { props.labelPosition = 0.5 }, down: (props) => { delete props.labelPosition }, }), { id: arrowShapeVersions.ExtractBindings, scope: 'store', up: (oldStore) => { type OldArrowTerminal = | { type: 'point' x: number y: number } | { type: 'binding' boundShapeId: TLShapeId normalizedAnchor: VecModel isExact: boolean isPrecise: boolean } // new type: | { type?: undefined; x: number; y: number } type OldArrow = TLBaseShape<'arrow', { start: OldArrowTerminal; end: OldArrowTerminal }> const arrows = Object.values(oldStore).filter( (r: any): r is OldArrow => r.typeName === 'shape' && r.type === 'arrow' ) for (const arrow of arrows) { const { start, end } = arrow.props if (start.type === 'binding') { const id = createBindingId() const binding = { typeName: 'binding', id, type: 'arrow', fromId: arrow.id, toId: start.boundShapeId, meta: {}, props: { terminal: 'start', normalizedAnchor: start.normalizedAnchor, isExact: start.isExact, isPrecise: start.isPrecise, }, } oldStore[id] = binding arrow.props.start = { x: 0, y: 0 } } else { delete arrow.props.start.type } if (end.type === 'binding') { const id = createBindingId() const binding = { typeName: 'binding', id, type: 'arrow', fromId: arrow.id, toId: end.boundShapeId, meta: {}, props: { terminal: 'end', normalizedAnchor: end.normalizedAnchor, isExact: end.isExact, isPrecise: end.isPrecise, }, } oldStore[id] = binding arrow.props.end = { x: 0, y: 0 } } else { delete arrow.props.end.type } } }, }, propsMigration({ id: arrowShapeVersions.AddScale, up: (props) => { props.scale = 1 }, down: (props) => { delete props.scale }, }), propsMigration({ id: arrowShapeVersions.AddElbow, up: (props) => { props.kind = 'arc' props.elbowMidPoint = 0.5 }, down: (props) => { delete props.kind delete props.elbowMidPoint }, }), propsMigration({ id: arrowShapeVersions.AddRichText, up: (props) => { props.richText = toRichText(props.text) delete props.text }, // N.B. Explicitly no down state so that we force clients to update. // down: (props) => { // delete props.richText // }, }), ], })