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.

group.ts 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import Command from './command'
  2. import history from '../history'
  3. import { Data, GroupShape, Shape, ShapeType } from 'types'
  4. import {
  5. getCommonBounds,
  6. getPage,
  7. getSelectedIds,
  8. getSelectedShapes,
  9. getShape,
  10. setSelectedIds,
  11. } from 'utils/utils'
  12. import { current } from 'immer'
  13. import { createShape, getShapeUtils } from 'lib/shape-utils'
  14. import commands from '.'
  15. export default function groupCommand(data: Data) {
  16. const cData = current(data)
  17. const { currentPageId } = cData
  18. const oldSelectedIds = getSelectedIds(cData)
  19. const initialShapes = getSelectedShapes(cData).sort(
  20. (a, b) => a.childIndex - b.childIndex
  21. )
  22. const isAllSameParent = initialShapes.every(
  23. (shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId
  24. )
  25. let newGroupParentId: string
  26. let newGroupShape: GroupShape
  27. let newGroupChildIndex: number
  28. const initialShapeIds = initialShapes.map((s) => s.id)
  29. const parentIds = Array.from(
  30. new Set(initialShapes.map((s) => s.parentId)).values()
  31. )
  32. const commonBounds = getCommonBounds(
  33. ...initialShapes.map((shape) =>
  34. getShapeUtils(shape).getRotatedBounds(shape)
  35. )
  36. )
  37. if (isAllSameParent) {
  38. const parentId = initialShapes[0].parentId
  39. if (parentId === currentPageId) {
  40. newGroupParentId = currentPageId
  41. } else {
  42. // Are all of the parent's children selected?
  43. const parent = getShape(data, parentId) as GroupShape
  44. if (parent.children.length === initialShapes.length) {
  45. // !!! Hey! We're not going any further. We need to ungroup those shapes.
  46. commands.ungroup(data)
  47. return
  48. } else {
  49. // Make the group inside of the current group
  50. newGroupParentId = parentId
  51. }
  52. }
  53. } else {
  54. // Find the least-deep parent among the shapes and add the group as a child
  55. let minDepth = Infinity
  56. for (let parentId of initialShapes.map((shape) => shape.parentId)) {
  57. const depth = getShapeDepth(data, parentId)
  58. if (depth < minDepth) {
  59. minDepth = depth
  60. newGroupParentId = parentId
  61. }
  62. }
  63. }
  64. newGroupShape = createShape(ShapeType.Group, {
  65. parentId: newGroupParentId,
  66. point: [commonBounds.minX, commonBounds.minY],
  67. size: [commonBounds.width, commonBounds.height],
  68. children: initialShapeIds,
  69. childIndex: initialShapes[0].childIndex,
  70. })
  71. history.execute(
  72. data,
  73. new Command({
  74. name: 'group_shapes',
  75. category: 'canvas',
  76. manualSelection: true,
  77. do(data) {
  78. const { shapes } = getPage(data, currentPageId)
  79. // Create the new group
  80. shapes[newGroupShape.id] = newGroupShape
  81. // Assign the group to its new parent
  82. if (newGroupParentId !== data.currentPageId) {
  83. const parent = shapes[newGroupParentId]
  84. getShapeUtils(parent).setProperty(parent, 'children', [
  85. ...parent.children,
  86. newGroupShape.id,
  87. ])
  88. }
  89. // Assign the shapes to their new parent
  90. initialShapes.forEach((initialShape, i) => {
  91. // Remove shape from its old parent
  92. if (initialShape.parentId !== currentPageId) {
  93. const oldParent = shapes[initialShape.parentId] as GroupShape
  94. getShapeUtils(oldParent).setProperty(
  95. oldParent,
  96. 'children',
  97. oldParent.children.filter((id) => !oldSelectedIds.has(id))
  98. )
  99. }
  100. // Assign the shape to its new parent, with its new childIndex
  101. const shape = shapes[initialShape.id]
  102. getShapeUtils(shape)
  103. .setProperty(shape, 'childIndex', i)
  104. .setProperty(shape, 'parentId', newGroupShape.id)
  105. })
  106. setSelectedIds(data, [newGroupShape.id])
  107. },
  108. undo(data) {
  109. const { shapes } = getPage(data, currentPageId)
  110. const group = shapes[newGroupShape.id]
  111. // remove the group from its parent
  112. if (group.parentId !== data.currentPageId) {
  113. const parent = shapes[group.parentId]
  114. getShapeUtils(parent).setProperty(
  115. parent,
  116. 'children',
  117. parent.children.filter((id) => id !== newGroupShape.id)
  118. )
  119. }
  120. // Move the shapes back to their previous parent / childIndex
  121. initialShapes.forEach(({ id, parentId, childIndex }) => {
  122. const shape = shapes[id]
  123. getShapeUtils(shape)
  124. .setProperty(shape, 'parentId', parentId)
  125. .setProperty(shape, 'childIndex', childIndex)
  126. if (parentId !== data.currentPageId) {
  127. const parent = shapes[parentId]
  128. getShapeUtils(parent).setProperty(parent, 'children', [
  129. ...parent.children,
  130. id,
  131. ])
  132. }
  133. })
  134. // Delete the group
  135. delete shapes[newGroupShape.id]
  136. // Reselect the children of the group
  137. setSelectedIds(data, initialShapeIds)
  138. },
  139. })
  140. )
  141. }
  142. function getShapeDepth(data: Data, id: string, depth = 0) {
  143. if (id === data.currentPageId) {
  144. return depth
  145. }
  146. return getShapeDepth(data, getShape(data, id).parentId, depth + 1)
  147. }