kopia lustrzana https://github.com/Tldraw/Tldraw
518 wiersze
13 KiB
TypeScript
518 wiersze
13 KiB
TypeScript
import { StoreSnapshot } from '@tldraw/store'
|
|
import { TLRecord } from './TLRecord'
|
|
import { Vec2dModel } from './geometry-types'
|
|
|
|
/** @internal */
|
|
export function CLIENT_FIXUP_SCRIPT(persistedStore: StoreSnapshot<TLRecord>) {
|
|
const records = Object.values(persistedStore)
|
|
|
|
for (let i = 0; i < records.length; i++) {
|
|
if (!records[i]) continue
|
|
|
|
const { record } = fixupRecord(records[i])
|
|
|
|
if (record) {
|
|
persistedStore[records[i].id] = record
|
|
} else {
|
|
delete persistedStore[records[i].id]
|
|
}
|
|
}
|
|
|
|
return persistedStore
|
|
}
|
|
|
|
/** @internal */
|
|
export function fixupRecord(oldRecord: TLRecord) {
|
|
const issues: string[] = []
|
|
|
|
let record = JSON.parse(JSON.stringify(oldRecord))
|
|
|
|
switch (record.typeName) {
|
|
case 'user_presence': {
|
|
if (!record.cursor) {
|
|
issues.push('no cursor')
|
|
record.cursor = { x: 0, y: 0 }
|
|
}
|
|
|
|
if (record.cursor.x === undefined || record.cursor.x === null) {
|
|
issues.push('no cursor x')
|
|
record.cursor.x = 0
|
|
}
|
|
if (record.cursor.y === undefined || record.cursor.y === null) {
|
|
issues.push('no cursor y')
|
|
record.cursor.y = 0
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
case 'asset': {
|
|
switch (record.type) {
|
|
case 'image':
|
|
case 'video': {
|
|
if (!record.props) {
|
|
issues.push('no props in asset')
|
|
record.props = {
|
|
w: 100,
|
|
h: 100,
|
|
name: 'old_asset',
|
|
isAnimated: false,
|
|
mimeType: null,
|
|
src: null,
|
|
}
|
|
}
|
|
|
|
if (!record.props.mimeType) {
|
|
issues.push('no mimeType in asset props')
|
|
record.props.mimeType = 'image/png'
|
|
}
|
|
|
|
if (!record.props.src) {
|
|
issues.push('no src in asset props')
|
|
record.props.src = ''
|
|
}
|
|
|
|
if (record.props.isAnimated == null) {
|
|
issues.push('no isAnimated in asset props')
|
|
record.props.isAnimated = false
|
|
}
|
|
|
|
if (record.props.name === undefined) {
|
|
record.props.name = 'asset'
|
|
}
|
|
|
|
if ('width' in record) {
|
|
issues.push('width in asset')
|
|
record.props.w = record.width
|
|
delete record.width
|
|
}
|
|
if (
|
|
'width' in record.props &&
|
|
typeof record.props.width === 'number' &&
|
|
record.props.width
|
|
) {
|
|
issues.push('no w in asset props')
|
|
record.props.w = record.props.width
|
|
delete record.props.width
|
|
}
|
|
|
|
if ('height' in record) {
|
|
issues.push('height in asset')
|
|
record.props.h = record.height
|
|
delete record.height
|
|
}
|
|
|
|
if (
|
|
'height' in record.props &&
|
|
typeof record.props.height === 'number' &&
|
|
record.props.height
|
|
) {
|
|
issues.push('no h in asset props')
|
|
record.props.h = record.props.height
|
|
delete record.props.height
|
|
}
|
|
if (!record.props.w) {
|
|
issues.push('no w in asset props')
|
|
record.props.w = 100
|
|
}
|
|
if (!record.props.h) {
|
|
issues.push('no h in asset props')
|
|
record.props.h = 100
|
|
}
|
|
|
|
if ('src' in record) {
|
|
issues.push('src in asset')
|
|
record.props.src = record.src
|
|
delete record.src
|
|
}
|
|
|
|
if ('name' in record) {
|
|
issues.push('name in asset')
|
|
delete record.name
|
|
}
|
|
break
|
|
}
|
|
case 'bookmark':
|
|
if (!record.props) {
|
|
issues.push('no asset props')
|
|
record.props = {
|
|
title: '',
|
|
description: '',
|
|
image: '',
|
|
src: 'url' in record && typeof record.url === 'string' ? record.url : '',
|
|
}
|
|
}
|
|
|
|
if (!record.props.title) {
|
|
issues.push('no title in bookmark asset props')
|
|
record.props.title = ''
|
|
}
|
|
if (!record.props.description) {
|
|
issues.push('no description in bookmark asset props')
|
|
record.props.description = ''
|
|
}
|
|
if (!record.props.image) {
|
|
issues.push('no image in bookmark asset props')
|
|
record.props.image = ''
|
|
}
|
|
|
|
if ('src' in record) {
|
|
issues.push('leftover src in bookmark asset')
|
|
delete record.src
|
|
}
|
|
if ('width' in record) {
|
|
issues.push('leftover width in bookmark asset')
|
|
delete record.width
|
|
}
|
|
if ('height' in record) {
|
|
issues.push('leftover height in bookmark asset')
|
|
delete record.height
|
|
}
|
|
if ('name' in record) {
|
|
issues.push('leftover name in bookmark asset')
|
|
delete record.name
|
|
}
|
|
|
|
if ('meta' in record) {
|
|
delete record.meta
|
|
}
|
|
|
|
break
|
|
}
|
|
break
|
|
}
|
|
case 'camera': {
|
|
if (record.x === undefined || record.x === null) {
|
|
issues.push('no x in camera')
|
|
record.x = 0
|
|
}
|
|
if (record.y === undefined || record.y === null) {
|
|
issues.push('no y in camera')
|
|
record.y = 0
|
|
}
|
|
break
|
|
}
|
|
case 'instance': {
|
|
if ('props' in record) {
|
|
issues.push('leftover props in instance')
|
|
delete record.props
|
|
}
|
|
|
|
if (record.isToolLocked === undefined) {
|
|
issues.push('no isToolLocked in instance')
|
|
record.isToolLocked = false
|
|
}
|
|
|
|
if (record.propsForNextShape === undefined) {
|
|
issues.push('no props in instance')
|
|
record.propsForNextShape = {
|
|
opacity: '1',
|
|
color: 'black',
|
|
dash: 'draw',
|
|
fill: 'none',
|
|
size: 'm',
|
|
icon: 'file',
|
|
font: 'draw',
|
|
align: 'middle',
|
|
geo: 'rectangle',
|
|
arrowheadStart: 'none',
|
|
arrowheadEnd: 'arrow',
|
|
spline: 'line',
|
|
}
|
|
}
|
|
|
|
if ('url' in record.propsForNextShape) {
|
|
issues.push('leftover url in instance.propsForNextShape')
|
|
delete record.propsForNextShape.url
|
|
}
|
|
|
|
if ('lang' in record.propsForNextShape) {
|
|
issues.push('leftover lang in instance.propsForNextShape')
|
|
delete record.propsForNextShape.lang
|
|
}
|
|
|
|
if (record.exportBackground === undefined) {
|
|
issues.push(`no export background in ${record.typeName}`)
|
|
record.exportBackground = false
|
|
}
|
|
if (record.brush === undefined) {
|
|
issues.push(`no brush in ${record.typeName}`)
|
|
record.brush = null
|
|
}
|
|
if (record.scribble === undefined) {
|
|
issues.push(`no scribble in ${record.typeName}`)
|
|
record.scribble = null
|
|
}
|
|
|
|
if (record.dialog !== undefined) {
|
|
issues.push(`no dialog in ${record.typeName}`)
|
|
delete record.dialog
|
|
}
|
|
|
|
if (record.screenBounds === undefined) {
|
|
issues.push(`no screen bounds in ${record.typeName}`)
|
|
record.screenBounds = { x: 0, y: 0, w: 1080, h: 720 }
|
|
}
|
|
|
|
break
|
|
}
|
|
case 'user': {
|
|
if (!record.name) {
|
|
issues.push(`no name in user`)
|
|
record.name = 'User'
|
|
}
|
|
if (!record.locale) {
|
|
issues.push(`no locale in user`)
|
|
record.locale = 'en'
|
|
}
|
|
if ('cursor' in record) {
|
|
issues.push('leftover cursor in user')
|
|
delete record.cursor
|
|
}
|
|
if ('color' in record) {
|
|
issues.push('leftover color in user')
|
|
delete record.color
|
|
}
|
|
if ('brush' in record) {
|
|
issues.push('leftover brush in user')
|
|
delete record.brush
|
|
}
|
|
if ('selectedIds' in record) {
|
|
issues.push('leftover selectedIds in user')
|
|
delete record.selectedIds
|
|
}
|
|
if ('scribble' in record) {
|
|
issues.push('leftover scribble in user')
|
|
delete record.scribble
|
|
}
|
|
if ('currentPageId' in record) {
|
|
issues.push('leftover currentPageId in user')
|
|
delete record.currentPageId
|
|
}
|
|
break
|
|
}
|
|
case 'user_document': {
|
|
if (record.isMobileMode === undefined) {
|
|
issues.push(`no ismobilemode in user document`)
|
|
record.isMobileMode = false
|
|
}
|
|
|
|
if (record.isSnapMode === undefined) {
|
|
issues.push(`no issnapmode in user document`)
|
|
record.isSnapMode = false
|
|
}
|
|
break
|
|
}
|
|
case 'shape': {
|
|
if ('url' in record) {
|
|
delete record.url
|
|
}
|
|
|
|
if (record.x === undefined || record.x === null) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.x = 0
|
|
}
|
|
if (record.y === undefined || record.y === null) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.y = 0
|
|
}
|
|
|
|
if (record.type === 'image') {
|
|
if (record.props.playing === undefined) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.playing = false
|
|
}
|
|
|
|
if ('loaded' in record.props) {
|
|
delete record.props.loaded
|
|
}
|
|
}
|
|
|
|
if (record.type === 'arrow') {
|
|
if (record.props.start.type === 'binding') {
|
|
if (
|
|
record.props.start.normalizedAnchor.x === undefined ||
|
|
record.props.start.normalizedAnchor.x === null
|
|
) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.start.normalizedAnchor.x = 0
|
|
}
|
|
if (
|
|
record.props.start.normalizedAnchor.y === undefined ||
|
|
record.props.start.normalizedAnchor.y === null
|
|
) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.start.normalizedAnchor.y = 0
|
|
}
|
|
} else {
|
|
if (record.props.start.x === undefined || record.props.start.x === null) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.start.x = 0
|
|
}
|
|
if (record.props.start.y === undefined || record.props.start.y === null) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.start.y = 0
|
|
}
|
|
if ('boundShapeId' in record.props.start) {
|
|
issues.push(`leftover bound shape id in arrow`)
|
|
delete record.props.start.boundShapeId
|
|
}
|
|
if ('normalizedAnchor' in record.props.start) {
|
|
issues.push(`leftover normalize anchor in arrow`)
|
|
delete record.props.start.normalizedAnchor
|
|
}
|
|
if ('isExact' in record.props.start) {
|
|
issues.push(`leftover isExact in arrow`)
|
|
delete record.props.start.isExact
|
|
}
|
|
}
|
|
|
|
if (record.props.end.type === 'binding') {
|
|
if (
|
|
record.props.end.normalizedAnchor.x === undefined ||
|
|
record.props.end.normalizedAnchor.x === null
|
|
) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.end.normalizedAnchor.x = 0
|
|
}
|
|
if (
|
|
record.props.end.normalizedAnchor.y === undefined ||
|
|
record.props.end.normalizedAnchor.y === null
|
|
) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.end.normalizedAnchor.y = 0
|
|
}
|
|
} else {
|
|
if (record.props.end.x === undefined || record.props.end.x === null) {
|
|
issues.push(`no x in arrow end`)
|
|
record.props.end.x = 0
|
|
}
|
|
if (record.props.end.y === undefined || record.props.end.y === null) {
|
|
issues.push(`no y in arrow end`)
|
|
record.props.end.y = 0
|
|
}
|
|
if ('boundShapeId' in record.props.end) {
|
|
issues.push(`leftover bound shape id in arrow`)
|
|
delete record.props.end.boundShapeId
|
|
}
|
|
if ('normalizedAnchor' in record.props.end) {
|
|
issues.push(`leftover normalize anchor in arrow`)
|
|
delete record.props.end.normalizedAnchor
|
|
}
|
|
if ('isExact' in record.props.end) {
|
|
issues.push(`leftover isExact in arrow`)
|
|
delete record.props.end.isExact
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
record.type === 'note' ||
|
|
record.type === 'video' ||
|
|
record.type === 'image' ||
|
|
record.type === 'geo' ||
|
|
record.type === 'bookmark'
|
|
) {
|
|
if (record.props.url === undefined) {
|
|
issues.push(`missing url prop in ${record.type} shape`)
|
|
record.props.url = ''
|
|
}
|
|
}
|
|
|
|
if (record.type === 'bookmark') {
|
|
if (record.props.assetId === undefined) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.assetId = null
|
|
}
|
|
|
|
if ('src' in record) {
|
|
issues.push(`leftover src in bookmark`)
|
|
delete record.src
|
|
}
|
|
}
|
|
|
|
if (record.type === 'geo') {
|
|
if ('width' in record.props) {
|
|
issues.push(`leftover width in geo`)
|
|
delete record.props.width
|
|
}
|
|
if ('height' in record.props) {
|
|
issues.push(`leftover height in geo`)
|
|
delete record.props.height
|
|
}
|
|
}
|
|
|
|
if (record.type === 'draw') {
|
|
if (record.props.segments === undefined) {
|
|
issues.push(`some bug in ${record.typeName} ${record.type}`)
|
|
record.props.segments = [
|
|
{
|
|
points: [
|
|
{ x: 0, y: 0, z: 0.5 },
|
|
{ x: 1, y: 1, z: 0.5 },
|
|
],
|
|
type: 'free',
|
|
},
|
|
]
|
|
}
|
|
for (const segment of record.props.segments) {
|
|
for (const point of segment.points) {
|
|
if (point.x === undefined || point.y === null) {
|
|
issues.push(`some bug in ${record.typeName}`)
|
|
point.x = 0
|
|
}
|
|
|
|
if (point.y === undefined || point.y === null) {
|
|
issues.push(`some bug in ${record.typeName}`)
|
|
point.y = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
if ('points' in record.props) {
|
|
delete record.props.points
|
|
}
|
|
}
|
|
|
|
if (record.type === 'bookmark') {
|
|
if ('loaded' in record.props) {
|
|
issues.push('leftover loaded in bookmark')
|
|
delete record.props.loaded
|
|
}
|
|
}
|
|
|
|
if (record.type === 'draw') {
|
|
if ('points' in record.props && record.props.segments === undefined) {
|
|
record.props.segments = [{ type: 'free', points: record.props.points as Vec2dModel[] }]
|
|
}
|
|
}
|
|
|
|
if (record.type === 'image') {
|
|
if (record.props.w < 1) {
|
|
record.props.w = 1
|
|
issues.push(`zero w image in ${record.typeName}`)
|
|
}
|
|
if (record.props.h < 1) {
|
|
record.props.h = 1
|
|
issues.push(`zero h image in ${record.typeName}`)
|
|
}
|
|
}
|
|
|
|
if (record.type === 'embed') {
|
|
if ('loaded' in record.props) {
|
|
issues.push('leftover loaded in embed')
|
|
delete record.props.loaded
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
case undefined: {
|
|
record = null
|
|
}
|
|
}
|
|
|
|
// Assign the new record into the store
|
|
return { record, issues }
|
|
}
|