選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

group.ts 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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 newGroupChildIndex: number
  27. const initialShapeIds = 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. // !!! Hey! We're not going any further. We need to ungroup those shapes.
  45. commands.ungroup(data)
  46. return
  47. } else {
  48. // Make the group inside of the current group
  49. newGroupParentId = parentId
  50. }
  51. }
  52. } else {
  53. // Find the least-deep parent among the shapes and add the group as a child
  54. let minDepth = Infinity
  55. for (let parentId of initialShapes.map((shape) => shape.parentId)) {
  56. const depth = getShapeDepth(data, parentId)
  57. if (depth < minDepth) {
  58. minDepth = depth
  59. newGroupParentId = parentId
  60. }
  61. }
  62. }
  63. newGroupShape = createShape(ShapeType.Group, {
  64. parentId: newGroupParentId,
  65. point: [commonBounds.minX, commonBounds.minY],
  66. size: [commonBounds.width, commonBounds.height],
  67. children: initialShapeIds,
  68. childIndex: initialShapes[0].childIndex,
  69. })
  70. history.execute(
  71. data,
  72. new Command({
  73. name: 'group_shapes',
  74. category: 'canvas',
  75. manualSelection: true,
  76. do(data) {
  77. const { shapes } = getPage(data, currentPageId)
  78. // Create the new group
  79. shapes[newGroupShape.id] = newGroupShape
  80. // Assign the group to its new parent
  81. if (newGroupParentId !== data.currentPageId) {
  82. const parent = shapes[newGroupParentId]
  83. getShapeUtils(parent).setProperty(parent, 'children', [
  84. ...parent.children,
  85. newGroupShape.id,
  86. ])
  87. }
  88. // Assign the shapes to their new parent
  89. initialShapes.forEach((initialShape, i) => {
  90. // Remove shape from its old parent
  91. if (initialShape.parentId !== currentPageId) {
  92. const oldParent = shapes[initialShape.parentId] as GroupShape
  93. getShapeUtils(oldParent).setProperty(
  94. oldParent,
  95. 'children',
  96. oldParent.children.filter((id) => !selectedIds.has(id))
  97. )
  98. }
  99. // Assign the shape to its new parent, with its new childIndex
  100. const shape = shapes[initialShape.id]
  101. getShapeUtils(shape)
  102. .setProperty(shape, 'childIndex', i)
  103. .setProperty(shape, 'parentId', newGroupShape.id)
  104. })
  105. data.selectedIds.clear()
  106. data.selectedIds.add(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. data.selectedIds = new Set(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. }