All files / src/layout groups.ts

74.02% Statements 57/77
56.25% Branches 18/32
100% Functions 2/2
74.66% Lines 56/75

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148        1x             24x   24x         24x 24x     24x 36x 74x     74x           74x 74x 74x 74x     74x 120x 120x   120x 120x 120x 120x   120x 120x 120x 120x       74x 24x 24x 24x 24x 24x   24x 24x 24x 24x       74x           74x 74x 74x 74x 74x   74x         74x   74x       24x                           74x 74x 74x   74x 74x 74x       74x       74x       74x           74x                 74x 74x              
import type { LayoutState, LGraphGroup } from "./types";
import { debugLog } from "../debug";
 
/** Title bar height for groups */
const GROUP_TITLE_HEIGHT = 50;
 
/**
 * Resize groups to fit their contents after layout
 * Uses bottom-up processing: deepest groups first, then parents
 */
export function resizeGroupsToFit(state: LayoutState): number {
  const { groupsByDepth, allNodes, config } = state;
 
  Iif (!groupsByDepth || !allNodes) {
    debugLog("Missing groupsByDepth or allNodes");
    return 0;
  }
 
  let resizedCount = 0;
  const maxDepth = groupsByDepth.length - 1;
 
  // Process from deepest to shallowest (bottom-up)
  for (let depth = maxDepth; depth >= 0; depth--) {
    for (const layoutGroup of groupsByDepth[depth]) {
      const { group, memberIds, childGroups } = layoutGroup;
 
      // Skip groups with no content
      Iif (memberIds.size === 0 && childGroups.length === 0) {
        debugLog(`Skipping empty group "${group.title}"`);
        continue;
      }
 
      // Calculate bounding box from positioned members and child groups
      let minX = Infinity;
      let minY = Infinity;
      let maxX = -Infinity;
      let maxY = -Infinity;
 
      // Include direct member nodes
      for (const nodeId of memberIds) {
        const node = allNodes.get(nodeId);
        Iif (!node) continue;
 
        const nx = node.pos?.[0] ?? 0;
        const ny = node.pos?.[1] ?? 0;
        const nw = node.size?.[0] ?? 200;
        const nh = node.size?.[1] ?? 100;
 
        minX = Math.min(minX, nx);
        minY = Math.min(minY, ny);
        maxX = Math.max(maxX, nx + nw);
        maxY = Math.max(maxY, ny + nh);
      }
 
      // Include child groups (already resized in previous iterations)
      for (const child of childGroups) {
        const cg = child.group;
        const cx = cg.pos[0];
        const cy = cg.pos[1];
        const cw = cg.size[0];
        const ch = cg.size[1];
 
        minX = Math.min(minX, cx);
        minY = Math.min(minY, cy);
        maxX = Math.max(maxX, cx + cw);
        maxY = Math.max(maxY, cy + ch);
      }
 
      // Handle empty bounds (shouldn't happen but be safe)
      Iif (minX === Infinity) {
        debugLog(`No content bounds for group "${group.title}"`);
        continue;
      }
 
      // Calculate final group bounds with padding
      const padding = config.groupPadding;
      const newX = minX - padding;
      const newY = minY - padding - GROUP_TITLE_HEIGHT;
      const newWidth = maxX - minX + padding * 2;
      const newHeight = maxY - minY + padding * 2 + GROUP_TITLE_HEIGHT;
 
      debugLog(
        `Resizing group "${group.title}" (depth ${layoutGroup.depth}) to [${newX}, ${newY}, ${newWidth}, ${newHeight}]`
      );
 
      // Update group bounds
      updateGroupBounds(group, newX, newY, newWidth, newHeight);
 
      resizedCount++;
    }
  }
 
  return resizedCount;
}
 
/**
 * Update group bounds using multiple methods for compatibility
 */
function updateGroupBounds(
  group: LGraphGroup,
  x: number,
  y: number,
  width: number,
  height: number
): void {
  // Method 1: Direct pos/size array mutation (most common)
  Eif (Array.isArray(group.pos)) {
    group.pos[0] = x;
    group.pos[1] = y;
  }
  Eif (Array.isArray(group.size)) {
    group.size[0] = width;
    group.size[1] = height;
  }
 
  // Method 2: Try _pos/_size internal properties
  const g = group as LGraphGroup & {
    _pos?: [number, number];
    _size?: [number, number];
  };
  Iif (Array.isArray(g._pos)) {
    g._pos[0] = x;
    g._pos[1] = y;
  }
  Iif (Array.isArray(g._size)) {
    g._size[0] = width;
    g._size[1] = height;
  }
 
  // Method 3: Update _bounding Rectangle if present
  Iif (group._bounding) {
    const b = group._bounding;
    b.x = x;
    b.y = y;
    b.width = width;
    b.height = height;
  }
 
  // Method 4: Try bounding array (older format)
  const gAny = group as LGraphGroup & { bounding?: number[] };
  Iif (Array.isArray(gAny.bounding)) {
    gAny.bounding[0] = x;
    gAny.bounding[1] = y;
    gAny.bounding[2] = width;
    gAny.bounding[3] = height;
  }
}