diff --git a/components/canvas/page.tsx b/components/canvas/page.tsx index 27e9a0306..8cb27d638 100644 --- a/components/canvas/page.tsx +++ b/components/canvas/page.tsx @@ -29,7 +29,6 @@ export default function Page() { id={shapeId} isSelecting={isSelecting} parentPoint={noOffset} - parentRotation={0} /> ))} diff --git a/components/canvas/shape.tsx b/components/canvas/shape.tsx index 1f4b829f0..b4545c68e 100644 --- a/components/canvas/shape.tsx +++ b/components/canvas/shape.tsx @@ -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(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 && ( - - )} + {!shape.isHidden && } {isGroup && shape.children.map((shapeId) => ( ))} ) } +interface RealShapeProps { + isGroup: boolean + id: string + style: Partial> +} + +const ReadShape = memo(function RealShape({ + isGroup, + id, + style, +}: RealShapeProps) { + return +}) + const StyledShape = styled('path', { strokeLinecap: 'round', strokeLinejoin: 'round', diff --git a/lib/shape-utils/group.tsx b/lib/shape-utils/group.tsx index bf1d61374..f7bd9fd05 100644 --- a/lib/shape-utils/group.tsx +++ b/lib/shape-utils/group.tsx @@ -28,7 +28,7 @@ const group = registerShapeUtils({ id: uuid(), type: ShapeType.Group, isGenerated: false, - name: 'Rectangle', + name: 'Group', parentId: 'page0', childIndex: 0, point: [0, 0], diff --git a/state/commands/group.ts b/state/commands/group.ts index f45f1fbd8..19e481656 100644 --- a/state/commands/group.ts +++ b/state/commands/group.ts @@ -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) }, }) ) diff --git a/state/commands/transform-single.ts b/state/commands/transform-single.ts index c8df36fe2..44102c3bf 100644 --- a/state/commands/transform-single.ts +++ b/state/commands/transform-single.ts @@ -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]) } }, }) diff --git a/state/commands/transform.ts b/state/commands/transform.ts index bd9184dc0..89542319f 100644 --- a/state/commands/transform.ts +++ b/state/commands/transform.ts @@ -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)) diff --git a/state/commands/translate.ts b/state/commands/translate.ts index bd97082f1..f091fe5f6 100644 --- a/state/commands/translate.ts +++ b/state/commands/translate.ts @@ -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 diff --git a/state/sessions/transform-single-session.ts b/state/sessions/transform-single-session.ts index 700b8fdb8..222e0aad6 100644 --- a/state/sessions/transform-single-session.ts +++ b/state/sessions/transform-single-session.ts @@ -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]) } diff --git a/state/sessions/translate-session.ts b/state/sessions/translate-session.ts index cf5bee80d..8fc9b5ce2 100644 --- a/state/sessions/translate-session.ts +++ b/state/sessions/translate-session.ts @@ -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