[fix] rotate center (#213)
* fixes rotate center after translating / transforming * Adds test, fixes issue on undo/redo * Update tsconfig.base.json
This commit is contained in:
parent
cb96740dbc
commit
12e425ddc4
12 changed files with 95 additions and 35 deletions
|
@ -66,7 +66,7 @@
|
||||||
"@radix-ui/react-tooltip": "^0.1.1",
|
"@radix-ui/react-tooltip": "^0.1.1",
|
||||||
"@stitches/core": "^1.2.5",
|
"@stitches/core": "^1.2.5",
|
||||||
"@stitches/react": "^1.0.0",
|
"@stitches/react": "^1.0.0",
|
||||||
"@tldraw/core": "^0.1.9",
|
"@tldraw/core": "^0.1.10",
|
||||||
"@tldraw/intersect": "^0.0.132",
|
"@tldraw/intersect": "^0.0.132",
|
||||||
"@tldraw/vec": "^0.0.132",
|
"@tldraw/vec": "^0.0.132",
|
||||||
"perfect-freehand": "^1.0.16",
|
"perfect-freehand": "^1.0.16",
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { Vec } from '@tldraw/vec'
|
import { Vec } from '@tldraw/vec'
|
||||||
import type { Data, TLDrawCommand, PagePartial } from '~types'
|
import { Data, TLDrawCommand, PagePartial, Session } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export function translate(data: Data, ids: string[], delta: number[]): TLDrawCommand {
|
export function translate(data: Data, ids: string[], delta: number[]): TLDrawCommand {
|
||||||
const { currentPageId } = data.appState
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
|
// Clear session cache
|
||||||
|
Session.cache.selectedIds = TLDR.getSelectedIds(data, data.appState.currentPageId)
|
||||||
|
|
||||||
const before: PagePartial = {
|
const before: PagePartial = {
|
||||||
shapes: {},
|
shapes: {},
|
||||||
bindings: {},
|
bindings: {},
|
||||||
|
|
|
@ -119,16 +119,64 @@ describe('Rotate session', () => {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
tlstate.startSession(SessionType.Rotate, [50, 0]).updateSession([100, 50])
|
tlstate.startSession(SessionType.Rotate, [50, 0]).updateSession([100, 50]).completeSession()
|
||||||
|
|
||||||
const centerAfter = Vec.round(
|
const centerAfterA = Vec.round(
|
||||||
|
Utils.getBoundsCenter(
|
||||||
|
Utils.getCommonBounds(tlstate.selectedIds.map((id) => tlstate.getShapeBounds(id)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tlstate.startSession(SessionType.Rotate, [100, 0]).updateSession([50, 0]).completeSession()
|
||||||
|
|
||||||
|
const centerAfterB = Vec.round(
|
||||||
Utils.getBoundsCenter(
|
Utils.getBoundsCenter(
|
||||||
Utils.getCommonBounds(tlstate.selectedIds.map((id) => tlstate.getShapeBounds(id)))
|
Utils.getCommonBounds(tlstate.selectedIds.map((id) => tlstate.getShapeBounds(id)))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(tlstate.getShape('rect1').rotation)
|
expect(tlstate.getShape('rect1').rotation)
|
||||||
expect(centerBefore).toStrictEqual(centerAfter)
|
expect(centerBefore).toStrictEqual(centerAfterA)
|
||||||
|
expect(centerAfterA).toStrictEqual(centerAfterB)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.todo('clears the cached center after transforming')
|
||||||
|
it.todo('clears the cached center after translating')
|
||||||
|
it.todo('clears the cached center after undoing')
|
||||||
|
it.todo('clears the cached center after redoing')
|
||||||
|
it.todo('clears the cached center after any command other than a rotate command, tbh')
|
||||||
|
|
||||||
|
it('changes the center after nudging', () => {
|
||||||
|
const tlstate = new TLDrawState().loadDocument(mockDocument).select('rect1', 'rect2')
|
||||||
|
|
||||||
|
const centerBefore = Vec.round(
|
||||||
|
Utils.getBoundsCenter(
|
||||||
|
Utils.getCommonBounds(tlstate.selectedIds.map((id) => tlstate.getShapeBounds(id)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tlstate.startSession(SessionType.Rotate, [50, 0]).updateSession([100, 50]).completeSession()
|
||||||
|
|
||||||
|
const centerAfterA = Vec.round(
|
||||||
|
Utils.getBoundsCenter(
|
||||||
|
Utils.getCommonBounds(tlstate.selectedIds.map((id) => tlstate.getShapeBounds(id)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(tlstate.getShape('rect1').rotation)
|
||||||
|
expect(centerBefore).toStrictEqual(centerAfterA)
|
||||||
|
|
||||||
|
tlstate.selectAll().nudge([10, 10])
|
||||||
|
|
||||||
|
tlstate.startSession(SessionType.Rotate, [50, 0]).updateSession([100, 50]).completeSession()
|
||||||
|
|
||||||
|
const centerAfterB = Vec.round(
|
||||||
|
Utils.getBoundsCenter(
|
||||||
|
Utils.getCommonBounds(tlstate.selectedIds.map((id) => tlstate.getShapeBounds(id)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(centerAfterB).not.toStrictEqual(centerAfterA)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,8 +4,6 @@ import { Session, SessionType, TLDrawShape, TLDrawStatus } from '~types'
|
||||||
import type { Data } from '~types'
|
import type { Data } from '~types'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
const centerCache = new WeakMap<string[], number[]>()
|
|
||||||
|
|
||||||
export class RotateSession extends Session {
|
export class RotateSession extends Session {
|
||||||
static type = SessionType.Rotate
|
static type = SessionType.Rotate
|
||||||
status = TLDrawStatus.Transforming
|
status = TLDrawStatus.Transforming
|
||||||
|
@ -17,6 +15,7 @@ export class RotateSession extends Session {
|
||||||
|
|
||||||
constructor(data: Data, viewport: TLBounds, point: number[]) {
|
constructor(data: Data, viewport: TLBounds, point: number[]) {
|
||||||
super(viewport)
|
super(viewport)
|
||||||
|
|
||||||
this.origin = point
|
this.origin = point
|
||||||
this.snapshot = getRotateSnapshot(data)
|
this.snapshot = getRotateSnapshot(data)
|
||||||
this.initialAngle = Vec.angle(this.snapshot.commonBoundsCenter, this.origin)
|
this.initialAngle = Vec.angle(this.snapshot.commonBoundsCenter, this.origin)
|
||||||
|
@ -134,19 +133,25 @@ export function getRotateSnapshot(data: Data) {
|
||||||
const pageState = TLDR.getPageState(data, currentPageId)
|
const pageState = TLDR.getPageState(data, currentPageId)
|
||||||
const initialShapes = TLDR.getSelectedBranchSnapshot(data, currentPageId)
|
const initialShapes = TLDR.getSelectedBranchSnapshot(data, currentPageId)
|
||||||
|
|
||||||
const commonBoundsCenter = Utils.getFromCache(centerCache, pageState.selectedIds, () => {
|
if (initialShapes.length === 0) {
|
||||||
if (initialShapes.length === 0) {
|
throw Error('No selected shapes!')
|
||||||
throw Error('No selected shapes!')
|
}
|
||||||
|
|
||||||
|
let commonBoundsCenter: number[]
|
||||||
|
|
||||||
|
if (Session.cache.selectedIds === pageState.selectedIds) {
|
||||||
|
if (Session.cache.center === undefined) {
|
||||||
|
throw Error('The center was not added to the cache!')
|
||||||
}
|
}
|
||||||
|
|
||||||
const shapesBounds = Object.fromEntries(
|
commonBoundsCenter = Session.cache.center
|
||||||
initialShapes.map((shape) => [shape.id, TLDR.getBounds(shape)])
|
} else {
|
||||||
|
commonBoundsCenter = Utils.getBoundsCenter(
|
||||||
|
Utils.getCommonBounds(initialShapes.map(TLDR.getBounds))
|
||||||
)
|
)
|
||||||
|
Session.cache.selectedIds = pageState.selectedIds
|
||||||
const bounds = Utils.getCommonBounds(Object.values(shapesBounds))
|
Session.cache.center = commonBoundsCenter
|
||||||
|
}
|
||||||
return Utils.getBoundsCenter(bounds)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commonBoundsCenter,
|
commonBoundsCenter,
|
||||||
|
|
|
@ -47,6 +47,7 @@ export class TransformSingleSession extends Session {
|
||||||
this.transformType = transformType
|
this.transformType = transformType
|
||||||
this.snapshot = getTransformSingleSnapshot(data, transformType)
|
this.snapshot = getTransformSingleSnapshot(data, transformType)
|
||||||
this.isCreate = isCreate
|
this.isCreate = isCreate
|
||||||
|
Session.cache.selectedIds = [...this.snapshot.initialShape.id]
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (data: Data) => {
|
start = (data: Data) => {
|
||||||
|
|
|
@ -43,6 +43,7 @@ export class TransformSession extends Session {
|
||||||
this.snapshot = getTransformSnapshot(data, transformType)
|
this.snapshot = getTransformSnapshot(data, transformType)
|
||||||
this.isCreate = isCreate
|
this.isCreate = isCreate
|
||||||
this.initialSelectedIds = TLDR.getSelectedIds(data, data.appState.currentPageId)
|
this.initialSelectedIds = TLDR.getSelectedIds(data, data.appState.currentPageId)
|
||||||
|
Session.cache.selectedIds = [...this.initialSelectedIds]
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (data: Data) => {
|
start = (data: Data) => {
|
||||||
|
@ -59,8 +60,6 @@ export class TransformSession extends Session {
|
||||||
|
|
||||||
const shapes = {} as Record<string, TLDrawShape>
|
const shapes = {} as Record<string, TLDrawShape>
|
||||||
|
|
||||||
const pageState = TLDR.getPageState(data, data.appState.currentPageId)
|
|
||||||
|
|
||||||
const delta = Vec.sub(point, this.origin)
|
const delta = Vec.sub(point, this.origin)
|
||||||
|
|
||||||
let newBounds = Utils.getTransformedBoundingBox(
|
let newBounds = Utils.getTransformedBoundingBox(
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
GroupShape,
|
GroupShape,
|
||||||
SessionType,
|
SessionType,
|
||||||
ArrowBinding,
|
ArrowBinding,
|
||||||
TLDrawShapeType,
|
|
||||||
} from '~types'
|
} from '~types'
|
||||||
import { SLOW_SPEED, SNAP_DISTANCE } from '~constants'
|
import { SLOW_SPEED, SNAP_DISTANCE } from '~constants'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
@ -70,6 +69,7 @@ export class TranslateSession extends Session {
|
||||||
this.snapshot = getTranslateSnapshot(data, isLinked)
|
this.snapshot = getTranslateSnapshot(data, isLinked)
|
||||||
this.isCreate = isCreate
|
this.isCreate = isCreate
|
||||||
this.isLinked = isLinked
|
this.isLinked = isLinked
|
||||||
|
Session.cache.selectedIds = [...TLDR.getSelectedIds(data, data.appState.currentPageId)]
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (data: Data) => {
|
start = (data: Data) => {
|
||||||
|
@ -94,14 +94,7 @@ export class TranslateSession extends Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update = (
|
update = (data: Data, point: number[], shiftKey = false, altKey = false, metaKey = false) => {
|
||||||
data: Data,
|
|
||||||
point: number[],
|
|
||||||
shiftKey = false,
|
|
||||||
altKey = false,
|
|
||||||
metaKey = false,
|
|
||||||
viewPort = {} as TLBounds
|
|
||||||
) => {
|
|
||||||
const { selectedIds, initialParentChildren, initialShapes, bindingsToDelete } = this.snapshot
|
const { selectedIds, initialParentChildren, initialShapes, bindingsToDelete } = this.snapshot
|
||||||
|
|
||||||
const { currentPageId } = data.appState
|
const { currentPageId } = data.appState
|
||||||
|
|
|
@ -347,6 +347,10 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
this.clearSelectHistory()
|
this.clearSelectHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id.startsWith('undo') || id.startsWith('redo')) {
|
||||||
|
Session.cache.selectedIds = [...this.selectedIds]
|
||||||
|
}
|
||||||
|
|
||||||
this._onChange?.(this, state, id)
|
this._onChange?.(this, state, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1670,6 +1674,7 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Session = getSession(type)
|
const Session = getSession(type)
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.session = new Session(this.state, this.viewport, ...args)
|
this.session = new Session(this.state, this.viewport, ...args)
|
||||||
|
|
|
@ -155,6 +155,14 @@ export abstract class Session {
|
||||||
updateViewport = (viewport: TLBounds) => {
|
updateViewport = (viewport: TLBounds) => {
|
||||||
this.viewport = viewport
|
this.viewport = viewport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static cache: {
|
||||||
|
selectedIds: string[]
|
||||||
|
center: number[]
|
||||||
|
} = {
|
||||||
|
selectedIds: [],
|
||||||
|
center: [0, 0],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TLDrawStatus {
|
export enum TLDrawStatus {
|
||||||
|
|
|
@ -11,10 +11,7 @@ async function main() {
|
||||||
bundle: true,
|
bundle: true,
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
target: 'es6',
|
target: 'es6',
|
||||||
jsxFactory: 'React.createElement',
|
|
||||||
jsxFragment: 'React.Fragment',
|
|
||||||
tsconfig: './tsconfig.json',
|
tsconfig: './tsconfig.json',
|
||||||
external: ['react', 'react-dom'],
|
|
||||||
incremental: true,
|
incremental: true,
|
||||||
watch: {
|
watch: {
|
||||||
onRebuild(error) {
|
onRebuild(error) {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"importsNotUsedAsValues": "error",
|
"importsNotUsedAsValues": "error",
|
||||||
|
"resolveJsonModule": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"lib": ["dom", "esnext"],
|
"lib": ["dom", "esnext"],
|
||||||
|
|
|
@ -3722,10 +3722,10 @@
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
"@testing-library/dom" "^8.0.0"
|
"@testing-library/dom" "^8.0.0"
|
||||||
|
|
||||||
"@tldraw/core@^0.1.9":
|
"@tldraw/core@^0.1.10":
|
||||||
version "0.1.9"
|
version "0.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/@tldraw/core/-/core-0.1.9.tgz#006748ee0c395ef15756ee1aa3c93b7eef109432"
|
resolved "https://registry.yarnpkg.com/@tldraw/core/-/core-0.1.10.tgz#fe6aa880b619361473ebccb5c932082efb535518"
|
||||||
integrity sha512-3vzFceettMZVkVM+ASaAFyFy2YibwnEkIcIiWTmKER+5R0HIEKnwDuH+zXiv+C1dcbagUvArHRw0AiuzA5ft8g==
|
integrity sha512-kLqCDQHRykWfskMvcbSn3Ll8Ssd46hMmMdTpqw1O3i3AV2zuWDYkdgGD3PO6PRSkkMZyGNr5/zpkU+Yrk5DYZQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@use-gesture/react" "^10.0.2"
|
"@use-gesture/react" "^10.0.2"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue