Adds direction session

This commit is contained in:
Steve Ruiz 2021-05-15 16:20:21 +01:00
parent a4643edd62
commit 8a650a99d6
13 changed files with 245 additions and 93 deletions

View file

@ -6,13 +6,13 @@ export default function Toolbar() {
const activeTool = useSelector((state) =>
state.whenIn({
selecting: "select",
creatingDot: "dot",
creatingCircle: "circle",
creatingEllipse: "ellipse",
creatingRay: "ray",
creatingLine: "line",
creatingPolyline: "polyline",
creatingRectangle: "rectangle",
dot: "dot",
circle: "circle",
ellipse: "ellipse",
ray: "ray",
line: "line",
polyline: "polyline",
rectangle: "rectangle",
})
)

View file

@ -87,10 +87,6 @@ const circle = createShape<CircleShape>({
return shape
},
stretch(shape, scaleX, scaleY) {
return shape
},
transform(shape, bounds, { anchor }) {
// Set the new corner or position depending on the anchor
switch (anchor) {

View file

@ -66,16 +66,12 @@ const dot = createShape<DotShape>({
return shape
},
translate(shape, delta) {
shape.point = vec.add(shape.point, delta)
return shape
},
scale(shape, scale: number) {
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
translate(shape, delta) {
shape.point = vec.add(shape.point, delta)
return shape
},

View file

@ -93,10 +93,6 @@ const ellipse = createShape<EllipseShape>({
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY]
shape.radiusX = bounds.width / 2

View file

@ -67,9 +67,6 @@ export interface ShapeUtility<K extends Shape> {
// Apply a scale to a shape.
scale(this: ShapeUtility<K>, shape: K, scale: number): K
// Apply a stretch to a shape.
stretch(this: ShapeUtility<K>, shape: K, scaleX: number, scaleY: number): K
// Render a shape to JSX.
render(this: ShapeUtility<K>, shape: K): JSX.Element

View file

@ -84,10 +84,6 @@ const line = createShape<LineShape>({
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY]

View file

@ -98,10 +98,6 @@ const polyline = createShape<PolylineShape>({
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform(
shape,
bounds,

View file

@ -17,9 +17,12 @@ const ray = createShape<RayShape>({
parentId: "page0",
childIndex: 0,
point: [0, 0],
direction: [0, 0],
direction: [0, 1],
rotation: 0,
style: {},
style: {
stroke: "#000",
strokeWidth: 1,
},
...props,
}
},
@ -83,11 +86,9 @@ const ray = createShape<RayShape>({
return shape
},
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY]
return shape
},

View file

@ -0,0 +1,36 @@
import Command from "./command"
import history from "../history"
import { DirectionSnapshot } from "state/sessions/direction-session"
import { Data, LineShape, RayShape } from "types"
export default function translateCommand(
data: Data,
before: DirectionSnapshot,
after: DirectionSnapshot
) {
history.execute(
data,
new Command({
name: "set_direction",
category: "canvas",
do(data) {
const { shapes } = data.document.pages[after.currentPageId]
for (let { id, direction } of after.shapes) {
const shape = shapes[id] as RayShape | LineShape
shape.direction = direction
}
},
undo(data) {
const { shapes } = data.document.pages[before.currentPageId]
for (let { id, direction } of after.shapes) {
const shape = shapes[id] as RayShape | LineShape
shape.direction = direction
}
},
})
)
}

View file

@ -2,7 +2,14 @@ import translate from "./translate"
import transform from "./transform"
import generateShapes from "./generate-shapes"
import createShape from "./create-shape"
import direction from "./direction"
const commands = { translate, transform, generateShapes, createShape }
const commands = {
translate,
transform,
generateShapes,
createShape,
direction,
}
export default commands

View file

@ -0,0 +1,71 @@
import { Data, LineShape, RayShape } from "types"
import * as vec from "utils/vec"
import BaseSession from "./base-session"
import commands from "state/commands"
import { current } from "immer"
export default class DirectionSession extends BaseSession {
delta = [0, 0]
origin: number[]
snapshot: DirectionSnapshot
constructor(data: Data, point: number[]) {
super(data)
this.origin = point
this.snapshot = getDirectionSnapshot(data)
}
update(data: Data, point: number[]) {
const { currentPageId, shapes } = this.snapshot
const { document } = data
for (let { id } of shapes) {
const shape = document.pages[currentPageId].shapes[id] as
| RayShape
| LineShape
shape.direction = vec.uni(vec.vec(shape.point, point))
}
}
cancel(data: Data) {
const { document } = data
for (let { id, direction } of this.snapshot.shapes) {
const shape = document.pages[this.snapshot.currentPageId].shapes[id] as
| RayShape
| LineShape
shape.direction = direction
}
}
complete(data: Data) {
commands.direction(data, this.snapshot, getDirectionSnapshot(data))
}
}
export function getDirectionSnapshot(data: Data) {
const {
document: { pages },
currentPageId,
} = current(data)
const { shapes } = pages[currentPageId]
let snapshapes: { id: string; direction: number[] }[] = []
data.selectedIds.forEach((id) => {
const shape = shapes[id]
if ("direction" in shape) {
snapshapes.push({ id: shape.id, direction: shape.direction })
}
})
return {
currentPageId,
shapes: snapshapes,
}
}
export type DirectionSnapshot = ReturnType<typeof getDirectionSnapshot>

View file

@ -2,5 +2,12 @@ import BaseSession from "./base-session"
import BrushSession from "./brush-session"
import TranslateSession from "./translate-session"
import TransformSession from "./transform-session"
import DirectionSession from "./direction-session"
export { BrushSession, BaseSession, TranslateSession, TransformSession }
export {
BrushSession,
BaseSession,
TranslateSession,
TransformSession,
DirectionSession,
}

View file

@ -44,13 +44,13 @@ const state = createState({
do: "panCamera",
},
SELECTED_SELECT_TOOL: { to: "selecting" },
SELECTED_DOT_TOOL: { unless: "isReadOnly", to: "creatingDot" },
SELECTED_CIRCLE_TOOL: { unless: "isReadOnly", to: "creatingCircle" },
SELECTED_ELLIPSE_TOOL: { unless: "isReadOnly", to: "creatingEllipse" },
SELECTED_RAY_TOOL: { unless: "isReadOnly", to: "creatingRay" },
SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "creatingLine" },
SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "creatingPolyline" },
SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "creatingRectangle" },
SELECTED_DOT_TOOL: { unless: "isReadOnly", to: "dot" },
SELECTED_CIRCLE_TOOL: { unless: "isReadOnly", to: "circle" },
SELECTED_ELLIPSE_TOOL: { unless: "isReadOnly", to: "ellipse" },
SELECTED_RAY_TOOL: { unless: "isReadOnly", to: "ray" },
SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "line" },
SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "polyline" },
SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" },
},
initial: "selecting",
states: {
@ -154,18 +154,18 @@ const state = createState({
},
},
},
creatingDot: {
dot: {
initial: "creating",
states: {
creating: {
on: {
POINTED_CANVAS: {
do: "createDot",
to: "creatingDot.positioning",
to: "dot.editing",
},
},
},
positioning: {
editing: {
onEnter: "startTranslateSession",
on: {
MOVED_POINTER: "updateTranslateSession",
@ -179,12 +179,36 @@ const state = createState({
},
},
},
creatingCircle: {},
creatingEllipse: {},
creatingRay: {},
creatingLine: {},
creatingPolyline: {},
creatingRectangle: {},
circle: {},
ellipse: {},
ray: {
initial: "creating",
states: {
creating: {
on: {
POINTED_CANVAS: {
do: "createRay",
to: "ray.editing",
},
},
},
editing: {
onEnter: "startDirectionSession",
on: {
MOVED_POINTER: "updateDirectionSession",
PANNED_CAMERA: "updateDirectionSession",
STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: {
do: ["cancelSession", "deleteSelectedIds"],
to: "selecting",
},
},
},
},
},
line: {},
polyline: {},
rectangle: {},
},
conditions: {
isPointingBounds(data, payload: PointerInfo) {
@ -216,41 +240,31 @@ const state = createState({
},
},
actions: {
// Shapes
/* --------------------- Shapes --------------------- */
// Dot
createDot(data, payload: PointerInfo) {
const shape = shapeUtilityMap[ShapeType.Dot].create({
point: screenToWorld(payload.point, data),
})
commands.createShape(data, shape)
data.selectedIds.add(shape.id)
},
// History
enableHistory() {
history.enable()
},
disableHistory() {
history.disable()
},
undo(data) {
history.undo(data)
},
redo(data) {
history.redo(data)
// Ray
createRay(data, payload: PointerInfo) {
const shape = shapeUtilityMap[ShapeType.Ray].create({
point: screenToWorld(payload.point, data),
})
commands.createShape(data, shape)
data.selectedIds.add(shape.id)
},
// Code
setGeneratedShapes(data, payload: { shapes: Shape[] }) {
commands.generateShapes(data, data.currentPageId, payload.shapes)
},
increaseCodeFontSize(data) {
data.settings.fontSize++
},
decreaseCodeFontSize(data) {
data.settings.fontSize--
},
/* -------------------- Sessions -------------------- */
// Sessions
// Shared
cancelSession(data) {
session.cancel(data)
session = undefined
@ -297,20 +311,19 @@ const state = createState({
session.update(data, screenToWorld(payload.point, data))
},
// Selection
deleteSelectedIds(data) {
const { document, currentPageId } = data
const shapes = document.pages[currentPageId].shapes
data.selectedIds.forEach((id) => {
delete shapes[id]
// TODO: recursively delete children
})
data.selectedIds.clear()
data.hoveredId = undefined
data.pointedId = undefined
// Direction
startDirectionSession(data, payload: PointerInfo) {
session = new Sessions.DirectionSession(
data,
screenToWorld(payload.point, data)
)
},
updateDirectionSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data))
},
/* -------------------- Selection ------------------- */
setHoveredId(data, payload: PointerInfo) {
data.hoveredId = payload.target
},
@ -357,6 +370,46 @@ const state = createState({
vec.div(payload.delta, camera.zoom)
)
},
deleteSelectedIds(data) {
const { document, currentPageId } = data
const shapes = document.pages[currentPageId].shapes
data.selectedIds.forEach((id) => {
delete shapes[id]
// TODO: recursively delete children
})
data.selectedIds.clear()
data.hoveredId = undefined
data.pointedId = undefined
},
/* ---------------------- Misc ---------------------- */
// History
enableHistory() {
history.enable()
},
disableHistory() {
history.disable()
},
undo(data) {
history.undo(data)
},
redo(data) {
history.redo(data)
},
// Code
setGeneratedShapes(data, payload: { shapes: Shape[] }) {
commands.generateShapes(data, data.currentPageId, payload.shapes)
},
increaseCodeFontSize(data) {
data.settings.fontSize++
},
decreaseCodeFontSize(data) {
data.settings.fontSize--
},
},
values: {
selectedIds(data) {