Fixes nesting groups
This commit is contained in:
parent
72accc5f44
commit
a52e91459f
9 changed files with 138 additions and 102 deletions
|
@ -29,7 +29,6 @@ export default function Page() {
|
|||
id={shapeId}
|
||||
isSelecting={isSelecting}
|
||||
parentPoint={noOffset}
|
||||
parentRotation={0}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useSelector } from 'state'
|
|||
import styled from 'styles'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getPage } from 'utils/utils'
|
||||
import { ShapeType } from 'types'
|
||||
import { ShapeStyles, ShapeType } from 'types'
|
||||
import useShapeEvents from 'hooks/useShapeEvents'
|
||||
import * as vec from 'utils/vec'
|
||||
import { getShapeStyle } from 'lib/shape-styles'
|
||||
|
@ -12,10 +12,9 @@ interface ShapeProps {
|
|||
id: string
|
||||
isSelecting: boolean
|
||||
parentPoint: number[]
|
||||
parentRotation: number
|
||||
}
|
||||
|
||||
function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
|
||||
function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
|
||||
const shape = useSelector(({ data }) => getPage(data).shapes[id])
|
||||
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
|
@ -26,7 +25,9 @@ function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
|
|||
// may sometimes run before the hook in the Page component, which means
|
||||
// a deleted shape will still be pulled here before the page component
|
||||
// detects the change and pulls this component.
|
||||
if (!shape) return null
|
||||
if (!shape) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isGroup = shape.type === ShapeType.Group
|
||||
|
||||
|
@ -50,9 +51,7 @@ function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
|
|||
{...events}
|
||||
/>
|
||||
)}
|
||||
{!shape.isHidden && (
|
||||
<StyledShape as="use" data-shy={isGroup} href={'#' + id} {...style} />
|
||||
)}
|
||||
{!shape.isHidden && <ReadShape isGroup={isGroup} id={id} style={style} />}
|
||||
{isGroup &&
|
||||
shape.children.map((shapeId) => (
|
||||
<Shape
|
||||
|
@ -60,13 +59,26 @@ function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
|
|||
id={shapeId}
|
||||
isSelecting={isSelecting}
|
||||
parentPoint={shape.point}
|
||||
parentRotation={shape.rotation}
|
||||
/>
|
||||
))}
|
||||
</StyledGroup>
|
||||
)
|
||||
}
|
||||
|
||||
interface RealShapeProps {
|
||||
isGroup: boolean
|
||||
id: string
|
||||
style: Partial<React.SVGProps<SVGUseElement>>
|
||||
}
|
||||
|
||||
const ReadShape = memo(function RealShape({
|
||||
isGroup,
|
||||
id,
|
||||
style,
|
||||
}: RealShapeProps) {
|
||||
return <StyledShape as="use" data-shy={isGroup} href={'#' + id} {...style} />
|
||||
})
|
||||
|
||||
const StyledShape = styled('path', {
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
|
|
|
@ -28,7 +28,7 @@ const group = registerShapeUtils<GroupShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Group,
|
||||
isGenerated: false,
|
||||
name: 'Rectangle',
|
||||
name: 'Group',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
|
|
|
@ -27,9 +27,9 @@ export default function groupCommand(data: Data) {
|
|||
|
||||
let newGroupParentId: string
|
||||
let newGroupShape: GroupShape
|
||||
let oldGroupShape: GroupShape
|
||||
let newGroupChildIndex: number
|
||||
|
||||
const selectedShapeIds = initialShapes.map((s) => s.id)
|
||||
const initialShapeIds = initialShapes.map((s) => s.id)
|
||||
|
||||
const parentIds = Array.from(
|
||||
new Set(initialShapes.map((s) => s.parentId)).values()
|
||||
|
@ -50,13 +50,11 @@ export default function groupCommand(data: Data) {
|
|||
const parent = getShape(data, parentId) as GroupShape
|
||||
|
||||
if (parent.children.length === initialShapes.length) {
|
||||
// !
|
||||
// !
|
||||
// !
|
||||
// Hey! We're not going any further. We need to ungroup those shapes.
|
||||
// !!! Hey! We're not going any further. We need to ungroup those shapes.
|
||||
commands.ungroup(data)
|
||||
return
|
||||
} else {
|
||||
// Make the group inside of the current group
|
||||
newGroupParentId = parentId
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +75,8 @@ export default function groupCommand(data: Data) {
|
|||
parentId: newGroupParentId,
|
||||
point: [commonBounds.minX, commonBounds.minY],
|
||||
size: [commonBounds.width, commonBounds.height],
|
||||
children: selectedShapeIds,
|
||||
children: initialShapeIds,
|
||||
childIndex: initialShapes[0].childIndex,
|
||||
})
|
||||
|
||||
history.execute(
|
||||
|
@ -85,43 +84,80 @@ export default function groupCommand(data: Data) {
|
|||
new Command({
|
||||
name: 'group_shapes',
|
||||
category: 'canvas',
|
||||
manualSelection: true,
|
||||
do(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
// Remove shapes from old parents
|
||||
for (const parentId of parentIds) {
|
||||
if (parentId === currentPageId) continue
|
||||
// Create the new group
|
||||
shapes[newGroupShape.id] = newGroupShape
|
||||
|
||||
const shape = shapes[parentId] as GroupShape
|
||||
getShapeUtils(shape).setProperty(
|
||||
shape,
|
||||
'children',
|
||||
shape.children.filter((id) => !selectedIds.has(id))
|
||||
)
|
||||
// Assign the group to its new parent
|
||||
if (newGroupParentId !== data.currentPageId) {
|
||||
const parent = shapes[newGroupParentId]
|
||||
getShapeUtils(parent).setProperty(parent, 'children', [
|
||||
...parent.children,
|
||||
newGroupShape.id,
|
||||
])
|
||||
}
|
||||
|
||||
shapes[newGroupShape.id] = newGroupShape
|
||||
// Assign the shapes to their new parent
|
||||
initialShapes.forEach((initialShape, i) => {
|
||||
// Remove shape from its old parent
|
||||
if (initialShape.parentId !== currentPageId) {
|
||||
const oldParent = shapes[initialShape.parentId] as GroupShape
|
||||
getShapeUtils(oldParent).setProperty(
|
||||
oldParent,
|
||||
'children',
|
||||
oldParent.children.filter((id) => !selectedIds.has(id))
|
||||
)
|
||||
}
|
||||
|
||||
// Assign the shape to its new parent, with its new childIndex
|
||||
const shape = shapes[initialShape.id]
|
||||
getShapeUtils(shape)
|
||||
.setProperty(shape, 'childIndex', i)
|
||||
.setProperty(shape, 'parentId', newGroupShape.id)
|
||||
})
|
||||
|
||||
data.selectedIds.clear()
|
||||
data.selectedIds.add(newGroupShape.id)
|
||||
initialShapes.forEach(({ id }, i) => {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape)
|
||||
.setProperty(shape, 'parentId', newGroupShape.id)
|
||||
.setProperty(shape, 'childIndex', i)
|
||||
})
|
||||
},
|
||||
undo(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
data.selectedIds.clear()
|
||||
|
||||
delete shapes[newGroupShape.id]
|
||||
initialShapes.forEach(({ id, parentId, childIndex }, i) => {
|
||||
data.selectedIds.add(id)
|
||||
const group = shapes[newGroupShape.id]
|
||||
|
||||
// remove the group from its parent
|
||||
if (group.parentId !== data.currentPageId) {
|
||||
const parent = shapes[group.parentId]
|
||||
getShapeUtils(parent).setProperty(
|
||||
parent,
|
||||
'children',
|
||||
parent.children.filter((id) => id !== newGroupShape.id)
|
||||
)
|
||||
}
|
||||
|
||||
// Move the shapes back to their previous parent / childIndex
|
||||
initialShapes.forEach(({ id, parentId, childIndex }) => {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape)
|
||||
.setProperty(shape, 'parentId', parentId)
|
||||
.setProperty(shape, 'childIndex', childIndex)
|
||||
|
||||
if (parentId !== data.currentPageId) {
|
||||
const parent = shapes[parentId]
|
||||
getShapeUtils(parent).setProperty(parent, 'children', [
|
||||
...parent.children,
|
||||
id,
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
// Delete the group
|
||||
delete shapes[newGroupShape.id]
|
||||
|
||||
// Reselect the children of the group
|
||||
data.selectedIds = new Set(initialShapeIds)
|
||||
},
|
||||
})
|
||||
)
|
||||
|
|
|
@ -33,26 +33,18 @@ export default function transformSingleCommand(
|
|||
updateParents(data, [id])
|
||||
},
|
||||
undo(data) {
|
||||
const { id, type, initialShapeBounds } = before
|
||||
const { id, initialShape } = before
|
||||
|
||||
const { shapes } = getPage(data, before.currentPageId)
|
||||
|
||||
data.selectedIds.clear()
|
||||
|
||||
if (isCreating) {
|
||||
data.selectedIds.clear()
|
||||
delete shapes[id]
|
||||
} else {
|
||||
const shape = shapes[id]
|
||||
data.selectedIds.add(id)
|
||||
|
||||
getShapeUtils(shape).transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape: after.initialShape,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
const page = getPage(data)
|
||||
page.shapes[id] = initialShape
|
||||
updateParents(data, [id])
|
||||
data.selectedIds = new Set([id])
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -18,8 +18,6 @@ export default function transformCommand(
|
|||
name: 'translate_shapes',
|
||||
category: 'canvas',
|
||||
do(data, isInitial) {
|
||||
if (isInitial) return
|
||||
|
||||
const { type, shapeBounds } = after
|
||||
|
||||
const { shapes } = getPage(data)
|
||||
|
@ -27,15 +25,18 @@ export default function transformCommand(
|
|||
for (let id in shapeBounds) {
|
||||
const { initialShape, initialShapeBounds, transformOrigin } =
|
||||
shapeBounds[id]
|
||||
|
||||
const shape = shapes[id]
|
||||
|
||||
getShapeUtils(shape).transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
scaleX,
|
||||
scaleY,
|
||||
})
|
||||
getShapeUtils(shape)
|
||||
.transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
scaleX,
|
||||
scaleY,
|
||||
})
|
||||
.onSessionComplete(shape)
|
||||
}
|
||||
|
||||
updateParents(data, Object.keys(shapeBounds))
|
||||
|
@ -50,13 +51,15 @@ export default function transformCommand(
|
|||
shapeBounds[id]
|
||||
const shape = shapes[id]
|
||||
|
||||
getShapeUtils(shape).transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
scaleX: scaleX < 0 ? scaleX * -1 : scaleX,
|
||||
scaleY: scaleX < 0 ? scaleX * -1 : scaleX,
|
||||
})
|
||||
getShapeUtils(shape)
|
||||
.transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
scaleX: scaleX < 0 ? scaleX * -1 : scaleX,
|
||||
scaleY: scaleX < 0 ? scaleX * -1 : scaleX,
|
||||
})
|
||||
.onSessionComplete(shape)
|
||||
}
|
||||
|
||||
updateParents(data, Object.keys(shapeBounds))
|
||||
|
|
|
@ -2,7 +2,7 @@ import Command from './command'
|
|||
import history from '../history'
|
||||
import { TranslateSnapshot } from 'state/sessions/translate-session'
|
||||
import { Data, GroupShape, Shape, ShapeType } from 'types'
|
||||
import { getPage, updateParents } from 'utils/utils'
|
||||
import { getDocumentBranch, getPage, updateParents } from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
|
@ -43,8 +43,10 @@ export default function translateCommand(
|
|||
|
||||
// Move shapes (these initialShapes will include clones if any)
|
||||
for (const { id, point } of initialShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, point)
|
||||
getDocumentBranch(data, id).forEach((id) => {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, point)
|
||||
})
|
||||
}
|
||||
|
||||
// Set selected shapes
|
||||
|
|
|
@ -67,18 +67,10 @@ export default class TransformSingleSession extends BaseSession {
|
|||
}
|
||||
|
||||
cancel(data: Data) {
|
||||
const { id, initialShape, initialShapeBounds, currentPageId } =
|
||||
this.snapshot
|
||||
const { id, initialShape } = this.snapshot
|
||||
|
||||
const shape = getShape(data, id, currentPageId)
|
||||
|
||||
getShapeUtils(shape).transform(shape, initialShapeBounds, {
|
||||
initialShape,
|
||||
type: this.transformType,
|
||||
scaleX: this.scaleX,
|
||||
scaleY: this.scaleY,
|
||||
transformOrigin: [0.5, 0.5],
|
||||
})
|
||||
const page = getPage(data)
|
||||
page.shapes[id] = initialShape
|
||||
|
||||
updateParents(data, [id])
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { current } from 'immer'
|
|||
import { v4 as uuid } from 'uuid'
|
||||
import {
|
||||
getChildIndexAbove,
|
||||
getDocumentBranch,
|
||||
getPage,
|
||||
getSelectedShapes,
|
||||
updateParents,
|
||||
|
@ -14,6 +15,7 @@ import { getShapeUtils } from 'lib/shape-utils'
|
|||
|
||||
export default class TranslateSession extends BaseSession {
|
||||
delta = [0, 0]
|
||||
prev = [0, 0]
|
||||
origin: number[]
|
||||
snapshot: TranslateSnapshot
|
||||
isCloning = false
|
||||
|
@ -30,6 +32,9 @@ export default class TranslateSession extends BaseSession {
|
|||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
const delta = vec.vec(this.origin, point)
|
||||
const trueDelta = vec.sub(delta, this.prev)
|
||||
this.delta = delta
|
||||
this.prev = delta
|
||||
|
||||
if (isAligned) {
|
||||
if (Math.abs(delta[0]) < Math.abs(delta[1])) {
|
||||
|
@ -92,17 +97,10 @@ export default class TranslateSession extends BaseSession {
|
|||
}
|
||||
|
||||
for (const initialShape of initialShapes) {
|
||||
const shape = shapes[initialShape.id]
|
||||
const next = vec.add(initialShape.point, delta)
|
||||
const deltaForShape = vec.sub(next, shape.point)
|
||||
getShapeUtils(shape).translateTo(shape, next)
|
||||
|
||||
if (shape.type === ShapeType.Group) {
|
||||
for (let childId of shape.children) {
|
||||
const childShape = shapes[childId]
|
||||
getShapeUtils(childShape).translateBy(childShape, deltaForShape)
|
||||
}
|
||||
}
|
||||
getDocumentBranch(data, initialShape.id).forEach((id) => {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateBy(shape, trueDelta)
|
||||
})
|
||||
}
|
||||
|
||||
updateParents(
|
||||
|
@ -117,17 +115,11 @@ export default class TranslateSession extends BaseSession {
|
|||
this.snapshot
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
for (const { id, point } of initialShapes) {
|
||||
const shape = shapes[id]
|
||||
const deltaForShape = vec.sub(point, shape.point)
|
||||
getShapeUtils(shape).translateTo(shape, point)
|
||||
|
||||
if (shape.type === ShapeType.Group) {
|
||||
for (let childId of shape.children) {
|
||||
const childShape = shapes[childId]
|
||||
getShapeUtils(childShape).translateBy(childShape, deltaForShape)
|
||||
}
|
||||
}
|
||||
for (const { id } of initialShapes) {
|
||||
getDocumentBranch(data, id).forEach((id) => {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateBy(shape, vec.neg(this.delta))
|
||||
})
|
||||
}
|
||||
|
||||
for (const { id } of clones) {
|
||||
|
@ -159,6 +151,14 @@ export default class TranslateSession extends BaseSession {
|
|||
|
||||
export function getTranslateSnapshot(data: Data) {
|
||||
const cData = current(data)
|
||||
|
||||
// Get selected shapes
|
||||
// Filter out the locked shapes
|
||||
// Collect the branch children for each remaining shape
|
||||
// Filter out doubles using a set
|
||||
// End up with an array of ids for all of the shapes that will change
|
||||
// Map into shapes from data snapshot
|
||||
|
||||
const page = getPage(cData)
|
||||
const selectedShapes = getSelectedShapes(cData).filter(
|
||||
(shape) => !shape.isLocked
|
||||
|
|
Loading…
Reference in a new issue