Tldraw/packages/tlschema/src/records/TLInstance.ts

287 wiersze
6.8 KiB
TypeScript
Czysty Zwykły widok Historia

import { BaseRecord, createRecordType, defineMigrations, ID } from '@tldraw/store'
import { T } from '@tldraw/validate'
2023-04-25 11:01:25 +00:00
import { Box2dModel } from '../geometry-types'
import { TL_STYLE_TYPES, TLStyleType } from '../style-types'
import { cursorValidator, scribbleTypeValidator, TLCursor, TLScribble } from '../ui-types'
import {
alignValidator,
arrowheadValidator,
colorValidator,
dashValidator,
fillValidator,
fontValidator,
geoValidator,
iconValidator,
idValidator,
opacityValidator,
pageIdValidator,
sizeValidator,
splineValidator,
verticalAlignValidator,
2023-04-25 11:01:25 +00:00
} from '../validation'
import { TLPageId } from './TLPage'
import { TLShapeProps } from './TLShape'
/** @public */
export type TLInstancePropsForNextShape = Pick<TLShapeProps, TLStyleType>
/**
* TLInstance
*
* State that is particular to a single browser tab
*
* @public
*/
export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
2023-04-25 11:01:25 +00:00
currentPageId: TLPageId
followingUserId: string | null
2023-04-25 11:01:25 +00:00
brush: Box2dModel | null
propsForNextShape: TLInstancePropsForNextShape
cursor: TLCursor
scribble: TLScribble | null
isFocusMode: boolean
isDebugMode: boolean
isToolLocked: boolean
exportBackground: boolean
screenBounds: Box2dModel
zoomBrush: Box2dModel | null
}
/** @public */
export type TLInstanceId = ID<TLInstance>
/** @public */
export const instanceTypeValidator: T.Validator<TLInstance> = T.model(
'instance',
T.object({
typeName: T.literal('instance'),
id: idValidator<TLInstanceId>('instance'),
currentPageId: pageIdValidator,
followingUserId: T.string.nullable(),
2023-04-25 11:01:25 +00:00
brush: T.boxModel.nullable(),
propsForNextShape: T.object({
color: colorValidator,
labelColor: colorValidator,
dash: dashValidator,
fill: fillValidator,
size: sizeValidator,
opacity: opacityValidator,
font: fontValidator,
align: alignValidator,
verticalAlign: verticalAlignValidator,
2023-04-25 11:01:25 +00:00
icon: iconValidator,
geo: geoValidator,
arrowheadStart: arrowheadValidator,
arrowheadEnd: arrowheadValidator,
spline: splineValidator,
}),
cursor: cursorValidator,
scribble: scribbleTypeValidator.nullable(),
isFocusMode: T.boolean,
isDebugMode: T.boolean,
isToolLocked: T.boolean,
exportBackground: T.boolean,
screenBounds: T.boxModel,
zoomBrush: T.boxModel.nullable(),
})
)
const Versions = {
AddTransparentExportBgs: 1,
RemoveDialog: 2,
AddToolLockMode: 3,
RemoveExtraPropsForNextShape: 4,
AddLabelColor: 5,
AddFollowingUserId: 6,
RemoveAlignJustify: 7,
AddZoom: 8,
AddVerticalAlign: 9,
AddScribbleDelay: 10,
RemoveUserId: 11,
2023-04-25 11:01:25 +00:00
} as const
export { Versions as instanceTypeVersions }
2023-04-25 11:01:25 +00:00
/** @public */
export const instanceTypeMigrations = defineMigrations({
currentVersion: Versions.RemoveUserId,
2023-04-25 11:01:25 +00:00
migrators: {
[Versions.AddTransparentExportBgs]: {
up: (instance: TLInstance) => {
return { ...instance, exportBackground: true }
},
down: ({ exportBackground: _, ...instance }: TLInstance) => {
return instance
},
},
[Versions.RemoveDialog]: {
up: ({ dialog: _, ...instance }: any) => {
return instance
},
down: (instance: TLInstance) => {
return { ...instance, dialog: null }
},
},
[Versions.AddToolLockMode]: {
up: (instance: TLInstance) => {
return { ...instance, isToolLocked: false }
},
down: ({ isToolLocked: _, ...instance }: TLInstance) => {
return instance
},
},
[Versions.RemoveExtraPropsForNextShape]: {
up: ({ propsForNextShape, ...instance }: any) => {
return {
...instance,
propsForNextShape: Object.fromEntries(
Object.entries(propsForNextShape).filter(([key]) =>
TL_STYLE_TYPES.has(key as TLStyleType)
)
),
}
},
down: (instance: TLInstance) => {
// we can't restore these, so do nothing :/
return instance
},
},
[Versions.AddLabelColor]: {
up: ({ propsForNextShape, ...instance }: any) => {
return {
...instance,
propsForNextShape: {
...propsForNextShape,
labelColor: 'black',
},
}
},
down: (instance: TLInstance) => {
const { labelColor: _, ...rest } = instance.propsForNextShape
return {
...instance,
propsForNextShape: {
...rest,
},
}
},
},
[Versions.AddFollowingUserId]: {
up: (instance: TLInstance) => {
return { ...instance, followingUserId: null }
},
down: ({ followingUserId: _, ...instance }: TLInstance) => {
return instance
},
},
[Versions.RemoveAlignJustify]: {
up: (instance: any) => {
let newAlign = instance.propsForNextShape.align
if (newAlign === 'justify') {
newAlign = 'start'
}
return {
...instance,
propsForNextShape: {
...instance.propsForNextShape,
align: newAlign,
},
}
},
down: (instance: TLInstance) => {
return { ...instance }
},
},
[Versions.AddZoom]: {
up: (instance: TLInstance) => {
return { ...instance, zoomBrush: null }
},
down: ({ zoomBrush: _, ...instance }: TLInstance) => {
return instance
},
},
[Versions.AddVerticalAlign]: {
up: (instance: TLInstance) => {
return {
...instance,
propsForNextShape: {
...instance.propsForNextShape,
verticalAlign: 'middle',
},
}
},
down: (instance: TLInstance) => {
const { verticalAlign: _, ...propsForNextShape } = instance.propsForNextShape
return {
...instance,
propsForNextShape,
}
},
},
[Versions.AddScribbleDelay]: {
up: (instance) => {
if (instance.scribble !== null) {
return { ...instance, scribble: { ...instance.scribble, delay: 0 } }
}
return { ...instance }
},
down: (instance) => {
if (instance.scribble !== null) {
const { delay: _delay, ...rest } = instance.scribble
return { ...instance, scribble: rest }
}
return { ...instance }
},
},
[Versions.RemoveUserId]: {
up: ({ userId: _, ...instance }: any) => {
return instance
},
down: (instance: TLInstance) => {
return { ...instance, userId: 'user:none' }
},
},
2023-04-25 11:01:25 +00:00
},
})
/** @public */
export const InstanceRecordType = createRecordType<TLInstance>('instance', {
2023-04-25 11:01:25 +00:00
migrations: instanceTypeMigrations,
validator: instanceTypeValidator,
derived presence state (#1204) This PR adds - A new `TLInstancePresence` record type, to collect info about the presence state in a particular instance of the editor. This will eventually be used to sync presence data instead of sending instance-only state across the wire. - **Record Scopes** `RecordType` now has a `scope` property which can be one of three things: - `document`: the record belongs to the document and should be synced and persisted freely. Currently: `TLDocument`, `TLPage`, `TLShape`, and `TLAsset` - `instance`: the record belongs to a single instance of the store and should not be synced at all. It should not be persisted directly in most cases, but rather compiled into a kind of 'instance configuration' to store alongside the local document data so that when reopening the associated document it can remember some of the previous instance state. Currently: `TLInstance`, `TLInstancePageState`, `TLCamera`, `TLUser`, `TLUserDocument`, `TLUserPresence` - `presence`: the record belongs to a single instance of the store and should not be persisted, but may be synced using the special presence sync protocol. Currently just `TLInstancePresence` This sets us up for the following changes, which are gonna be pretty high-impact in terms of integrating tldraw into existing systems: - Removing `instanceId` as a config option. Each instance gets a randomly generated ID. - We'd replace it with an `instanceConfig` option that has stuff like selectedIds, camera positions, and so on. Then it's up to library users to get and reinstate the instance config at persistence boundaries. - Removing `userId` as config option, and removing the `TLUser` type altogether. - We might need to revisit when doing auth-enabled features like locking shapes, but I suspect that will be separate.
2023-04-27 18:03:19 +00:00
scope: 'instance',
2023-04-25 11:01:25 +00:00
}).withDefaultProperties(
(): Omit<TLInstance, 'typeName' | 'id' | 'currentPageId'> => ({
2023-04-25 11:01:25 +00:00
followingUserId: null,
propsForNextShape: {
opacity: '1',
color: 'black',
labelColor: 'black',
dash: 'draw',
fill: 'none',
size: 'm',
icon: 'file',
font: 'draw',
align: 'middle',
verticalAlign: 'middle',
2023-04-25 11:01:25 +00:00
geo: 'rectangle',
arrowheadStart: 'none',
arrowheadEnd: 'arrow',
spline: 'line',
},
brush: null,
scribble: null,
cursor: {
type: 'default',
color: 'black',
rotation: 0,
},
isFocusMode: false,
exportBackground: false,
isDebugMode: process.env.NODE_ENV === 'development',
isToolLocked: false,
screenBounds: { x: 0, y: 0, w: 1080, h: 720 },
zoomBrush: null,
})
)