Adds deleting, z-index re-ordering
This commit is contained in:
parent
f11c35e941
commit
780eba3d98
7 changed files with 263 additions and 125 deletions
|
@ -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
|
||||
}
|
||||
|
|
44
state/commands/delete-selected.ts
Normal file
44
state/commands/delete-selected.ts
Normal 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)
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
|
@ -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
165
state/commands/move.ts
Normal 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)
|
||||
}
|
||||
}
|
121
state/state.ts
121
state/state.ts
|
@ -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 ---------------------- */
|
||||
|
|
8
types.ts
8
types.ts
|
@ -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 */
|
||||
/* -------------------------------------------------- */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue