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

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