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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import Command from './command'
  2. import history from '../history'
  3. import { Data, MoveType, Shape } from 'types'
  4. import {
  5. forceIntegerChildIndices,
  6. getChildren,
  7. getPage,
  8. getSelectedIds,
  9. setToArray,
  10. } from 'utils/utils'
  11. import { getShapeUtils } from 'state/shape-utils'
  12. export default function moveCommand(data: Data, type: MoveType): void {
  13. const { currentPageId } = data
  14. const page = getPage(data)
  15. const selectedIds = setToArray(getSelectedIds(data))
  16. const initialIndices = Object.fromEntries(
  17. selectedIds.map((id) => [id, page.shapes[id].childIndex])
  18. )
  19. history.execute(
  20. data,
  21. new Command({
  22. name: 'change_child_index',
  23. category: 'canvas',
  24. manualSelection: true,
  25. do(data) {
  26. const page = getPage(data, currentPageId)
  27. const shapes = selectedIds.map((id) => page.shapes[id])
  28. const shapesByParentId = shapes.reduce<Record<string, Shape[]>>(
  29. (acc, shape) => {
  30. if (acc[shape.parentId] === undefined) {
  31. acc[shape.parentId] = []
  32. }
  33. acc[shape.parentId].push(shape)
  34. return acc
  35. },
  36. {}
  37. )
  38. switch (type) {
  39. case MoveType.ToFront: {
  40. for (const id in shapesByParentId) {
  41. moveToFront(shapesByParentId[id], getChildren(data, id))
  42. }
  43. break
  44. }
  45. case MoveType.ToBack: {
  46. for (const id in shapesByParentId) {
  47. moveToBack(shapesByParentId[id], getChildren(data, id))
  48. }
  49. break
  50. }
  51. case MoveType.Forward: {
  52. for (const id in shapesByParentId) {
  53. const visited = new Set<string>()
  54. const siblings = getChildren(data, id)
  55. shapesByParentId[id]
  56. .sort((a, b) => b.childIndex - a.childIndex)
  57. .forEach((shape) => moveForward(shape, siblings, visited))
  58. }
  59. break
  60. }
  61. case MoveType.Backward: {
  62. for (const id in shapesByParentId) {
  63. const visited = new Set<string>()
  64. const siblings = getChildren(data, id)
  65. shapesByParentId[id]
  66. .sort((a, b) => a.childIndex - b.childIndex)
  67. .forEach((shape) => moveBackward(shape, siblings, visited))
  68. }
  69. break
  70. }
  71. }
  72. },
  73. undo(data) {
  74. const page = getPage(data)
  75. for (const id of selectedIds) {
  76. const shape = page.shapes[id]
  77. getShapeUtils(shape).setProperty(
  78. shape,
  79. 'childIndex',
  80. initialIndices[id]
  81. )
  82. }
  83. },
  84. })
  85. )
  86. }
  87. function moveToFront(shapes: Shape[], siblings: Shape[]) {
  88. shapes.sort((a, b) => a.childIndex - b.childIndex)
  89. const diff = siblings
  90. .filter((sib) => !shapes.includes(sib))
  91. .sort((a, b) => b.childIndex - a.childIndex)
  92. if (diff.length === 0) return
  93. const startIndex = Math.ceil(diff[0].childIndex) + 1
  94. shapes.forEach((shape, i) =>
  95. getShapeUtils(shape).setProperty(shape, 'childIndex', startIndex + i)
  96. )
  97. }
  98. function moveToBack(shapes: Shape[], siblings: Shape[]) {
  99. shapes.sort((a, b) => b.childIndex - a.childIndex)
  100. const diff = siblings
  101. .filter((sib) => !shapes.includes(sib))
  102. .sort((a, b) => a.childIndex - b.childIndex)
  103. if (diff.length === 0) return
  104. const startIndex = diff[0]?.childIndex
  105. const step = startIndex / (shapes.length + 1)
  106. shapes.forEach((shape, i) =>
  107. getShapeUtils(shape).setProperty(
  108. shape,
  109. 'childIndex',
  110. startIndex - (i + 1) * step
  111. )
  112. )
  113. }
  114. function moveForward(shape: Shape, siblings: Shape[], visited: Set<string>) {
  115. visited.add(shape.id)
  116. const index = siblings.indexOf(shape)
  117. const nextSibling = siblings[index + 1]
  118. if (nextSibling && !visited.has(nextSibling.id)) {
  119. const nextNextSibling = siblings[index + 2]
  120. let nextIndex = nextNextSibling
  121. ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
  122. : Math.ceil(nextSibling.childIndex + 1)
  123. if (nextIndex === nextSibling.childIndex) {
  124. forceIntegerChildIndices(siblings)
  125. nextIndex = nextNextSibling
  126. ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
  127. : Math.ceil(nextSibling.childIndex + 1)
  128. }
  129. getShapeUtils(shape).setProperty(shape, 'childIndex', nextIndex)
  130. siblings.sort((a, b) => a.childIndex - b.childIndex)
  131. }
  132. }
  133. function moveBackward(shape: Shape, siblings: Shape[], visited: Set<string>) {
  134. visited.add(shape.id)
  135. const index = siblings.indexOf(shape)
  136. const nextSibling = siblings[index - 1]
  137. if (nextSibling && !visited.has(nextSibling.id)) {
  138. const nextNextSibling = siblings[index - 2]
  139. const nextIndex = nextNextSibling
  140. ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
  141. : nextSibling.childIndex / 2
  142. if (shape.childIndex === nextSibling.childIndex) {
  143. forceIntegerChildIndices(siblings)
  144. nextNextSibling
  145. ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
  146. : nextSibling.childIndex / 2
  147. }
  148. getShapeUtils(shape).setProperty(shape, 'childIndex', nextIndex)
  149. siblings.sort((a, b) => a.childIndex - b.childIndex)
  150. }
  151. }