Adds copy, fix bug on mutating bound shapes, adds binding indicator, adds binding to text

This commit is contained in:
Steve Ruiz 2021-09-01 12:18:50 +01:00
parent 89dfd22bac
commit f6934dedb8
10 changed files with 174 additions and 58 deletions

View file

@ -10,7 +10,7 @@ export function Defs({ zoom }: DefProps): JSX.Element {
<circle id="dot" className="tl-counter-scaled tl-dot" r={4} /> <circle id="dot" className="tl-counter-scaled tl-dot" r={4} />
<circle id="handle-bg" className="tl-handle-bg" pointerEvents="all" r={12} /> <circle id="handle-bg" className="tl-handle-bg" pointerEvents="all" r={12} />
<circle id="handle" className="tl-counter-scaled tl-handle" pointerEvents="none" r={4} /> <circle id="handle" className="tl-counter-scaled tl-handle" pointerEvents="none" r={4} />
<g id="cross" className="tl-binding-indicator"> <g id="cross" className="tl-anchor-indicator">
<line x1={-6} y1={-6} x2={6} y2={6} /> <line x1={-6} y1={-6} x2={6} y2={6} />
<line x1={6} y1={-6} x2={-6} y2={6} /> <line x1={6} y1={-6} x2={-6} y2={6} />
</g> </g>

View file

@ -16,6 +16,7 @@ function addToShapeTree<T extends TLShape, M extends Record<string, unknown>>(
shapes: TLPage<T, TLBinding>['shapes'], shapes: TLPage<T, TLBinding>['shapes'],
selectedIds: string[], selectedIds: string[],
pageState: { pageState: {
bindingTargetId?: string
bindingId?: string bindingId?: string
hoveredId?: string hoveredId?: string
currentParentId?: string currentParentId?: string
@ -28,7 +29,7 @@ function addToShapeTree<T extends TLShape, M extends Record<string, unknown>>(
shape, shape,
isCurrentParent: pageState.currentParentId === shape.id, isCurrentParent: pageState.currentParentId === shape.id,
isEditing: pageState.editingId === shape.id, isEditing: pageState.editingId === shape.id,
isBinding: pageState.bindingId === shape.id, isBinding: pageState.bindingTargetId === shape.id,
meta, meta,
} }
@ -102,13 +103,17 @@ export function useShapeTree<T extends TLShape, M extends Record<string, unknown
rPreviousCount.current = shapesToRender.length rPreviousCount.current = shapesToRender.length
} }
const bindingTargetId = pageState.bindingId ? page.bindings[pageState.bindingId].toId : undefined
// Populate the shape tree // Populate the shape tree
const tree: IShapeTreeNode<M>[] = [] const tree: IShapeTreeNode<M>[] = []
shapesToRender shapesToRender
.sort((a, b) => a.childIndex - b.childIndex) .sort((a, b) => a.childIndex - b.childIndex)
.forEach((shape) => addToShapeTree(shape, tree, page.shapes, selectedIds, pageState, meta)) .forEach((shape) =>
addToShapeTree(shape, tree, page.shapes, selectedIds, { ...pageState, bindingTargetId }, meta)
)
return tree return tree
} }

View file

@ -241,7 +241,7 @@ const tlcss = css`
} }
.tl-binding-indicator { .tl-binding-indicator {
stroke-width: calc(3px * var(--tl-scale)); stroke-width: calc(3px * var(--tl-scale));
fill: none; fill: var(--tl-selectFill);
stroke: var(--tl-selected); stroke: var(--tl-selected);
} }
.tl-shape-group { .tl-shape-group {

View file

@ -101,12 +101,12 @@ export const ContextMenu = React.memo(({ children }: ContextMenuProps): JSX.Elem
tlstate.delete() tlstate.delete()
}, [tlstate]) }, [tlstate])
const handleCopyAsJson = React.useCallback(() => { const handlecopyJson = React.useCallback(() => {
tlstate.copyAsJson() tlstate.copyJson()
}, [tlstate]) }, [tlstate])
const handleCopyAsSvg = React.useCallback(() => { const handlecopySvg = React.useCallback(() => {
tlstate.copyAsSvg() tlstate.copySvg()
}, [tlstate]) }, [tlstate])
const handleUndo = React.useCallback(() => { const handleUndo = React.useCallback(() => {
@ -180,12 +180,12 @@ export const ContextMenu = React.memo(({ children }: ContextMenuProps): JSX.Elem
)} )}
{/* <MoveToPageMenu /> */} {/* <MoveToPageMenu /> */}
{isDebugMode && ( {isDebugMode && (
<ContextMenuButton onSelect={handleCopyAsJson}> <ContextMenuButton onSelect={handlecopyJson}>
<span>Copy Data</span> <span>Copy Data</span>
<Kbd variant="menu">#C</Kbd> <Kbd variant="menu">#C</Kbd>
</ContextMenuButton> </ContextMenuButton>
)} )}
<ContextMenuButton onSelect={handleCopyAsSvg}> <ContextMenuButton onSelect={handlecopySvg}>
<span>Copy to SVG</span> <span>Copy to SVG</span>
<Kbd variant="menu">#C</Kbd> <Kbd variant="menu">#C</Kbd>
</ContextMenuButton> </ContextMenuButton>

View file

@ -66,8 +66,8 @@ function SelectedShapeContent(): JSX.Element {
tlstate.paste() tlstate.paste()
}, [tlstate]) }, [tlstate])
const handleCopyAsSvg = React.useCallback(() => { const handlecopySvg = React.useCallback(() => {
tlstate.copyAsSvg() tlstate.copySvg()
}, [tlstate]) }, [tlstate])
return ( return (
@ -88,7 +88,7 @@ function SelectedShapeContent(): JSX.Element {
<span>Paste</span> <span>Paste</span>
{showKbds && <Kbd variant="menu">#V</Kbd>} {showKbds && <Kbd variant="menu">#V</Kbd>}
</RowButton> </RowButton>
<RowButton bp={breakpoints} onClick={handleCopyAsSvg}> <RowButton bp={breakpoints} onClick={handlecopySvg}>
<span>Copy to SVG</span> <span>Copy to SVG</span>
{showKbds && <Kbd variant="menu">#C</Kbd>} {showKbds && <Kbd variant="menu">#C</Kbd>}
</RowButton> </RowButton>

View file

@ -11,6 +11,8 @@ import {
import styled from '~styles' import styled from '~styles'
import TextAreaUtils from './text-utils' import TextAreaUtils from './text-utils'
const LETTER_SPACING = -1.5
function normalizeText(text: string) { function normalizeText(text: string) {
return text.replace(/\r?\n|\r/g, '\n') return text.replace(/\r?\n|\r/g, '\n')
} }
@ -31,7 +33,7 @@ function getMeasurementDiv() {
border: '1px solid red', border: '1px solid red',
padding: '4px', padding: '4px',
margin: '0px', margin: '0px',
letterSpacing: '-2.5px', letterSpacing: `${LETTER_SPACING}px`,
opacity: '0', opacity: '0',
position: 'absolute', position: 'absolute',
top: '-500px', top: '-500px',
@ -93,6 +95,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
ref, ref,
meta, meta,
isEditing, isEditing,
isBinding,
onTextBlur, onTextBlur,
onTextChange, onTextChange,
onTextFocus, onTextFocus,
@ -162,15 +165,15 @@ export class Text extends TLDrawShapeUtil<TextShape> {
if (!isEditing) { if (!isEditing) {
return ( return (
<> <>
{/* {isBinding && ( {isBinding && (
<BindingIndicator <rect
as="rect" className="tl-binding-indicator"
x={-32} x={-16}
y={-32} y={-16}
width={bounds.width + 64} width={bounds.width + 32}
height={bounds.height + 64} height={bounds.height + 32}
/> />
)} */} )}
{text.split('\n').map((str, i) => ( {text.split('\n').map((str, i) => (
<text <text
key={i} key={i}
@ -179,7 +182,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
fontFamily="Caveat Brush" fontFamily="Caveat Brush"
fontStyle="normal" fontStyle="normal"
fontWeight="500" fontWeight="500"
letterSpacing="-2.5" letterSpacing={LETTER_SPACING}
fontSize={fontSize} fontSize={fontSize}
width={bounds.width} width={bounds.width}
height={bounds.height} height={bounds.height}
@ -372,6 +375,81 @@ export class Text extends TLDrawShapeUtil<TextShape> {
return shape.text.trim().length === 0 return shape.text.trim().length === 0
} }
getBindingPoint(
shape: TextShape,
point: number[],
origin: number[],
direction: number[],
padding: number,
anywhere: boolean
) {
const bounds = this.getBounds(shape)
const expandedBounds = Utils.expandBounds(bounds, padding)
let bindingPoint: number[]
let distance: number
// The point must be inside of the expanded bounding box
if (!Utils.pointInBounds(point, expandedBounds)) return
// The point is inside of the shape, so we'll assume the user is
// indicating a specific point inside of the shape.
if (anywhere) {
if (Vec.dist(point, this.getCenter(shape)) < 12) {
bindingPoint = [0.5, 0.5]
} else {
bindingPoint = Vec.divV(Vec.sub(point, [expandedBounds.minX, expandedBounds.minY]), [
expandedBounds.width,
expandedBounds.height,
])
}
distance = 0
} else {
// Find furthest intersection between ray from
// origin through point and expanded bounds.
// TODO: Make this a ray vs rounded rect intersection
const intersection = Intersect.ray
.bounds(origin, direction, expandedBounds)
.filter((int) => int.didIntersect)
.map((int) => int.points[0])
.sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]
// The anchor is a point between the handle and the intersection
const anchor = Vec.med(point, intersection)
// If we're close to the center, snap to the center
if (Vec.distanceToLineSegment(point, anchor, this.getCenter(shape)) < 12) {
bindingPoint = [0.5, 0.5]
} else {
// Or else calculate a normalized point
bindingPoint = Vec.divV(Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]), [
expandedBounds.width,
expandedBounds.height,
])
}
if (Utils.pointInBounds(point, bounds)) {
distance = 16
} else {
// If the binding point was close to the shape's center, snap to the center
// Find the distance between the point and the real bounds of the shape
distance = Math.max(
16,
Utils.getBoundsSides(bounds)
.map((side) => Vec.distanceToLineSegment(side[1][0], side[1][1], point))
.sort((a, b) => a - b)[0]
)
}
}
return {
point: Vec.clampV(bindingPoint, 0, 1),
distance,
}
}
// getBindingPoint(shape, point, origin, direction, expandDistance) { // getBindingPoint(shape, point, origin, direction, expandDistance) {
// const bounds = this.getBounds(shape) // const bounds = this.getBounds(shape)
@ -442,7 +520,7 @@ const StyledTextArea = styled('textarea', {
minHeight: 1, minHeight: 1,
minWidth: 1, minWidth: 1,
lineHeight: 1.4, lineHeight: 1.4,
letterSpacing: -2.5, letterSpacing: LETTER_SPACING,
outline: 0, outline: 0,
fontWeight: '500', fontWeight: '500',
backgroundColor: '$boundsBg', backgroundColor: '$boundsBg',

View file

@ -7,9 +7,7 @@ export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>):
const { before, after } = TLDR.mutateShapes( const { before, after } = TLDR.mutateShapes(
data, data,
ids, ids,
(shape) => { (shape) => ({ style: { ...shape.style, ...changes } }),
return { style: { ...shape.style, ...changes } }
},
currentPageId currentPageId
) )
@ -18,7 +16,9 @@ export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>):
before: { before: {
document: { document: {
pages: { pages: {
[currentPageId]: { shapes: before }, [currentPageId]: {
shapes: before,
},
}, },
}, },
appState: { appState: {
@ -28,7 +28,9 @@ export function style(data: Data, ids: string[], changes: Partial<ShapeStyles>):
after: { after: {
document: { document: {
pages: { pages: {
[currentPageId]: { shapes: after }, [currentPageId]: {
shapes: after,
},
}, },
}, },
appState: { appState: {

View file

@ -427,9 +427,19 @@ export class TLDR {
afterShapes[id] = change afterShapes[id] = change
}) })
const dataWithMutations = Utils.deepMerge(data, {
document: {
pages: {
[data.appState.currentPageId]: {
shapes: afterShapes,
},
},
},
})
const dataWithChildrenChanges = ids.reduce<Data>((cData, id) => { const dataWithChildrenChanges = ids.reduce<Data>((cData, id) => {
return this.recursivelyUpdateChildren(cData, id, beforeShapes, afterShapes, pageId) return this.recursivelyUpdateChildren(cData, id, beforeShapes, afterShapes, pageId)
}, data) }, dataWithMutations)
const dataWithParentChanges = ids.reduce<Data>((cData, id) => { const dataWithParentChanges = ids.reduce<Data>((cData, id) => {
return this.recursivelyUpdateParents(cData, id, beforeShapes, afterShapes, pageId) return this.recursivelyUpdateParents(cData, id, beforeShapes, afterShapes, pageId)

View file

@ -228,10 +228,12 @@ describe('TLDrawState', () => {
}) })
describe('Copies to JSON', () => { describe('Copies to JSON', () => {
// TODO tlstate.selectAll()
tlstate.copyJson()
}) })
describe('Copies to SVG', () => { describe('Copies to SVG', () => {
// TODO tlstate.selectAll()
tlstate.copySvg()
}) })
}) })

View file

@ -717,7 +717,7 @@ export class TLDrawState extends StateManager<Data> {
* @param pageId The page from which to copy the shapes. * @param pageId The page from which to copy the shapes.
* @returns A string containing the JSON. * @returns A string containing the JSON.
*/ */
copyAsSvg = (ids = this.selectedIds, pageId = this.currentPageId) => { copySvg = (ids = this.selectedIds, pageId = this.currentPageId) => {
if (ids.length === 0) return if (ids.length === 0) return
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
@ -767,7 +767,7 @@ export class TLDrawState extends StateManager<Data> {
* @param pageId The page from which to copy the shapes. * @param pageId The page from which to copy the shapes.
* @returns A string containing the JSON. * @returns A string containing the JSON.
*/ */
copyAsJson = (ids = this.selectedIds, pageId = this.currentPageId) => { copyJson = (ids = this.selectedIds, pageId = this.currentPageId) => {
const shapes = ids.map((id) => this.getShape(id, pageId)) const shapes = ids.map((id) => this.getShape(id, pageId))
const json = JSON.stringify(shapes, null, 2) const json = JSON.stringify(shapes, null, 2)
TLDR.copyStringToClipboard(json) TLDR.copyStringToClipboard(json)
@ -784,7 +784,10 @@ export class TLDrawState extends StateManager<Data> {
* @param args arguments of the session's start method. * @param args arguments of the session's start method.
* @returns this * @returns this
*/ */
startSession<T extends Session>(session: T, ...args: ParametersExceptFirst<T['start']>): this { startSession = <T extends Session>(
session: T,
...args: ParametersExceptFirst<T['start']>
): this => {
this.session = session this.session = session
const result = session.start(this.state, ...args) const result = session.start(this.state, ...args)
@ -813,7 +816,7 @@ export class TLDrawState extends StateManager<Data> {
* @param args The arguments of the current session's update method. * @param args The arguments of the current session's update method.
* @returns this * @returns this
*/ */
updateSession<T extends Session>(...args: ParametersExceptFirst<T['update']>): this { updateSession = <T extends Session>(...args: ParametersExceptFirst<T['update']>): this => {
const { session } = this const { session } = this
if (!session) return this if (!session) return this
const patch = session.update(this.state, ...args) const patch = session.update(this.state, ...args)
@ -826,7 +829,7 @@ export class TLDrawState extends StateManager<Data> {
* @param args The arguments of the current session's cancel method. * @param args The arguments of the current session's cancel method.
* @returns this * @returns this
*/ */
cancelSession<T extends Session>(...args: ParametersExceptFirst<T['cancel']>): this { cancelSession = <T extends Session>(...args: ParametersExceptFirst<T['cancel']>): this => {
const { session } = this const { session } = this
if (!session) return this if (!session) return this
this.session = undefined this.session = undefined
@ -882,7 +885,7 @@ export class TLDrawState extends StateManager<Data> {
* @param args The arguments of the current session's complete method. * @param args The arguments of the current session's complete method.
* @returns this * @returns this
*/ */
completeSession<T extends Session>(...args: ParametersExceptFirst<T['complete']>) { completeSession = <T extends Session>(...args: ParametersExceptFirst<T['complete']>): this => {
const { session } = this const { session } = this
if (!session) return this if (!session) return this
@ -1008,20 +1011,22 @@ export class TLDrawState extends StateManager<Data> {
/** /**
* Clear the selection history (undo/redo stack for selection). * Clear the selection history (undo/redo stack for selection).
*/ */
private clearSelectHistory() { private clearSelectHistory = (): this => {
this.selectHistory.pointer = 0 this.selectHistory.pointer = 0
this.selectHistory.stack = [this.selectedIds] this.selectHistory.stack = [this.selectedIds]
return this
} }
/** /**
* Adds a selection to the selection history (undo/redo stack for selection). * Adds a selection to the selection history (undo/redo stack for selection).
*/ */
private addToSelectHistory(ids: string[]) { private addToSelectHistory = (ids: string[]): this => {
if (this.selectHistory.pointer < this.selectHistory.stack.length) { if (this.selectHistory.pointer < this.selectHistory.stack.length) {
this.selectHistory.stack = this.selectHistory.stack.slice(0, this.selectHistory.pointer + 1) this.selectHistory.stack = this.selectHistory.stack.slice(0, this.selectHistory.pointer + 1)
} }
this.selectHistory.pointer++ this.selectHistory.pointer++
this.selectHistory.stack.push(ids) this.selectHistory.stack.push(ids)
return this
} }
/** /**
@ -1030,7 +1035,7 @@ export class TLDrawState extends StateManager<Data> {
* @param push Whether to add the ids to the current selection instead. * @param push Whether to add the ids to the current selection instead.
* @returns this * @returns this
*/ */
private setSelectedIds(ids: string[], push = false): this { private setSelectedIds = (ids: string[], push = false): this => {
return this.patchState( return this.patchState(
{ {
appState: { appState: {
@ -1053,7 +1058,7 @@ export class TLDrawState extends StateManager<Data> {
* Undo the most recent selection. * Undo the most recent selection.
* @returns this * @returns this
*/ */
undoSelect(): this { undoSelect = (): this => {
if (this.selectHistory.pointer > 0) { if (this.selectHistory.pointer > 0) {
this.selectHistory.pointer-- this.selectHistory.pointer--
this.setSelectedIds(this.selectHistory.stack[this.selectHistory.pointer]) this.setSelectedIds(this.selectHistory.stack[this.selectHistory.pointer])
@ -1065,7 +1070,7 @@ export class TLDrawState extends StateManager<Data> {
* Redo the previous selection. * Redo the previous selection.
* @returns this * @returns this
*/ */
redoSelect(): this { redoSelect = (): this => {
if (this.selectHistory.pointer < this.selectHistory.stack.length - 1) { if (this.selectHistory.pointer < this.selectHistory.stack.length - 1) {
this.selectHistory.pointer++ this.selectHistory.pointer++
this.setSelectedIds(this.selectHistory.stack[this.selectHistory.pointer]) this.setSelectedIds(this.selectHistory.stack[this.selectHistory.pointer])
@ -1178,7 +1183,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
style = (style: Partial<ShapeStyles>, ids = this.selectedIds) => { style = (style: Partial<ShapeStyles>, ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.style(this.state, ids, style)) return this.setState(Commands.style(this.state, ids, style))
} }
@ -1188,7 +1194,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
align = (type: AlignType, ids = this.selectedIds) => { align = (type: AlignType, ids = this.selectedIds): this => {
if (ids.length < 2) return this
return this.setState(Commands.align(this.state, ids, type)) return this.setState(Commands.align(this.state, ids, type))
} }
@ -1198,7 +1205,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
distribute = (direction: DistributeType, ids = this.selectedIds) => { distribute = (direction: DistributeType, ids = this.selectedIds): this => {
if (ids.length < 3) return this
return this.setState(Commands.distribute(this.state, ids, direction)) return this.setState(Commands.distribute(this.state, ids, direction))
} }
@ -1208,7 +1216,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
stretch = (direction: StretchType, ids = this.selectedIds) => { stretch = (direction: StretchType, ids = this.selectedIds): this => {
if (ids.length < 2) return this
return this.setState(Commands.stretch(this.state, ids, direction)) return this.setState(Commands.stretch(this.state, ids, direction))
} }
@ -1217,7 +1226,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
flipHorizontal = (ids = this.selectedIds) => { flipHorizontal = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.flip(this.state, ids, FlipType.Horizontal)) return this.setState(Commands.flip(this.state, ids, FlipType.Horizontal))
} }
@ -1226,7 +1236,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
flipVertical = (ids = this.selectedIds) => { flipVertical = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.flip(this.state, ids, FlipType.Vertical)) return this.setState(Commands.flip(this.state, ids, FlipType.Vertical))
} }
@ -1235,7 +1246,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
moveToBack = (ids = this.selectedIds) => { moveToBack = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.move(this.state, ids, MoveType.ToBack)) return this.setState(Commands.move(this.state, ids, MoveType.ToBack))
} }
@ -1244,7 +1256,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
moveBackward = (ids = this.selectedIds) => { moveBackward = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.move(this.state, ids, MoveType.Backward)) return this.setState(Commands.move(this.state, ids, MoveType.Backward))
} }
@ -1253,7 +1266,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
moveForward = (ids = this.selectedIds) => { moveForward = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.move(this.state, ids, MoveType.Forward)) return this.setState(Commands.move(this.state, ids, MoveType.Forward))
} }
@ -1262,7 +1276,8 @@ export class TLDrawState extends StateManager<Data> {
* @param ids The ids of the shapes to change (defaults to selection). * @param ids The ids of the shapes to change (defaults to selection).
* @returns this * @returns this
*/ */
moveToFront = (ids = this.selectedIds) => { moveToFront = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.move(this.state, ids, MoveType.ToFront)) return this.setState(Commands.move(this.state, ids, MoveType.ToFront))
} }
@ -1274,6 +1289,7 @@ export class TLDrawState extends StateManager<Data> {
* @returns this * @returns this
*/ */
nudge = (delta: number[], isMajor = false, ids = this.selectedIds): this => { nudge = (delta: number[], isMajor = false, ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.translate(this.state, ids, Vec.mul(delta, isMajor ? 10 : 1))) return this.setState(Commands.translate(this.state, ids, Vec.mul(delta, isMajor ? 10 : 1)))
} }
@ -1283,6 +1299,7 @@ export class TLDrawState extends StateManager<Data> {
* @returns this * @returns this
*/ */
duplicate = (ids = this.selectedIds): this => { duplicate = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.duplicate(this.state, ids)) return this.setState(Commands.duplicate(this.state, ids))
} }
@ -1292,6 +1309,7 @@ export class TLDrawState extends StateManager<Data> {
* @returns this * @returns this
*/ */
toggleHidden = (ids = this.selectedIds): this => { toggleHidden = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.toggle(this.state, ids, 'isHidden')) return this.setState(Commands.toggle(this.state, ids, 'isHidden'))
} }
@ -1301,6 +1319,7 @@ export class TLDrawState extends StateManager<Data> {
* @returns this * @returns this
*/ */
toggleLocked = (ids = this.selectedIds): this => { toggleLocked = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.toggle(this.state, ids, 'isLocked')) return this.setState(Commands.toggle(this.state, ids, 'isLocked'))
} }
@ -1310,6 +1329,7 @@ export class TLDrawState extends StateManager<Data> {
* @returns this * @returns this
*/ */
toggleAspectRatioLocked = (ids = this.selectedIds): this => { toggleAspectRatioLocked = (ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.toggle(this.state, ids, 'isAspectRatioLocked')) return this.setState(Commands.toggle(this.state, ids, 'isAspectRatioLocked'))
} }
@ -1320,13 +1340,10 @@ export class TLDrawState extends StateManager<Data> {
* @returns this * @returns this
*/ */
toggleDecoration = (handleId: string, ids = this.selectedIds): this => { toggleDecoration = (handleId: string, ids = this.selectedIds): this => {
if (handleId === 'start' || handleId === 'end') { if (ids.length === 0 || !(handleId === 'start' || handleId === 'end')) return this
return this.setState(Commands.toggleDecoration(this.state, ids, handleId)) return this.setState(Commands.toggleDecoration(this.state, ids, handleId))
} }
return this
}
/** /**
* Rotate one or more shapes by a delta. * Rotate one or more shapes by a delta.
* @param delta The delta in radians. * @param delta The delta in radians.
@ -1334,6 +1351,7 @@ export class TLDrawState extends StateManager<Data> {
* @returns this * @returns this
*/ */
rotate = (delta = Math.PI * -0.5, ids = this.selectedIds): this => { rotate = (delta = Math.PI * -0.5, ids = this.selectedIds): this => {
if (ids.length === 0) return this
return this.setState(Commands.rotate(this.state, ids, delta)) return this.setState(Commands.rotate(this.state, ids, delta))
} }
@ -1343,6 +1361,7 @@ export class TLDrawState extends StateManager<Data> {
* @todo * @todo
*/ */
group = (): this => { group = (): this => {
// TODO
return this return this
} }