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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. getSelectedShapes,
  8. getShape,
  9. } from 'utils/utils'
  10. import { current } from 'immer'
  11. import { createShape, getShapeUtils } from 'lib/shape-utils'
  12. import { PropsOfType } from 'types'
  13. import { v4 as uuid } from 'uuid'
  14. import commands from '.'
  15. export default function groupCommand(data: Data) {
  16. const cData = current(data)
  17. const { currentPageId, selectedIds } = cData
  18. const initialShapes = getSelectedShapes(cData).sort(
  19. (a, b) => a.childIndex - b.childIndex
  20. )
  21. const isAllSameParent = initialShapes.every(
  22. (shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId
  23. )
  24. let newGroupParentId: string
  25. let newGroupShape: GroupShape
  26. let oldGroupShape: GroupShape
  27. const selectedShapeIds = initialShapes.map((s) => s.id)
  28. const parentIds = Array.from(
  29. new Set(initialShapes.map((s) => s.parentId)).values()
  30. )
  31. const commonBounds = getCommonBounds(
  32. ...initialShapes.map((shape) =>
  33. getShapeUtils(shape).getRotatedBounds(shape)
  34. )
  35. )
  36. if (isAllSameParent) {
  37. const parentId = initialShapes[0].parentId
  38. if (parentId === currentPageId) {
  39. newGroupParentId = currentPageId
  40. } else {
  41. // Are all of the parent's children selected?
  42. const parent = getShape(data, parentId) as GroupShape
  43. if (parent.children.length === initialShapes.length) {
  44. // !
  45. // !
  46. // !
  47. // Hey! We're not going any further. We need to ungroup those shapes.
  48. commands.ungroup(data)
  49. return
  50. } else {
  51. newGroupParentId = parentId
  52. }
  53. }
  54. } else {
  55. // Find the least-deep parent among the shapes and add the group as a child
  56. let minDepth = Infinity
  57. for (let parentId of initialShapes.map((shape) => shape.parentId)) {
  58. const depth = getShapeDepth(data, parentId)
  59. if (depth < minDepth) {
  60. minDepth = depth
  61. newGroupParentId = parentId
  62. }
  63. }
  64. }
  65. newGroupShape = createShape(ShapeType.Group, {
  66. parentId: newGroupParentId,
  67. point: [commonBounds.minX, commonBounds.minY],
  68. size: [commonBounds.width, commonBounds.height],
  69. children: selectedShapeIds,
  70. })
  71. history.execute(
  72. data,
  73. new Command({
  74. name: 'group_shapes',
  75. category: 'canvas',
  76. do(data) {
  77. const { shapes } = getPage(data, currentPageId)
  78. // Remove shapes from old parents
  79. for (const parentId of parentIds) {
  80. if (parentId === currentPageId) continue
  81. const shape = shapes[parentId] as GroupShape
  82. getShapeUtils(shape).setProperty(
  83. shape,
  84. 'children',
  85. shape.children.filter((id) => !selectedIds.has(id))
  86. )
  87. }
  88. shapes[newGroupShape.id] = newGroupShape
  89. data.selectedIds.clear()
  90. data.selectedIds.add(newGroupShape.id)
  91. initialShapes.forEach(({ id }, i) => {
  92. const shape = shapes[id]
  93. getShapeUtils(shape)
  94. .setProperty(shape, 'parentId', newGroupShape.id)
  95. .setProperty(shape, 'childIndex', i)
  96. })
  97. },
  98. undo(data) {
  99. const { shapes } = getPage(data, currentPageId)
  100. data.selectedIds.clear()
  101. delete shapes[newGroupShape.id]
  102. initialShapes.forEach(({ id, parentId, childIndex }, i) => {
  103. data.selectedIds.add(id)
  104. const shape = shapes[id]
  105. getShapeUtils(shape)
  106. .setProperty(shape, 'parentId', parentId)
  107. .setProperty(shape, 'childIndex', childIndex)
  108. })
  109. },
  110. })
  111. )
  112. }
  113. function getShapeDepth(data: Data, id: string, depth = 0) {
  114. if (id === data.currentPageId) {
  115. return depth
  116. }
  117. return getShapeDepth(data, getShape(data, id).parentId, depth + 1)
  118. }