Quellcode durchsuchen

feat(whiteboard): pack shapes into rect action

Peng Xiao vor 3 Jahren
Ursprung
Commit
0c8e5b59a3

+ 7 - 0
tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx

@@ -96,6 +96,13 @@ export const ContextMenu = observer(function ContextMenu({
                 </Button>
               </div>
               <ReactContextMenu.Separator className="menu-separator" />
+              <ReactContextMenu.Item
+                className="tl-menu-item"
+                onClick={() => runAndTransition(app.packIntoRectangle)}
+              >
+                Pack shapes into rectangle
+              </ReactContextMenu.Item>
+              <ReactContextMenu.Separator className="menu-separator" />
             </ReactContextMenu.Item>
           )}
           {app.selectedShapes?.size > 0 && (

+ 1 - 0
tldraw/packages/core/package.json

@@ -45,6 +45,7 @@
     "mobx": "^6.6.2",
     "mobx-react-lite": "^3.4.0",
     "mousetrap": "^1.6.5",
+    "potpack": "^1.0.2",
     "proxy-compare": "^2.3.0",
     "rbush": "^3.0.1",
     "uuid": "^8.0.0"

+ 15 - 0
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -417,6 +417,21 @@ export class TLApp<
     return this
   }
 
+  packIntoRectangle = (shapes: S[] = this.selectedShapesArray): this => {
+    if (shapes.length < 2) return this
+
+    const deltaMap = Object.fromEntries(
+      BoundsUtils.getPackedDistributions(shapes).map(d => [d.id, d])
+    )
+
+    shapes.forEach(shape => {
+      if (deltaMap[shape.id]) shape.update({ point: deltaMap[shape.id].next })
+    })
+
+    this.persist()
+    return this
+  }
+
   /* --------------------- Assets --------------------- */
 
   @observable assets: Record<string, TLAsset> = {}

+ 34 - 0
tldraw/packages/core/src/utils/BoundsUtils.ts

@@ -1,5 +1,6 @@
 /* eslint-disable @typescript-eslint/no-extra-semi */
 import { Vec } from '@tldraw/vec'
+import potpack from 'potpack'
 import type { TLShape } from '../lib'
 import {
   type TLBounds,
@@ -992,4 +993,37 @@ left past the initial left edge) then swap points on that axis.
 
     return results
   }
+
+  // pack shapes into a rectangle
+  static getPackedDistributions(shapes: TLShape[]) {
+    const commonBounds = BoundsUtils.getCommonBounds(shapes.map(({ bounds }) => bounds))
+    const origin = [commonBounds.minX, commonBounds.minY]
+    const shapesPosOriginal: Record<string, number[]> = Object.fromEntries(
+      shapes.map(s => [s.id, s.bounds.minX, s.bounds.minY])
+    )
+    const entries = shapes
+      .filter(s => s.type !== 'line')
+      .map(shape => {
+        const bounds = shape.getBounds()
+        return {
+          id: shape.id,
+          w: bounds.width + 16,
+          h: bounds.height + 16,
+          x: bounds.minX,
+          y: bounds.minY,
+        }
+      })
+
+    potpack(entries)
+
+    const entriesToMove = entries.map(({ id, x, y }) => {
+      return {
+        id,
+        prev: shapesPosOriginal[id],
+        next: [x + origin[0], y + origin[1]],
+      }
+    })
+
+    return entriesToMove
+  }
 }

+ 5 - 0
tldraw/yarn.lock

@@ -3597,6 +3597,11 @@ postcss@^8.4.17:
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
+potpack@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
+  integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==
+
 prelude-ls@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"