Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

transform-session.ts 5.6KB

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