Starts implementing locked shapes
This commit is contained in:
parent
3329c16e57
commit
6118dfcc2c
9 changed files with 138 additions and 87 deletions
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import { Edge, Corner } from 'types'
|
||||
import { useSelector } from 'state'
|
||||
import { getSelectedShapes, isMobile } from 'utils/utils'
|
||||
import { getPage, getSelectedShapes, isMobile } from 'utils/utils'
|
||||
|
||||
import CenterHandle from './center-handle'
|
||||
import CornerHandle from './corner-handle'
|
||||
|
@ -13,10 +13,18 @@ export default function Bounds() {
|
|||
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
||||
const zoom = useSelector((s) => s.data.camera.zoom)
|
||||
const bounds = useSelector((s) => s.values.selectedBounds)
|
||||
|
||||
const rotation = useSelector(({ data }) =>
|
||||
data.selectedIds.size === 1 ? getSelectedShapes(data)[0].rotation : 0
|
||||
)
|
||||
|
||||
const isAllLocked = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
return Array.from(s.data.selectedIds.values()).every(
|
||||
(id) => page.shapes[id].isLocked
|
||||
)
|
||||
})
|
||||
|
||||
if (!bounds) return null
|
||||
if (!isSelecting) return null
|
||||
|
||||
|
@ -31,16 +39,28 @@ export default function Bounds() {
|
|||
${(bounds.minY + bounds.maxY) / 2})
|
||||
translate(${bounds.minX},${bounds.minY})`}
|
||||
>
|
||||
<CenterHandle bounds={bounds} />
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Top} />
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Right} />
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Bottom} />
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Left} />
|
||||
<CornerHandle size={size} bounds={bounds} corner={Corner.TopLeft} />
|
||||
<CornerHandle size={size} bounds={bounds} corner={Corner.TopRight} />
|
||||
<CornerHandle size={size} bounds={bounds} corner={Corner.BottomRight} />
|
||||
<CornerHandle size={size} bounds={bounds} corner={Corner.BottomLeft} />
|
||||
<RotateHandle size={size} bounds={bounds} />
|
||||
<CenterHandle bounds={bounds} isLocked={isAllLocked} />
|
||||
{!isAllLocked && (
|
||||
<>
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Top} />
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Right} />
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Bottom} />
|
||||
<EdgeHandle size={size} bounds={bounds} edge={Edge.Left} />
|
||||
<CornerHandle size={size} bounds={bounds} corner={Corner.TopLeft} />
|
||||
<CornerHandle size={size} bounds={bounds} corner={Corner.TopRight} />
|
||||
<CornerHandle
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={Corner.BottomRight}
|
||||
/>
|
||||
<CornerHandle
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={Corner.BottomLeft}
|
||||
/>
|
||||
<RotateHandle size={size} bounds={bounds} />
|
||||
</>
|
||||
)}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,34 @@
|
|||
import styled from "styles"
|
||||
import { Bounds } from "types"
|
||||
import styled from 'styles'
|
||||
import { Bounds } from 'types'
|
||||
|
||||
export default function CenterHandle({ bounds }: { bounds: Bounds }) {
|
||||
export default function CenterHandle({
|
||||
bounds,
|
||||
isLocked,
|
||||
}: {
|
||||
bounds: Bounds
|
||||
isLocked: boolean
|
||||
}) {
|
||||
return (
|
||||
<StyledBounds
|
||||
width={bounds.width}
|
||||
height={bounds.height}
|
||||
pointerEvents="none"
|
||||
isLocked={isLocked}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledBounds = styled("rect", {
|
||||
fill: "none",
|
||||
stroke: "$bounds",
|
||||
const StyledBounds = styled('rect', {
|
||||
fill: 'none',
|
||||
stroke: '$bounds',
|
||||
zStrokeWidth: 2,
|
||||
|
||||
variants: {
|
||||
isLocked: {
|
||||
true: {
|
||||
zStrokeWidth: 1,
|
||||
zDash: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -43,6 +43,7 @@ export function ShapeOutline({ id }: { id: string }) {
|
|||
as="use"
|
||||
href={'#' + id}
|
||||
transform={transform}
|
||||
isLocked={shape.isLocked}
|
||||
{...events}
|
||||
/>
|
||||
)
|
||||
|
@ -55,4 +56,13 @@ const Indicator = styled('path', {
|
|||
stroke: '$selected',
|
||||
fill: 'transparent',
|
||||
pointerEvents: 'all',
|
||||
|
||||
variants: {
|
||||
isLocked: {
|
||||
true: {
|
||||
zDash: 2,
|
||||
},
|
||||
false: {},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,59 +1,57 @@
|
|||
import Command from "./command"
|
||||
import history from "../history"
|
||||
import { Data, Corner, Edge } from "types"
|
||||
import { TransformSnapshot } from "state/sessions/transform-session"
|
||||
import { getShapeUtils } from "lib/shape-utils"
|
||||
import { getPage } from "utils/utils"
|
||||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { Data } from 'types'
|
||||
import { TransformSnapshot } from 'state/sessions/transform-session'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getPage } from 'utils/utils'
|
||||
|
||||
export default function transformCommand(
|
||||
data: Data,
|
||||
before: TransformSnapshot,
|
||||
after: TransformSnapshot,
|
||||
scaleX: number,
|
||||
scaleY: number
|
||||
after: TransformSnapshot
|
||||
) {
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: "translate_shapes",
|
||||
category: "canvas",
|
||||
name: 'translate_shapes',
|
||||
category: 'canvas',
|
||||
do(data) {
|
||||
const { type, selectedIds } = after
|
||||
const { type, shapeBounds } = after
|
||||
|
||||
const { shapes } = getPage(data)
|
||||
|
||||
selectedIds.forEach((id) => {
|
||||
for (let id in shapeBounds) {
|
||||
const { initialShape, initialShapeBounds, transformOrigin } =
|
||||
after.shapeBounds[id]
|
||||
shapeBounds[id]
|
||||
const shape = shapes[id]
|
||||
|
||||
getShapeUtils(shape).transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
transformOrigin,
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
undo(data) {
|
||||
const { type, selectedIds } = before
|
||||
const { type, shapeBounds } = before
|
||||
|
||||
const { shapes } = getPage(data)
|
||||
|
||||
selectedIds.forEach((id) => {
|
||||
for (let id in shapeBounds) {
|
||||
const { initialShape, initialShapeBounds, transformOrigin } =
|
||||
before.shapeBounds[id]
|
||||
shapeBounds[id]
|
||||
const shape = shapes[id]
|
||||
|
||||
getShapeUtils(shape).transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
transformOrigin,
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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 { getShapeUtils } from "lib/shape-utils"
|
||||
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 { getShapeUtils } from 'lib/shape-utils'
|
||||
|
||||
export default function translateCommand(
|
||||
data: Data,
|
||||
|
@ -14,8 +14,8 @@ export default function translateCommand(
|
|||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: isCloning ? "clone_shapes" : "translate_shapes",
|
||||
category: "canvas",
|
||||
name: isCloning ? 'clone_shapes' : 'translate_shapes',
|
||||
category: 'canvas',
|
||||
manualSelection: true,
|
||||
do(data, initial) {
|
||||
if (initial) return
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import { Data, Edge, Corner } from "types"
|
||||
import * as vec from "utils/vec"
|
||||
import BaseSession from "./base-session"
|
||||
import commands from "state/commands"
|
||||
import { current } from "immer"
|
||||
import { getShapeUtils } from "lib/shape-utils"
|
||||
import { Data, Edge, Corner } from 'types'
|
||||
import * as vec from 'utils/vec'
|
||||
import BaseSession from './base-session'
|
||||
import commands from 'state/commands'
|
||||
import { current } from 'immer'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import {
|
||||
getBoundsCenter,
|
||||
getBoundsFromPoints,
|
||||
getCommonBounds,
|
||||
getPage,
|
||||
getRelativeTransformedBoundingBox,
|
||||
getSelectedShapes,
|
||||
getShapes,
|
||||
getTransformedBoundingBox,
|
||||
} from "utils/utils"
|
||||
} from 'utils/utils'
|
||||
|
||||
export default class TransformSession extends BaseSession {
|
||||
scaleX = 1
|
||||
|
@ -31,7 +32,7 @@ export default class TransformSession extends BaseSession {
|
|||
update(data: Data, point: number[], isAspectRatioLocked = false) {
|
||||
const { transformType } = this
|
||||
|
||||
const { selectedIds, shapeBounds, initialBounds } = this.snapshot
|
||||
const { shapeBounds, initialBounds } = this.snapshot
|
||||
|
||||
const { shapes } = getPage(data)
|
||||
|
||||
|
@ -48,7 +49,7 @@ export default class TransformSession extends BaseSession {
|
|||
|
||||
// Now work backward to calculate a new bounding box for each of the shapes.
|
||||
|
||||
selectedIds.forEach((id) => {
|
||||
for (let id in shapeBounds) {
|
||||
const { initialShape, initialShapeBounds, transformOrigin } =
|
||||
shapeBounds[id]
|
||||
|
||||
|
@ -69,15 +70,15 @@ export default class TransformSession extends BaseSession {
|
|||
scaleY: this.scaleY,
|
||||
transformOrigin,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cancel(data: Data) {
|
||||
const { currentPageId, selectedIds, shapeBounds } = this.snapshot
|
||||
const { currentPageId, shapeBounds } = this.snapshot
|
||||
|
||||
const page = getPage(data, currentPageId)
|
||||
|
||||
selectedIds.forEach((id) => {
|
||||
for (let id in shapeBounds) {
|
||||
const shape = page.shapes[id]
|
||||
|
||||
const { initialShape, initialShapeBounds, transformOrigin } =
|
||||
|
@ -90,35 +91,33 @@ export default class TransformSession extends BaseSession {
|
|||
scaleY: 1,
|
||||
transformOrigin,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
complete(data: Data) {
|
||||
commands.transform(
|
||||
data,
|
||||
this.snapshot,
|
||||
getTransformSnapshot(data, this.transformType),
|
||||
this.scaleX,
|
||||
this.scaleY
|
||||
getTransformSnapshot(data, this.transformType)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
|
||||
const {
|
||||
document: { pages },
|
||||
selectedIds,
|
||||
currentPageId,
|
||||
} = current(data)
|
||||
const cData = current(data)
|
||||
const { currentPageId } = cData
|
||||
|
||||
const pageShapes = pages[currentPageId].shapes
|
||||
const initialShapes = getSelectedShapes(cData).filter(
|
||||
(shape) => !shape.isLocked
|
||||
)
|
||||
const hasShapes = initialShapes.length > 0
|
||||
|
||||
// A mapping of selected shapes and their bounds
|
||||
const shapesBounds = Object.fromEntries(
|
||||
Array.from(selectedIds.values()).map((id) => {
|
||||
const shape = pageShapes[id]
|
||||
return [shape.id, getShapeUtils(shape).getBounds(shape)]
|
||||
})
|
||||
initialShapes.map((shape) => [
|
||||
shape.id,
|
||||
getShapeUtils(shape).getBounds(shape),
|
||||
])
|
||||
)
|
||||
|
||||
const boundsArr = Object.values(shapesBounds)
|
||||
|
@ -132,20 +131,19 @@ export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
|
|||
// positions of the shape's bounds within the common bounds shape.
|
||||
return {
|
||||
type: transformType,
|
||||
hasShapes,
|
||||
currentPageId,
|
||||
selectedIds: new Set(selectedIds),
|
||||
initialBounds: bounds,
|
||||
shapeBounds: Object.fromEntries(
|
||||
Array.from(selectedIds.values()).map((id) => {
|
||||
const shape = pageShapes[id]
|
||||
const initialShapeBounds = shapesBounds[id]
|
||||
initialShapes.map((shape) => {
|
||||
const initialShapeBounds = shapesBounds[shape.id]
|
||||
const ic = getBoundsCenter(initialShapeBounds)
|
||||
|
||||
let ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
|
||||
let iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
|
||||
|
||||
return [
|
||||
id,
|
||||
shape.id,
|
||||
{
|
||||
initialShape: shape,
|
||||
initialShapeBounds,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Data } from "types"
|
||||
import * as vec from "utils/vec"
|
||||
import BaseSession from "./base-session"
|
||||
import commands from "state/commands"
|
||||
import { current } from "immer"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { getChildIndexAbove, getPage, getSelectedShapes } from "utils/utils"
|
||||
import { getShapeUtils } from "lib/shape-utils"
|
||||
import { Data } from 'types'
|
||||
import * as vec from 'utils/vec'
|
||||
import BaseSession from './base-session'
|
||||
import commands from 'state/commands'
|
||||
import { current } from 'immer'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { getChildIndexAbove, getPage, getSelectedShapes } from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
|
||||
export default class TranslateSession extends BaseSession {
|
||||
delta = [0, 0]
|
||||
|
@ -89,6 +89,8 @@ export default class TranslateSession extends BaseSession {
|
|||
}
|
||||
|
||||
complete(data: Data) {
|
||||
if (!this.snapshot.hasShapes) return
|
||||
|
||||
commands.translate(
|
||||
data,
|
||||
this.snapshot,
|
||||
|
@ -100,7 +102,8 @@ export default class TranslateSession extends BaseSession {
|
|||
|
||||
export function getTranslateSnapshot(data: Data) {
|
||||
const cData = current(data)
|
||||
const shapes = getSelectedShapes(cData)
|
||||
const shapes = getSelectedShapes(cData).filter((shape) => !shape.isLocked)
|
||||
const hasShapes = shapes.length > 0
|
||||
|
||||
return {
|
||||
currentPageId: data.currentPageId,
|
||||
|
@ -110,6 +113,7 @@ export function getTranslateSnapshot(data: Data) {
|
|||
id: uuid(),
|
||||
childIndex: getChildIndexAbove(cData, shape.id),
|
||||
})),
|
||||
hasShapes,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -141,7 +141,6 @@ const state = createState({
|
|||
UNDO: 'undo',
|
||||
REDO: 'redo',
|
||||
SAVED_CODE: 'saveCode',
|
||||
CANCELLED: 'clearSelectedIds',
|
||||
DELETED: 'deleteSelectedIds',
|
||||
STARTED_PINCHING: { to: 'pinching' },
|
||||
INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
|
||||
|
@ -159,6 +158,7 @@ const state = createState({
|
|||
states: {
|
||||
notPointing: {
|
||||
on: {
|
||||
CANCELLED: 'clearSelectedIds',
|
||||
POINTED_CANVAS: { to: 'brushSelecting' },
|
||||
POINTED_BOUNDS: { to: 'pointingBounds' },
|
||||
POINTED_BOUNDS_HANDLE: {
|
||||
|
|
|
@ -43,6 +43,11 @@ const { styled, global, css, theme, getCssString } = createCss({
|
|||
transitions: {},
|
||||
},
|
||||
utils: {
|
||||
zDash: () => (value: number) => {
|
||||
return {
|
||||
strokeDasharray: `calc(${value}px / var(--camera-zoom)) calc(${value}px / var(--camera-zoom))`,
|
||||
}
|
||||
},
|
||||
zStrokeWidth: () => (value: number | number[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
// const [val, min, max] = value
|
||||
|
|
Loading…
Reference in a new issue