Adds deleting, z-index re-ordering

This commit is contained in:
Steve Ruiz 2021-05-23 18:09:23 +01:00
parent f11c35e941
commit 780eba3d98
7 changed files with 263 additions and 125 deletions

View file

@ -24,23 +24,27 @@ export default function useKeyboardEvents() {
}
break
}
case "": {
if (metaKey(e)) {
state.send("MOVED_TO_FRONT", getKeyboardEventInfo(e))
}
break
}
case "“": {
if (metaKey(e)) {
state.send("MOVED_TO_BACK", getKeyboardEventInfo(e))
}
break
}
case "]": {
if (metaKey(e)) {
if (e.altKey) {
state.send("MOVED_TO_FRONT", getKeyboardEventInfo(e))
} else {
state.send("MOVED_FORWARD", getKeyboardEventInfo(e))
}
state.send("MOVED_FORWARD", getKeyboardEventInfo(e))
}
break
}
case "[": {
if (metaKey(e)) {
if (e.altKey) {
state.send("MOVED_TO_BACK", getKeyboardEventInfo(e))
} else {
state.send("MOVED_BACKWARD", getKeyboardEventInfo(e))
}
state.send("MOVED_BACKWARD", getKeyboardEventInfo(e))
}
break
}

View file

@ -0,0 +1,44 @@
import Command from "./command"
import history from "../history"
import { TranslateSnapshot } from "state/sessions/translate-session"
import { Data } from "types"
import { getPage } from "utils/utils"
import { current } from "immer"
export default function deleteSelected(data: Data) {
const { currentPageId } = data
const selectedIds = Array.from(data.selectedIds.values())
const page = getPage(current(data))
const shapes = selectedIds.map((id) => page.shapes[id])
data.selectedIds.clear()
history.execute(
data,
new Command({
name: "delete_shapes",
category: "canvas",
manualSelection: true,
do(data) {
const page = getPage(data, currentPageId)
for (let id of selectedIds) {
delete page.shapes[id]
}
data.selectedIds.clear()
},
undo(data) {
const page = getPage(data, currentPageId)
data.selectedIds.clear()
for (let shape of shapes) {
page.shapes[shape.id] = shape
data.selectedIds.add(shape.id)
}
},
})
)
}

View file

@ -4,6 +4,8 @@ import transformSingle from "./transform-single"
import generate from "./generate"
import direct from "./direct"
import rotate from "./rotate"
import move from "./move"
import deleteSelected from "./delete-selected"
const commands = {
translate,
@ -12,6 +14,8 @@ const commands = {
generate,
direct,
rotate,
move,
deleteSelected,
}
export default commands

165
state/commands/move.ts Normal file
View file

@ -0,0 +1,165 @@
import Command from "./command"
import history from "../history"
import { Data, MoveType, Shape } from "types"
import { forceIntegerChildIndices, getChildren, getPage } from "utils/utils"
export default function moveCommand(data: Data, type: MoveType) {
const { currentPageId } = data
const page = getPage(data)
const selectedIds = Array.from(data.selectedIds.values())
const initialIndices = Object.fromEntries(
selectedIds.map((id) => [id, page.shapes[id].childIndex])
)
history.execute(
data,
new Command({
name: "move_shapes",
category: "canvas",
manualSelection: true,
do(data) {
const page = getPage(data, currentPageId)
const shapes = selectedIds.map((id) => page.shapes[id])
const shapesByParentId = shapes.reduce<Record<string, Shape[]>>(
(acc, shape) => {
if (acc[shape.parentId] === undefined) {
acc[shape.parentId] = []
}
acc[shape.parentId].push(shape)
return acc
},
{}
)
switch (type) {
case MoveType.ToFront: {
for (let id in shapesByParentId) {
moveToFront(shapesByParentId[id], getChildren(data, id))
}
break
}
case MoveType.ToBack: {
for (let id in shapesByParentId) {
moveToBack(shapesByParentId[id], getChildren(data, id))
}
break
}
case MoveType.Forward: {
for (let id in shapesByParentId) {
const visited = new Set<string>()
const siblings = getChildren(data, id)
shapesByParentId[id]
.sort((a, b) => b.childIndex - a.childIndex)
.forEach((shape) => moveForward(shape, siblings, visited))
}
break
}
case MoveType.Backward: {
for (let id in shapesByParentId) {
const visited = new Set<string>()
const siblings = getChildren(data, id)
shapesByParentId[id]
.sort((a, b) => a.childIndex - b.childIndex)
.forEach((shape) => moveBackward(shape, siblings, visited))
}
break
}
}
},
undo(data) {
const page = getPage(data)
for (let id of selectedIds) {
page.shapes[id].childIndex = initialIndices[id]
}
},
})
)
}
function moveToFront(shapes: Shape[], siblings: Shape[]) {
shapes.sort((a, b) => a.childIndex - b.childIndex)
const diff = siblings
.filter((sib) => !shapes.includes(sib))
.sort((a, b) => b.childIndex - a.childIndex)
if (diff.length === 0) return
const startIndex = Math.ceil(diff[0].childIndex) + 1
shapes.forEach((shape, i) => (shape.childIndex = startIndex + i))
}
function moveToBack(shapes: Shape[], siblings: Shape[]) {
shapes.sort((a, b) => b.childIndex - a.childIndex)
const diff = siblings
.filter((sib) => !shapes.includes(sib))
.sort((a, b) => a.childIndex - b.childIndex)
if (diff.length === 0) return
const startIndex = diff[0]?.childIndex
const step = startIndex / (shapes.length + 1)
shapes.forEach((shape, i) => (shape.childIndex = startIndex - (i + 1) * step))
}
function moveForward(shape: Shape, siblings: Shape[], visited: Set<string>) {
visited.add(shape.id)
const index = siblings.indexOf(shape)
const nextSibling = siblings[index + 1]
if (nextSibling && !visited.has(nextSibling.id)) {
const nextNextSibling = siblings[index + 2]
let nextIndex = nextNextSibling
? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
: Math.ceil(nextSibling.childIndex + 1)
if (nextIndex === nextSibling.childIndex) {
forceIntegerChildIndices(siblings)
nextIndex = nextNextSibling
? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
: Math.ceil(nextSibling.childIndex + 1)
}
shape.childIndex = nextIndex
siblings.sort((a, b) => a.childIndex - b.childIndex)
}
}
function moveBackward(shape: Shape, siblings: Shape[], visited: Set<string>) {
visited.add(shape.id)
const index = siblings.indexOf(shape)
const nextSibling = siblings[index - 1]
if (nextSibling && !visited.has(nextSibling.id)) {
const nextNextSibling = siblings[index - 2]
let nextIndex = nextNextSibling
? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
: nextSibling.childIndex / 2
if (shape.childIndex === nextSibling.childIndex) {
forceIntegerChildIndices(siblings)
nextNextSibling
? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
: nextSibling.childIndex / 2
}
shape.childIndex = nextIndex
siblings.sort((a, b) => a.childIndex - b.childIndex)
}
}

View file

@ -17,6 +17,7 @@ import {
Corner,
Edge,
CodeControl,
MoveType,
} from "types"
import inputs from "./inputs"
import { defaultDocument } from "./data"
@ -280,7 +281,7 @@ const state = createState({
MOVED_POINTER: {
if: "distanceImpliesDrag",
then: {
get: "newDot",
get: "newCircle",
do: "createShape",
to: "drawingShape.bounds",
},
@ -676,114 +677,16 @@ const state = createState({
data.selectedIds.add(data.pointedId)
},
moveSelectionToFront(data) {
const { selectedIds } = data
commands.move(data, MoveType.ToFront)
},
moveSelectionToBack(data) {
const { selectedIds } = data
commands.move(data, MoveType.ToBack)
},
moveSelectionForward(data) {
const { selectedIds } = data
const page = getPage(data)
const shapes = Array.from(selectedIds.values()).map(
(id) => page.shapes[id]
)
const shapesByParentId = shapes.reduce<Record<string, Shape[]>>(
(acc, shape) => {
if (acc[shape.parentId] === undefined) {
acc[shape.parentId] = []
}
acc[shape.parentId].push(shape)
return acc
},
{}
)
const visited = new Set<string>()
for (let id in shapesByParentId) {
const children = getChildren(data, id)
shapesByParentId[id]
.sort((a, b) => b.childIndex - a.childIndex)
.forEach((shape) => {
visited.add(shape.id)
children.sort((a, b) => a.childIndex - b.childIndex)
const index = children.indexOf(shape)
const nextSibling = children[index + 1]
if (!nextSibling || visited.has(nextSibling.id)) {
// At the top already, no change
return
}
const nextNextSibling = children[index + 2]
if (!nextNextSibling) {
// Moving to the top
shape.childIndex = nextSibling.childIndex + 1
return
}
shape.childIndex =
(nextSibling.childIndex + nextNextSibling.childIndex) / 2
})
}
commands.move(data, MoveType.Forward)
},
moveSelectionBackward(data) {
const { selectedIds } = data
const page = getPage(data)
const shapes = Array.from(selectedIds.values()).map(
(id) => page.shapes[id]
)
const shapesByParentId = shapes.reduce<Record<string, Shape[]>>(
(acc, shape) => {
if (acc[shape.parentId] === undefined) {
acc[shape.parentId] = []
}
acc[shape.parentId].push(shape)
return acc
},
{}
)
const visited = new Set<string>()
for (let id in shapesByParentId) {
const children = getChildren(data, id)
shapesByParentId[id]
.sort((a, b) => a.childIndex - b.childIndex)
.forEach((shape) => {
visited.add(shape.id)
children.sort((a, b) => a.childIndex - b.childIndex)
const index = children.indexOf(shape)
const nextSibling = children[index - 1]
if (!nextSibling || visited.has(nextSibling.id)) {
// At the bottom already, no change
return
}
const nextNextSibling = children[index - 2]
if (!nextNextSibling) {
// Moving to the bottom
shape.childIndex = nextSibling.childIndex / 2
return
}
shape.childIndex =
(nextSibling.childIndex + nextNextSibling.childIndex) / 2
})
}
commands.move(data, MoveType.Backward)
},
/* --------------------- Camera --------------------- */
@ -826,17 +729,7 @@ const state = createState({
)
},
deleteSelectedIds(data) {
const page = getPage(data)
data.hoveredId = undefined
data.pointedId = undefined
data.selectedIds.forEach((id) => {
delete page.shapes[id]
// TODO: recursively delete children
})
data.selectedIds.clear()
commands.deleteSelected(data)
},
/* ---------------------- History ---------------------- */

View file

@ -209,6 +209,14 @@ export type ShapeUtil<K extends Shape> = {
stretch(shape: K, scaleX: number, scaleY: number): K
render(shape: K): JSX.Element
}
export enum MoveType {
Backward,
Forward,
ToFront,
ToBack,
}
/* -------------------------------------------------- */
/* Code Editor */
/* -------------------------------------------------- */

View file

@ -1428,7 +1428,14 @@ export function getChildIndexAbove(
return shape.childIndex + 1
}
return (shape.childIndex + nextSibling.childIndex) / 2
let nextIndex = (shape.childIndex + nextSibling.childIndex) / 2
if (nextIndex === nextSibling.childIndex) {
forceIntegerChildIndices(siblings)
nextIndex = (shape.childIndex + nextSibling.childIndex) / 2
}
return nextIndex
}
export function getChildIndexBelow(
@ -1452,5 +1459,18 @@ export function getChildIndexBelow(
return shape.childIndex / 2
}
let nextIndex = (shape.childIndex + prevSibling.childIndex) / 2
if (nextIndex === prevSibling.childIndex) {
forceIntegerChildIndices(siblings)
nextIndex = (shape.childIndex + prevSibling.childIndex) / 2
}
return (shape.childIndex + prevSibling.childIndex) / 2
}
export function forceIntegerChildIndices(shapes: Shape[]) {
for (let i = 0; i < shapes.length; i++) {
shapes[i].childIndex = i + 1
}
}