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.

distribute.command.ts 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { Utils } from '@tldraw/core'
  2. import { DistributeType, TLDrawShape, Data, Command } from '~types'
  3. import { TLDR } from '~state/tldr'
  4. export function distribute(data: Data, ids: string[], type: DistributeType): Command {
  5. const initialShapes = ids.map((id) => TLDR.getShape(data, id))
  6. const deltaMap = Object.fromEntries(getDistributions(initialShapes, type).map((d) => [d.id, d]))
  7. const { before, after } = TLDR.mutateShapes(data, ids, (shape) => {
  8. if (!deltaMap[shape.id]) return shape
  9. return { point: deltaMap[shape.id].next }
  10. })
  11. return {
  12. id: 'distribute_shapes',
  13. before: {
  14. document: {
  15. pages: {
  16. [data.appState.currentPageId]: { shapes: before },
  17. },
  18. },
  19. },
  20. after: {
  21. document: {
  22. pages: {
  23. [data.appState.currentPageId]: { shapes: after },
  24. },
  25. },
  26. },
  27. }
  28. }
  29. function getDistributions(initialShapes: TLDrawShape[], type: DistributeType) {
  30. const entries = initialShapes.map((shape) => {
  31. const utils = TLDR.getShapeUtils(shape)
  32. return {
  33. id: shape.id,
  34. point: [...shape.point],
  35. bounds: utils.getBounds(shape),
  36. center: utils.getCenter(shape),
  37. }
  38. })
  39. const len = entries.length
  40. const commonBounds = Utils.getCommonBounds(entries.map(({ bounds }) => bounds))
  41. const results: { id: string; prev: number[]; next: number[] }[] = []
  42. switch (type) {
  43. case DistributeType.Horizontal: {
  44. const span = entries.reduce((a, c) => a + c.bounds.width, 0)
  45. if (span > commonBounds.width) {
  46. const left = entries.sort((a, b) => a.bounds.minX - b.bounds.minX)[0]
  47. const right = entries.sort((a, b) => b.bounds.maxX - a.bounds.maxX)[0]
  48. const entriesToMove = entries
  49. .filter((a) => a !== left && a !== right)
  50. .sort((a, b) => a.center[0] - b.center[0])
  51. const step = (right.center[0] - left.center[0]) / (len - 1)
  52. const x = left.center[0] + step
  53. entriesToMove.forEach(({ id, point, bounds }, i) => {
  54. results.push({
  55. id,
  56. prev: point,
  57. next: [x + step * i - bounds.width / 2, bounds.minY],
  58. })
  59. })
  60. } else {
  61. const entriesToMove = entries.sort((a, b) => a.center[0] - b.center[0])
  62. let x = commonBounds.minX
  63. const step = (commonBounds.width - span) / (len - 1)
  64. entriesToMove.forEach(({ id, point, bounds }) => {
  65. results.push({ id, prev: point, next: [x, bounds.minY] })
  66. x += bounds.width + step
  67. })
  68. }
  69. break
  70. }
  71. case DistributeType.Vertical: {
  72. const span = entries.reduce((a, c) => a + c.bounds.height, 0)
  73. if (span > commonBounds.height) {
  74. const top = entries.sort((a, b) => a.bounds.minY - b.bounds.minY)[0]
  75. const bottom = entries.sort((a, b) => b.bounds.maxY - a.bounds.maxY)[0]
  76. const entriesToMove = entries
  77. .filter((a) => a !== top && a !== bottom)
  78. .sort((a, b) => a.center[1] - b.center[1])
  79. const step = (bottom.center[1] - top.center[1]) / (len - 1)
  80. const y = top.center[1] + step
  81. entriesToMove.forEach(({ id, point, bounds }, i) => {
  82. results.push({
  83. id,
  84. prev: point,
  85. next: [bounds.minX, y + step * i - bounds.height / 2],
  86. })
  87. })
  88. } else {
  89. const entriesToMove = entries.sort((a, b) => a.center[1] - b.center[1])
  90. let y = commonBounds.minY
  91. const step = (commonBounds.height - span) / (len - 1)
  92. entriesToMove.forEach(({ id, point, bounds }) => {
  93. results.push({ id, prev: point, next: [bounds.minX, y] })
  94. y += bounds.height + step
  95. })
  96. }
  97. break
  98. }
  99. }
  100. return results
  101. }