You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

transform-session.ts 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import { Data, Edge, Corner } from 'types'
  2. import vec from 'utils/vec'
  3. import BaseSession from './base-session'
  4. import commands from 'state/commands'
  5. import { getShapeUtils } from 'state/shape-utils'
  6. import tld from 'utils/tld'
  7. import {
  8. deepClone,
  9. getBoundsCenter,
  10. getBoundsFromPoints,
  11. getCommonBounds,
  12. getRelativeTransformedBoundingBox,
  13. getTransformedBoundingBox,
  14. } from 'utils'
  15. export default class TransformSession extends BaseSession {
  16. scaleX = 1
  17. scaleY = 1
  18. transformType: Edge | Corner
  19. origin: number[]
  20. snapshot: TransformSnapshot
  21. constructor(data: Data, transformType: Corner | Edge, point: number[]) {
  22. super(data)
  23. this.origin = point
  24. this.transformType = transformType
  25. this.snapshot = getTransformSnapshot(data, transformType)
  26. }
  27. update(data: Data, point: number[], isAspectRatioLocked = false): void {
  28. const { transformType } = this
  29. const { shapeBounds, initialBounds, isAllAspectRatioLocked } = this.snapshot
  30. const { shapes } = tld.getPage(data)
  31. const newBoundingBox = getTransformedBoundingBox(
  32. initialBounds,
  33. transformType,
  34. vec.vec(this.origin, point),
  35. data.boundsRotation,
  36. isAspectRatioLocked || isAllAspectRatioLocked
  37. )
  38. this.scaleX = newBoundingBox.scaleX
  39. this.scaleY = newBoundingBox.scaleY
  40. // Now work backward to calculate a new bounding box for each of the shapes.
  41. for (const id in shapeBounds) {
  42. const { initialShape, initialShapeBounds, transformOrigin } =
  43. shapeBounds[id]
  44. const newShapeBounds = getRelativeTransformedBoundingBox(
  45. newBoundingBox,
  46. initialBounds,
  47. initialShapeBounds,
  48. this.scaleX < 0,
  49. this.scaleY < 0
  50. )
  51. const shape = shapes[id]
  52. getShapeUtils(shape).transform(shape, newShapeBounds, {
  53. type: this.transformType,
  54. initialShape,
  55. scaleX: this.scaleX,
  56. scaleY: this.scaleY,
  57. transformOrigin,
  58. })
  59. shapes[id] = deepClone(shape)
  60. }
  61. tld.updateParents(data, Object.keys(shapeBounds))
  62. }
  63. cancel(data: Data): void {
  64. const { shapeBounds } = this.snapshot
  65. const { shapes } = tld.getPage(data)
  66. for (const id in shapeBounds) {
  67. const shape = shapes[id]
  68. const { initialShape, initialShapeBounds, transformOrigin } =
  69. shapeBounds[id]
  70. getShapeUtils(shape).transform(shape, initialShapeBounds, {
  71. type: this.transformType,
  72. initialShape,
  73. scaleX: 1,
  74. scaleY: 1,
  75. transformOrigin,
  76. })
  77. tld.updateParents(data, Object.keys(shapeBounds))
  78. }
  79. }
  80. complete(data: Data): void {
  81. const { initialShapes, hasUnlockedShapes } = this.snapshot
  82. if (!hasUnlockedShapes) return
  83. const page = tld.getPage(data)
  84. const finalShapes = initialShapes.map((shape) =>
  85. deepClone(page.shapes[shape.id])
  86. )
  87. commands.mutate(data, initialShapes, finalShapes)
  88. }
  89. }
  90. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  91. export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
  92. const { currentPageId } = data
  93. const initialShapes = tld.getSelectedBranchSnapshot(data)
  94. const hasUnlockedShapes = initialShapes.length > 0
  95. const isAllAspectRatioLocked = initialShapes.every(
  96. (shape) =>
  97. shape.isAspectRatioLocked || !getShapeUtils(shape).canChangeAspectRatio
  98. )
  99. const shapesBounds = Object.fromEntries(
  100. initialShapes.map((shape) => [
  101. shape.id,
  102. getShapeUtils(shape).getBounds(shape),
  103. ])
  104. )
  105. const boundsArr = Object.values(shapesBounds)
  106. const commonBounds = getCommonBounds(...boundsArr)
  107. const initialInnerBounds = getBoundsFromPoints(boundsArr.map(getBoundsCenter))
  108. // Return a mapping of shapes to bounds together with the relative
  109. // positions of the shape's bounds within the common bounds shape.
  110. return {
  111. type: transformType,
  112. hasUnlockedShapes,
  113. isAllAspectRatioLocked,
  114. currentPageId,
  115. initialShapes,
  116. initialBounds: commonBounds,
  117. shapeBounds: Object.fromEntries(
  118. initialShapes.map((shape) => {
  119. const initialShapeBounds = shapesBounds[shape.id]
  120. const ic = getBoundsCenter(initialShapeBounds)
  121. const ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
  122. const iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
  123. return [
  124. shape.id,
  125. {
  126. initialShape: shape,
  127. initialShapeBounds,
  128. transformOrigin: [ix, iy],
  129. },
  130. ]
  131. })
  132. ),
  133. }
  134. }
  135. export type TransformSnapshot = ReturnType<typeof getTransformSnapshot>
  136. // const transformOrigins = {
  137. // [Edge.Top]: [0.5, 1],
  138. // [Edge.Right]: [0, 0.5],
  139. // [Edge.Bottom]: [0.5, 0],
  140. // [Edge.Left]: [1, 0.5],
  141. // [Corner.TopLeft]: [1, 1],
  142. // [Corner.TopRight]: [0, 1],
  143. // [Corner.BottomLeft]: [1, 0],
  144. // [Corner.BottomRight]: [0, 0],
  145. // }
  146. // const origin = transformOrigins[this.transformType]
  147. // function resizeDescendants(data: Data, shapeId: string, bounds: Bounds) {
  148. // const { initialShape, initialShapeBounds, transformOrigin } =
  149. // shapeBounds[id]
  150. // const newShapeBounds = getRelativeTransformedBoundingBox(
  151. // newBoundingBox,
  152. // initialBounds,
  153. // initialShapeBounds,
  154. // this.scaleX < 0,
  155. // this.scaleY < 0
  156. // )
  157. // const shape = shapes[id]
  158. // getShapeUtils(shape).transform(shape, newShapeBounds, {
  159. // type: this.transformType,
  160. // initialShape,
  161. // scaleX: this.scaleX,
  162. // scaleY: this.scaleY,
  163. // transformOrigin,
  164. // })
  165. // }