Improves status, create session handling
This commit is contained in:
parent
ffc180fa1c
commit
40cbf2d92b
15 changed files with 478 additions and 266 deletions
|
@ -3,16 +3,19 @@ import { useTLDrawContext } from '~hooks'
|
|||
import type { Data } from '~types'
|
||||
import styled from '~styles'
|
||||
|
||||
const statusSelector = (s: Data) => s.appState.status.current
|
||||
const activeToolSelector = (s: Data) => s.appState.activeTool
|
||||
|
||||
export function StatusBar(): JSX.Element | null {
|
||||
const { useSelector } = useTLDrawContext()
|
||||
const status = useSelector(statusSelector)
|
||||
const activeTool = useSelector(activeToolSelector)
|
||||
|
||||
return (
|
||||
<StatusBarContainer size={{ '@sm': 'small' }}>
|
||||
<Section>{activeTool}</Section>
|
||||
{/* <Section>{shapesInView || '0'} Shapes</Section> */}
|
||||
<Section>
|
||||
{activeTool} | {status}
|
||||
</Section>
|
||||
</StatusBarContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
Intersect,
|
||||
TLHandle,
|
||||
TLPointerInfo,
|
||||
Svg,
|
||||
} from '@tldraw/core'
|
||||
import getStroke from 'perfect-freehand'
|
||||
import { defaultStyle, getPerfectDashProps, getShapeStyle } from '~shape/shape-styles'
|
||||
|
@ -27,7 +26,7 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
type = TLDrawShapeType.Arrow as const
|
||||
toolType = TLDrawToolType.Handle
|
||||
canStyleFill = false
|
||||
simplePathCache = new WeakMap<ArrowShape, string>()
|
||||
simplePathCache = new WeakMap<ArrowShape['handles'], string>()
|
||||
pathCache = new WeakMap<ArrowShape, string>()
|
||||
|
||||
defaultProps = {
|
||||
|
@ -72,12 +71,63 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
}
|
||||
|
||||
render = (shape: ArrowShape, { isDarkMode }: TLRenderInfo) => {
|
||||
const { bend, handles, style } = shape
|
||||
const { start, end, bend: _bend } = handles
|
||||
const {
|
||||
handles: { start, bend, end },
|
||||
decorations = {},
|
||||
style,
|
||||
} = shape
|
||||
|
||||
const isStraightLine = Vec.dist(_bend.point, Vec.round(Vec.med(start.point, end.point))) < 1
|
||||
const isDraw = style.dash === DashStyle.Draw
|
||||
|
||||
const isDraw = shape.style.dash === DashStyle.Draw
|
||||
// if (!isDraw) {
|
||||
// const styles = getShapeStyle(style, isDarkMode)
|
||||
|
||||
// const { strokeWidth } = styles
|
||||
|
||||
// const arrowDist = Vec.dist(start.point, end.point)
|
||||
|
||||
// const sw = strokeWidth * 1.618
|
||||
|
||||
// const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
// arrowDist,
|
||||
// sw,
|
||||
// shape.style.dash,
|
||||
// 2
|
||||
// )
|
||||
|
||||
// const path = getArrowPath(shape)
|
||||
|
||||
// return (
|
||||
// <g pointerEvents="none">
|
||||
// <path
|
||||
// d={path}
|
||||
// fill="none"
|
||||
// stroke="transparent"
|
||||
// strokeWidth={Math.max(8, strokeWidth * 2)}
|
||||
// strokeDasharray="none"
|
||||
// strokeDashoffset="none"
|
||||
// strokeLinecap="round"
|
||||
// strokeLinejoin="round"
|
||||
// pointerEvents="stroke"
|
||||
// />
|
||||
// <path
|
||||
// d={path}
|
||||
// fill={isDraw ? styles.stroke : 'none'}
|
||||
// stroke={styles.stroke}
|
||||
// strokeWidth={sw}
|
||||
// strokeDasharray={strokeDasharray}
|
||||
// strokeDashoffset={strokeDashoffset}
|
||||
// strokeLinecap="round"
|
||||
// strokeLinejoin="round"
|
||||
// pointerEvents="stroke"
|
||||
// />
|
||||
// </g>
|
||||
// )
|
||||
// }
|
||||
|
||||
// TODO: Improve drawn arrows
|
||||
|
||||
const isStraightLine = Vec.dist(bend.point, Vec.round(Vec.med(start.point, end.point))) < 1
|
||||
|
||||
const styles = getShapeStyle(style, isDarkMode)
|
||||
|
||||
|
@ -85,11 +135,11 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
|
||||
const arrowDist = Vec.dist(start.point, end.point)
|
||||
|
||||
const arrowHeadlength = Math.min(arrowDist / 3, strokeWidth * 8)
|
||||
const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8)
|
||||
|
||||
let shaftPath: JSX.Element
|
||||
let insetStart: number[]
|
||||
let insetEnd: number[]
|
||||
let startArrowHead: { left: number[]; right: number[] } | undefined
|
||||
let endArrowHead: { left: number[]; right: number[] } | undefined
|
||||
|
||||
if (isStraightLine) {
|
||||
const sw = strokeWidth * (isDraw ? 0.618 : 1.618)
|
||||
|
@ -107,8 +157,13 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
2
|
||||
)
|
||||
|
||||
insetStart = Vec.nudge(start.point, end.point, arrowHeadlength)
|
||||
insetEnd = Vec.nudge(end.point, start.point, arrowHeadlength)
|
||||
if (decorations.start) {
|
||||
startArrowHead = getStraightArrowHeadPoints(start.point, end.point, arrowHeadLength)
|
||||
}
|
||||
|
||||
if (decorations.end) {
|
||||
endArrowHead = getStraightArrowHeadPoints(end.point, start.point, arrowHeadLength)
|
||||
}
|
||||
|
||||
// Straight arrow path
|
||||
shaftPath = (
|
||||
|
@ -144,31 +199,37 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
const path = Utils.getFromCache(this.pathCache, shape, () =>
|
||||
isDraw
|
||||
? renderCurvedFreehandArrowShaft(shape, circle)
|
||||
: getArrowArcPath(start, end, circle, bend)
|
||||
: getArrowArcPath(start, end, circle, shape.bend)
|
||||
)
|
||||
|
||||
const arcLength = Utils.getArcLength(
|
||||
[circle[0], circle[1]],
|
||||
circle[2],
|
||||
start.point,
|
||||
end.point
|
||||
)
|
||||
const { center, radius, length } = getArrowArc(shape)
|
||||
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
arcLength - 1,
|
||||
length - 1,
|
||||
sw,
|
||||
shape.style.dash,
|
||||
2
|
||||
)
|
||||
|
||||
const center = [circle[0], circle[1]]
|
||||
const radius = circle[2]
|
||||
const sa = Vec.angle(center, start.point)
|
||||
const ea = Vec.angle(center, end.point)
|
||||
const t = arrowHeadlength / Math.abs(arcLength)
|
||||
if (decorations.start) {
|
||||
startArrowHead = getCurvedArrowHeadPoints(
|
||||
start.point,
|
||||
arrowHeadLength,
|
||||
center,
|
||||
radius,
|
||||
length < 0
|
||||
)
|
||||
}
|
||||
|
||||
insetStart = Vec.nudgeAtAngle(center, Utils.lerpAngles(sa, ea, t), radius)
|
||||
insetEnd = Vec.nudgeAtAngle(center, Utils.lerpAngles(ea, sa, t), radius)
|
||||
if (decorations.end) {
|
||||
endArrowHead = getCurvedArrowHeadPoints(
|
||||
end.point,
|
||||
arrowHeadLength,
|
||||
center,
|
||||
radius,
|
||||
length >= 0
|
||||
)
|
||||
}
|
||||
|
||||
// Curved arrow path
|
||||
shaftPath = (
|
||||
|
@ -204,9 +265,9 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
return (
|
||||
<g pointerEvents="none">
|
||||
{shaftPath}
|
||||
{shape.decorations?.start === Decoration.Arrow && (
|
||||
{startArrowHead && (
|
||||
<path
|
||||
d={getArrowHeadPath(shape, start.point, insetStart)}
|
||||
d={`M ${startArrowHead.left} L ${start.point} ${startArrowHead.right}`}
|
||||
fill="none"
|
||||
stroke={styles.stroke}
|
||||
strokeWidth={sw}
|
||||
|
@ -217,9 +278,9 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
pointerEvents="stroke"
|
||||
/>
|
||||
)}
|
||||
{shape.decorations?.end === Decoration.Arrow && (
|
||||
{endArrowHead && (
|
||||
<path
|
||||
d={getArrowHeadPath(shape, end.point, insetEnd)}
|
||||
d={`M ${endArrowHead.left} L ${end.point} ${endArrowHead.right}`}
|
||||
fill="none"
|
||||
stroke={styles.stroke}
|
||||
strokeWidth={sw}
|
||||
|
@ -235,91 +296,9 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
}
|
||||
|
||||
renderIndicator(shape: ArrowShape) {
|
||||
const {
|
||||
decorations,
|
||||
handles: { start, end, bend: _bend },
|
||||
style,
|
||||
} = shape
|
||||
const path = Utils.getFromCache(this.simplePathCache, shape.handles, () => getArrowPath(shape))
|
||||
|
||||
const { strokeWidth } = getShapeStyle(style, false)
|
||||
|
||||
const arrowDist = Vec.dist(start.point, end.point)
|
||||
|
||||
const arrowHeadlength = Math.min(arrowDist / 3, strokeWidth * 8)
|
||||
|
||||
const aw = arrowHeadlength / 2
|
||||
|
||||
const path: (string | number)[] = []
|
||||
|
||||
const isStraightLine = Vec.dist(_bend.point, Vec.round(Vec.med(start.point, end.point))) < 1
|
||||
|
||||
if (isStraightLine) {
|
||||
path.push(Svg.moveTo(start.point), Svg.lineTo(end.point))
|
||||
|
||||
if (decorations?.start) {
|
||||
const point = start.point
|
||||
const ints = Intersect.circle.lineSegment(start.point, aw, start.point, end.point).points
|
||||
const int = ints[0]
|
||||
|
||||
path.push(
|
||||
Svg.moveTo(Vec.nudge(Vec.rotWith(int, point, Math.PI / 6), point, -aw)),
|
||||
Svg.lineTo(start.point),
|
||||
Svg.lineTo(Vec.nudge(Vec.rotWith(int, point, -Math.PI / 6), point, -aw))
|
||||
)
|
||||
}
|
||||
|
||||
if (decorations?.end) {
|
||||
const point = end.point
|
||||
const ints = Intersect.circle.lineSegment(end.point, aw, start.point, end.point).points
|
||||
const int = ints[0]
|
||||
|
||||
path.push(
|
||||
Svg.moveTo(Vec.nudge(Vec.rotWith(int, point, Math.PI / 6), point, -aw)),
|
||||
Svg.lineTo(end.point),
|
||||
Svg.lineTo(Vec.nudge(Vec.rotWith(int, point, -Math.PI / 6), point, -aw))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const circle = getCtp(shape)
|
||||
const center = [circle[0], circle[1]]
|
||||
const radius = circle[2]
|
||||
const sweep = Utils.getArcLength(center, radius, start.point, end.point) > 0
|
||||
|
||||
path.push(
|
||||
Svg.moveTo(start.point),
|
||||
`A ${radius} ${radius} 0 0 ${sweep ? '1' : '0'} ${end.point}`
|
||||
)
|
||||
|
||||
if (decorations?.start) {
|
||||
const point = start.point
|
||||
const ints = Intersect.circle.circle(center, radius, point, aw).points
|
||||
const int = sweep ? ints[0] : ints[1]
|
||||
|
||||
path.push(
|
||||
Svg.moveTo(Vec.nudge(Vec.rotWith(int, point, Math.PI / 6), point, -aw)),
|
||||
Svg.lineTo(start.point),
|
||||
Svg.lineTo(Vec.nudge(Vec.rotWith(int, point, -Math.PI / 6), point, -aw))
|
||||
)
|
||||
}
|
||||
|
||||
if (decorations?.end) {
|
||||
const point = end.point
|
||||
const ints = Intersect.circle.circle(center, radius, point, aw).points
|
||||
const int = sweep ? ints[1] : ints[0]
|
||||
|
||||
path.push(
|
||||
Svg.moveTo(Vec.nudge(Vec.rotWith(int, point, Math.PI / 6), point, -aw)),
|
||||
Svg.lineTo(end.point),
|
||||
Svg.lineTo(Vec.nudge(Vec.rotWith(int, point, -Math.PI / 6), point, -aw))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<g>
|
||||
<path d={path.join()} />
|
||||
</g>
|
||||
)
|
||||
return <path d={path} />
|
||||
}
|
||||
|
||||
getBounds = (shape: ArrowShape) => {
|
||||
|
@ -764,3 +743,143 @@ function getCtp(shape: ArrowShape) {
|
|||
const { start, end, bend } = shape.handles
|
||||
return Utils.circleFromThreePoints(start.point, end.point, bend.point)
|
||||
}
|
||||
|
||||
function getArrowArc(shape: ArrowShape) {
|
||||
const { start, end, bend } = shape.handles
|
||||
const [cx, cy, radius] = Utils.circleFromThreePoints(start.point, end.point, bend.point)
|
||||
const center = [cx, cy]
|
||||
const length = Utils.getArcLength(center, radius, start.point, end.point)
|
||||
return { center, radius, length }
|
||||
}
|
||||
|
||||
function getCurvedArrowHeadPoints(
|
||||
A: number[],
|
||||
r1: number,
|
||||
C: number[],
|
||||
r2: number,
|
||||
sweep: boolean
|
||||
) {
|
||||
const ints = Intersect.circle.circle(A, r1 * 0.618, C, r2).points
|
||||
const int = sweep ? ints[0] : ints[1]
|
||||
const left = Vec.nudge(Vec.rotWith(int, A, Math.PI / 6), A, r1 * -0.382)
|
||||
const right = Vec.nudge(Vec.rotWith(int, A, -Math.PI / 6), A, r1 * -0.382)
|
||||
return { left, right }
|
||||
}
|
||||
|
||||
function getStraightArrowHeadPoints(A: number[], B: number[], r: number) {
|
||||
const ints = Intersect.circle.lineSegment(A, r, A, B).points
|
||||
const int = ints[0]
|
||||
const left = Vec.rotWith(int, A, Math.PI / 6)
|
||||
const right = Vec.rotWith(int, A, -Math.PI / 6)
|
||||
return { left, right }
|
||||
}
|
||||
|
||||
function getCurvedArrowHeadPath(A: number[], r1: number, C: number[], r2: number, sweep: boolean) {
|
||||
const { left, right } = getCurvedArrowHeadPoints(A, r1, C, r2, sweep)
|
||||
return `M ${left} L ${A} ${right}`
|
||||
}
|
||||
|
||||
function getStraightArrowHeadPath(A: number[], B: number[], r: number) {
|
||||
const { left, right } = getStraightArrowHeadPoints(A, B, r)
|
||||
return `M ${left} L ${A} ${right}`
|
||||
}
|
||||
|
||||
function getArrowPath(shape: ArrowShape) {
|
||||
const {
|
||||
decorations,
|
||||
handles: { start, end, bend: _bend },
|
||||
style,
|
||||
} = shape
|
||||
|
||||
const { strokeWidth } = getShapeStyle(style, false)
|
||||
|
||||
const arrowDist = Vec.dist(start.point, end.point)
|
||||
|
||||
const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8)
|
||||
|
||||
const path: (string | number)[] = []
|
||||
|
||||
const isStraightLine = Vec.dist(_bend.point, Vec.round(Vec.med(start.point, end.point))) < 1
|
||||
|
||||
if (isStraightLine) {
|
||||
// Path (line segment)
|
||||
path.push(`M ${start.point} L ${end.point}`)
|
||||
|
||||
// Start arrow head
|
||||
if (decorations?.start) {
|
||||
path.push(getStraightArrowHeadPath(start.point, end.point, arrowHeadLength))
|
||||
}
|
||||
|
||||
// End arrow head
|
||||
if (decorations?.end) {
|
||||
path.push(getStraightArrowHeadPath(end.point, start.point, arrowHeadLength))
|
||||
}
|
||||
} else {
|
||||
const { center, radius, length } = getArrowArc(shape)
|
||||
|
||||
// Path (arc)
|
||||
path.push(`M ${start.point} A ${radius} ${radius} 0 0 ${length > 0 ? '1' : '0'} ${end.point}`)
|
||||
|
||||
// Start Arrow head
|
||||
if (decorations?.start) {
|
||||
path.push(getCurvedArrowHeadPath(start.point, arrowHeadLength, center, radius, length < 0))
|
||||
}
|
||||
|
||||
// End arrow head
|
||||
if (decorations?.end) {
|
||||
path.push(getCurvedArrowHeadPath(end.point, arrowHeadLength, center, radius, length >= 0))
|
||||
}
|
||||
}
|
||||
|
||||
return path.join(' ')
|
||||
}
|
||||
|
||||
function getDrawArrowPath(shape: ArrowShape) {
|
||||
const {
|
||||
decorations,
|
||||
handles: { start, end, bend: _bend },
|
||||
style,
|
||||
} = shape
|
||||
|
||||
const { strokeWidth } = getShapeStyle(style, false)
|
||||
|
||||
const arrowDist = Vec.dist(start.point, end.point)
|
||||
|
||||
const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8)
|
||||
|
||||
const path: (string | number)[] = []
|
||||
|
||||
const isStraightLine = Vec.dist(_bend.point, Vec.round(Vec.med(start.point, end.point))) < 1
|
||||
|
||||
if (isStraightLine) {
|
||||
// Path (line segment)
|
||||
path.push(`M ${start.point} L ${end.point}`)
|
||||
|
||||
// Start arrow head
|
||||
if (decorations?.start) {
|
||||
path.push(getStraightArrowHeadPath(start.point, end.point, arrowHeadLength))
|
||||
}
|
||||
|
||||
// End arrow head
|
||||
if (decorations?.end) {
|
||||
path.push(getStraightArrowHeadPath(end.point, start.point, arrowHeadLength))
|
||||
}
|
||||
} else {
|
||||
const { center, radius, length } = getArrowArc(shape)
|
||||
|
||||
// Path (arc)
|
||||
path.push(`M ${start.point} A ${radius} ${radius} 0 0 ${length > 0 ? '1' : '0'} ${end.point}`)
|
||||
|
||||
// Start Arrow head
|
||||
if (decorations?.start) {
|
||||
path.push(getCurvedArrowHeadPath(start.point, arrowHeadLength, center, radius, length < 0))
|
||||
}
|
||||
|
||||
// End arrow head
|
||||
if (decorations?.end) {
|
||||
path.push(getCurvedArrowHeadPath(end.point, arrowHeadLength, center, radius, length >= 0))
|
||||
}
|
||||
}
|
||||
|
||||
return path.join(' ')
|
||||
}
|
||||
|
|
|
@ -45,11 +45,11 @@ describe('Move command', () => {
|
|||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['b'])
|
||||
tlstate.moveToBack()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bacd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('bacd')
|
||||
tlstate.undo()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abcd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('abcd')
|
||||
tlstate.redo()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bacd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('bacd')
|
||||
})
|
||||
|
||||
describe('to back', () => {
|
||||
|
@ -57,21 +57,21 @@ describe('Move command', () => {
|
|||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['b'])
|
||||
tlstate.moveToBack()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bacd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('bacd')
|
||||
})
|
||||
|
||||
it('moves two adjacent siblings to back', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['b', 'c'])
|
||||
tlstate.moveToBack()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bcad')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('bcad')
|
||||
})
|
||||
|
||||
it('moves two non-adjacent siblings to back', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['b', 'd'])
|
||||
tlstate.moveToBack()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bdac')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('bdac')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -80,35 +80,35 @@ describe('Move command', () => {
|
|||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['c'])
|
||||
tlstate.moveBackward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acbd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('acbd')
|
||||
})
|
||||
|
||||
it('moves a shape at first index backward', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['a'])
|
||||
tlstate.moveBackward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abcd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('abcd')
|
||||
})
|
||||
|
||||
it('moves two adjacent siblings backward', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['c', 'd'])
|
||||
tlstate.moveBackward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acdb')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('acdb')
|
||||
})
|
||||
|
||||
it('moves two non-adjacent siblings backward', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['b', 'd'])
|
||||
tlstate.moveBackward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('badc')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('badc')
|
||||
})
|
||||
|
||||
it('moves two adjacent siblings backward at zero index', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['a', 'b'])
|
||||
tlstate.moveBackward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abcd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('abcd')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -117,7 +117,7 @@ describe('Move command', () => {
|
|||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['c'])
|
||||
tlstate.moveForward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abdc')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('abdc')
|
||||
})
|
||||
|
||||
it('moves a shape forward at the top index', () => {
|
||||
|
@ -126,28 +126,28 @@ describe('Move command', () => {
|
|||
tlstate.moveForward()
|
||||
tlstate.moveForward()
|
||||
tlstate.moveForward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acdb')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('acdb')
|
||||
})
|
||||
|
||||
it('moves two adjacent siblings forward', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['a', 'b'])
|
||||
tlstate.moveForward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('cabd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('cabd')
|
||||
})
|
||||
|
||||
it('moves two non-adjacent siblings forward', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['a', 'c'])
|
||||
tlstate.moveForward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('badc')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('badc')
|
||||
})
|
||||
|
||||
it('moves two adjacent siblings forward at top index', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['c', 'd'])
|
||||
tlstate.moveForward()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abcd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('abcd')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -156,28 +156,28 @@ describe('Move command', () => {
|
|||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['b'])
|
||||
tlstate.moveToFront()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acdb')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('acdb')
|
||||
})
|
||||
|
||||
it('moves two adjacent siblings to front', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['a', 'b'])
|
||||
tlstate.moveToFront()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('cdab')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('cdab')
|
||||
})
|
||||
|
||||
it('moves two non-adjacent siblings to front', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['a', 'c'])
|
||||
tlstate.moveToFront()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bdac')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('bdac')
|
||||
})
|
||||
|
||||
it('moves siblings already at front to front', () => {
|
||||
tlstate.loadDocument(doc)
|
||||
tlstate.setSelectedIds(['c', 'd'])
|
||||
tlstate.moveToFront()
|
||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abcd')
|
||||
expect(getSortedShapeIds(tlstate.data)).toBe('abcd')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -33,3 +33,13 @@ When we mutate shapes inside of a command, we:
|
|||
- When the history "does" the command, merge the "redo" data into the current `Data`.
|
||||
- When the history "undoes" the command, merge the "undo" data into the current `Data`.
|
||||
- When the history "redoes" the command, merge the "redo" data into the current `Data`.
|
||||
|
||||
## onChange Events
|
||||
|
||||
When something changes in the state, we need to produce an onChange event that is compatible with multiplayer implementations. This still requires some research, however at minimum we want to include:
|
||||
|
||||
- The current user's id
|
||||
- The current document id
|
||||
- The event patch (what's changed)
|
||||
|
||||
The first step would be to implement onChange events for commands. These are already set up as patches and always produce a history entry.
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import type { ArrowBinding, ArrowShape, TLDrawShape, TLDrawBinding, Data, Session } from '~types'
|
||||
import {
|
||||
ArrowBinding,
|
||||
ArrowShape,
|
||||
TLDrawShape,
|
||||
TLDrawBinding,
|
||||
Data,
|
||||
Session,
|
||||
TLDrawStatus,
|
||||
} from '~types'
|
||||
import { Vec, Utils } from '@tldraw/core'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class ArrowSession implements Session {
|
||||
id = 'transform_single'
|
||||
status = TLDrawStatus.TranslatingHandle
|
||||
newBindingId = Utils.uniqueId()
|
||||
delta = [0, 0]
|
||||
offset = [0, 0]
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { brushUpdater, Utils, Vec } from '@tldraw/core'
|
||||
import type { Data, Session } from '~types'
|
||||
import { Data, Session, TLDrawStatus } from '~types'
|
||||
import { getShapeUtils } from '~shape'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class BrushSession implements Session {
|
||||
id = 'brush'
|
||||
status = TLDrawStatus.Brushing
|
||||
origin: number[]
|
||||
snapshot: BrushSnapshot
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Utils, Vec } from '@tldraw/core'
|
||||
import type { Data, DrawShape, Session } from '~types'
|
||||
import { Data, DrawShape, Session, TLDrawStatus } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class DrawSession implements Session {
|
||||
id = 'draw'
|
||||
status = TLDrawStatus.Creating
|
||||
origin: number[]
|
||||
previous: number[]
|
||||
last: number[]
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { Vec } from '@tldraw/core'
|
||||
import type { ShapesWithProp } from '~types'
|
||||
import { ShapesWithProp, TLDrawStatus } from '~types'
|
||||
import type { Session } from '~types'
|
||||
import type { Data } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class HandleSession implements Session {
|
||||
id = 'transform_single'
|
||||
status = TLDrawStatus.TranslatingHandle
|
||||
commandId: string
|
||||
delta = [0, 0]
|
||||
origin: number[]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Utils, Vec } from '@tldraw/core'
|
||||
import type { Session } from '~types'
|
||||
import { Session, TLDrawStatus } from '~types'
|
||||
import type { Data } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
|
@ -7,6 +7,7 @@ const PI2 = Math.PI * 2
|
|||
|
||||
export class RotateSession implements Session {
|
||||
id = 'rotate'
|
||||
status = TLDrawStatus.Transforming
|
||||
delta = [0, 0]
|
||||
origin: number[]
|
||||
snapshot: RotateSnapshot
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import type { TextShape } from '~types'
|
||||
import { TextShape, TLDrawStatus } from '~types'
|
||||
import type { Session } from '~types'
|
||||
import type { Data } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class TextSession implements Session {
|
||||
id = 'text'
|
||||
status = TLDrawStatus.EditingText
|
||||
initialShape: TextShape
|
||||
|
||||
constructor(data: Data, id?: string) {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core'
|
||||
import type { TLDrawShape } from '~types'
|
||||
import { TLDrawShape, TLDrawStatus } from '~types'
|
||||
import type { Session } from '~types'
|
||||
import type { Data } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class TransformSingleSession implements Session {
|
||||
id = 'transform_single'
|
||||
status = TLDrawStatus.Transforming
|
||||
commandId: string
|
||||
transformType: TLBoundsEdge | TLBoundsCorner
|
||||
origin: number[]
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core'
|
||||
import type { Session } from '~types'
|
||||
import { Session, TLDrawStatus } from '~types'
|
||||
import type { Data } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class TransformSession implements Session {
|
||||
id = 'transform'
|
||||
status = TLDrawStatus.Transforming
|
||||
scaleX = 1
|
||||
scaleY = 1
|
||||
transformType: TLBoundsEdge | TLBoundsCorner
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import { Utils, Vec } from '@tldraw/core'
|
||||
import type { TLDrawShape, TLDrawBinding, PagePartial, Session, Data, Command } from '~types'
|
||||
import {
|
||||
TLDrawShape,
|
||||
TLDrawBinding,
|
||||
PagePartial,
|
||||
Session,
|
||||
Data,
|
||||
Command,
|
||||
TLDrawStatus,
|
||||
} from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
||||
export class TranslateSession implements Session {
|
||||
id = 'translate'
|
||||
status = TLDrawStatus.Translating
|
||||
delta = [0, 0]
|
||||
prev = [0, 0]
|
||||
origin: number[]
|
||||
|
|
|
@ -62,6 +62,10 @@ const initialData: Data = {
|
|||
isToolLocked: false,
|
||||
isStyleOpen: false,
|
||||
isEmptyCanvas: false,
|
||||
status: {
|
||||
current: TLDrawStatus.Idle,
|
||||
previous: TLDrawStatus.Idle,
|
||||
},
|
||||
},
|
||||
page: {
|
||||
id: 'page',
|
||||
|
@ -87,10 +91,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
clipboard?: TLDrawShape[]
|
||||
session?: Session
|
||||
status: { current: TLDrawStatus; previous: TLDrawStatus } = {
|
||||
current: 'idle',
|
||||
previous: 'idle',
|
||||
}
|
||||
pointedId?: string
|
||||
pointedHandle?: string
|
||||
editingId?: string
|
||||
|
@ -99,12 +99,28 @@ export class TLDrawState implements TLCallbacks {
|
|||
currentPageId = 'page'
|
||||
pages: Record<string, TLPage<TLDrawShape, TLDrawBinding>> = { page: initialData.page }
|
||||
pageStates: Record<string, TLPageState> = { page: initialData.pageState }
|
||||
isCreating = false
|
||||
_onChange?: (state: TLDrawState, reason: string) => void
|
||||
|
||||
// Low API
|
||||
getState = this.store.getState
|
||||
private getState = this.store.getState
|
||||
|
||||
setState = <T extends keyof Data>(data: Partial<Data> | ((data: Data) => Partial<Data>)) => {
|
||||
private setStatus(status: TLDrawStatus) {
|
||||
this.store.setState((state) => ({
|
||||
appState: {
|
||||
...state.appState,
|
||||
status: {
|
||||
current: status,
|
||||
previous: state.appState.status.current,
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
private setState = <T extends keyof Data>(
|
||||
data: Partial<Data> | ((data: Data) => Partial<Data>),
|
||||
status?: TLDrawStatus
|
||||
) => {
|
||||
const current = this.getState()
|
||||
|
||||
// Apply incoming change
|
||||
|
@ -190,6 +206,16 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
}
|
||||
|
||||
if (status) {
|
||||
next.appState = {
|
||||
...next.appState,
|
||||
status: {
|
||||
current: status,
|
||||
previous: next.appState.status.current,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
this.store.setState(next as PartialState<Data, T, T, T>)
|
||||
this.pages[next.page.id] = next.page
|
||||
this.pageStates[next.page.id] = next.pageState
|
||||
|
@ -249,12 +275,12 @@ export class TLDrawState implements TLCallbacks {
|
|||
return this
|
||||
}
|
||||
/* --------------------- Status --------------------- */
|
||||
setStatus(status: TLDrawStatus) {
|
||||
this.status.previous = this.status.current
|
||||
this.status.current = status
|
||||
// console.log(this.status.previous, ' -> ', this.status.current)
|
||||
return this
|
||||
}
|
||||
// setStatus(status: TLDrawStatus) {
|
||||
// this.status.previous = this.status.current
|
||||
// this.status.current = status
|
||||
// // console.log(this.status.previous, ' -> ', this.status.current)
|
||||
// return this
|
||||
// }
|
||||
/* -------------------- App State ------------------- */
|
||||
reset = () => {
|
||||
this.setState((data) => ({
|
||||
|
@ -525,7 +551,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
/* -------------------- Sessions -------------------- */
|
||||
startSession<T extends Session>(session: T, ...args: ParametersExceptFirst<T['start']>) {
|
||||
this.session = session
|
||||
this.setState((data) => session.start(data, ...args))
|
||||
this.setState((data) => session.start(data, ...args), session.status)
|
||||
this._onChange?.(this, `session:start_${session.id}`)
|
||||
return this
|
||||
}
|
||||
|
@ -542,10 +568,13 @@ export class TLDrawState implements TLCallbacks {
|
|||
const { session } = this
|
||||
if (!session) return this
|
||||
|
||||
this.setState((data) => session.cancel(data, ...args))
|
||||
this.setStatus('idle')
|
||||
this.setState((data) => session.cancel(data, ...args), TLDrawStatus.Idle)
|
||||
|
||||
this.session = undefined
|
||||
this.isCreating = false
|
||||
|
||||
this._onChange?.(this, `session:cancel:${session.id}`)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -553,16 +582,9 @@ export class TLDrawState implements TLCallbacks {
|
|||
const { session } = this
|
||||
if (!session) return this
|
||||
|
||||
this.setStatus('idle')
|
||||
const current = this.getState()
|
||||
|
||||
const result = session.complete(this.store.getState(), ...args)
|
||||
|
||||
if ('after' in result) {
|
||||
this.do(result)
|
||||
} else {
|
||||
this.setState((data) => Utils.deepMerge<Data>(data, result))
|
||||
this._onChange?.(this, `session:complete:${session.id}`)
|
||||
}
|
||||
const result = session.complete(current, ...args)
|
||||
|
||||
const { isToolLocked, activeTool } = this.appState
|
||||
|
||||
|
@ -571,6 +593,35 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
|
||||
this.session = undefined
|
||||
|
||||
if ('after' in result) {
|
||||
// Session ended with a command
|
||||
|
||||
if (this.isCreating) {
|
||||
// We're currently creating a shape. Override the command's
|
||||
// before state so that when we undo the command, we remove
|
||||
// the shape we just created.
|
||||
result.before = {
|
||||
page: {
|
||||
shapes: Object.fromEntries(current.pageState.selectedIds.map((id) => [id, undefined])),
|
||||
},
|
||||
pageState: {
|
||||
selectedIds: [],
|
||||
editingId: undefined,
|
||||
bindingId: undefined,
|
||||
hoveredId: undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
this.isCreating = false
|
||||
|
||||
this.do(result)
|
||||
} else {
|
||||
this.setState((data) => Utils.deepMerge<Data>(data, result), TLDrawStatus.Idle)
|
||||
this._onChange?.(this, `session:complete:${session.id}`)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -586,12 +637,14 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
history.pointer = history.stack.length - 1
|
||||
|
||||
this.setState((data) =>
|
||||
this.setState(
|
||||
(data) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(command.after).map(([key, partial]) => {
|
||||
return [key, Utils.deepMerge(data[key as keyof Data], partial)]
|
||||
})
|
||||
)
|
||||
),
|
||||
TLDrawStatus.Idle
|
||||
)
|
||||
|
||||
this._onChange?.(this, `command:${command.id}`)
|
||||
|
@ -606,12 +659,14 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
const command = history.stack[history.pointer]
|
||||
|
||||
this.setState((data) =>
|
||||
this.setState(
|
||||
(data) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(command.before).map(([key, partial]) => {
|
||||
return [key, Utils.deepMerge(data[key as keyof Data], partial)]
|
||||
})
|
||||
)
|
||||
),
|
||||
TLDrawStatus.Idle
|
||||
)
|
||||
|
||||
history.pointer--
|
||||
|
@ -630,13 +685,16 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
const command = history.stack[history.pointer]
|
||||
|
||||
this.setState((data) =>
|
||||
this.setState(
|
||||
(data) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(command.after).map(([key, partial]) => {
|
||||
return [key, Utils.deepMerge(data[key as keyof Data], partial)]
|
||||
})
|
||||
),
|
||||
TLDrawStatus.Idle
|
||||
)
|
||||
)
|
||||
|
||||
this._onChange?.(this, `redo:${command.id}`)
|
||||
|
||||
return this
|
||||
|
@ -834,7 +892,14 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
|
||||
cancel = () => {
|
||||
switch (this.status.current) {
|
||||
if (this.isCreating) {
|
||||
this.cancelSession()
|
||||
this.delete()
|
||||
this.isCreating = false
|
||||
return
|
||||
}
|
||||
|
||||
switch (this.appState.status.current) {
|
||||
case 'idle': {
|
||||
this.deselectAll()
|
||||
this.selectTool('select')
|
||||
|
@ -857,11 +922,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
this.cancelSession()
|
||||
break
|
||||
}
|
||||
case 'creating': {
|
||||
this.cancelSession()
|
||||
this.delete()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
|
@ -968,7 +1028,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
/* -------------------- Sessions -------------------- */
|
||||
startBrushSession = (point: number[]) => {
|
||||
this.setStatus('brushing')
|
||||
this.startSession(new Sessions.BrushSession(this.store.getState(), point))
|
||||
return this
|
||||
}
|
||||
|
@ -979,7 +1038,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
|
||||
startTranslateSession = (point: number[]) => {
|
||||
this.setStatus('translating')
|
||||
this.startSession(new Sessions.TranslateSession(this.store.getState(), point))
|
||||
return this
|
||||
}
|
||||
|
@ -998,8 +1056,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
if (selectedIds.length === 0) return this
|
||||
|
||||
this.setStatus('transforming')
|
||||
|
||||
this.pointedBoundsHandle = handle
|
||||
|
||||
if (this.pointedBoundsHandle === 'rotate') {
|
||||
|
@ -1032,7 +1088,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
startTextSession = (id?: string) => {
|
||||
this.editingId = id
|
||||
this.setStatus('editing-text')
|
||||
this.startSession(new Sessions.TextSession(this.store.getState(), id))
|
||||
return this
|
||||
}
|
||||
|
@ -1043,7 +1098,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
|
||||
startDrawSession = (id: string, point: number[]) => {
|
||||
this.setStatus('creating')
|
||||
this.startSession(new Sessions.DrawSession(this.store.getState(), id, point))
|
||||
return this
|
||||
}
|
||||
|
@ -1077,44 +1131,41 @@ export class TLDrawState implements TLCallbacks {
|
|||
return this
|
||||
}
|
||||
|
||||
updatenPointerMove: TLPointerEventHandler = (info) => {
|
||||
updateOnPointerMove: TLPointerEventHandler = (info) => {
|
||||
switch (this.status.current) {
|
||||
case 'pointingBoundsHandle': {
|
||||
case TLDrawStatus.PointingBoundsHandle: {
|
||||
if (!this.pointedBoundsHandle) throw Error('No pointed bounds handle')
|
||||
if (Vec.dist(info.origin, info.point) > 4) {
|
||||
this.setStatus('transforming')
|
||||
this.startTransformSession(this.getPagePoint(info.origin), this.pointedBoundsHandle)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'pointingHandle': {
|
||||
case TLDrawStatus.PointingHandle: {
|
||||
if (!this.pointedHandle) throw Error('No pointed handle')
|
||||
if (Vec.dist(info.origin, info.point) > 4) {
|
||||
this.setStatus('translatingHandle')
|
||||
this.startHandleSession(this.getPagePoint(info.origin), this.pointedHandle)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'pointingBounds': {
|
||||
case TLDrawStatus.PointingBounds: {
|
||||
if (Vec.dist(info.origin, info.point) > 4) {
|
||||
this.setStatus('translating')
|
||||
this.startTranslateSession(this.getPagePoint(info.origin))
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'brushing': {
|
||||
case TLDrawStatus.Brushing: {
|
||||
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
||||
break
|
||||
}
|
||||
case 'translating': {
|
||||
case TLDrawStatus.Translating: {
|
||||
this.updateTranslateSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||
break
|
||||
}
|
||||
case 'transforming': {
|
||||
case TLDrawStatus.Transforming: {
|
||||
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||
break
|
||||
}
|
||||
case 'translatingHandle': {
|
||||
case TLDrawStatus.TranslatingHandle: {
|
||||
this.updateHandleSession(
|
||||
this.getPagePoint(info.point),
|
||||
info.shiftKey,
|
||||
|
@ -1123,7 +1174,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
)
|
||||
break
|
||||
}
|
||||
case 'creating': {
|
||||
case TLDrawStatus.Creating: {
|
||||
switch (this.appState.activeToolType) {
|
||||
case 'draw': {
|
||||
this.updateDrawSession(this.getPagePoint(info.point), info.pressure, info.shiftKey)
|
||||
|
@ -1193,7 +1244,9 @@ export class TLDrawState implements TLCallbacks {
|
|||
selectedIds: [id],
|
||||
},
|
||||
}
|
||||
})
|
||||
}, TLDrawStatus.Creating)
|
||||
|
||||
this.isCreating = true
|
||||
|
||||
const { activeTool, activeToolType } = this.getAppState()
|
||||
|
||||
|
@ -1231,10 +1284,10 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
|
||||
switch (this.status.current) {
|
||||
case 'idle': {
|
||||
case TLDrawStatus.Idle: {
|
||||
break
|
||||
}
|
||||
case 'brushing': {
|
||||
case TLDrawStatus.Brushing: {
|
||||
if (key === 'Meta' || key === 'Control') {
|
||||
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
||||
return
|
||||
|
@ -1242,7 +1295,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
break
|
||||
}
|
||||
case 'translating': {
|
||||
case TLDrawStatus.Translating: {
|
||||
if (key === 'Escape') {
|
||||
this.cancelSession(this.getPagePoint(info.point))
|
||||
}
|
||||
|
@ -1252,7 +1305,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
break
|
||||
}
|
||||
case 'transforming': {
|
||||
case TLDrawStatus.Transforming: {
|
||||
if (key === 'Escape') {
|
||||
this.cancelSession(this.getPagePoint(info.point))
|
||||
}
|
||||
|
@ -1262,7 +1315,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
break
|
||||
}
|
||||
case 'translatingHandle': {
|
||||
case TLDrawStatus.TranslatingHandle: {
|
||||
if (key === 'Escape') {
|
||||
this.cancelSession(this.getPagePoint(info.point))
|
||||
}
|
||||
|
@ -1282,25 +1335,25 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
onKeyUp = (key: string, info: TLKeyboardInfo) => {
|
||||
switch (this.status.current) {
|
||||
case 'brushing': {
|
||||
case TLDrawStatus.Brushing: {
|
||||
if (key === 'Meta' || key === 'Control') {
|
||||
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'transforming': {
|
||||
case TLDrawStatus.Transforming: {
|
||||
if (key === 'Shift' || key === 'Alt') {
|
||||
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'translating': {
|
||||
case TLDrawStatus.Translating: {
|
||||
if (key === 'Shift' || key === 'Alt') {
|
||||
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'translatingHandle': {
|
||||
case TLDrawStatus.TranslatingHandle: {
|
||||
if (key === 'Escape') {
|
||||
this.cancelSession(this.getPagePoint(info.point))
|
||||
}
|
||||
|
@ -1320,7 +1373,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
/* ------------- Renderer Event Handlers ------------ */
|
||||
onPinchStart: TLPinchEventHandler = () => {
|
||||
this.setStatus('pinching')
|
||||
this.setStatus(TLDrawStatus.Pinching)
|
||||
}
|
||||
|
||||
onPinchEnd: TLPinchEventHandler = () => {
|
||||
|
@ -1328,14 +1381,14 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
|
||||
onPinch: TLPinchEventHandler = (info, e) => {
|
||||
if (this.status.current !== 'pinching') return
|
||||
if (this.status.current !== TLDrawStatus.Pinching) return
|
||||
|
||||
this.pinchZoom(info.origin, info.delta, info.delta[2] / 350)
|
||||
this.updatenPointerMove(info, e as any)
|
||||
this.updateOnPointerMove(info, e as any)
|
||||
}
|
||||
|
||||
onPan: TLWheelEventHandler = (info, e) => {
|
||||
if (this.status.current === 'pinching') return
|
||||
if (this.status.current === TLDrawStatus.Pinching) return
|
||||
// TODO: Pan and pinchzoom are firing at the same time. Considering turning one of them off!
|
||||
|
||||
const delta = Vec.div(info.delta, this.getPageState().camera.zoom)
|
||||
|
@ -1345,36 +1398,32 @@ export class TLDrawState implements TLCallbacks {
|
|||
if (Vec.isEqual(next, prev)) return
|
||||
|
||||
this.pan(delta)
|
||||
this.updatenPointerMove(info, e as any)
|
||||
this.updateOnPointerMove(info, e as any)
|
||||
}
|
||||
|
||||
onZoom: TLWheelEventHandler = (info, e) => {
|
||||
this.zoom(info.delta[2] / 100)
|
||||
this.updatenPointerMove(info, e as any)
|
||||
this.updateOnPointerMove(info, e as any)
|
||||
}
|
||||
|
||||
// Pointer Events
|
||||
onPointerDown: TLPointerEventHandler = (info) => {
|
||||
switch (this.status.current) {
|
||||
case 'idle': {
|
||||
case TLDrawStatus.Idle: {
|
||||
switch (this.appState.activeTool) {
|
||||
case 'draw': {
|
||||
this.setStatus('creating')
|
||||
this.createActiveToolShape(info.point)
|
||||
break
|
||||
}
|
||||
case 'rectangle': {
|
||||
this.setStatus('creating')
|
||||
this.createActiveToolShape(info.point)
|
||||
break
|
||||
}
|
||||
case 'ellipse': {
|
||||
this.setStatus('creating')
|
||||
this.createActiveToolShape(info.point)
|
||||
break
|
||||
}
|
||||
case 'arrow': {
|
||||
this.setStatus('creating')
|
||||
this.createActiveToolShape(info.point)
|
||||
break
|
||||
}
|
||||
|
@ -1384,14 +1433,14 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
|
||||
onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||
this.updatenPointerMove(info, e)
|
||||
this.updateOnPointerMove(info, e)
|
||||
}
|
||||
|
||||
onPointerUp: TLPointerEventHandler = (info) => {
|
||||
const data = this.getState()
|
||||
|
||||
switch (this.status.current) {
|
||||
case 'pointingBounds': {
|
||||
case TLDrawStatus.PointingBounds: {
|
||||
if (info.target === 'bounds') {
|
||||
// If we just clicked the selecting bounds's background, clear the selection
|
||||
this.deselectAll()
|
||||
|
@ -1412,41 +1461,41 @@ export class TLDrawState implements TLCallbacks {
|
|||
this.pointedId = undefined
|
||||
}
|
||||
|
||||
this.setStatus('idle')
|
||||
this.setStatus(TLDrawStatus.Idle)
|
||||
this.pointedId = undefined
|
||||
break
|
||||
}
|
||||
case 'pointingBoundsHandle': {
|
||||
this.setStatus('idle')
|
||||
case TLDrawStatus.PointingBoundsHandle: {
|
||||
this.setStatus(TLDrawStatus.Idle)
|
||||
this.pointedBoundsHandle = undefined
|
||||
break
|
||||
}
|
||||
case 'pointingHandle': {
|
||||
this.setStatus('idle')
|
||||
case TLDrawStatus.PointingHandle: {
|
||||
this.setStatus(TLDrawStatus.Idle)
|
||||
this.pointedHandle = undefined
|
||||
break
|
||||
}
|
||||
case 'translatingHandle': {
|
||||
case TLDrawStatus.TranslatingHandle: {
|
||||
this.completeSession<Sessions.HandleSession>()
|
||||
this.pointedHandle = undefined
|
||||
break
|
||||
}
|
||||
case 'brushing': {
|
||||
case TLDrawStatus.Brushing: {
|
||||
this.completeSession<Sessions.BrushSession>()
|
||||
brushUpdater.clear()
|
||||
break
|
||||
}
|
||||
case 'translating': {
|
||||
this.completeSession(this.getPagePoint(info.point))
|
||||
case TLDrawStatus.Translating: {
|
||||
this.completeSession<Sessions.TranslateSession>()
|
||||
this.pointedId = undefined
|
||||
break
|
||||
}
|
||||
case 'transforming': {
|
||||
this.completeSession(this.getPagePoint(info.point))
|
||||
case TLDrawStatus.Transforming: {
|
||||
this.completeSession<Sessions.TransformSession>()
|
||||
this.pointedBoundsHandle = undefined
|
||||
break
|
||||
}
|
||||
case 'creating': {
|
||||
case TLDrawStatus.Creating: {
|
||||
this.completeSession(this.getPagePoint(info.point))
|
||||
this.pointedHandle = undefined
|
||||
}
|
||||
|
@ -1485,7 +1534,6 @@ export class TLDrawState implements TLCallbacks {
|
|||
switch (this.appState.activeTool) {
|
||||
case 'text': {
|
||||
// Create a text shape
|
||||
this.setStatus('creating')
|
||||
this.createActiveToolShape(info.point)
|
||||
break
|
||||
}
|
||||
|
@ -1530,7 +1578,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
this.setSelectedIds([info.target], info.shiftKey)
|
||||
}
|
||||
|
||||
this.setStatus('pointingBounds')
|
||||
this.setStatus(TLDrawStatus.PointingBounds)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -1581,7 +1629,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
// Bounds (bounding box background)
|
||||
onPointBounds: TLBoundsEventHandler = () => {
|
||||
this.setStatus('pointingBounds')
|
||||
this.setStatus(TLDrawStatus.PointingBounds)
|
||||
}
|
||||
|
||||
onDoubleClickBounds: TLBoundsEventHandler = () => {
|
||||
|
@ -1606,11 +1654,11 @@ export class TLDrawState implements TLCallbacks {
|
|||
|
||||
onReleaseBounds: TLBoundsEventHandler = (info) => {
|
||||
switch (this.status.current) {
|
||||
case 'translating': {
|
||||
case TLDrawStatus.Translating: {
|
||||
this.completeSession(this.getPagePoint(info.point))
|
||||
break
|
||||
}
|
||||
case 'brushing': {
|
||||
case TLDrawStatus.Brushing: {
|
||||
this.completeSession<Sessions.BrushSession>()
|
||||
brushUpdater.clear()
|
||||
break
|
||||
|
@ -1621,7 +1669,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
// Bounds handles (corners, edges)
|
||||
onPointBoundsHandle: TLBoundsHandleEventHandler = (info) => {
|
||||
this.pointedBoundsHandle = info.target
|
||||
this.setStatus('pointingBoundsHandle')
|
||||
this.setStatus(TLDrawStatus.PointingBoundsHandle)
|
||||
}
|
||||
|
||||
onDoubleClickBoundsHandle: TLBoundsHandleEventHandler = () => {
|
||||
|
@ -1651,7 +1699,7 @@ export class TLDrawState implements TLCallbacks {
|
|||
// Handles (ie the handles of a selected arrow)
|
||||
onPointHandle: TLPointerEventHandler = (info) => {
|
||||
this.pointedHandle = info.target
|
||||
this.setStatus('pointingHandle')
|
||||
this.setStatus(TLDrawStatus.PointingHandle)
|
||||
}
|
||||
|
||||
onDoubleClickHandle: TLPointerEventHandler = (info) => {
|
||||
|
@ -1766,4 +1814,8 @@ export class TLDrawState implements TLCallbacks {
|
|||
get appState() {
|
||||
return this.data.appState
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.appState.status
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export interface Data {
|
|||
isToolLocked: boolean
|
||||
isStyleOpen: boolean
|
||||
isEmptyCanvas: boolean
|
||||
status: { current: TLDrawStatus; previous: TLDrawStatus }
|
||||
}
|
||||
}
|
||||
export interface PagePartial {
|
||||
|
@ -63,25 +64,27 @@ export interface History {
|
|||
|
||||
export interface Session {
|
||||
id: string
|
||||
status: TLDrawStatus
|
||||
start: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
||||
update: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
||||
complete: (data: Readonly<Data>, ...args: any[]) => Partial<Data> | Command
|
||||
cancel: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
||||
}
|
||||
|
||||
export type TLDrawStatus =
|
||||
| 'idle'
|
||||
| 'pointingHandle'
|
||||
| 'pointingBounds'
|
||||
| 'pointingBoundsHandle'
|
||||
| 'translatingHandle'
|
||||
| 'translating'
|
||||
| 'transforming'
|
||||
| 'rotating'
|
||||
| 'pinching'
|
||||
| 'brushing'
|
||||
| 'creating'
|
||||
| 'editing-text'
|
||||
export enum TLDrawStatus {
|
||||
Idle = 'idle',
|
||||
PointingHandle = 'pointingHandle',
|
||||
PointingBounds = 'pointingBounds',
|
||||
PointingBoundsHandle = 'pointingBoundsHandle',
|
||||
TranslatingHandle = 'translatingHandle',
|
||||
Translating = 'translating',
|
||||
Transforming = 'transforming',
|
||||
Rotating = 'rotating',
|
||||
Pinching = 'pinching',
|
||||
Brushing = 'brushing',
|
||||
Creating = 'creating',
|
||||
EditingText = 'editing-text',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type ParametersExceptFirst<F> = F extends (arg0: any, ...rest: infer R) => any ? R : never
|
||||
|
|
Loading…
Reference in a new issue