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 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { Data, TransformEdge, TransformCorner } from "types"
  2. import * as vec from "utils/vec"
  3. import BaseSession from "./base-session"
  4. import commands from "state/commands"
  5. import { current } from "immer"
  6. import { getShapeUtils } from "lib/shape-utils"
  7. import {
  8. getCommonBounds,
  9. getRelativeTransformedBoundingBox,
  10. getTransformedBoundingBox,
  11. } from "utils/utils"
  12. export default class TransformSession extends BaseSession {
  13. scaleX = 1
  14. scaleY = 1
  15. transformType: TransformEdge | TransformCorner
  16. origin: number[]
  17. snapshot: TransformSnapshot
  18. constructor(
  19. data: Data,
  20. transformType: TransformCorner | TransformEdge,
  21. point: number[]
  22. ) {
  23. super(data)
  24. this.origin = point
  25. this.transformType = transformType
  26. this.snapshot = getTransformSnapshot(data, transformType)
  27. }
  28. update(data: Data, point: number[], isAspectRatioLocked = false) {
  29. const { transformType } = this
  30. const { currentPageId, selectedIds, shapeBounds, initialBounds } =
  31. this.snapshot
  32. const newBoundingBox = getTransformedBoundingBox(
  33. initialBounds,
  34. transformType,
  35. vec.vec(this.origin, point),
  36. data.boundsRotation,
  37. isAspectRatioLocked
  38. )
  39. this.scaleX = newBoundingBox.scaleX
  40. this.scaleY = newBoundingBox.scaleY
  41. // Now work backward to calculate a new bounding box for each of the shapes.
  42. selectedIds.forEach((id) => {
  43. const { initialShape, initialShapeBounds } = shapeBounds[id]
  44. const newShapeBounds = getRelativeTransformedBoundingBox(
  45. newBoundingBox,
  46. initialBounds,
  47. initialShapeBounds,
  48. this.scaleX < 0,
  49. this.scaleY < 0
  50. )
  51. const shape = data.document.pages[currentPageId].shapes[id]
  52. getShapeUtils(shape).transform(shape, newShapeBounds, {
  53. type: this.transformType,
  54. initialShape,
  55. scaleX: this.scaleX,
  56. scaleY: this.scaleY,
  57. })
  58. })
  59. }
  60. cancel(data: Data) {
  61. const { currentPageId, selectedIds, shapeBounds } = this.snapshot
  62. selectedIds.forEach((id) => {
  63. const shape = data.document.pages[currentPageId].shapes[id]
  64. const { initialShape, initialShapeBounds } = shapeBounds[id]
  65. getShapeUtils(shape).transform(shape, initialShapeBounds, {
  66. type: this.transformType,
  67. initialShape,
  68. scaleX: 1,
  69. scaleY: 1,
  70. })
  71. })
  72. }
  73. complete(data: Data) {
  74. commands.transform(
  75. data,
  76. this.snapshot,
  77. getTransformSnapshot(data, this.transformType),
  78. this.scaleX,
  79. this.scaleY
  80. )
  81. }
  82. }
  83. export function getTransformSnapshot(
  84. data: Data,
  85. transformType: TransformEdge | TransformCorner
  86. ) {
  87. const {
  88. document: { pages },
  89. selectedIds,
  90. currentPageId,
  91. } = current(data)
  92. const pageShapes = pages[currentPageId].shapes
  93. // A mapping of selected shapes and their bounds
  94. const shapesBounds = Object.fromEntries(
  95. Array.from(selectedIds.values()).map((id) => {
  96. const shape = pageShapes[id]
  97. return [shape.id, getShapeUtils(shape).getBounds(shape)]
  98. })
  99. )
  100. // The common (exterior) bounds of the selected shapes
  101. const bounds = getCommonBounds(...Object.values(shapesBounds))
  102. // Return a mapping of shapes to bounds together with the relative
  103. // positions of the shape's bounds within the common bounds shape.
  104. return {
  105. type: transformType,
  106. currentPageId,
  107. selectedIds: new Set(selectedIds),
  108. initialBounds: bounds,
  109. shapeBounds: Object.fromEntries(
  110. Array.from(selectedIds.values()).map((id) => {
  111. return [
  112. id,
  113. {
  114. initialShape: pageShapes[id],
  115. initialShapeBounds: shapesBounds[id],
  116. },
  117. ]
  118. })
  119. ),
  120. }
  121. }
  122. export type TransformSnapshot = ReturnType<typeof getTransformSnapshot>