Adds manual create and update, more jsdoc

This commit is contained in:
Steve Ruiz 2021-09-01 09:37:07 +01:00
parent 92b8df4b51
commit 16fda2fddf
13 changed files with 304 additions and 61 deletions

View file

@ -1,6 +1,11 @@
import * as React from 'react'
import { TLDraw } from '@tldraw/tldraw'
import { TLDraw, TLDrawState } from '@tldraw/tldraw'
export default function Editor(): JSX.Element {
return <TLDraw id="tldraw" />
const handleMount = React.useCallback((tlstate: TLDrawState) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.tlstate = tlstate
}, [])
return <TLDraw id="tldraw" onMount={handleMount} />
}

View file

@ -312,7 +312,7 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape> {
}
}
transformSingle(shape: RectangleShape, bounds: TLBounds) {
transformSingle(_shape: RectangleShape, bounds: TLBounds) {
return {
size: Vec.round([bounds.width, bounds.height]),
point: Vec.round([bounds.minX, bounds.minY]),

View file

@ -56,7 +56,7 @@ if (typeof window !== 'undefined') {
export class Text extends TLDrawShapeUtil<TextShape> {
type = TLDrawShapeType.Text as const
toolType = TLDrawToolType.Text
canChangeAspectRatio = false
isAspectRatioLocked = true
isEditableText = true
canBind = true
@ -325,7 +325,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
transformSingle(
_shape: TextShape,
bounds: TLBounds,
{ initialShape, scaleX }: TLTransformInfo<TextShape>
{ initialShape, scaleX, scaleY }: TLTransformInfo<TextShape>
): Partial<TextShape> {
const {
style: { scale = 1 },
@ -335,7 +335,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
point: Vec.round([bounds.minX, bounds.minY]),
style: {
...initialShape.style,
scale: scale * Math.abs(scaleX),
scale: scale * Math.max(Math.abs(scaleY), Math.abs(scaleX)),
},
}
}
@ -369,7 +369,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
}
shouldDelete(shape: TextShape): boolean {
return shape.text.length === 0
return shape.text.trim().length === 0
}
// getBindingPoint(shape, point, origin, direction, expandDistance) {

View file

@ -16,3 +16,4 @@ export * from './delete-page'
export * from './rename-page'
export * from './duplicate-page'
export * from './change-page'
export * from './update'

View file

@ -44,7 +44,7 @@ describe('Move command', () => {
it('does, undoes and redoes command', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['b'])
tlstate.select('b')
tlstate.moveToBack()
expect(getSortedShapeIds(tlstate.state)).toBe('bacd')
tlstate.undo()
@ -56,21 +56,21 @@ describe('Move command', () => {
describe('to back', () => {
it('moves a shape to back', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['b'])
tlstate.select('b')
tlstate.moveToBack()
expect(getSortedShapeIds(tlstate.state)).toBe('bacd')
})
it('moves two adjacent siblings to back', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['b', 'c'])
tlstate.select('b', 'c')
tlstate.moveToBack()
expect(getSortedShapeIds(tlstate.state)).toBe('bcad')
})
it('moves two non-adjacent siblings to back', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['b', 'd'])
tlstate.select('b', 'd')
tlstate.moveToBack()
expect(getSortedShapeIds(tlstate.state)).toBe('bdac')
})
@ -79,35 +79,35 @@ describe('Move command', () => {
describe('backward', () => {
it('moves a shape backward', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['c'])
tlstate.select('c')
tlstate.moveBackward()
expect(getSortedShapeIds(tlstate.state)).toBe('acbd')
})
it('moves a shape at first index backward', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['a'])
tlstate.select('a')
tlstate.moveBackward()
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
})
it('moves two adjacent siblings backward', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['c', 'd'])
tlstate.select('c', 'd')
tlstate.moveBackward()
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
})
it('moves two non-adjacent siblings backward', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['b', 'd'])
tlstate.select('b', 'd')
tlstate.moveBackward()
expect(getSortedShapeIds(tlstate.state)).toBe('badc')
})
it('moves two adjacent siblings backward at zero index', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['a', 'b'])
tlstate.select('a', 'b')
tlstate.moveBackward()
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
})
@ -116,14 +116,14 @@ describe('Move command', () => {
describe('forward', () => {
it('moves a shape forward', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['c'])
tlstate.select('c')
tlstate.moveForward()
expect(getSortedShapeIds(tlstate.state)).toBe('abdc')
})
it('moves a shape forward at the top index', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['b'])
tlstate.select('b')
tlstate.moveForward()
tlstate.moveForward()
tlstate.moveForward()
@ -132,21 +132,21 @@ describe('Move command', () => {
it('moves two adjacent siblings forward', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['a', 'b'])
tlstate.select('a', 'b')
tlstate.moveForward()
expect(getSortedShapeIds(tlstate.state)).toBe('cabd')
})
it('moves two non-adjacent siblings forward', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['a', 'c'])
tlstate.select('a', 'c')
tlstate.moveForward()
expect(getSortedShapeIds(tlstate.state)).toBe('badc')
})
it('moves two adjacent siblings forward at top index', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['c', 'd'])
tlstate.select('c', 'd')
tlstate.moveForward()
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
})
@ -155,28 +155,28 @@ describe('Move command', () => {
describe('to front', () => {
it('moves a shape to front', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['b'])
tlstate.select('b')
tlstate.moveToFront()
expect(getSortedShapeIds(tlstate.state)).toBe('acdb')
})
it('moves two adjacent siblings to front', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['a', 'b'])
tlstate.select('a', 'b')
tlstate.moveToFront()
expect(getSortedShapeIds(tlstate.state)).toBe('cdab')
})
it('moves two non-adjacent siblings to front', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['a', 'c'])
tlstate.select('a', 'c')
tlstate.moveToFront()
expect(getSortedShapeIds(tlstate.state)).toBe('bdac')
})
it('moves siblings already at front to front', () => {
tlstate.loadDocument(doc)
tlstate.setSelectedIds(['c', 'd'])
tlstate.select('c', 'd')
tlstate.moveToFront()
expect(getSortedShapeIds(tlstate.state)).toBe('abcd')
})

View file

@ -26,7 +26,7 @@ describe('Toggle command', () => {
it('toggles on before off when mixed values', () => {
tlstate.loadDocument(mockDocument)
tlstate.setSelectedIds(['rect2'])
tlstate.select('rect2')
expect(tlstate.getShape<RectangleShape>('rect1').isAspectRatioLocked).toBe(undefined)
expect(tlstate.getShape<RectangleShape>('rect2').isAspectRatioLocked).toBe(undefined)
tlstate.toggleAspectRatioLocked()

View file

@ -0,0 +1 @@
export * from './update.command'

View file

@ -0,0 +1,19 @@
import { TLDrawState } from '~state'
import { mockDocument } from '~test'
import { Utils } from '@tldraw/core'
const doc = Utils.deepClone(mockDocument)
describe('Move command', () => {
const tlstate = new TLDrawState()
it('does, undoes and redoes command', () => {
tlstate.loadDocument(doc)
tlstate.updateShapes({ id: 'rect1', point: [100, 100] })
expect(tlstate.getShape('rect1').point).toStrictEqual([100, 100])
tlstate.undo()
expect(tlstate.getShape('rect1').point).toStrictEqual([0, 0])
tlstate.redo()
expect(tlstate.getShape('rect1').point).toStrictEqual([100, 100])
})
})

View file

@ -0,0 +1,47 @@
import type { Data, TLDrawCommand, PagePartial, TLDrawShape } from '~types'
import { TLDR } from '~state/tldr'
export function update(
data: Data,
updates: ({ id: string } & Partial<TLDrawShape>)[]
): TLDrawCommand {
const ids = updates.map((update) => update.id)
const before: PagePartial = {
shapes: {},
bindings: {},
}
const after: PagePartial = {
shapes: {},
bindings: {},
}
const change = TLDR.mutateShapes(
data,
ids,
(_shape, i) => updates[i],
data.appState.currentPageId
)
before.shapes = change.before
after.shapes = change.after
return {
id: 'translate_shapes',
before: {
document: {
pages: {
[data.appState.currentPageId]: before,
},
},
},
after: {
document: {
pages: {
[data.appState.currentPageId]: after,
},
},
},
}
}

View file

@ -1,4 +1,4 @@
import { TextShape, TLDrawShape, TLDrawStatus } from '~types'
import { TextShape, TLDrawStatus } from '~types'
import type { Session } from '~types'
import type { Data } from '~types'
import { TLDR } from '~state/tldr'

View file

@ -24,6 +24,7 @@ export class TransformSession implements Session {
start = () => void null
// eslint-disable-next-line @typescript-eslint/no-unused-vars
update = (data: Data, point: number[], isAspectRatioLocked = false, _altKey = false) => {
const {
transformType,
@ -37,7 +38,7 @@ export class TransformSession implements Session {
const newBoundingBox = Utils.getTransformedBoundingBox(
initialBounds,
transformType,
Vec.vec(this.origin, point),
Vec.sub(point, this.origin),
pageState.boundsRotation,
isAspectRatioLocked || isAllAspectRatioLocked
)

View file

@ -668,8 +668,6 @@ export class TLDR {
next = this.onChildrenChange(data, next, pageId) || next
}
// data.page.shapes[next.id] = next
return next
}

View file

@ -135,14 +135,14 @@ export class TLDrawState extends StateManager<Data> {
}
/* -------------------- Internal -------------------- */
protected onStateWillChange = (_state: Data, id: string): void => {
if (!id.startsWith('patch')) {
this.selectHistory.stack = [[]]
this.selectHistory.pointer = 0
}
}
// protected onStateWillChange = (_state: Data, id: string): void => {
// }
protected onStateDidChange = (state: Data, id: string): void => {
if (!id.startsWith('patch')) {
this.clearSelectHistory()
}
this._onChange?.(this, state, id)
}
@ -531,7 +531,9 @@ export class TLDrawState extends StateManager<Data> {
loadDocument = (document: TLDrawDocument, onChange?: TLDrawState['_onChange']): this => {
this._onChange = onChange
this.deselectAll()
this.resetHistory()
this.clearSelectHistory()
// this.selectHistory.pointer = 0
// this.selectHistory.stack = [[]]
@ -942,7 +944,13 @@ export class TLDrawState extends StateManager<Data> {
this.selectHistory.stack.push(ids)
}
setSelectedIds(ids: string[], push = false): this {
/**
* Set the current selection.
* @param ids The ids to select
* @param push Whether to add the ids to the current selection instead.
* @returns this
*/
private setSelectedIds(ids: string[], push = false): this {
return this.patchState(
{
appState: {
@ -961,6 +969,10 @@ export class TLDrawState extends StateManager<Data> {
)
}
/**
* Undo the most recent selection.
* @returns this
*/
undoSelect(): this {
if (this.selectHistory.pointer > 0) {
this.selectHistory.pointer--
@ -969,6 +981,10 @@ export class TLDrawState extends StateManager<Data> {
return this
}
/**
* Redo the previous selection.
* @returns this
*/
redoSelect(): this {
if (this.selectHistory.pointer < this.selectHistory.stack.length - 1) {
this.selectHistory.pointer++
@ -977,12 +993,21 @@ export class TLDrawState extends StateManager<Data> {
return this
}
/**
* Select one or more shapes.
* @param ids The shape ids to select.
* @returns this
*/
select = (...ids: string[]): this => {
this.setSelectedIds(ids)
this.addToSelectHistory(ids)
return this
}
/**
* Select all shapes on the page.
* @returns this
*/
selectAll = (): this => {
if (this.session) return this
this.setSelectedIds(Object.keys(this.page.shapes))
@ -993,6 +1018,10 @@ export class TLDrawState extends StateManager<Data> {
return this
}
/**
* Deselect any selected shapes.
* @returns this
*/
deselectAll = (): this => {
this.setSelectedIds([])
this.addToSelectHistory(this.selectedIds)
@ -1003,66 +1032,213 @@ export class TLDrawState extends StateManager<Data> {
/* Shape Functions */
/* -------------------------------------------------- */
/**
* Manually create shapes on the page.
* @param shapes An array of shape partials, containing the initial props for the shapes.
* @command
* @returns this
*/
createShapes = (
...shapes: ({ id: string; type: TLDrawShapeType } & Partial<TLDrawShape>)[]
): this => {
if (shapes.length === 0) return this
return this.create(
...shapes.map((shape) => {
return TLDR.getShapeUtils(shape as TLDrawShape).create(shape)
})
)
}
/**
* Manually update a set of shapes.
* @param shapes An array of shape partials, containing the changes to be made to each shape.
* @command
* @returns this
*/
updateShapes = (...shapes: ({ id: string } & Partial<TLDrawShape>)[]): this => {
if (shapes.length === 0) return this
return this.setState(Commands.update(this.state, shapes), 'updated_shape')
}
/**
* Create one or more shapes.
* @param shapes An array of shapes.
* @command
* @returns this
*/
create = (...shapes: TLDrawShape[]): this => {
if (shapes.length === 0) return this
return this.setState(Commands.create(this.state, shapes))
}
/**
* Delete one or more shapes.
* @param ids The ids of the shapes to delete.
* @command
* @returns this
*/
delete = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.deleteShapes(this.state, ids))
}
/**
* Delete all shapes on the page.
* @returns this
*/
clear = (): this => {
this.selectAll()
this.delete()
return this
}
/**
* Change the style for one or more shapes.
* @param style A style partial to apply to the shapes.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
style = (style: Partial<ShapeStyles>, ids = this.selectedIds) => {
return this.setState(Commands.style(this.state, ids, style))
}
/**
* Align one or more shapes.
* @param direction Whether to align horizontally or vertically.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
align = (type: AlignType, ids = this.selectedIds) => {
return this.setState(Commands.align(this.state, ids, type))
}
distribute = (type: DistributeType, ids = this.selectedIds) => {
return this.setState(Commands.distribute(this.state, ids, type))
/**
* Distribute one or more shapes.
* @param direction Whether to distribute horizontally or vertically..
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
distribute = (direction: DistributeType, ids = this.selectedIds) => {
return this.setState(Commands.distribute(this.state, ids, direction))
}
stretch = (type: StretchType, ids = this.selectedIds) => {
return this.setState(Commands.stretch(this.state, ids, type))
/**
* Stretch one or more shapes to their common bounds.
* @param direction Whether to stretch horizontally or vertically.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
stretch = (direction: StretchType, ids = this.selectedIds) => {
return this.setState(Commands.stretch(this.state, ids, direction))
}
/**
* Flip one or more shapes horizontally.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
flipHorizontal = (ids = this.selectedIds) => {
return this.setState(Commands.flip(this.state, ids, FlipType.Horizontal))
}
/**
* Flip one or more shapes vertically.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
flipVertical = (ids = this.selectedIds) => {
return this.setState(Commands.flip(this.state, ids, FlipType.Vertical))
}
/**
* Move one or more shapes to the back of the page.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
moveToBack = (ids = this.selectedIds) => {
return this.setState(Commands.move(this.state, ids, MoveType.ToBack))
}
/**
* Move one or more shapes backward on of the page.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
moveBackward = (ids = this.selectedIds) => {
return this.setState(Commands.move(this.state, ids, MoveType.Backward))
}
/**
* Move one or more shapes forward on the page.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
moveForward = (ids = this.selectedIds) => {
return this.setState(Commands.move(this.state, ids, MoveType.Forward))
}
/**
* Move one or more shapes to the front of the page.
* @param ids The ids of the shapes to change (defaults to selection).
* @returns this
*/
moveToFront = (ids = this.selectedIds) => {
return this.setState(Commands.move(this.state, ids, MoveType.ToFront))
}
/**
* Nudge one or more shapes in a direction.
* @param delta The direction to nudge the shapes.
* @param isMajor Whether this is a major (i.e. shift) nudge.
* @param ids The ids to change (defaults to selection).
* @returns this
*/
nudge = (delta: number[], isMajor = false, ids = this.selectedIds): this => {
return this.setState(Commands.translate(this.state, ids, Vec.mul(delta, isMajor ? 10 : 1)))
}
/**
* Duplicate one or more shapes.
* @param ids The ids to duplicate (defaults to selection).
* @returns this
*/
duplicate = (ids = this.selectedIds): this => {
return this.setState(Commands.duplicate(this.state, ids))
}
/**
* Toggle the hidden property of one or more shapes.
* @param ids The ids to change (defaults to selection).
* @returns this
*/
toggleHidden = (ids = this.selectedIds): this => {
return this.setState(Commands.toggle(this.state, ids, 'isHidden'))
}
/**
* Toggle the locked property of one or more shapes.
* @param ids The ids to change (defaults to selection).
* @returns this
*/
toggleLocked = (ids = this.selectedIds): this => {
return this.setState(Commands.toggle(this.state, ids, 'isLocked'))
}
/**
* Toggle the fixed-aspect-ratio property of one or more shapes.
* @param ids The ids to change (defaults to selection).
* @returns this
*/
toggleAspectRatioLocked = (ids = this.selectedIds): this => {
return this.setState(Commands.toggle(this.state, ids, 'isAspectRatioLocked'))
}
/**
* Toggle the decoration at a handle of one or more shapes.
* @param handleId The handle to toggle.
* @param ids The ids of the shapes to toggle the decoration on.
* @returns this
*/
toggleDecoration = (handleId: string, ids = this.selectedIds): this => {
if (handleId === 'start' || handleId === 'end') {
return this.setState(Commands.toggleDecoration(this.state, ids, handleId))
@ -1071,34 +1247,29 @@ export class TLDrawState extends StateManager<Data> {
return this
}
/**
* Rotate one or more shapes by a delta.
* @param delta The delta in radians.
* @param ids The ids to rotate (defaults to selection).
* @returns this
*/
rotate = (delta = Math.PI * -0.5, ids = this.selectedIds): this => {
return this.setState(Commands.rotate(this.state, ids, delta))
}
/**
* Group one or more shapes.
* @returns this
* @todo
*/
group = (): this => {
// TODO
//
//
// this.setState(Commands.toggle(this.state, ids, 'isAspectRatioLocked'))
return this
}
create = (...shapes: TLDrawShape[]): this => {
return this.setState(Commands.create(this.state, shapes))
}
delete = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.deleteShapes(this.state, ids))
}
clear = (): this => {
this.selectAll()
this.delete()
return this
}
/**
* Cancel the current session.
* @returns this
*/
cancel = (): this => {
switch (this.state.appState.status.current) {
case TLDrawStatus.Idle: {