useCalcGridLayout.ts 4.41 KB
import { unref } from 'vue';
import { Layout } from 'vue3-grid-layout';
import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, DEFAULT_MAX_COL } from '../config/config';

interface GapRecord {
  maxGap: number;
  startIndex: Nullable<number>;
  endIndex: Nullable<number>;
}

export function useCalcGridLayout() {
  const calcLayoutInfo = (
    layoutInfo: Layout[],
    randomLayout = { width: DEFAULT_WIDGET_WIDTH, height: DEFAULT_WIDGET_HEIGHT }
  ) => {
    let maxWidth = 0;
    let maxHeight = 0;
    let maxWidthRecord = {} as unknown as Layout;
    let maxHeightRecord = {} as unknown as Layout;

    for (const item of unref(layoutInfo)) {
      const { x, y, h, w } = item;
      if (x + w > maxWidth) {
        maxWidth = x + w;
        maxWidthRecord = item;
      }
      if (y + h > maxHeight) {
        maxHeight = y + h;
        maxHeightRecord = item;
      }
    }

    maxWidth = maxWidthRecord.x + maxWidthRecord.w;
    maxHeight = maxHeightRecord.y + maxHeightRecord.h;

    const array = Array.from({ length: maxHeight }, (_value) => {
      return Array.from({ length: maxWidth });
    });

    for (const item of layoutInfo) {
      const { x, y, w, h } = item;

      for (let i = 0; i < h; i++) {
        const rowIndex = y + i > array.length - 1 ? array.length - 1 : y + i;
        const colEnd = x + w;
        const row = array[rowIndex];
        row.fill(true, x, colEnd);
      }
    }

    const checkAreaIsAvaliable = (rowIndex: number, rowRecord: GapRecord[]) => {
      const { height } = randomLayout;

      for (const { startIndex: colStartIndex } of rowRecord) {
        let record: GapRecord = { maxGap: 0, startIndex: null, endIndex: null };
        const heightGapRecord: GapRecord[] = [];
        for (let i = 0; i < height; i++) {
          const rowStartIndex = rowIndex + i > array.length - 1 ? array.length - 1 : rowIndex + i;
          const row = array[rowStartIndex];
          const col = row[colStartIndex!];
          if (col) {
            if (record.maxGap > 0) heightGapRecord.push(record);
            record = { maxGap: 0, startIndex: null, endIndex: null };
          }
          if (!col) {
            record = {
              maxGap: record.maxGap + 1,
              startIndex: record.startIndex === null ? rowStartIndex : record.startIndex,
              endIndex: rowStartIndex,
            };
          }
          if (i + 1 === height) if (record.maxGap > 0) heightGapRecord.push(record);
        }
        const minHeight = heightGapRecord.length
          ? Math.min(...heightGapRecord.map((item) => item.maxGap))
          : 0;
        if (minHeight >= height) {
          let flag = true;
          for (let colIndex = colStartIndex!; colIndex < record.endIndex!; colIndex++) {
            for (let _rowIndex = rowIndex; _rowIndex < height; _rowIndex++) {
              if (array[_rowIndex][colIndex]) {
                flag = false;
                break;
              }
            }
          }
          if (flag) return { y: rowIndex, x: colStartIndex!, flag: true };
        }
      }

      return { flag: false, x: 0, y: 0 };
    };

    for (let rowIndex = 0; rowIndex < array.length; rowIndex++) {
      const row = array[rowIndex];
      let record: GapRecord = { maxGap: 0, startIndex: null, endIndex: null };
      const widthGapRecord: GapRecord[] = [];

      const { width } = unref(randomLayout);

      for (let colIndex = 0; colIndex < DEFAULT_MAX_COL; colIndex++) {
        const col = row[colIndex];
        if (col) {
          if (record.maxGap > 0) widthGapRecord.push(record);
          record = { maxGap: 0, startIndex: null, endIndex: null };
        }
        if (!col) {
          record = {
            maxGap: record.maxGap + 1,
            startIndex: record.startIndex === null ? colIndex : record.startIndex,
            endIndex: colIndex,
          };
        }
        if (colIndex + 1 === DEFAULT_MAX_COL) if (record.maxGap > 0) widthGapRecord.push(record);
      }

      const maxWidth = widthGapRecord.length
        ? Math.max(...widthGapRecord.map((item) => item.maxGap))
        : 0;

      if (maxWidth >= width) {
        const maxRecordList = widthGapRecord.filter((item) => item.maxGap >= maxWidth);
        const { flag, x, y } = checkAreaIsAvaliable(rowIndex, maxRecordList);
        if (flag) return { x, y, w: randomLayout.width, h: randomLayout.height };
      }
    }

    return { x: 0, y: array.length, w: randomLayout.width, h: randomLayout.height };
  };

  return { calcLayoutInfo };
}