149 lines
3.7 KiB
TypeScript
149 lines
3.7 KiB
TypeScript
import { Data, ShapeType } from 'types'
|
|
import vec from 'utils/vec'
|
|
import BaseSession from './base-session'
|
|
import commands from 'state/commands'
|
|
import { current } from 'immer'
|
|
import {
|
|
clampToRotationToSegments,
|
|
getBoundsCenter,
|
|
getCommonBounds,
|
|
getPage,
|
|
getRotatedBounds,
|
|
getShapeBounds,
|
|
updateParents,
|
|
getDocumentBranch,
|
|
setToArray,
|
|
getSelectedIds,
|
|
} from 'utils'
|
|
import { getShapeUtils } from 'state/shape-utils'
|
|
|
|
const PI2 = Math.PI * 2
|
|
|
|
export default class RotateSession extends BaseSession {
|
|
delta = [0, 0]
|
|
origin: number[]
|
|
snapshot: RotateSnapshot
|
|
prev = 0
|
|
|
|
constructor(data: Data, point: number[]) {
|
|
super(data)
|
|
this.origin = point
|
|
this.snapshot = getRotateSnapshot(data)
|
|
}
|
|
|
|
update(data: Data, point: number[], isLocked: boolean): void {
|
|
const { commonBoundsCenter, initialShapes } = this.snapshot
|
|
|
|
const page = getPage(data)
|
|
const a1 = vec.angle(commonBoundsCenter, this.origin)
|
|
const a2 = vec.angle(commonBoundsCenter, point)
|
|
|
|
let rot = a2 - a1
|
|
|
|
const delta = rot - this.prev
|
|
this.prev = rot
|
|
|
|
if (isLocked) {
|
|
rot = clampToRotationToSegments(rot, 24)
|
|
}
|
|
|
|
data.boundsRotation = (PI2 + (this.snapshot.boundsRotation + rot)) % PI2
|
|
|
|
for (const { id, center, offset, rotation } of initialShapes) {
|
|
const shape = page.shapes[id]
|
|
|
|
const nextRotation =
|
|
PI2 +
|
|
((isLocked
|
|
? clampToRotationToSegments(rotation + rot, 24)
|
|
: rotation + rot) %
|
|
PI2)
|
|
|
|
const nextPoint = vec.sub(
|
|
vec.rotWith(center, commonBoundsCenter, rot),
|
|
offset
|
|
)
|
|
|
|
getShapeUtils(shape)
|
|
.rotateTo(shape, nextRotation, delta)
|
|
.translateTo(shape, nextPoint)
|
|
}
|
|
|
|
updateParents(
|
|
data,
|
|
initialShapes.map((s) => s.id)
|
|
)
|
|
}
|
|
|
|
cancel(data: Data): void {
|
|
const { currentPageId, initialShapes } = this.snapshot
|
|
const page = getPage(data, currentPageId)
|
|
|
|
for (const { id, point, rotation } of initialShapes) {
|
|
const shape = page.shapes[id]
|
|
getShapeUtils(shape)
|
|
.rotateTo(shape, rotation, rotation - shape.rotation)
|
|
.translateTo(shape, point)
|
|
}
|
|
|
|
updateParents(
|
|
data,
|
|
initialShapes.map((s) => s.id)
|
|
)
|
|
}
|
|
|
|
complete(data: Data): void {
|
|
if (!this.snapshot.hasUnlockedShapes) return
|
|
commands.rotate(data, this.snapshot, getRotateSnapshot(data))
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
export function getRotateSnapshot(data: Data) {
|
|
const cData = current(data)
|
|
const page = getPage(cData)
|
|
|
|
const initialShapes = setToArray(getSelectedIds(data))
|
|
.flatMap((id) => getDocumentBranch(cData, id).map((id) => page.shapes[id]))
|
|
.filter((shape) => !shape.isLocked)
|
|
|
|
const hasUnlockedShapes = initialShapes.length > 0
|
|
|
|
const shapesBounds = Object.fromEntries(
|
|
initialShapes.map((shape) => [shape.id, getShapeBounds(shape)])
|
|
)
|
|
|
|
const bounds = getCommonBounds(...Object.values(shapesBounds))
|
|
|
|
const commonBoundsCenter = getBoundsCenter(bounds)
|
|
|
|
return {
|
|
hasUnlockedShapes,
|
|
currentPageId: data.currentPageId,
|
|
boundsRotation: data.boundsRotation,
|
|
commonBoundsCenter,
|
|
initialShapes: initialShapes
|
|
.filter((shape) => shape.type !== ShapeType.Group)
|
|
.map((shape) => {
|
|
const bounds = shapesBounds[shape.id]
|
|
const center = getBoundsCenter(bounds)
|
|
const offset = vec.sub(center, shape.point)
|
|
|
|
const rotationOffset = vec.sub(
|
|
center,
|
|
getBoundsCenter(getRotatedBounds(shape))
|
|
)
|
|
|
|
return {
|
|
id: shape.id,
|
|
point: shape.point,
|
|
rotation: shape.rotation,
|
|
offset,
|
|
rotationOffset,
|
|
center,
|
|
}
|
|
}),
|
|
}
|
|
}
|
|
|
|
export type RotateSnapshot = ReturnType<typeof getRotateSnapshot>
|