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

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