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 type { Data } from '~types'
|
||||||
import styled from '~styles'
|
import styled from '~styles'
|
||||||
|
|
||||||
|
const statusSelector = (s: Data) => s.appState.status.current
|
||||||
const activeToolSelector = (s: Data) => s.appState.activeTool
|
const activeToolSelector = (s: Data) => s.appState.activeTool
|
||||||
|
|
||||||
export function StatusBar(): JSX.Element | null {
|
export function StatusBar(): JSX.Element | null {
|
||||||
const { useSelector } = useTLDrawContext()
|
const { useSelector } = useTLDrawContext()
|
||||||
|
const status = useSelector(statusSelector)
|
||||||
const activeTool = useSelector(activeToolSelector)
|
const activeTool = useSelector(activeToolSelector)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusBarContainer size={{ '@sm': 'small' }}>
|
<StatusBarContainer size={{ '@sm': 'small' }}>
|
||||||
<Section>{activeTool}</Section>
|
<Section>
|
||||||
{/* <Section>{shapesInView || '0'} Shapes</Section> */}
|
{activeTool} | {status}
|
||||||
|
</Section>
|
||||||
</StatusBarContainer>
|
</StatusBarContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
Intersect,
|
Intersect,
|
||||||
TLHandle,
|
TLHandle,
|
||||||
TLPointerInfo,
|
TLPointerInfo,
|
||||||
Svg,
|
|
||||||
} from '@tldraw/core'
|
} from '@tldraw/core'
|
||||||
import getStroke from 'perfect-freehand'
|
import getStroke from 'perfect-freehand'
|
||||||
import { defaultStyle, getPerfectDashProps, getShapeStyle } from '~shape/shape-styles'
|
import { defaultStyle, getPerfectDashProps, getShapeStyle } from '~shape/shape-styles'
|
||||||
|
@ -27,7 +26,7 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
type = TLDrawShapeType.Arrow as const
|
type = TLDrawShapeType.Arrow as const
|
||||||
toolType = TLDrawToolType.Handle
|
toolType = TLDrawToolType.Handle
|
||||||
canStyleFill = false
|
canStyleFill = false
|
||||||
simplePathCache = new WeakMap<ArrowShape, string>()
|
simplePathCache = new WeakMap<ArrowShape['handles'], string>()
|
||||||
pathCache = new WeakMap<ArrowShape, string>()
|
pathCache = new WeakMap<ArrowShape, string>()
|
||||||
|
|
||||||
defaultProps = {
|
defaultProps = {
|
||||||
|
@ -72,12 +71,63 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render = (shape: ArrowShape, { isDarkMode }: TLRenderInfo) => {
|
render = (shape: ArrowShape, { isDarkMode }: TLRenderInfo) => {
|
||||||
const { bend, handles, style } = shape
|
const {
|
||||||
const { start, end, bend: _bend } = handles
|
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)
|
const styles = getShapeStyle(style, isDarkMode)
|
||||||
|
|
||||||
|
@ -85,11 +135,11 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
|
|
||||||
const arrowDist = Vec.dist(start.point, end.point)
|
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 shaftPath: JSX.Element
|
||||||
let insetStart: number[]
|
let startArrowHead: { left: number[]; right: number[] } | undefined
|
||||||
let insetEnd: number[]
|
let endArrowHead: { left: number[]; right: number[] } | undefined
|
||||||
|
|
||||||
if (isStraightLine) {
|
if (isStraightLine) {
|
||||||
const sw = strokeWidth * (isDraw ? 0.618 : 1.618)
|
const sw = strokeWidth * (isDraw ? 0.618 : 1.618)
|
||||||
|
@ -107,8 +157,13 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
|
||||||
insetStart = Vec.nudge(start.point, end.point, arrowHeadlength)
|
if (decorations.start) {
|
||||||
insetEnd = Vec.nudge(end.point, start.point, arrowHeadlength)
|
startArrowHead = getStraightArrowHeadPoints(start.point, end.point, arrowHeadLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decorations.end) {
|
||||||
|
endArrowHead = getStraightArrowHeadPoints(end.point, start.point, arrowHeadLength)
|
||||||
|
}
|
||||||
|
|
||||||
// Straight arrow path
|
// Straight arrow path
|
||||||
shaftPath = (
|
shaftPath = (
|
||||||
|
@ -144,31 +199,37 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
const path = Utils.getFromCache(this.pathCache, shape, () =>
|
const path = Utils.getFromCache(this.pathCache, shape, () =>
|
||||||
isDraw
|
isDraw
|
||||||
? renderCurvedFreehandArrowShaft(shape, circle)
|
? renderCurvedFreehandArrowShaft(shape, circle)
|
||||||
: getArrowArcPath(start, end, circle, bend)
|
: getArrowArcPath(start, end, circle, shape.bend)
|
||||||
)
|
)
|
||||||
|
|
||||||
const arcLength = Utils.getArcLength(
|
const { center, radius, length } = getArrowArc(shape)
|
||||||
[circle[0], circle[1]],
|
|
||||||
circle[2],
|
|
||||||
start.point,
|
|
||||||
end.point
|
|
||||||
)
|
|
||||||
|
|
||||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||||
arcLength - 1,
|
length - 1,
|
||||||
sw,
|
sw,
|
||||||
shape.style.dash,
|
shape.style.dash,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
|
||||||
const center = [circle[0], circle[1]]
|
if (decorations.start) {
|
||||||
const radius = circle[2]
|
startArrowHead = getCurvedArrowHeadPoints(
|
||||||
const sa = Vec.angle(center, start.point)
|
start.point,
|
||||||
const ea = Vec.angle(center, end.point)
|
arrowHeadLength,
|
||||||
const t = arrowHeadlength / Math.abs(arcLength)
|
center,
|
||||||
|
radius,
|
||||||
|
length < 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
insetStart = Vec.nudgeAtAngle(center, Utils.lerpAngles(sa, ea, t), radius)
|
if (decorations.end) {
|
||||||
insetEnd = Vec.nudgeAtAngle(center, Utils.lerpAngles(ea, sa, t), radius)
|
endArrowHead = getCurvedArrowHeadPoints(
|
||||||
|
end.point,
|
||||||
|
arrowHeadLength,
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
length >= 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Curved arrow path
|
// Curved arrow path
|
||||||
shaftPath = (
|
shaftPath = (
|
||||||
|
@ -204,9 +265,9 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
return (
|
return (
|
||||||
<g pointerEvents="none">
|
<g pointerEvents="none">
|
||||||
{shaftPath}
|
{shaftPath}
|
||||||
{shape.decorations?.start === Decoration.Arrow && (
|
{startArrowHead && (
|
||||||
<path
|
<path
|
||||||
d={getArrowHeadPath(shape, start.point, insetStart)}
|
d={`M ${startArrowHead.left} L ${start.point} ${startArrowHead.right}`}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke={styles.stroke}
|
stroke={styles.stroke}
|
||||||
strokeWidth={sw}
|
strokeWidth={sw}
|
||||||
|
@ -217,9 +278,9 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
pointerEvents="stroke"
|
pointerEvents="stroke"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shape.decorations?.end === Decoration.Arrow && (
|
{endArrowHead && (
|
||||||
<path
|
<path
|
||||||
d={getArrowHeadPath(shape, end.point, insetEnd)}
|
d={`M ${endArrowHead.left} L ${end.point} ${endArrowHead.right}`}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke={styles.stroke}
|
stroke={styles.stroke}
|
||||||
strokeWidth={sw}
|
strokeWidth={sw}
|
||||||
|
@ -235,91 +296,9 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIndicator(shape: ArrowShape) {
|
renderIndicator(shape: ArrowShape) {
|
||||||
const {
|
const path = Utils.getFromCache(this.simplePathCache, shape.handles, () => getArrowPath(shape))
|
||||||
decorations,
|
|
||||||
handles: { start, end, bend: _bend },
|
|
||||||
style,
|
|
||||||
} = shape
|
|
||||||
|
|
||||||
const { strokeWidth } = getShapeStyle(style, false)
|
return <path d={path} />
|
||||||
|
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBounds = (shape: ArrowShape) => {
|
getBounds = (shape: ArrowShape) => {
|
||||||
|
@ -764,3 +743,143 @@ function getCtp(shape: ArrowShape) {
|
||||||
const { start, end, bend } = shape.handles
|
const { start, end, bend } = shape.handles
|
||||||
return Utils.circleFromThreePoints(start.point, end.point, bend.point)
|
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.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['b'])
|
tlstate.setSelectedIds(['b'])
|
||||||
tlstate.moveToBack()
|
tlstate.moveToBack()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bacd')
|
expect(getSortedShapeIds(tlstate.data)).toBe('bacd')
|
||||||
tlstate.undo()
|
tlstate.undo()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abcd')
|
expect(getSortedShapeIds(tlstate.data)).toBe('abcd')
|
||||||
tlstate.redo()
|
tlstate.redo()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bacd')
|
expect(getSortedShapeIds(tlstate.data)).toBe('bacd')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('to back', () => {
|
describe('to back', () => {
|
||||||
|
@ -57,21 +57,21 @@ describe('Move command', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['b'])
|
tlstate.setSelectedIds(['b'])
|
||||||
tlstate.moveToBack()
|
tlstate.moveToBack()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bacd')
|
expect(getSortedShapeIds(tlstate.data)).toBe('bacd')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings to back', () => {
|
it('moves two adjacent siblings to back', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['b', 'c'])
|
tlstate.setSelectedIds(['b', 'c'])
|
||||||
tlstate.moveToBack()
|
tlstate.moveToBack()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bcad')
|
expect(getSortedShapeIds(tlstate.data)).toBe('bcad')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings to back', () => {
|
it('moves two non-adjacent siblings to back', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['b', 'd'])
|
tlstate.setSelectedIds(['b', 'd'])
|
||||||
tlstate.moveToBack()
|
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.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['c'])
|
tlstate.setSelectedIds(['c'])
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acbd')
|
expect(getSortedShapeIds(tlstate.data)).toBe('acbd')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape at first index backward', () => {
|
it('moves a shape at first index backward', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['a'])
|
tlstate.setSelectedIds(['a'])
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abcd')
|
expect(getSortedShapeIds(tlstate.data)).toBe('abcd')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings backward', () => {
|
it('moves two adjacent siblings backward', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['c', 'd'])
|
tlstate.setSelectedIds(['c', 'd'])
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acdb')
|
expect(getSortedShapeIds(tlstate.data)).toBe('acdb')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings backward', () => {
|
it('moves two non-adjacent siblings backward', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['b', 'd'])
|
tlstate.setSelectedIds(['b', 'd'])
|
||||||
tlstate.moveBackward()
|
tlstate.moveBackward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('badc')
|
expect(getSortedShapeIds(tlstate.data)).toBe('badc')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings backward at zero index', () => {
|
it('moves two adjacent siblings backward at zero index', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['a', 'b'])
|
tlstate.setSelectedIds(['a', 'b'])
|
||||||
tlstate.moveBackward()
|
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.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['c'])
|
tlstate.setSelectedIds(['c'])
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('abdc')
|
expect(getSortedShapeIds(tlstate.data)).toBe('abdc')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape forward at the top index', () => {
|
it('moves a shape forward at the top index', () => {
|
||||||
|
@ -126,28 +126,28 @@ describe('Move command', () => {
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acdb')
|
expect(getSortedShapeIds(tlstate.data)).toBe('acdb')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings forward', () => {
|
it('moves two adjacent siblings forward', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['a', 'b'])
|
tlstate.setSelectedIds(['a', 'b'])
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('cabd')
|
expect(getSortedShapeIds(tlstate.data)).toBe('cabd')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings forward', () => {
|
it('moves two non-adjacent siblings forward', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['a', 'c'])
|
tlstate.setSelectedIds(['a', 'c'])
|
||||||
tlstate.moveForward()
|
tlstate.moveForward()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('badc')
|
expect(getSortedShapeIds(tlstate.data)).toBe('badc')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings forward at top index', () => {
|
it('moves two adjacent siblings forward at top index', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['c', 'd'])
|
tlstate.setSelectedIds(['c', 'd'])
|
||||||
tlstate.moveForward()
|
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.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['b'])
|
tlstate.setSelectedIds(['b'])
|
||||||
tlstate.moveToFront()
|
tlstate.moveToFront()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('acdb')
|
expect(getSortedShapeIds(tlstate.data)).toBe('acdb')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings to front', () => {
|
it('moves two adjacent siblings to front', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['a', 'b'])
|
tlstate.setSelectedIds(['a', 'b'])
|
||||||
tlstate.moveToFront()
|
tlstate.moveToFront()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('cdab')
|
expect(getSortedShapeIds(tlstate.data)).toBe('cdab')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings to front', () => {
|
it('moves two non-adjacent siblings to front', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['a', 'c'])
|
tlstate.setSelectedIds(['a', 'c'])
|
||||||
tlstate.moveToFront()
|
tlstate.moveToFront()
|
||||||
expect(getSortedShapeIds(tlstate.getState())).toBe('bdac')
|
expect(getSortedShapeIds(tlstate.data)).toBe('bdac')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves siblings already at front to front', () => {
|
it('moves siblings already at front to front', () => {
|
||||||
tlstate.loadDocument(doc)
|
tlstate.loadDocument(doc)
|
||||||
tlstate.setSelectedIds(['c', 'd'])
|
tlstate.setSelectedIds(['c', 'd'])
|
||||||
tlstate.moveToFront()
|
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 "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 "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`.
|
- 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 { Vec, Utils } from '@tldraw/core'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class ArrowSession implements Session {
|
export class ArrowSession implements Session {
|
||||||
id = 'transform_single'
|
id = 'transform_single'
|
||||||
|
status = TLDrawStatus.TranslatingHandle
|
||||||
newBindingId = Utils.uniqueId()
|
newBindingId = Utils.uniqueId()
|
||||||
delta = [0, 0]
|
delta = [0, 0]
|
||||||
offset = [0, 0]
|
offset = [0, 0]
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { brushUpdater, Utils, Vec } from '@tldraw/core'
|
import { brushUpdater, Utils, Vec } from '@tldraw/core'
|
||||||
import type { Data, Session } from '~types'
|
import { Data, Session, TLDrawStatus } from '~types'
|
||||||
import { getShapeUtils } from '~shape'
|
import { getShapeUtils } from '~shape'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class BrushSession implements Session {
|
export class BrushSession implements Session {
|
||||||
id = 'brush'
|
id = 'brush'
|
||||||
|
status = TLDrawStatus.Brushing
|
||||||
origin: number[]
|
origin: number[]
|
||||||
snapshot: BrushSnapshot
|
snapshot: BrushSnapshot
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Utils, Vec } from '@tldraw/core'
|
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'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class DrawSession implements Session {
|
export class DrawSession implements Session {
|
||||||
id = 'draw'
|
id = 'draw'
|
||||||
|
status = TLDrawStatus.Creating
|
||||||
origin: number[]
|
origin: number[]
|
||||||
previous: number[]
|
previous: number[]
|
||||||
last: number[]
|
last: number[]
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Vec } from '@tldraw/core'
|
import { Vec } from '@tldraw/core'
|
||||||
import type { ShapesWithProp } from '~types'
|
import { ShapesWithProp, TLDrawStatus } from '~types'
|
||||||
import type { Session } from '~types'
|
import type { Session } from '~types'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class HandleSession implements Session {
|
export class HandleSession implements Session {
|
||||||
id = 'transform_single'
|
id = 'transform_single'
|
||||||
|
status = TLDrawStatus.TranslatingHandle
|
||||||
commandId: string
|
commandId: string
|
||||||
delta = [0, 0]
|
delta = [0, 0]
|
||||||
origin: number[]
|
origin: number[]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Utils, Vec } from '@tldraw/core'
|
import { Utils, Vec } from '@tldraw/core'
|
||||||
import type { Session } from '~types'
|
import { Session, TLDrawStatus } from '~types'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ const PI2 = Math.PI * 2
|
||||||
|
|
||||||
export class RotateSession implements Session {
|
export class RotateSession implements Session {
|
||||||
id = 'rotate'
|
id = 'rotate'
|
||||||
|
status = TLDrawStatus.Transforming
|
||||||
delta = [0, 0]
|
delta = [0, 0]
|
||||||
origin: number[]
|
origin: number[]
|
||||||
snapshot: RotateSnapshot
|
snapshot: RotateSnapshot
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import type { TextShape } from '~types'
|
import { TextShape, TLDrawStatus } from '~types'
|
||||||
import type { Session } from '~types'
|
import type { Session } from '~types'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class TextSession implements Session {
|
export class TextSession implements Session {
|
||||||
id = 'text'
|
id = 'text'
|
||||||
|
status = TLDrawStatus.EditingText
|
||||||
initialShape: TextShape
|
initialShape: TextShape
|
||||||
|
|
||||||
constructor(data: Data, id?: string) {
|
constructor(data: Data, id?: string) {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core'
|
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 { Session } from '~types'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class TransformSingleSession implements Session {
|
export class TransformSingleSession implements Session {
|
||||||
id = 'transform_single'
|
id = 'transform_single'
|
||||||
|
status = TLDrawStatus.Transforming
|
||||||
commandId: string
|
commandId: string
|
||||||
transformType: TLBoundsEdge | TLBoundsCorner
|
transformType: TLBoundsEdge | TLBoundsCorner
|
||||||
origin: number[]
|
origin: number[]
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core'
|
import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core'
|
||||||
import type { Session } from '~types'
|
import { Session, TLDrawStatus } from '~types'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class TransformSession implements Session {
|
export class TransformSession implements Session {
|
||||||
id = 'transform'
|
id = 'transform'
|
||||||
|
status = TLDrawStatus.Transforming
|
||||||
scaleX = 1
|
scaleX = 1
|
||||||
scaleY = 1
|
scaleY = 1
|
||||||
transformType: TLBoundsEdge | TLBoundsCorner
|
transformType: TLBoundsEdge | TLBoundsCorner
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import { Utils, Vec } from '@tldraw/core'
|
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'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class TranslateSession implements Session {
|
export class TranslateSession implements Session {
|
||||||
id = 'translate'
|
id = 'translate'
|
||||||
|
status = TLDrawStatus.Translating
|
||||||
delta = [0, 0]
|
delta = [0, 0]
|
||||||
prev = [0, 0]
|
prev = [0, 0]
|
||||||
origin: number[]
|
origin: number[]
|
||||||
|
|
|
@ -62,6 +62,10 @@ const initialData: Data = {
|
||||||
isToolLocked: false,
|
isToolLocked: false,
|
||||||
isStyleOpen: false,
|
isStyleOpen: false,
|
||||||
isEmptyCanvas: false,
|
isEmptyCanvas: false,
|
||||||
|
status: {
|
||||||
|
current: TLDrawStatus.Idle,
|
||||||
|
previous: TLDrawStatus.Idle,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
id: 'page',
|
id: 'page',
|
||||||
|
@ -87,10 +91,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
clipboard?: TLDrawShape[]
|
clipboard?: TLDrawShape[]
|
||||||
session?: Session
|
session?: Session
|
||||||
status: { current: TLDrawStatus; previous: TLDrawStatus } = {
|
|
||||||
current: 'idle',
|
|
||||||
previous: 'idle',
|
|
||||||
}
|
|
||||||
pointedId?: string
|
pointedId?: string
|
||||||
pointedHandle?: string
|
pointedHandle?: string
|
||||||
editingId?: string
|
editingId?: string
|
||||||
|
@ -99,12 +99,28 @@ export class TLDrawState implements TLCallbacks {
|
||||||
currentPageId = 'page'
|
currentPageId = 'page'
|
||||||
pages: Record<string, TLPage<TLDrawShape, TLDrawBinding>> = { page: initialData.page }
|
pages: Record<string, TLPage<TLDrawShape, TLDrawBinding>> = { page: initialData.page }
|
||||||
pageStates: Record<string, TLPageState> = { page: initialData.pageState }
|
pageStates: Record<string, TLPageState> = { page: initialData.pageState }
|
||||||
|
isCreating = false
|
||||||
_onChange?: (state: TLDrawState, reason: string) => void
|
_onChange?: (state: TLDrawState, reason: string) => void
|
||||||
|
|
||||||
// Low API
|
// 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()
|
const current = this.getState()
|
||||||
|
|
||||||
// Apply incoming change
|
// 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.store.setState(next as PartialState<Data, T, T, T>)
|
||||||
this.pages[next.page.id] = next.page
|
this.pages[next.page.id] = next.page
|
||||||
this.pageStates[next.page.id] = next.pageState
|
this.pageStates[next.page.id] = next.pageState
|
||||||
|
@ -249,12 +275,12 @@ export class TLDrawState implements TLCallbacks {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
/* --------------------- Status --------------------- */
|
/* --------------------- Status --------------------- */
|
||||||
setStatus(status: TLDrawStatus) {
|
// setStatus(status: TLDrawStatus) {
|
||||||
this.status.previous = this.status.current
|
// this.status.previous = this.status.current
|
||||||
this.status.current = status
|
// this.status.current = status
|
||||||
// console.log(this.status.previous, ' -> ', this.status.current)
|
// // console.log(this.status.previous, ' -> ', this.status.current)
|
||||||
return this
|
// return this
|
||||||
}
|
// }
|
||||||
/* -------------------- App State ------------------- */
|
/* -------------------- App State ------------------- */
|
||||||
reset = () => {
|
reset = () => {
|
||||||
this.setState((data) => ({
|
this.setState((data) => ({
|
||||||
|
@ -525,7 +551,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
/* -------------------- Sessions -------------------- */
|
/* -------------------- Sessions -------------------- */
|
||||||
startSession<T extends Session>(session: T, ...args: ParametersExceptFirst<T['start']>) {
|
startSession<T extends Session>(session: T, ...args: ParametersExceptFirst<T['start']>) {
|
||||||
this.session = session
|
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}`)
|
this._onChange?.(this, `session:start_${session.id}`)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -542,10 +568,13 @@ export class TLDrawState implements TLCallbacks {
|
||||||
const { session } = this
|
const { session } = this
|
||||||
if (!session) return this
|
if (!session) return this
|
||||||
|
|
||||||
this.setState((data) => session.cancel(data, ...args))
|
this.setState((data) => session.cancel(data, ...args), TLDrawStatus.Idle)
|
||||||
this.setStatus('idle')
|
|
||||||
this.session = undefined
|
this.session = undefined
|
||||||
|
this.isCreating = false
|
||||||
|
|
||||||
this._onChange?.(this, `session:cancel:${session.id}`)
|
this._onChange?.(this, `session:cancel:${session.id}`)
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,16 +582,9 @@ export class TLDrawState implements TLCallbacks {
|
||||||
const { session } = this
|
const { session } = this
|
||||||
if (!session) return this
|
if (!session) return this
|
||||||
|
|
||||||
this.setStatus('idle')
|
const current = this.getState()
|
||||||
|
|
||||||
const result = session.complete(this.store.getState(), ...args)
|
const result = session.complete(current, ...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 { isToolLocked, activeTool } = this.appState
|
const { isToolLocked, activeTool } = this.appState
|
||||||
|
|
||||||
|
@ -571,6 +593,35 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session = undefined
|
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
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,12 +637,14 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
history.pointer = history.stack.length - 1
|
history.pointer = history.stack.length - 1
|
||||||
|
|
||||||
this.setState((data) =>
|
this.setState(
|
||||||
Object.fromEntries(
|
(data) =>
|
||||||
Object.entries(command.after).map(([key, partial]) => {
|
Object.fromEntries(
|
||||||
return [key, Utils.deepMerge(data[key as keyof Data], partial)]
|
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}`)
|
this._onChange?.(this, `command:${command.id}`)
|
||||||
|
@ -606,12 +659,14 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
const command = history.stack[history.pointer]
|
const command = history.stack[history.pointer]
|
||||||
|
|
||||||
this.setState((data) =>
|
this.setState(
|
||||||
Object.fromEntries(
|
(data) =>
|
||||||
Object.entries(command.before).map(([key, partial]) => {
|
Object.fromEntries(
|
||||||
return [key, Utils.deepMerge(data[key as keyof Data], partial)]
|
Object.entries(command.before).map(([key, partial]) => {
|
||||||
})
|
return [key, Utils.deepMerge(data[key as keyof Data], partial)]
|
||||||
)
|
})
|
||||||
|
),
|
||||||
|
TLDrawStatus.Idle
|
||||||
)
|
)
|
||||||
|
|
||||||
history.pointer--
|
history.pointer--
|
||||||
|
@ -630,13 +685,16 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
const command = history.stack[history.pointer]
|
const command = history.stack[history.pointer]
|
||||||
|
|
||||||
this.setState((data) =>
|
this.setState(
|
||||||
Object.fromEntries(
|
(data) =>
|
||||||
Object.entries(command.after).map(([key, partial]) => {
|
Object.fromEntries(
|
||||||
return [key, Utils.deepMerge(data[key as keyof Data], partial)]
|
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}`)
|
this._onChange?.(this, `redo:${command.id}`)
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
@ -834,7 +892,14 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel = () => {
|
cancel = () => {
|
||||||
switch (this.status.current) {
|
if (this.isCreating) {
|
||||||
|
this.cancelSession()
|
||||||
|
this.delete()
|
||||||
|
this.isCreating = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.appState.status.current) {
|
||||||
case 'idle': {
|
case 'idle': {
|
||||||
this.deselectAll()
|
this.deselectAll()
|
||||||
this.selectTool('select')
|
this.selectTool('select')
|
||||||
|
@ -857,11 +922,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
this.cancelSession()
|
this.cancelSession()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'creating': {
|
|
||||||
this.cancelSession()
|
|
||||||
this.delete()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
@ -968,7 +1028,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
/* -------------------- Sessions -------------------- */
|
/* -------------------- Sessions -------------------- */
|
||||||
startBrushSession = (point: number[]) => {
|
startBrushSession = (point: number[]) => {
|
||||||
this.setStatus('brushing')
|
|
||||||
this.startSession(new Sessions.BrushSession(this.store.getState(), point))
|
this.startSession(new Sessions.BrushSession(this.store.getState(), point))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -979,7 +1038,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
startTranslateSession = (point: number[]) => {
|
startTranslateSession = (point: number[]) => {
|
||||||
this.setStatus('translating')
|
|
||||||
this.startSession(new Sessions.TranslateSession(this.store.getState(), point))
|
this.startSession(new Sessions.TranslateSession(this.store.getState(), point))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -998,8 +1056,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
if (selectedIds.length === 0) return this
|
if (selectedIds.length === 0) return this
|
||||||
|
|
||||||
this.setStatus('transforming')
|
|
||||||
|
|
||||||
this.pointedBoundsHandle = handle
|
this.pointedBoundsHandle = handle
|
||||||
|
|
||||||
if (this.pointedBoundsHandle === 'rotate') {
|
if (this.pointedBoundsHandle === 'rotate') {
|
||||||
|
@ -1032,7 +1088,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
startTextSession = (id?: string) => {
|
startTextSession = (id?: string) => {
|
||||||
this.editingId = id
|
this.editingId = id
|
||||||
this.setStatus('editing-text')
|
|
||||||
this.startSession(new Sessions.TextSession(this.store.getState(), id))
|
this.startSession(new Sessions.TextSession(this.store.getState(), id))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -1043,7 +1098,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
startDrawSession = (id: string, point: number[]) => {
|
startDrawSession = (id: string, point: number[]) => {
|
||||||
this.setStatus('creating')
|
|
||||||
this.startSession(new Sessions.DrawSession(this.store.getState(), id, point))
|
this.startSession(new Sessions.DrawSession(this.store.getState(), id, point))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -1077,44 +1131,41 @@ export class TLDrawState implements TLCallbacks {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
updatenPointerMove: TLPointerEventHandler = (info) => {
|
updateOnPointerMove: TLPointerEventHandler = (info) => {
|
||||||
switch (this.status.current) {
|
switch (this.status.current) {
|
||||||
case 'pointingBoundsHandle': {
|
case TLDrawStatus.PointingBoundsHandle: {
|
||||||
if (!this.pointedBoundsHandle) throw Error('No pointed bounds handle')
|
if (!this.pointedBoundsHandle) throw Error('No pointed bounds handle')
|
||||||
if (Vec.dist(info.origin, info.point) > 4) {
|
if (Vec.dist(info.origin, info.point) > 4) {
|
||||||
this.setStatus('transforming')
|
|
||||||
this.startTransformSession(this.getPagePoint(info.origin), this.pointedBoundsHandle)
|
this.startTransformSession(this.getPagePoint(info.origin), this.pointedBoundsHandle)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'pointingHandle': {
|
case TLDrawStatus.PointingHandle: {
|
||||||
if (!this.pointedHandle) throw Error('No pointed handle')
|
if (!this.pointedHandle) throw Error('No pointed handle')
|
||||||
if (Vec.dist(info.origin, info.point) > 4) {
|
if (Vec.dist(info.origin, info.point) > 4) {
|
||||||
this.setStatus('translatingHandle')
|
|
||||||
this.startHandleSession(this.getPagePoint(info.origin), this.pointedHandle)
|
this.startHandleSession(this.getPagePoint(info.origin), this.pointedHandle)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'pointingBounds': {
|
case TLDrawStatus.PointingBounds: {
|
||||||
if (Vec.dist(info.origin, info.point) > 4) {
|
if (Vec.dist(info.origin, info.point) > 4) {
|
||||||
this.setStatus('translating')
|
|
||||||
this.startTranslateSession(this.getPagePoint(info.origin))
|
this.startTranslateSession(this.getPagePoint(info.origin))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'brushing': {
|
case TLDrawStatus.Brushing: {
|
||||||
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translating': {
|
case TLDrawStatus.Translating: {
|
||||||
this.updateTranslateSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
this.updateTranslateSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'transforming': {
|
case TLDrawStatus.Transforming: {
|
||||||
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translatingHandle': {
|
case TLDrawStatus.TranslatingHandle: {
|
||||||
this.updateHandleSession(
|
this.updateHandleSession(
|
||||||
this.getPagePoint(info.point),
|
this.getPagePoint(info.point),
|
||||||
info.shiftKey,
|
info.shiftKey,
|
||||||
|
@ -1123,7 +1174,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'creating': {
|
case TLDrawStatus.Creating: {
|
||||||
switch (this.appState.activeToolType) {
|
switch (this.appState.activeToolType) {
|
||||||
case 'draw': {
|
case 'draw': {
|
||||||
this.updateDrawSession(this.getPagePoint(info.point), info.pressure, info.shiftKey)
|
this.updateDrawSession(this.getPagePoint(info.point), info.pressure, info.shiftKey)
|
||||||
|
@ -1193,7 +1244,9 @@ export class TLDrawState implements TLCallbacks {
|
||||||
selectedIds: [id],
|
selectedIds: [id],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
}, TLDrawStatus.Creating)
|
||||||
|
|
||||||
|
this.isCreating = true
|
||||||
|
|
||||||
const { activeTool, activeToolType } = this.getAppState()
|
const { activeTool, activeToolType } = this.getAppState()
|
||||||
|
|
||||||
|
@ -1231,10 +1284,10 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.status.current) {
|
switch (this.status.current) {
|
||||||
case 'idle': {
|
case TLDrawStatus.Idle: {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'brushing': {
|
case TLDrawStatus.Brushing: {
|
||||||
if (key === 'Meta' || key === 'Control') {
|
if (key === 'Meta' || key === 'Control') {
|
||||||
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
||||||
return
|
return
|
||||||
|
@ -1242,7 +1295,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translating': {
|
case TLDrawStatus.Translating: {
|
||||||
if (key === 'Escape') {
|
if (key === 'Escape') {
|
||||||
this.cancelSession(this.getPagePoint(info.point))
|
this.cancelSession(this.getPagePoint(info.point))
|
||||||
}
|
}
|
||||||
|
@ -1252,7 +1305,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'transforming': {
|
case TLDrawStatus.Transforming: {
|
||||||
if (key === 'Escape') {
|
if (key === 'Escape') {
|
||||||
this.cancelSession(this.getPagePoint(info.point))
|
this.cancelSession(this.getPagePoint(info.point))
|
||||||
}
|
}
|
||||||
|
@ -1262,7 +1315,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translatingHandle': {
|
case TLDrawStatus.TranslatingHandle: {
|
||||||
if (key === 'Escape') {
|
if (key === 'Escape') {
|
||||||
this.cancelSession(this.getPagePoint(info.point))
|
this.cancelSession(this.getPagePoint(info.point))
|
||||||
}
|
}
|
||||||
|
@ -1282,25 +1335,25 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
onKeyUp = (key: string, info: TLKeyboardInfo) => {
|
onKeyUp = (key: string, info: TLKeyboardInfo) => {
|
||||||
switch (this.status.current) {
|
switch (this.status.current) {
|
||||||
case 'brushing': {
|
case TLDrawStatus.Brushing: {
|
||||||
if (key === 'Meta' || key === 'Control') {
|
if (key === 'Meta' || key === 'Control') {
|
||||||
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
this.updateBrushSession(this.getPagePoint(info.point), info.metaKey)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'transforming': {
|
case TLDrawStatus.Transforming: {
|
||||||
if (key === 'Shift' || key === 'Alt') {
|
if (key === 'Shift' || key === 'Alt') {
|
||||||
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translating': {
|
case TLDrawStatus.Translating: {
|
||||||
if (key === 'Shift' || key === 'Alt') {
|
if (key === 'Shift' || key === 'Alt') {
|
||||||
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translatingHandle': {
|
case TLDrawStatus.TranslatingHandle: {
|
||||||
if (key === 'Escape') {
|
if (key === 'Escape') {
|
||||||
this.cancelSession(this.getPagePoint(info.point))
|
this.cancelSession(this.getPagePoint(info.point))
|
||||||
}
|
}
|
||||||
|
@ -1320,7 +1373,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
/* ------------- Renderer Event Handlers ------------ */
|
/* ------------- Renderer Event Handlers ------------ */
|
||||||
onPinchStart: TLPinchEventHandler = () => {
|
onPinchStart: TLPinchEventHandler = () => {
|
||||||
this.setStatus('pinching')
|
this.setStatus(TLDrawStatus.Pinching)
|
||||||
}
|
}
|
||||||
|
|
||||||
onPinchEnd: TLPinchEventHandler = () => {
|
onPinchEnd: TLPinchEventHandler = () => {
|
||||||
|
@ -1328,14 +1381,14 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPinch: TLPinchEventHandler = (info, e) => {
|
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.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) => {
|
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!
|
// 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)
|
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
|
if (Vec.isEqual(next, prev)) return
|
||||||
|
|
||||||
this.pan(delta)
|
this.pan(delta)
|
||||||
this.updatenPointerMove(info, e as any)
|
this.updateOnPointerMove(info, e as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
onZoom: TLWheelEventHandler = (info, e) => {
|
onZoom: TLWheelEventHandler = (info, e) => {
|
||||||
this.zoom(info.delta[2] / 100)
|
this.zoom(info.delta[2] / 100)
|
||||||
this.updatenPointerMove(info, e as any)
|
this.updateOnPointerMove(info, e as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer Events
|
// Pointer Events
|
||||||
onPointerDown: TLPointerEventHandler = (info) => {
|
onPointerDown: TLPointerEventHandler = (info) => {
|
||||||
switch (this.status.current) {
|
switch (this.status.current) {
|
||||||
case 'idle': {
|
case TLDrawStatus.Idle: {
|
||||||
switch (this.appState.activeTool) {
|
switch (this.appState.activeTool) {
|
||||||
case 'draw': {
|
case 'draw': {
|
||||||
this.setStatus('creating')
|
|
||||||
this.createActiveToolShape(info.point)
|
this.createActiveToolShape(info.point)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'rectangle': {
|
case 'rectangle': {
|
||||||
this.setStatus('creating')
|
|
||||||
this.createActiveToolShape(info.point)
|
this.createActiveToolShape(info.point)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'ellipse': {
|
case 'ellipse': {
|
||||||
this.setStatus('creating')
|
|
||||||
this.createActiveToolShape(info.point)
|
this.createActiveToolShape(info.point)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'arrow': {
|
case 'arrow': {
|
||||||
this.setStatus('creating')
|
|
||||||
this.createActiveToolShape(info.point)
|
this.createActiveToolShape(info.point)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -1384,14 +1433,14 @@ export class TLDrawState implements TLCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPointerMove: TLPointerEventHandler = (info, e) => {
|
onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||||
this.updatenPointerMove(info, e)
|
this.updateOnPointerMove(info, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
onPointerUp: TLPointerEventHandler = (info) => {
|
onPointerUp: TLPointerEventHandler = (info) => {
|
||||||
const data = this.getState()
|
const data = this.getState()
|
||||||
|
|
||||||
switch (this.status.current) {
|
switch (this.status.current) {
|
||||||
case 'pointingBounds': {
|
case TLDrawStatus.PointingBounds: {
|
||||||
if (info.target === 'bounds') {
|
if (info.target === 'bounds') {
|
||||||
// If we just clicked the selecting bounds's background, clear the selection
|
// If we just clicked the selecting bounds's background, clear the selection
|
||||||
this.deselectAll()
|
this.deselectAll()
|
||||||
|
@ -1412,41 +1461,41 @@ export class TLDrawState implements TLCallbacks {
|
||||||
this.pointedId = undefined
|
this.pointedId = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setStatus('idle')
|
this.setStatus(TLDrawStatus.Idle)
|
||||||
this.pointedId = undefined
|
this.pointedId = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'pointingBoundsHandle': {
|
case TLDrawStatus.PointingBoundsHandle: {
|
||||||
this.setStatus('idle')
|
this.setStatus(TLDrawStatus.Idle)
|
||||||
this.pointedBoundsHandle = undefined
|
this.pointedBoundsHandle = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'pointingHandle': {
|
case TLDrawStatus.PointingHandle: {
|
||||||
this.setStatus('idle')
|
this.setStatus(TLDrawStatus.Idle)
|
||||||
this.pointedHandle = undefined
|
this.pointedHandle = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translatingHandle': {
|
case TLDrawStatus.TranslatingHandle: {
|
||||||
this.completeSession<Sessions.HandleSession>()
|
this.completeSession<Sessions.HandleSession>()
|
||||||
this.pointedHandle = undefined
|
this.pointedHandle = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'brushing': {
|
case TLDrawStatus.Brushing: {
|
||||||
this.completeSession<Sessions.BrushSession>()
|
this.completeSession<Sessions.BrushSession>()
|
||||||
brushUpdater.clear()
|
brushUpdater.clear()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'translating': {
|
case TLDrawStatus.Translating: {
|
||||||
this.completeSession(this.getPagePoint(info.point))
|
this.completeSession<Sessions.TranslateSession>()
|
||||||
this.pointedId = undefined
|
this.pointedId = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'transforming': {
|
case TLDrawStatus.Transforming: {
|
||||||
this.completeSession(this.getPagePoint(info.point))
|
this.completeSession<Sessions.TransformSession>()
|
||||||
this.pointedBoundsHandle = undefined
|
this.pointedBoundsHandle = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'creating': {
|
case TLDrawStatus.Creating: {
|
||||||
this.completeSession(this.getPagePoint(info.point))
|
this.completeSession(this.getPagePoint(info.point))
|
||||||
this.pointedHandle = undefined
|
this.pointedHandle = undefined
|
||||||
}
|
}
|
||||||
|
@ -1485,7 +1534,6 @@ export class TLDrawState implements TLCallbacks {
|
||||||
switch (this.appState.activeTool) {
|
switch (this.appState.activeTool) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
// Create a text shape
|
// Create a text shape
|
||||||
this.setStatus('creating')
|
|
||||||
this.createActiveToolShape(info.point)
|
this.createActiveToolShape(info.point)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -1530,7 +1578,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
this.setSelectedIds([info.target], info.shiftKey)
|
this.setSelectedIds([info.target], info.shiftKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setStatus('pointingBounds')
|
this.setStatus(TLDrawStatus.PointingBounds)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1581,7 +1629,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
// Bounds (bounding box background)
|
// Bounds (bounding box background)
|
||||||
onPointBounds: TLBoundsEventHandler = () => {
|
onPointBounds: TLBoundsEventHandler = () => {
|
||||||
this.setStatus('pointingBounds')
|
this.setStatus(TLDrawStatus.PointingBounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleClickBounds: TLBoundsEventHandler = () => {
|
onDoubleClickBounds: TLBoundsEventHandler = () => {
|
||||||
|
@ -1606,11 +1654,11 @@ export class TLDrawState implements TLCallbacks {
|
||||||
|
|
||||||
onReleaseBounds: TLBoundsEventHandler = (info) => {
|
onReleaseBounds: TLBoundsEventHandler = (info) => {
|
||||||
switch (this.status.current) {
|
switch (this.status.current) {
|
||||||
case 'translating': {
|
case TLDrawStatus.Translating: {
|
||||||
this.completeSession(this.getPagePoint(info.point))
|
this.completeSession(this.getPagePoint(info.point))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'brushing': {
|
case TLDrawStatus.Brushing: {
|
||||||
this.completeSession<Sessions.BrushSession>()
|
this.completeSession<Sessions.BrushSession>()
|
||||||
brushUpdater.clear()
|
brushUpdater.clear()
|
||||||
break
|
break
|
||||||
|
@ -1621,7 +1669,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
// Bounds handles (corners, edges)
|
// Bounds handles (corners, edges)
|
||||||
onPointBoundsHandle: TLBoundsHandleEventHandler = (info) => {
|
onPointBoundsHandle: TLBoundsHandleEventHandler = (info) => {
|
||||||
this.pointedBoundsHandle = info.target
|
this.pointedBoundsHandle = info.target
|
||||||
this.setStatus('pointingBoundsHandle')
|
this.setStatus(TLDrawStatus.PointingBoundsHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleClickBoundsHandle: TLBoundsHandleEventHandler = () => {
|
onDoubleClickBoundsHandle: TLBoundsHandleEventHandler = () => {
|
||||||
|
@ -1651,7 +1699,7 @@ export class TLDrawState implements TLCallbacks {
|
||||||
// Handles (ie the handles of a selected arrow)
|
// Handles (ie the handles of a selected arrow)
|
||||||
onPointHandle: TLPointerEventHandler = (info) => {
|
onPointHandle: TLPointerEventHandler = (info) => {
|
||||||
this.pointedHandle = info.target
|
this.pointedHandle = info.target
|
||||||
this.setStatus('pointingHandle')
|
this.setStatus(TLDrawStatus.PointingHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleClickHandle: TLPointerEventHandler = (info) => {
|
onDoubleClickHandle: TLPointerEventHandler = (info) => {
|
||||||
|
@ -1766,4 +1814,8 @@ export class TLDrawState implements TLCallbacks {
|
||||||
get appState() {
|
get appState() {
|
||||||
return this.data.appState
|
return this.data.appState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return this.appState.status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ export interface Data {
|
||||||
isToolLocked: boolean
|
isToolLocked: boolean
|
||||||
isStyleOpen: boolean
|
isStyleOpen: boolean
|
||||||
isEmptyCanvas: boolean
|
isEmptyCanvas: boolean
|
||||||
|
status: { current: TLDrawStatus; previous: TLDrawStatus }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface PagePartial {
|
export interface PagePartial {
|
||||||
|
@ -63,25 +64,27 @@ export interface History {
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
id: string
|
id: string
|
||||||
|
status: TLDrawStatus
|
||||||
start: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
start: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
||||||
update: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
update: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
||||||
complete: (data: Readonly<Data>, ...args: any[]) => Partial<Data> | Command
|
complete: (data: Readonly<Data>, ...args: any[]) => Partial<Data> | Command
|
||||||
cancel: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
cancel: (data: Readonly<Data>, ...args: any[]) => Partial<Data>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TLDrawStatus =
|
export enum TLDrawStatus {
|
||||||
| 'idle'
|
Idle = 'idle',
|
||||||
| 'pointingHandle'
|
PointingHandle = 'pointingHandle',
|
||||||
| 'pointingBounds'
|
PointingBounds = 'pointingBounds',
|
||||||
| 'pointingBoundsHandle'
|
PointingBoundsHandle = 'pointingBoundsHandle',
|
||||||
| 'translatingHandle'
|
TranslatingHandle = 'translatingHandle',
|
||||||
| 'translating'
|
Translating = 'translating',
|
||||||
| 'transforming'
|
Transforming = 'transforming',
|
||||||
| 'rotating'
|
Rotating = 'rotating',
|
||||||
| 'pinching'
|
Pinching = 'pinching',
|
||||||
| 'brushing'
|
Brushing = 'brushing',
|
||||||
| 'creating'
|
Creating = 'creating',
|
||||||
| 'editing-text'
|
EditingText = 'editing-text',
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type ParametersExceptFirst<F> = F extends (arg0: any, ...rest: infer R) => any ? R : never
|
export type ParametersExceptFirst<F> = F extends (arg0: any, ...rest: infer R) => any ? R : never
|
||||||
|
|
Loading…
Reference in a new issue