Source: src/layouts/SquareLayout.ts

import FrameLayout, { IFrameLayoutInterface, IFrameShape } from "./FrameLayout";
import { fill } from "../utils";
import { IInfiniteGridItem } from "../types";

function makeShapeOutline(
	outline: number[],
	itemSize: number,
	columnLength: number,
	isAppend?: boolean,
) {
	const point = Math[isAppend ? "min" : "max"](...outline) || 0;

	if (outline.length !== columnLength) {
		return fill(new Array(columnLength), 0);
	}
	return outline.map(l => Math.floor((l - point) / itemSize));
}
function getColumn(item: IInfiniteGridItem) {
	if (item.column) {
		return item.column;
	}
	let column = 1;

	if (item.el) {
		column = parseInt(item.el.getAttribute("data-column")!, 10) || 1;
	}
	item.column = column;
	return column;
}
export interface ISquareLayoutOptions extends IFrameLayoutInterface {
	column: number;
}
/**
 * @classdesc SquareLayout is a layout that places all cards like squares on a checkerboard, and important cards are n times larger. The main card can be enlarged, and then a small card can be placed to naturally show the relationship of the card.
 * @ko SquareLayout은 바둑판처럼 모든 카드를 정사각형으로 배치하고 중요한 카드는 크기를 N배로 키워서 보여주는 레이아웃이다. 주요 카드를 크게 표시하고, 그 다음에 작은 카드를 배치해 자연스럽게 카드의 관계를 나타낼 수 있습니다.
 * @class eg.InfiniteGrid.SquareLayout
 * @extends eg.InfiniteGrid.FrameLayout
 * @param {Object} [options] The option object of eg.InfiniteGrid.SquareLayout module <ko>eg.InfiniteGrid.SquareLayout 모듈의 옵션 객체</ko>
 * @param {String} [options.margin=0] Margin used to create space around items <ko>아이템들 사이의 공간</ko>
 * @param {Boolean} [options.horizontal=false] Direction of the scroll movement (false: vertical, true: horizontal) <ko>스크롤 이동 방향 (false: 세로방향, true: 가로방향)</ko>
 * @param {Boolean} [options.itemSize=0] The size of the items. If it is 0, it is calculated as the size of the first item in items. (priority: `column` > `itemSize` > element's size)<ko> 아이템의 사이즈. 만약 아이템 사이즈가 0이면, 아이템들의 첫번째 아이템의 사이즈로 계산이 된다. (우선순위: `column` > `itemSize` > 엘리먼트의 사이즈) </ko>
 * @param {Boolean} [options.column=0] The number of columns in the layout. If it is 0, the column is returned by `itemSize`.  (priority: `column` > `itemSize` > element's size)<ko> 레이아웃의 열의 개수. 만약 column이 0이면, `itemSize`로 열을 구합니다. (우선순위: `column` > `itemSize` > 엘리먼트의 사이즈) </ko>
 * @example
```
<script>
var ig = new eg.InfiniteGrid("#grid". {
	horizontal: true,
});

ig.setLayout(eg.InfiniteGrid.SquareLayout, {
	margin: 10,
	itemSize: 200,
});

// or

var layout = new eg.InfiniteGrid.SquareLayout({
	margin: 10,
	itemSize: 200,
	horizontal: true,
});

var item1 = '<div data-column="2"></div>';
var item2 = "<div></div>"
layout.append([item1, item2]);
</script>
```
 **/
export default class SquareLayout extends FrameLayout {
	public options: ISquareLayoutOptions;

	constructor(options: Partial<ISquareLayoutOptions> = {}) {
		super(options);
	}
	protected _layout(
		items: IInfiniteGridItem[],
		outline: number[] = [],
		isAppend: boolean = false,
	) {
		const itemSize = this._getSquareSize(items[0]) as number;
		const margin = this.options.margin;
		const columnLength = this.options.column ||
			Math.floor((this._size + margin) / (itemSize + margin)) || 1;
		const length = items.length;
		const endOutline = makeShapeOutline(outline, Math.floor(itemSize), columnLength, isAppend);
		const pointCaculateName = isAppend ? "min" : "max";
		const shapes: IFrameShape[] = [];
		const sign = isAppend ? 1 : -1;
		const style = this._style;
		const pos1Name = style.startPos1;
		const pos2Name = style.startPos2;

		for (let i = 0; i < length; ++i) {
			const point = Math[pointCaculateName](...endOutline);
			let index = endOutline[isAppend ? "indexOf" : "lastIndexOf"](point);
			const item = items[i];
			const columnWidth = item.columnWidth;
			const column = (columnWidth && columnWidth[0] === columnLength &&
				columnWidth[1]) || getColumn(item);
			let columnCount = 1;

			if (column > 1) {
				for (let j = 1; j < column &&
					((isAppend && index + j < columnLength) || (!isAppend && index - j >= 0)); ++j) {
					if ((isAppend && endOutline[index + sign * j] <= point) ||
						(!isAppend && endOutline[index + sign * j] >= point)) {
						++columnCount;
						continue;
					}
					break;
				}
				if (!isAppend) {
					index -= columnCount - 1;
				}
			}
			item.columnWidth = [columnLength, columnCount];
			shapes.push({
				width: columnCount,
				height: columnCount,
				[pos1Name]: point - (!isAppend ? columnCount : 0),
				[pos2Name]: index,
				type: i + 1,
				index: i,
			});
			for (let j = 0; j < columnCount; ++j) {
				endOutline[index + j] = point + sign * columnCount;
			}
		}
		this._shapes = {
			shapes,
			[style.size2]: columnLength,
		};

		const result = super._layout(items, outline, isAppend);

		if (!isAppend) {
			shapes.sort((shape1, shape2) => {
				const item1pos1 = shape1[pos1Name]!;
				const item1pos2 = shape1[pos2Name]!;
				const item2pos1 = shape2[pos1Name]!;
				const item2pos2 = shape2[pos2Name]!;

				if (item1pos1 - item2pos1) {
					return item1pos1 - item2pos1;
				}
				return item1pos2 - item2pos2;
			});
			items.sort((a, b) => {
				const item1pos1 = a.rect[pos1Name];
				const item1pos2 = a.rect[pos2Name];
				const item2pos1 = b.rect[pos1Name];
				const item2pos2 = b.rect[pos2Name];

				if (item1pos1 - item2pos1) {
					return item1pos1 - item2pos1;
				}
				return item1pos2 - item2pos2;
			});
		}
		return result;
	}
	private _getSquareSize(item: IInfiniteGridItem) {
		const { column, margin, itemSize } = this.options;

		if (column) {
			// if column is in options, caculate itemSize from column.
			this._itemSize = (this._size + margin) / column - margin;
		} else if (itemSize) {
			this._itemSize = this.options.itemSize;
		} else {
			const sizeName = this._style.size2;
			// if frameSize is 0, caculate frameSize from item.size.
			const frameSize
				= this._shapes[sizeName]
				|| Math.floor((this._size + margin) / (item.size![sizeName]! + margin) / getColumn(item));
			this._itemSize = (this._size + margin) / frameSize - margin;
		}
		return this._itemSize;
	}
}
comments powered by Disqus