kopia lustrzana https://github.com/Tldraw/Tldraw
replace undefined with nulls
rodzic
97ba2443e8
commit
5f3ee95cbf
|
@ -34,7 +34,7 @@ export interface TLHandle {
|
|||
index: number
|
||||
point: number[]
|
||||
canBind?: boolean
|
||||
bindingId?: string
|
||||
bindingId: string | null
|
||||
}
|
||||
|
||||
export interface TLShape {
|
||||
|
|
|
@ -30,7 +30,8 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
pathCache = new WeakMap<ArrowShape, string>()
|
||||
|
||||
defaultProps = {
|
||||
id: 'id',
|
||||
id: 'arrow_id',
|
||||
nonce: 1,
|
||||
type: TLDrawShapeType.Arrow as const,
|
||||
name: 'Arrow',
|
||||
parentId: 'page',
|
||||
|
|
|
@ -22,7 +22,8 @@ export class Draw extends TLDrawShapeUtil<DrawShape> {
|
|||
polygonCache = new WeakMap<DrawShape['points'], string>([])
|
||||
|
||||
defaultProps: DrawShape = {
|
||||
id: 'id',
|
||||
id: 'draw_id',
|
||||
nonce: 1,
|
||||
type: TLDrawShapeType.Draw as const,
|
||||
name: 'Draw',
|
||||
parentId: 'page',
|
||||
|
|
|
@ -22,7 +22,8 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape> {
|
|||
canBind = true
|
||||
|
||||
defaultProps = {
|
||||
id: 'id',
|
||||
id: 'ellipse_id',
|
||||
nonce: 1,
|
||||
type: TLDrawShapeType.Ellipse as const,
|
||||
name: 'Ellipse',
|
||||
parentId: 'page',
|
||||
|
|
|
@ -23,7 +23,8 @@ export class Group extends TLDrawShapeUtil<GroupShape> {
|
|||
pathCache = new WeakMap<number[], string>([])
|
||||
|
||||
defaultProps: GroupShape = {
|
||||
id: 'id',
|
||||
id: 'group_id',
|
||||
nonce: 1,
|
||||
type: TLDrawShapeType.Group as const,
|
||||
name: 'Group',
|
||||
parentId: 'page',
|
||||
|
|
|
@ -23,7 +23,8 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape> {
|
|||
pathCache = new WeakMap<number[], string>([])
|
||||
|
||||
defaultProps: RectangleShape = {
|
||||
id: 'id',
|
||||
id: 'rectangle_id',
|
||||
nonce: 1,
|
||||
type: TLDrawShapeType.Rectangle as const,
|
||||
name: 'Rectangle',
|
||||
parentId: 'page',
|
||||
|
|
|
@ -66,7 +66,8 @@ export class Text extends TLDrawShapeUtil<TextShape> {
|
|||
pathCache = new WeakMap<number[], string>([])
|
||||
|
||||
defaultProps = {
|
||||
id: 'id',
|
||||
id: 'text_id',
|
||||
nonce: 1,
|
||||
type: TLDrawShapeType.Text as const,
|
||||
name: 'Text',
|
||||
parentId: 'page',
|
||||
|
|
|
@ -23,6 +23,7 @@ Array [
|
|||
"childIndex": 1,
|
||||
"id": "rect1",
|
||||
"name": "Rectangle",
|
||||
"nonce": 1487076708000,
|
||||
"parentId": "page1",
|
||||
"point": Array [
|
||||
0,
|
||||
|
@ -81,6 +82,7 @@ Array [
|
|||
"childIndex": 1,
|
||||
"id": "rect2",
|
||||
"name": "Rectangle",
|
||||
"nonce": 1487076708000,
|
||||
"parentId": "page1",
|
||||
"point": Array [
|
||||
0,
|
||||
|
|
|
@ -5,11 +5,11 @@ import type { TLDrawShape, Data, TLDrawCommand } from '~types'
|
|||
export function create(data: Data, shapes: TLDrawShape[]): TLDrawCommand {
|
||||
const { currentPageId } = data.appState
|
||||
|
||||
const beforeShapes: Record<string, Patch<TLDrawShape> | undefined> = {}
|
||||
const afterShapes: Record<string, Patch<TLDrawShape> | undefined> = {}
|
||||
const beforeShapes: Record<string, Patch<TLDrawShape> | null> = {}
|
||||
const afterShapes: Record<string, Patch<TLDrawShape> | null> = {}
|
||||
|
||||
shapes.forEach((shape) => {
|
||||
beforeShapes[shape.id] = undefined
|
||||
beforeShapes[shape.id] = null
|
||||
afterShapes[shape.id] = shape
|
||||
})
|
||||
|
||||
|
|
|
@ -31,10 +31,10 @@ export function duplicatePage(data: Data, pageId: string): TLDrawCommand {
|
|||
},
|
||||
document: {
|
||||
pages: {
|
||||
[newId]: undefined,
|
||||
[newId]: null,
|
||||
},
|
||||
pageStates: {
|
||||
[newId]: undefined,
|
||||
[newId]: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -53,10 +53,10 @@ export function duplicatePage(data: Data, pageId: string): TLDrawCommand {
|
|||
selectedIds: [],
|
||||
camera: { point: [-window.innerWidth / 2, -window.innerHeight / 2], zoom: 1 },
|
||||
currentParentId: newId,
|
||||
editingId: undefined,
|
||||
bindingId: undefined,
|
||||
hoveredId: undefined,
|
||||
pointedId: undefined,
|
||||
editingId: null,
|
||||
bindingId: null,
|
||||
hoveredId: null,
|
||||
pointedId: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -31,7 +31,7 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
|||
.filter((shape) => !ids.includes(shape.parentId))
|
||||
.forEach((shape) => {
|
||||
const duplicatedId = Utils.uniqueId()
|
||||
before.shapes[duplicatedId] = undefined
|
||||
before.shapes[duplicatedId] = null
|
||||
|
||||
after.shapes[duplicatedId] = {
|
||||
...Utils.deepClone(shape),
|
||||
|
@ -68,7 +68,7 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
|||
const child = TLDR.getShape(data, childId, currentPageId)
|
||||
const duplicatedId = Utils.uniqueId()
|
||||
const duplicatedParentId = duplicateMap[shape.id]
|
||||
before.shapes[duplicatedId] = undefined
|
||||
before.shapes[duplicatedId] = null
|
||||
after.shapes[duplicatedId] = {
|
||||
...Utils.deepClone(child),
|
||||
id: duplicatedId,
|
||||
|
@ -102,7 +102,7 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
|||
toId: duplicateMap[binding.toId],
|
||||
}
|
||||
|
||||
before.bindings[duplicatedBindingId] = undefined
|
||||
before.bindings[duplicatedBindingId] = null
|
||||
after.bindings[duplicatedBindingId] = duplicatedBinding
|
||||
|
||||
// Change the duplicated shape's handle so that it reference
|
||||
|
@ -119,7 +119,7 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
|||
const boundShape = after.shapes[duplicateMap[binding.fromId]]
|
||||
Object.values(boundShape!.handles!).forEach((handle) => {
|
||||
if (handle!.bindingId === binding.id) {
|
||||
handle!.bindingId = undefined
|
||||
handle!.bindingId = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,11 +10,11 @@ export function group(
|
|||
groupId: string,
|
||||
pageId: string
|
||||
): TLDrawCommand | undefined {
|
||||
const beforeShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||
const afterShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||
const beforeShapes: Record<string, Patch<TLDrawShape | null>> = {}
|
||||
const afterShapes: Record<string, Patch<TLDrawShape | null>> = {}
|
||||
|
||||
const beforeBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||
const afterBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||
const beforeBindings: Record<string, Patch<TLDrawBinding | null>> = {}
|
||||
const afterBindings: Record<string, Patch<TLDrawBinding | null>> = {}
|
||||
|
||||
const idsToGroup = [...ids]
|
||||
const shapesToGroup: TLDrawShape[] = []
|
||||
|
@ -24,7 +24,7 @@ export function group(
|
|||
// Collect all of the shapes to group (and their ids)
|
||||
for (const id of ids) {
|
||||
const shape = TLDR.getShape(data, id, pageId)
|
||||
if (shape.children === undefined) {
|
||||
if (!shape.children) {
|
||||
shapesToGroup.push(shape)
|
||||
} else {
|
||||
otherEffectedGroups.push(shape)
|
||||
|
@ -74,7 +74,7 @@ export function group(
|
|||
const groupBounds = Utils.getCommonBounds(shapesToGroup.map((shape) => TLDR.getBounds(shape)))
|
||||
|
||||
// Create the group
|
||||
beforeShapes[groupId] = undefined
|
||||
beforeShapes[groupId] = null
|
||||
|
||||
afterShapes[groupId] = TLDR.getShapeUtils({ type: TLDrawShapeType.Group } as TLDrawShape).create({
|
||||
id: groupId,
|
||||
|
@ -119,7 +119,7 @@ export function group(
|
|||
// If the parent has no children, remove it
|
||||
if (nextChildren.length === 0) {
|
||||
beforeShapes[shape.id] = shape
|
||||
afterShapes[shape.id] = undefined
|
||||
afterShapes[shape.id] = null
|
||||
|
||||
// And if that parent is part of a different group, mark it for cleanup
|
||||
// (This is necessary only when we implement nested groups.)
|
||||
|
@ -148,10 +148,10 @@ export function group(
|
|||
Object.values(page.bindings).forEach((binding) => {
|
||||
for (const id of [binding.toId, binding.fromId]) {
|
||||
// If the binding references a deleted shape...
|
||||
if (afterShapes[id] === undefined) {
|
||||
if (!afterShapes[id]) {
|
||||
// Delete this binding
|
||||
beforeBindings[binding.id] = binding
|
||||
afterBindings[binding.id] = undefined
|
||||
afterBindings[binding.id] = null
|
||||
|
||||
// Let's also look each the bound shape...
|
||||
const shape = TLDR.getShape(data, id, pageId)
|
||||
|
@ -177,7 +177,7 @@ export function group(
|
|||
...afterShapes[id],
|
||||
handles: {
|
||||
...afterShapes[id]?.handles,
|
||||
[handle.id]: { bindingId: undefined },
|
||||
[handle.id]: { bindingId: null },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export function moveToPage(
|
|||
.forEach((shape) => {
|
||||
movingShapeIds.add(shape.id)
|
||||
shapesToMove.add(shape)
|
||||
if (shape.children !== undefined) {
|
||||
if (shape.children) {
|
||||
shape.children.forEach((childId) => {
|
||||
movingShapeIds.add(childId)
|
||||
shapesToMove.add(TLDR.getShape(data, childId, fromPageId))
|
||||
|
@ -61,10 +61,10 @@ export function moveToPage(
|
|||
movingShapes.forEach((shape, i) => {
|
||||
// Remove the shape from the fromPage
|
||||
fromPage.before.shapes[shape.id] = shape
|
||||
fromPage.after.shapes[shape.id] = undefined
|
||||
fromPage.after.shapes[shape.id] = null
|
||||
|
||||
// But the moved shape on the "to" page
|
||||
toPage.before.shapes[shape.id] = undefined
|
||||
toPage.before.shapes[shape.id] = null
|
||||
toPage.after.shapes[shape.id] = shape
|
||||
|
||||
// If the shape's parent isn't moving too, reparent the shape to
|
||||
|
@ -98,7 +98,7 @@ export function moveToPage(
|
|||
// Always delete the binding from the from page
|
||||
|
||||
fromPage.before.bindings[binding.id] = binding
|
||||
fromPage.after.bindings[binding.id] = undefined
|
||||
fromPage.after.bindings[binding.id] = null
|
||||
|
||||
// Delete the reference from the binding's fromShape
|
||||
|
||||
|
@ -110,7 +110,7 @@ export function moveToPage(
|
|||
|
||||
if (shouldCopy) {
|
||||
// Just move the binding to the new page
|
||||
toPage.before.bindings[binding.id] = undefined
|
||||
toPage.before.bindings[binding.id] = null
|
||||
toPage.after.bindings[binding.id] = binding
|
||||
} else {
|
||||
if (movingShapeIds.has(binding.fromId)) {
|
||||
|
|
|
@ -36,6 +36,7 @@ describe('Toggle decoration command', () => {
|
|||
TLDR.getShapeUtils({ type: 'arrow' } as TLDrawShape).create({
|
||||
id: 'arrow1',
|
||||
parentId: 'page1',
|
||||
nonce: Date.now(),
|
||||
})
|
||||
)
|
||||
.select('arrow1')
|
||||
|
|
|
@ -34,7 +34,7 @@ export function translate(data: Data, ids: string[], delta: number[]): TLDrawCom
|
|||
|
||||
bindingsToDelete.forEach((binding) => {
|
||||
before.bindings[binding.id] = binding
|
||||
after.bindings[binding.id] = undefined
|
||||
after.bindings[binding.id] = null
|
||||
|
||||
for (const id of [binding.toId, binding.fromId]) {
|
||||
// Let's also look at the bound shape...
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import type { GroupShape, TLDrawBinding, TLDrawShape } from '~types'
|
||||
import type { Data, TLDrawCommand } from '~types'
|
||||
import type { Data, TLDrawCommand, Patch } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
import type { Patch } from 'rko'
|
||||
|
||||
export function ungroup(data: Data, groupId: string, pageId: string): TLDrawCommand | undefined {
|
||||
const beforeShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||
const afterShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
||||
const beforeShapes: Record<string, Patch<TLDrawShape> | null> = {}
|
||||
const afterShapes: Record<string, Patch<TLDrawShape> | null> = {}
|
||||
|
||||
const beforeBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||
const afterBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
||||
const beforeBindings: Record<string, Patch<TLDrawBinding> | null> = {}
|
||||
const afterBindings: Record<string, Patch<TLDrawBinding> | null> = {}
|
||||
|
||||
// The group shape
|
||||
const groupShape = TLDR.getShape<GroupShape>(data, groupId, pageId)
|
||||
|
@ -36,7 +35,7 @@ export function ungroup(data: Data, groupId: string, pageId: string): TLDrawComm
|
|||
|
||||
// Remove the group shape
|
||||
beforeShapes[groupId] = groupShape
|
||||
afterShapes[groupId] = undefined
|
||||
afterShapes[groupId] = null
|
||||
|
||||
// Reparent shapes to the page
|
||||
sortedShapes.forEach((shape, index) => {
|
||||
|
@ -59,10 +58,10 @@ export function ungroup(data: Data, groupId: string, pageId: string): TLDrawComm
|
|||
.forEach((binding) => {
|
||||
for (const id of [binding.toId, binding.fromId]) {
|
||||
// If the binding references the deleted group...
|
||||
if (afterShapes[id] === undefined) {
|
||||
if (!afterShapes[id]) {
|
||||
// Delete the binding
|
||||
beforeBindings[binding.id] = binding
|
||||
afterBindings[binding.id] = undefined
|
||||
afterBindings[binding.id] = null
|
||||
|
||||
// Let's also look each the bound shape...
|
||||
const shape = TLDR.getShape(data, id, pageId)
|
||||
|
@ -88,7 +87,7 @@ export function ungroup(data: Data, groupId: string, pageId: string): TLDrawComm
|
|||
...afterShapes[id],
|
||||
handles: {
|
||||
...afterShapes[id]?.handles,
|
||||
[handle.id]: { bindingId: undefined },
|
||||
[handle.id]: { bindingId: null },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,16 +22,16 @@ export function removeShapesFromPage(data: Data, ids: string[], pageId: string)
|
|||
deletedIds.add(id)
|
||||
const shape = TLDR.getShape(data, id, pageId)
|
||||
before.shapes[id] = shape
|
||||
after.shapes[id] = undefined
|
||||
after.shapes[id] = null
|
||||
|
||||
// Also delete the shape's children
|
||||
|
||||
if (shape.children !== undefined) {
|
||||
if (shape.children) {
|
||||
shape.children.forEach((childId) => {
|
||||
deletedIds.add(childId)
|
||||
const child = TLDR.getShape(data, childId, pageId)
|
||||
before.shapes[childId] = child
|
||||
after.shapes[childId] = undefined
|
||||
after.shapes[childId] = null
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -57,10 +57,10 @@ export function removeShapesFromPage(data: Data, ids: string[], pageId: string)
|
|||
.forEach((binding) => {
|
||||
for (const id of [binding.toId, binding.fromId]) {
|
||||
// If the binding references a deleted shape...
|
||||
if (after.shapes[id] === undefined) {
|
||||
if (!after.shapes[id]) {
|
||||
// Delete this binding
|
||||
before.bindings[binding.id] = binding
|
||||
after.bindings[binding.id] = undefined
|
||||
after.bindings[binding.id] = null
|
||||
|
||||
// Let's also look each the bound shape...
|
||||
const shape = TLDR.getShape(data, id, pageId)
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
Session,
|
||||
TLDrawStatus,
|
||||
} from '~types'
|
||||
import { Vec, Utils, TLHandle } from '@tldraw/core'
|
||||
import { Vec, Utils } from '@tldraw/core'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class ArrowSession implements Session {
|
||||
|
@ -40,7 +40,7 @@ export class ArrowSession implements Session {
|
|||
this.initialBinding = page.bindings[initialBindingId]
|
||||
} else {
|
||||
// Explicitly set this handle to undefined, so that it gets deleted on undo
|
||||
this.initialShape.handles[this.handleId].bindingId = undefined
|
||||
this.initialShape.handles[this.handleId].bindingId = null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ export class ArrowSession implements Session {
|
|||
|
||||
// If nothing changes, we want this to be the same object reference as
|
||||
// before. If it does change, we'll redefine this later on.
|
||||
let nextBindings: Record<string, TLDrawBinding | undefined> = page.bindings
|
||||
let nextBindings: Record<string, TLDrawBinding | null> = page.bindings
|
||||
|
||||
// If the handle can bind, then we need to search bindable shapes for
|
||||
// a binding. If the handle already has a binding, then we will either
|
||||
|
@ -99,8 +99,8 @@ export class ArrowSession implements Session {
|
|||
// metaKey
|
||||
// )
|
||||
|
||||
let binding: ArrowBinding | undefined = undefined
|
||||
let target: TLDrawShape | undefined = undefined
|
||||
let binding: ArrowBinding | null = null
|
||||
let target: TLDrawShape | null = null
|
||||
|
||||
// Alt key skips binding
|
||||
if (!altKey) {
|
||||
|
@ -153,21 +153,21 @@ export class ArrowSession implements Session {
|
|||
}
|
||||
|
||||
// If we didn't find a target...
|
||||
if (binding === undefined) {
|
||||
if (!binding) {
|
||||
this.didBind = false
|
||||
|
||||
if (handle.bindingId) {
|
||||
nextBindings = { ...nextBindings }
|
||||
nextBindings[handle.bindingId] = undefined
|
||||
nextBindings[handle.bindingId] = null
|
||||
}
|
||||
nextShape.handles[handleId].bindingId = undefined
|
||||
nextShape.handles[handleId].bindingId = null
|
||||
} else if (target) {
|
||||
this.didBind = true
|
||||
nextBindings = { ...nextBindings }
|
||||
|
||||
if (handle.bindingId && handle.bindingId !== this.newBindingId) {
|
||||
nextBindings[handle.bindingId] = undefined
|
||||
nextShape.handles[handleId].bindingId = undefined
|
||||
nextBindings[handle.bindingId] = null
|
||||
nextShape.handles[handleId].bindingId = null
|
||||
}
|
||||
|
||||
// If we found a new binding, add its id to the shape's handle...
|
||||
|
@ -254,8 +254,8 @@ export class ArrowSession implements Session {
|
|||
const { initialShape, initialBinding, handleId } = this
|
||||
const page = TLDR.getPage(data, data.appState.currentPageId)
|
||||
|
||||
const beforeBindings: Partial<Record<string, TLDrawBinding>> = {}
|
||||
const afterBindings: Partial<Record<string, TLDrawBinding>> = {}
|
||||
const beforeBindings: Partial<Record<string, TLDrawBinding | null>> = {}
|
||||
const afterBindings: Partial<Record<string, TLDrawBinding | null>> = {}
|
||||
|
||||
const currentShape = TLDR.getShape<ArrowShape>(
|
||||
data,
|
||||
|
@ -266,11 +266,11 @@ export class ArrowSession implements Session {
|
|||
|
||||
if (initialBinding) {
|
||||
beforeBindings[initialBinding.id] = initialBinding
|
||||
afterBindings[initialBinding.id] = undefined
|
||||
afterBindings[initialBinding.id] = null
|
||||
}
|
||||
|
||||
if (currentBindingId) {
|
||||
beforeBindings[currentBindingId] = undefined
|
||||
beforeBindings[currentBindingId] = null
|
||||
afterBindings[currentBindingId] = page.bindings[currentBindingId]
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ export function getBrushSnapshot(data: Data) {
|
|||
(shape) =>
|
||||
!(
|
||||
shape.isHidden ||
|
||||
shape.children !== undefined ||
|
||||
shape.children ||
|
||||
selectedIds.includes(shape.id) ||
|
||||
selectedIds.includes(shape.parentId)
|
||||
)
|
||||
|
|
|
@ -11,19 +11,11 @@ describe('Draw session', () => {
|
|||
expect(tlstate.getShape('draw1')).toBe(undefined)
|
||||
|
||||
tlstate
|
||||
.create({
|
||||
.createShapes({
|
||||
id: 'draw1',
|
||||
parentId: 'page1',
|
||||
name: 'Draw',
|
||||
childIndex: 5,
|
||||
type: TLDrawShapeType.Draw,
|
||||
point: [32, 32],
|
||||
points: [[0, 0]],
|
||||
style: {
|
||||
dash: DashStyle.Draw,
|
||||
size: SizeStyle.Medium,
|
||||
color: ColorStyle.Blue,
|
||||
},
|
||||
})
|
||||
.select('draw1')
|
||||
.startDrawSession('draw1', [0, 0])
|
||||
|
|
|
@ -153,7 +153,7 @@ export function getRotateSnapshot(data: Data) {
|
|||
boundsRotation: pageState.boundsRotation || 0,
|
||||
commonBoundsCenter,
|
||||
initialShapes: initialShapes
|
||||
.filter((shape) => shape.children === undefined)
|
||||
.filter((shape) => !shape.children)
|
||||
.map((shape) => {
|
||||
const bounds = TLDR.getBounds(shape)
|
||||
const center = Utils.getBoundsCenter(bounds)
|
||||
|
|
|
@ -34,7 +34,7 @@ export class TranslateSession implements Session {
|
|||
|
||||
const nextBindings: Patch<Record<string, TLDrawBinding>> = {}
|
||||
|
||||
bindingsToDelete.forEach((binding) => (nextBindings[binding.id] = undefined))
|
||||
bindingsToDelete.forEach((binding) => (nextBindings[binding.id] = null))
|
||||
|
||||
return {
|
||||
document: {
|
||||
|
@ -134,11 +134,11 @@ export class TranslateSession implements Session {
|
|||
|
||||
// Delete the bindings
|
||||
|
||||
bindingsToDelete.forEach((binding) => (nextBindings[binding.id] = undefined))
|
||||
bindingsToDelete.forEach((binding) => (nextBindings[binding.id] = null))
|
||||
|
||||
// Delete the clones
|
||||
clones.forEach((clone) => {
|
||||
nextShapes[clone.id] = undefined
|
||||
nextShapes[clone.id] = null
|
||||
if (clone.parentId !== currentPageId) {
|
||||
nextShapes[clone.parentId] = {
|
||||
...nextShapes[clone.parentId],
|
||||
|
@ -156,7 +156,7 @@ export class TranslateSession implements Session {
|
|||
|
||||
// Delete the cloned bindings
|
||||
for (const binding of this.snapshot.clonedBindings) {
|
||||
nextBindings[binding.id] = undefined
|
||||
nextBindings[binding.id] = null
|
||||
}
|
||||
|
||||
// Set selected ids
|
||||
|
@ -195,8 +195,8 @@ export class TranslateSession implements Session {
|
|||
cancel = (data: Data) => {
|
||||
const { initialShapes, clones, clonedBindings, bindingsToDelete } = this.snapshot
|
||||
|
||||
const nextBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
|
||||
const nextShapes: Record<string, Partial<TLDrawShape> | undefined> = {}
|
||||
const nextBindings: Record<string, Partial<TLDrawBinding> | null> = {}
|
||||
const nextShapes: Record<string, Partial<TLDrawShape> | null> = {}
|
||||
const nextPageState: Partial<TLPageState> = {}
|
||||
|
||||
// Put back any deleted bindings
|
||||
|
@ -206,10 +206,10 @@ export class TranslateSession implements Session {
|
|||
initialShapes.forEach(({ id, point }) => (nextShapes[id] = { ...nextShapes[id], point }))
|
||||
|
||||
// Delete clones
|
||||
clones.forEach((clone) => (nextShapes[clone.id] = undefined))
|
||||
clones.forEach((clone) => (nextShapes[clone.id] = null))
|
||||
|
||||
// Delete cloned bindings
|
||||
clonedBindings.forEach((binding) => (nextBindings[binding.id] = undefined))
|
||||
clonedBindings.forEach((binding) => (nextBindings[binding.id] = null))
|
||||
|
||||
nextPageState.selectedIds = this.snapshot.selectedIds
|
||||
|
||||
|
@ -243,7 +243,7 @@ export class TranslateSession implements Session {
|
|||
if (this.isCloning) {
|
||||
// Update the clones
|
||||
clones.forEach((clone) => {
|
||||
beforeShapes[clone.id] = undefined
|
||||
beforeShapes[clone.id] = null
|
||||
|
||||
afterShapes[clone.id] = TLDR.getShape(data, clone.id, pageId)
|
||||
|
||||
|
@ -262,7 +262,7 @@ export class TranslateSession implements Session {
|
|||
|
||||
// Update the cloned bindings
|
||||
clonedBindings.forEach((binding) => {
|
||||
beforeBindings[binding.id] = undefined
|
||||
beforeBindings[binding.id] = null
|
||||
afterBindings[binding.id] = TLDR.getBinding(data, binding.id, pageId)
|
||||
})
|
||||
} else {
|
||||
|
@ -399,7 +399,7 @@ export function getTranslateSnapshot(data: Data) {
|
|||
})
|
||||
|
||||
clones.forEach((clone) => {
|
||||
if (clone.children !== undefined) {
|
||||
if (clone.children) {
|
||||
clone.children = clone.children.map((childId) => cloneMap[childId])
|
||||
}
|
||||
})
|
||||
|
@ -445,7 +445,7 @@ export function getTranslateSnapshot(data: Data) {
|
|||
if (clone.handles) {
|
||||
for (const id in clone.handles) {
|
||||
const handle = clone.handles[id as keyof ArrowShape['handles']]
|
||||
handle.bindingId = handle.bindingId ? clonedBindingsMap[handle.bindingId] : undefined
|
||||
handle.bindingId = handle.bindingId ? clonedBindingsMap[handle.bindingId] : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ import {
|
|||
TLDrawBinding,
|
||||
GroupShape,
|
||||
TLDrawCommand,
|
||||
TLDrawPatch,
|
||||
PagePartial,
|
||||
} from '~types'
|
||||
import { TLDR } from './tldr'
|
||||
import { defaultStyle } from '~shape'
|
||||
|
@ -72,7 +74,7 @@ const initialData: Data = {
|
|||
settings: {
|
||||
isPenMode: false,
|
||||
isDarkMode: false,
|
||||
isZoomSnap: true,
|
||||
isZoomSnap: false,
|
||||
isDebugMode: process.env.NODE_ENV === 'development',
|
||||
isReadonlyMode: false,
|
||||
nudgeDistanceLarge: 10,
|
||||
|
@ -162,12 +164,16 @@ export class TLDrawState extends StateManager<Data> {
|
|||
protected cleanup = (state: Data, prev: Data, patch: Patch<Data>, reason?: string): Data => {
|
||||
const data = { ...state }
|
||||
|
||||
this._onPatch?.(this, reason || 'patch', patch)
|
||||
const isMergingExternalPatch = reason !== 'patch:merge'
|
||||
|
||||
if (reason !== 'patch:merge') {
|
||||
this._onPatch?.(this, reason || 'patch', patch)
|
||||
}
|
||||
|
||||
// Remove deleted shapes and bindings (in Commands, these will be set to undefined)
|
||||
if (data.document !== prev.document) {
|
||||
Object.entries(data.document.pages).forEach(([pageId, page]) => {
|
||||
if (page === undefined) {
|
||||
if (!page) {
|
||||
// If page is undefined, delete the page and pagestate
|
||||
delete data.document.pages[pageId]
|
||||
delete data.document.pageStates[pageId]
|
||||
|
@ -177,50 +183,55 @@ export class TLDrawState extends StateManager<Data> {
|
|||
const prevPage = prev.document.pages[pageId]
|
||||
|
||||
if (!prevPage || page.shapes !== prevPage.shapes || page.bindings !== prevPage.bindings) {
|
||||
// Work through the shapes and bindings
|
||||
page.shapes = { ...page.shapes }
|
||||
page.bindings = { ...page.bindings }
|
||||
|
||||
// And collect groups that need to be updated or removed
|
||||
const groupsToUpdate = new Set<GroupShape>()
|
||||
|
||||
// If shape is undefined, delete the shape
|
||||
Object.keys(page.shapes).forEach((id) => {
|
||||
// Find which shapes have changed
|
||||
const changedShapeIds = Object.keys(page.shapes).filter(
|
||||
(id) => prevPage?.shapes[id] !== page.shapes[id]
|
||||
)
|
||||
|
||||
changedShapeIds.forEach((id) => {
|
||||
const shape = page.shapes[id]
|
||||
let parentId: string
|
||||
const parentId = shape?.parentId || prevPage.shapes[id]?.parentId
|
||||
|
||||
if (!shape) {
|
||||
parentId = prevPage.shapes[id]?.parentId
|
||||
// If shape is undefined, delete the shape
|
||||
delete page.shapes[id]
|
||||
} else {
|
||||
parentId = shape.parentId
|
||||
if (!isMergingExternalPatch) {
|
||||
// Update the shape's nonce
|
||||
shape.nonce = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
// If the shape is the child of a group, then update the group
|
||||
// (unless the group is being deleted too)
|
||||
if (parentId && parentId !== pageId) {
|
||||
const group = page.shapes[parentId]
|
||||
if (group !== undefined) {
|
||||
groupsToUpdate.add(page.shapes[parentId] as GroupShape)
|
||||
}
|
||||
if (parentId !== pageId) {
|
||||
const group = page.shapes[parentId] as GroupShape | null
|
||||
if (group) groupsToUpdate.add(group)
|
||||
}
|
||||
})
|
||||
|
||||
// If binding is undefined, delete the binding
|
||||
// Scan the page's bindings, looking for bindings to delete
|
||||
Object.keys(page.bindings).forEach((id) => {
|
||||
if (!page.bindings[id]) delete page.bindings[id]
|
||||
})
|
||||
|
||||
// Find which shapes have changed
|
||||
const changedShapeIds = Object.values(page.shapes)
|
||||
.filter((shape) => prevPage?.shapes[shape.id] !== shape)
|
||||
.map((shape) => shape.id)
|
||||
|
||||
data.document.pages[pageId] = page
|
||||
|
||||
// Get bindings related to the changed shapes
|
||||
const bindingsToUpdate = TLDR.getRelatedBindings(data, changedShapeIds, pageId)
|
||||
|
||||
// Update all of the bindings we've just collected
|
||||
bindingsToUpdate.forEach((binding) => {
|
||||
// Update the nonce
|
||||
if (!isMergingExternalPatch) {
|
||||
binding.nonce = Date.now()
|
||||
}
|
||||
|
||||
const toShape = page.shapes[binding.toId]
|
||||
const fromShape = page.shapes[binding.fromId]
|
||||
const toUtils = TLDR.getShapeUtils(toShape)
|
||||
|
@ -248,7 +259,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
|
||||
groupsToUpdate.forEach((group) => {
|
||||
if (!group) throw Error('no group!')
|
||||
const children = group.children.filter((id) => page.shapes[id] !== undefined)
|
||||
const children = group.children.filter((id) => !!page.shapes[id])
|
||||
|
||||
const commonBounds = Utils.getCommonBounds(
|
||||
children
|
||||
|
@ -264,6 +275,8 @@ export class TLDrawState extends StateManager<Data> {
|
|||
children,
|
||||
}
|
||||
})
|
||||
|
||||
data.document.pages[pageId] = page
|
||||
}
|
||||
|
||||
// Clean up page state, preventing hovers on deleted shapes
|
||||
|
@ -286,6 +299,10 @@ export class TLDrawState extends StateManager<Data> {
|
|||
delete nextPageState.editingId
|
||||
}
|
||||
|
||||
// Delete any new selected ids
|
||||
|
||||
nextPageState.selectedIds = nextPageState.selectedIds.filter((id) => page.shapes[id])
|
||||
|
||||
data.document.pageStates[pageId] = nextPageState
|
||||
})
|
||||
}
|
||||
|
@ -322,7 +339,9 @@ export class TLDrawState extends StateManager<Data> {
|
|||
this.clearSelectHistory()
|
||||
}
|
||||
|
||||
this._onChange?.(this, id)
|
||||
if (id !== 'patch:merge') {
|
||||
this._onChange?.(this, id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -490,9 +509,103 @@ export class TLDrawState extends StateManager<Data> {
|
|||
* @param document
|
||||
*/
|
||||
mergeDocument = (document: TLDrawDocument): this => {
|
||||
const next = { ...this.state }
|
||||
next.document.pages[next.appState.currentPageId] = document.pages[next.appState.currentPageId]
|
||||
return this.replaceState(next, 'merge')
|
||||
const currentPageId = this.state.appState.currentPageId
|
||||
|
||||
const mergingPage = document.pages[currentPageId]
|
||||
|
||||
const currentPage = this.state.document.pages[currentPageId]
|
||||
|
||||
const nextPage: TLDrawPage = {
|
||||
id: currentPageId,
|
||||
shapes: {},
|
||||
bindings: {},
|
||||
}
|
||||
|
||||
let didChange = false
|
||||
|
||||
Object.entries(mergingPage.shapes).forEach(([id, shape]) => {
|
||||
if (currentPage.shapes[id] !== shape) {
|
||||
didChange = true
|
||||
nextPage.shapes[id] = shape
|
||||
}
|
||||
})
|
||||
|
||||
Object.entries(mergingPage.bindings).forEach(([id, binding]) => {
|
||||
if (currentPage.bindings[id] !== binding) {
|
||||
didChange = true
|
||||
nextPage.bindings[id] = binding
|
||||
}
|
||||
})
|
||||
|
||||
if (!didChange) return this
|
||||
|
||||
return this.replaceState(
|
||||
{
|
||||
...this.state,
|
||||
document: {
|
||||
...this.document,
|
||||
pages: {
|
||||
...this.document.pages,
|
||||
[currentPageId]: nextPage,
|
||||
},
|
||||
},
|
||||
},
|
||||
'merge'
|
||||
)
|
||||
}
|
||||
|
||||
mergePatch = (patch: TLDrawPatch): this => {
|
||||
const currentPageId = this.state.appState.currentPageId
|
||||
|
||||
const mergingPage = patch.document?.pages?.[currentPageId]
|
||||
|
||||
const currentPage = this.state.document.pages[currentPageId]
|
||||
|
||||
const patchPage: PagePartial = {
|
||||
shapes: {},
|
||||
bindings: {},
|
||||
}
|
||||
|
||||
let didChange = false
|
||||
|
||||
if (mergingPage) {
|
||||
Object.entries(mergingPage?.shapes || {}).forEach(([id, shape]) => {
|
||||
if (currentPage.shapes[id] !== shape) {
|
||||
didChange = true
|
||||
patchPage.shapes[id] = shape
|
||||
}
|
||||
})
|
||||
|
||||
Object.entries(mergingPage?.bindings || {}).forEach(([id, binding]) => {
|
||||
if (currentPage.bindings[id] !== binding) {
|
||||
didChange = true
|
||||
patchPage.bindings[id] = binding
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!didChange) return this
|
||||
|
||||
return this.patchState(
|
||||
{
|
||||
document: {
|
||||
pages: {
|
||||
[currentPageId]: patchPage,
|
||||
},
|
||||
},
|
||||
},
|
||||
'merge'
|
||||
)
|
||||
}
|
||||
|
||||
// merge a set of patches
|
||||
mergePatches = (patches: { time: number; patch: TLDrawPatch }[]): this => {
|
||||
for (const patch of patches) {
|
||||
setTimeout(() => {
|
||||
this.mergePatch(patch.patch)
|
||||
}, patch.time)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -848,14 +961,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
this.getPagePoint([window.innerWidth / 2, window.innerHeight / 2])
|
||||
)
|
||||
|
||||
this.create(
|
||||
TLDR.getShapeUtils(TLDrawShapeType.Text).create({
|
||||
id: Utils.uniqueId(),
|
||||
parentId: this.appState.currentPageId,
|
||||
childIndex,
|
||||
point: [boundsCenter.minX, boundsCenter.minY],
|
||||
})
|
||||
)
|
||||
this.create({ ...shape, point: [boundsCenter.minX, boundsCenter.minY] })
|
||||
}
|
||||
|
||||
return this
|
||||
|
@ -1647,6 +1753,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
...shapes.map((shape) => {
|
||||
return TLDR.getShapeUtils(shape as TLDrawShape).create({
|
||||
...shape,
|
||||
nonce: shape.nonce || Date.now(),
|
||||
parentId: shape.parentId || this.currentPageId,
|
||||
})
|
||||
})
|
||||
|
@ -2056,6 +2163,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
shapes: {
|
||||
[id]: utils.create({
|
||||
id,
|
||||
nonce: Date.now(),
|
||||
parentId: this.currentPageId,
|
||||
childIndex,
|
||||
point: pagePoint,
|
||||
|
|
|
@ -8,6 +8,7 @@ export const mockDocument: TLDrawDocument = {
|
|||
shapes: {
|
||||
rect1: {
|
||||
id: 'rect1',
|
||||
nonce: 0,
|
||||
parentId: 'page1',
|
||||
name: 'Rectangle',
|
||||
childIndex: 1,
|
||||
|
@ -22,6 +23,7 @@ export const mockDocument: TLDrawDocument = {
|
|||
},
|
||||
rect2: {
|
||||
id: 'rect2',
|
||||
nonce: 0,
|
||||
parentId: 'page1',
|
||||
name: 'Rectangle',
|
||||
childIndex: 2,
|
||||
|
@ -36,6 +38,7 @@ export const mockDocument: TLDrawDocument = {
|
|||
},
|
||||
rect3: {
|
||||
id: 'rect3',
|
||||
nonce: 0,
|
||||
parentId: 'page1',
|
||||
name: 'Rectangle',
|
||||
childIndex: 3,
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { TLBinding, TLRenderInfo } from '@tldraw/core'
|
|||
import { TLShape, TLShapeUtil, TLHandle } from '@tldraw/core'
|
||||
import type { TLPage, TLPageState } from '@tldraw/core'
|
||||
import type { StoreApi } from 'zustand'
|
||||
import type { Command, Patch } from 'rko'
|
||||
|
||||
export type TLStore = StoreApi<Data>
|
||||
|
||||
|
@ -52,6 +51,18 @@ export interface Data {
|
|||
}
|
||||
}
|
||||
|
||||
export type Patch<T> = Partial<
|
||||
{
|
||||
[P in keyof T]: Patch<T[P]> | null
|
||||
}
|
||||
>
|
||||
|
||||
export interface Command<T extends object> {
|
||||
id?: string
|
||||
before: Patch<T>
|
||||
after: Patch<T>
|
||||
}
|
||||
|
||||
export type TLDrawPatch = Patch<Data>
|
||||
|
||||
export type TLDrawCommand = Command<Data>
|
||||
|
@ -147,6 +158,7 @@ export enum Decoration {
|
|||
}
|
||||
|
||||
export interface TLDrawBaseShape extends TLShape {
|
||||
nonce?: number
|
||||
style: ShapeStyles
|
||||
type: TLDrawShapeType
|
||||
}
|
||||
|
@ -205,7 +217,11 @@ export abstract class TLDrawShapeUtil<T extends TLDrawShape> extends TLShapeUtil
|
|||
|
||||
export type TLDrawShapeUtils = Record<TLDrawShapeType, TLDrawShapeUtil<TLDrawShape>>
|
||||
|
||||
export interface ArrowBinding extends TLBinding {
|
||||
export interface TLDrawBaseBinding extends TLBinding {
|
||||
nonce?: number
|
||||
}
|
||||
|
||||
export interface ArrowBinding extends TLDrawBaseBinding {
|
||||
type: 'arrow'
|
||||
handleId: keyof ArrowShape['handles']
|
||||
distance: number
|
||||
|
|
|
@ -4,7 +4,7 @@ import Head from 'next/head'
|
|||
import dynamic from 'next/dynamic'
|
||||
import { supabase } from '-supabase/client'
|
||||
import type { TLDrawProject } from '-types'
|
||||
import type { TLDrawState } from '@tldraw/tldraw'
|
||||
import type { TLDrawState, TLDrawPatch } from '@tldraw/tldraw'
|
||||
import { Utils } from '@tldraw/core'
|
||||
const Editor = dynamic(() => import('components/editor'), { ssr: false })
|
||||
|
||||
|
@ -15,40 +15,81 @@ interface RoomProps {
|
|||
|
||||
export default function Room({ id }: RoomProps): JSX.Element {
|
||||
const rState = React.useRef<TLDrawState>(null)
|
||||
const [ready, setReady] = React.useState(false)
|
||||
|
||||
const rTimer = React.useRef(0)
|
||||
const rStartTime = React.useRef(0)
|
||||
const rPendingPatches = React.useRef<{ time: number; patch: TLDrawPatch }[]>([])
|
||||
const rUnsub = React.useRef<any>()
|
||||
|
||||
const userId = React.useRef(Utils.uniqueId())
|
||||
|
||||
React.useEffect(() => {
|
||||
const sub = supabase
|
||||
.from('projects')
|
||||
.on('*', (payload) => {
|
||||
if (payload.new.nonce !== userId.current) {
|
||||
rState.current.mergeDocument(payload.new.document)
|
||||
}
|
||||
})
|
||||
.subscribe()
|
||||
return () => {
|
||||
sub.unsubscribe()
|
||||
clearTimeout(rTimer.current)
|
||||
if (rUnsub.current) {
|
||||
rUnsub.current?.()
|
||||
}
|
||||
}
|
||||
}, [id])
|
||||
}, [id, ready])
|
||||
|
||||
const handleMount = React.useCallback((tlstate: TLDrawState) => {
|
||||
rState.current = tlstate
|
||||
}, [])
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
(tlstate: TLDrawState, reason: string) => {
|
||||
if (
|
||||
!(reason.startsWith('command') || reason.startsWith('undo') || reason.startsWith('redo'))
|
||||
) {
|
||||
return
|
||||
React.useEffect(() => {
|
||||
supabase
|
||||
.from<TLDrawProject>('projects')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.then(({ data }) => {
|
||||
const project = data[0]
|
||||
|
||||
if (project && rState.current) {
|
||||
rState.current.loadDocument(project.document)
|
||||
setReady(true)
|
||||
}
|
||||
})
|
||||
|
||||
const sub = supabase
|
||||
.from('projects')
|
||||
.on('*', (payload) => {
|
||||
if (payload.new.nonce !== userId.current) {
|
||||
rState.current.mergePatches(payload.new.patch)
|
||||
}
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
rUnsub.current = () => sub.unsubscribe()
|
||||
|
||||
return () => {
|
||||
rUnsub.current?.()
|
||||
}
|
||||
})
|
||||
|
||||
const handlePatch = React.useCallback(
|
||||
(tlstate: TLDrawState, reason: string, patch: TLDrawPatch) => {
|
||||
if (rPendingPatches.current.length === 0) {
|
||||
rStartTime.current = Date.now()
|
||||
const handle = setTimeout(() => {
|
||||
const patches = rPendingPatches.current
|
||||
rPendingPatches.current = []
|
||||
rStartTime.current = 0
|
||||
supabase
|
||||
.from<TLDrawProject>('projects')
|
||||
.update({
|
||||
document: tlstate.document,
|
||||
patch: patches,
|
||||
nonce: userId.current,
|
||||
})
|
||||
.eq('id', id)
|
||||
.then(() => void null)
|
||||
}, 100)
|
||||
|
||||
rTimer.current = handle as any
|
||||
}
|
||||
|
||||
supabase
|
||||
.from<TLDrawProject>('projects')
|
||||
.update({ document: tlstate.document, nonce: userId.current })
|
||||
.eq('id', id)
|
||||
.then(() => void null)
|
||||
rPendingPatches.current.push({ time: Date.now() - rStartTime.current, patch })
|
||||
},
|
||||
[id]
|
||||
)
|
||||
|
@ -58,7 +99,7 @@ export default function Room({ id }: RoomProps): JSX.Element {
|
|||
<Head>
|
||||
<title>tldraw</title>
|
||||
</Head>
|
||||
<Editor id={id} onChange={handleChange} onMount={handleMount} />
|
||||
<Editor id={id} onPatch={handlePatch} onMount={handleMount} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { TLDrawDocument } from '@tldraw/tldraw'
|
||||
import { TLDrawDocument, TLDrawPatch } from '@tldraw/tldraw'
|
||||
|
||||
export interface TLDrawProject {
|
||||
uuid: string
|
||||
id: string
|
||||
nonce: string
|
||||
patch: { time: number; patch: TLDrawPatch }[]
|
||||
document: TLDrawDocument
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import '@testing-library/jest-dom/extend-expect'
|
||||
import "fake-indexeddb/auto"
|
||||
import 'fake-indexeddb/auto'
|
||||
Date.now = jest.fn(() => 1487076708000) //14.02.2017
|
||||
|
|
Ładowanie…
Reference in New Issue