[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:
Steve Ruiz 2021-10-30 10:04:33 +01:00 committed by GitHub
parent cb96740dbc
commit 12e425ddc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 95 additions and 35 deletions

View file

@ -66,7 +66,7 @@
"@radix-ui/react-tooltip": "^0.1.1",
"@stitches/core": "^1.2.5",
"@stitches/react": "^1.0.0",
"@tldraw/core": "^0.1.9",
"@tldraw/core": "^0.1.10",
"@tldraw/intersect": "^0.0.132",
"@tldraw/vec": "^0.0.132",
"perfect-freehand": "^1.0.16",

View file

@ -1,10 +1,13 @@
import { Vec } from '@tldraw/vec'
import type { Data, TLDrawCommand, PagePartial } from '~types'
import { Data, TLDrawCommand, PagePartial, Session } from '~types'
import { TLDR } from '~state/tldr'
export function translate(data: Data, ids: string[], delta: number[]): TLDrawCommand {
const { currentPageId } = data.appState
// Clear session cache
Session.cache.selectedIds = TLDR.getSelectedIds(data, data.appState.currentPageId)
const before: PagePartial = {
shapes: {},
bindings: {},

View file

@ -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.getCommonBounds(tlstate.selectedIds.map((id) => tlstate.getShapeBounds(id)))
)
)
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)
})
})
})

View file

@ -4,8 +4,6 @@ import { Session, SessionType, TLDrawShape, TLDrawStatus } from '~types'
import type { Data } from '~types'
import { TLDR } from '~state/tldr'
const centerCache = new WeakMap<string[], number[]>()
export class RotateSession extends Session {
static type = SessionType.Rotate
status = TLDrawStatus.Transforming
@ -17,6 +15,7 @@ export class RotateSession extends Session {
constructor(data: Data, viewport: TLBounds, point: number[]) {
super(viewport)
this.origin = point
this.snapshot = getRotateSnapshot(data)
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 initialShapes = TLDR.getSelectedBranchSnapshot(data, currentPageId)
const commonBoundsCenter = Utils.getFromCache(centerCache, pageState.selectedIds, () => {
if (initialShapes.length === 0) {
throw Error('No selected shapes!')
if (initialShapes.length === 0) {
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(
initialShapes.map((shape) => [shape.id, TLDR.getBounds(shape)])
commonBoundsCenter = Session.cache.center
} else {
commonBoundsCenter = Utils.getBoundsCenter(
Utils.getCommonBounds(initialShapes.map(TLDR.getBounds))
)
const bounds = Utils.getCommonBounds(Object.values(shapesBounds))
return Utils.getBoundsCenter(bounds)
})
Session.cache.selectedIds = pageState.selectedIds
Session.cache.center = commonBoundsCenter
}
return {
commonBoundsCenter,

View file

@ -47,6 +47,7 @@ export class TransformSingleSession extends Session {
this.transformType = transformType
this.snapshot = getTransformSingleSnapshot(data, transformType)
this.isCreate = isCreate
Session.cache.selectedIds = [...this.snapshot.initialShape.id]
}
start = (data: Data) => {

View file

@ -43,6 +43,7 @@ export class TransformSession extends Session {
this.snapshot = getTransformSnapshot(data, transformType)
this.isCreate = isCreate
this.initialSelectedIds = TLDR.getSelectedIds(data, data.appState.currentPageId)
Session.cache.selectedIds = [...this.initialSelectedIds]
}
start = (data: Data) => {
@ -59,8 +60,6 @@ export class TransformSession extends Session {
const shapes = {} as Record<string, TLDrawShape>
const pageState = TLDR.getPageState(data, data.appState.currentPageId)
const delta = Vec.sub(point, this.origin)
let newBounds = Utils.getTransformedBoundingBox(

View file

@ -12,7 +12,6 @@ import {
GroupShape,
SessionType,
ArrowBinding,
TLDrawShapeType,
} from '~types'
import { SLOW_SPEED, SNAP_DISTANCE } from '~constants'
import { TLDR } from '~state/tldr'
@ -70,6 +69,7 @@ export class TranslateSession extends Session {
this.snapshot = getTranslateSnapshot(data, isLinked)
this.isCreate = isCreate
this.isLinked = isLinked
Session.cache.selectedIds = [...TLDR.getSelectedIds(data, data.appState.currentPageId)]
}
start = (data: Data) => {
@ -94,14 +94,7 @@ export class TranslateSession extends Session {
}
}
update = (
data: Data,
point: number[],
shiftKey = false,
altKey = false,
metaKey = false,
viewPort = {} as TLBounds
) => {
update = (data: Data, point: number[], shiftKey = false, altKey = false, metaKey = false) => {
const { selectedIds, initialParentChildren, initialShapes, bindingsToDelete } = this.snapshot
const { currentPageId } = data.appState

View file

@ -347,6 +347,10 @@ export class TLDrawState extends StateManager<Data> {
this.clearSelectHistory()
}
if (id.startsWith('undo') || id.startsWith('redo')) {
Session.cache.selectedIds = [...this.selectedIds]
}
this._onChange?.(this, state, id)
}
@ -1670,6 +1674,7 @@ export class TLDrawState extends StateManager<Data> {
}
const Session = getSession(type)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.session = new Session(this.state, this.viewport, ...args)

View file

@ -155,6 +155,14 @@ export abstract class Session {
updateViewport = (viewport: TLBounds) => {
this.viewport = viewport
}
static cache: {
selectedIds: string[]
center: number[]
} = {
selectedIds: [],
center: [0, 0],
}
}
export enum TLDrawStatus {

View file

@ -11,10 +11,7 @@ async function main() {
bundle: true,
format: 'cjs',
target: 'es6',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.json',
external: ['react', 'react-dom'],
incremental: true,
watch: {
onRebuild(error) {

View file

@ -11,6 +11,7 @@
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"importsNotUsedAsValues": "error",
"resolveJsonModule": true,
"incremental": true,
"jsx": "preserve",
"lib": ["dom", "esnext"],

View file

@ -3722,10 +3722,10 @@
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.0.0"
"@tldraw/core@^0.1.9":
version "0.1.9"
resolved "https://registry.yarnpkg.com/@tldraw/core/-/core-0.1.9.tgz#006748ee0c395ef15756ee1aa3c93b7eef109432"
integrity sha512-3vzFceettMZVkVM+ASaAFyFy2YibwnEkIcIiWTmKER+5R0HIEKnwDuH+zXiv+C1dcbagUvArHRw0AiuzA5ft8g==
"@tldraw/core@^0.1.10":
version "0.1.10"
resolved "https://registry.yarnpkg.com/@tldraw/core/-/core-0.1.10.tgz#fe6aa880b619361473ebccb5c932082efb535518"
integrity sha512-kLqCDQHRykWfskMvcbSn3Ll8Ssd46hMmMdTpqw1O3i3AV2zuWDYkdgGD3PO6PRSkkMZyGNr5/zpkU+Yrk5DYZQ==
dependencies:
"@use-gesture/react" "^10.0.2"