Source: src/layouts/GridLayout.ts

  1. import { ALIGN } from "../consts";
  2. import { getStyleNames, assignOptions, fill, cloneItems } from "../utils";
  3. import { ILayout, IAlign, IRectlProperties, IInfiniteGridItem, IInfiniteGridGroup } from "../types";
  4. // ALIGN
  5. const { START, CENTER, END, JUSTIFY } = ALIGN;
  6. /**
  7. * @classdesc The GridLayout is a layout that stacks cards with the same width as a stack of bricks. Adjust the width of all images to the same size, find the lowest height column, and insert a new card.
  8. * @ko GridLayout는 벽돌을 쌓아 올린 모양처럼 동일한 너비를 가진 카드를 쌓는 레이아웃이다. 모든 이미지의 너비를 동일한 크기로 조정하고, 가장 높이가 낮은 열을 찾아 새로운 이미지를 삽입한다. 따라서 배치된 카드 사이에 빈 공간이 생기지는 않지만 배치된 레이아웃의 아래쪽은 울퉁불퉁해진다.
  9. * @class eg.InfiniteGrid.GridLayout
  10. * @param {Object} [options] The option object of eg.InfiniteGrid.GridLayout module <ko>eg.InfiniteGrid.GridLayout 모듈의 옵션 객체</ko>
  11. * @param {String} [options.margin=0] Margin used to create space around items <ko>아이템들 사이의 공간</ko>
  12. * @param {Boolean} [options.horizontal=false] Direction of the scroll movement (false: vertical, true: horizontal) <ko>스크롤 이동 방향 (false: 세로방향, true: 가로방향)</ko>
  13. * @param {Boolean} [options.align=START] Align of the position of the items (START, CENTER, END, JUSTIFY) <ko>아이템들의 위치의 정렬 (START, CENTER, END, JUSTIFY)</ko>
  14. * @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. <ko> 아이템의 사이즈. 만약 아이템 사이즈가 0이면, 아이템들의 첫번째 아이템의 사이즈로 계산이 된다. </ko>
  15. * @example
  16. ```
  17. <script>
  18. var ig = new eg.InfiniteGrid("#grid". {
  19. horizontal: true,
  20. });
  21. ig.setLayout(eg.InfiniteGrid.GridLayout, {
  22. margin: 10,
  23. align: "start",
  24. itemSize: 200
  25. });
  26. // or
  27. var layout = new eg.InfiniteGrid.GridLayout({
  28. margin: 10,
  29. align: "center",
  30. itemSize: 200,
  31. horizontal: true,
  32. });
  33. </script>
  34. ```
  35. **/
  36. class GridLayout implements ILayout {
  37. public options: {
  38. horizontal: boolean,
  39. margin: number,
  40. align: IAlign[keyof IAlign],
  41. itemSize: number,
  42. };
  43. private _size: number;
  44. private _columnSize: number;
  45. private _columnLength: number;
  46. private _style: IRectlProperties;
  47. constructor(options: Partial<GridLayout["options"]> = {}) {
  48. this.options = assignOptions({
  49. margin: 0,
  50. horizontal: false,
  51. align: START,
  52. itemSize: 0,
  53. }, options);
  54. this._size = 0;
  55. this._columnSize = 0;
  56. this._columnLength = 0;
  57. this._style = getStyleNames(this.options.horizontal);
  58. }
  59. /**
  60. * Adds items at the bottom of a outline.
  61. * @ko 아이템들을 아웃라인 아래에 추가한다.
  62. * @method eg.InfiniteGrid.GridLayout#append
  63. * @param {Array} items Array of items to be layouted <ko>레이아웃할 아이템들의 배열</ko>
  64. * @param {Array} [outline=[]] Array of outline points to be reference points <ko>기준점이 되는 아웃라인 점들의 배열</ko>
  65. * @return {Object} Layouted items and outline of start and end <ko> 레이아웃이 된 아이템과 시작과 끝의 아웃라인이 담긴 정보</ko>
  66. * @example
  67. * layout.prepend(items, [100, 200, 300, 400]);
  68. */
  69. public append(items: IInfiniteGridItem[], outline?: number[], cache?: boolean) {
  70. return this._insert(items, outline, true, cache);
  71. }
  72. /**
  73. * Adds items at the top of a outline.
  74. * @ko 아이템을 아웃라인 위에 추가한다.
  75. * @method eg.InfiniteGrid.GridLayout#prepend
  76. * @param {Array} items Array of items to be layouted <ko>레이아웃할 아이템들의 배열</ko>
  77. * @param {Array} [outline=[]] Array of outline points to be reference points <ko>기준점이 되는 아웃라인 점들의 배열</ko>
  78. * @return {Object} Layouted items and outline of start and end <ko> 레이아웃이 된 아이템과 시작과 끝의 아웃라인이 담긴 정보</ko>
  79. * @example
  80. * layout.prepend(items, [100, 200, 300, 400]);
  81. */
  82. public prepend(items: IInfiniteGridItem[], outline?: number[], cache?: boolean) {
  83. return this._insert(items, outline, false, cache);
  84. }
  85. /**
  86. * Adds items of groups at the bottom of a outline.
  87. * @ko 그룹들의 아이템들을 아웃라인 아래에 추가한다.
  88. * @method eg.InfiniteGrid.GridLayout#layout
  89. * @param {Array} groups Array of groups to be layouted <ko>레이아웃할 그룹들의 배열</ko>
  90. * @param {Array} outline Array of outline points to be reference points <ko>기준점이 되는 아웃라인 점들의 배열</ko>
  91. * @return {eg.InfiniteGrid.GridLayout} An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
  92. * @example
  93. * layout.layout(groups, [100, 200, 300, 400]);
  94. */
  95. public layout(groups: IInfiniteGridGroup[] = [], outline: number[] = []) {
  96. const firstItem = (groups.length && groups[0].items.length && groups[0].items[0]) as IInfiniteGridItem;
  97. this.checkColumn(firstItem);
  98. // if outlines' length and columns' length are now same, re-caculate outlines.
  99. let startOutline: number[];
  100. if (outline.length !== this._columnLength) {
  101. const pos = outline.length === 0 ? 0 : Math.min(...outline);
  102. // re-layout items.
  103. startOutline = fill(new Array(this._columnLength), pos);
  104. } else {
  105. startOutline = outline.slice();
  106. }
  107. groups.forEach(group => {
  108. const items = group.items;
  109. const result = this._layout(items, startOutline, true);
  110. group.outlines = result;
  111. startOutline = result.end;
  112. });
  113. return this;
  114. }
  115. /**
  116. * Set the viewport size of the layout.
  117. * @ko 레이아웃의 가시 사이즈를 설정한다.
  118. * @method eg.InfiniteGrid.GridLayout#setSize
  119. * @param {Number} size The viewport size of container area where items are added to a layout <ko>레이아웃에 아이템을 추가하는 컨테이너 영역의 가시 사이즈</ko>
  120. * @return {eg.InfiniteGrid.GridLayout} An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
  121. * @example
  122. * layout.setSize(800);
  123. */
  124. public setSize(size: number) {
  125. this._size = size;
  126. return this;
  127. }
  128. private checkColumn(item: IInfiniteGridItem) {
  129. const { itemSize, margin, horizontal } = this.options;
  130. const sizeName = horizontal ? "height" : "width";
  131. const columnSize = Math.floor(itemSize || (item && item.size![sizeName]) || 0) || 0;
  132. this._columnSize = columnSize;
  133. if (!columnSize) {
  134. this._columnLength = 1;
  135. return;
  136. }
  137. this._columnLength = Math.max(Math.floor((this._size + margin) / (columnSize + margin)), 1);
  138. }
  139. private _layout(items: IInfiniteGridItem[], outline: number[], isAppend?: boolean) {
  140. const length = items.length;
  141. const margin = this.options.margin;
  142. const align = this.options.align;
  143. const style = this._style;
  144. const size1Name = style.size1;
  145. const size2Name = style.size2;
  146. const pos1Name = style.startPos1;
  147. const pos2Name = style.startPos2;
  148. const columnSize = this._columnSize;
  149. const columnLength = this._columnLength;
  150. const size = this._size;
  151. const viewDist = (size - (columnSize + margin) * columnLength + margin);
  152. const pointCaculateName = isAppend ? "min" : "max";
  153. const indexCaculateName = isAppend ? "indexOf" : "lastIndexOf";
  154. const startOutline = outline.slice();
  155. const endOutline = outline.slice();
  156. for (let i = 0; i < length; ++i) {
  157. const point = Math[pointCaculateName](...endOutline) || 0;
  158. let index = endOutline[indexCaculateName](point);
  159. const item = items[isAppend ? i : length - 1 - i];
  160. const itemSize = item.size;
  161. if (!itemSize) {
  162. continue;
  163. }
  164. const size1 = itemSize[size1Name];
  165. const size2 = itemSize[size2Name];
  166. const pos1 = isAppend ? point : point - margin - size1;
  167. const endPos1 = pos1 + size1 + margin;
  168. if (index === -1) {
  169. index = 0;
  170. }
  171. let pos2 = (columnSize + margin) * index;
  172. // ALIGN
  173. if (align === CENTER) {
  174. pos2 += viewDist / 2;
  175. } else if (align === END) {
  176. pos2 += viewDist + columnSize - size2;
  177. } else if (align === JUSTIFY) {
  178. if (columnLength <= 1) {
  179. pos2 += viewDist / 2;
  180. } else {
  181. pos2 = (size - columnSize) / (columnLength - 1) * index;
  182. }
  183. }
  184. // tetris
  185. item.rect = {
  186. [pos1Name as "top"]: pos1,
  187. [pos2Name as "left"]: pos2,
  188. };
  189. item.column = index;
  190. endOutline[index] = isAppend ? endPos1 : pos1;
  191. }
  192. if (!isAppend) {
  193. items.sort((a, b) => {
  194. const item1pos1 = a.rect[pos1Name];
  195. const item1pos2 = a.rect[pos2Name];
  196. const item2pos1 = b.rect[pos1Name];
  197. const item2pos2 = b.rect[pos2Name];
  198. if (item1pos1 - item2pos1) {
  199. return item1pos1 - item2pos1;
  200. }
  201. return item1pos2 - item2pos2;
  202. });
  203. }
  204. // if append items, startOutline is low, endOutline is high
  205. // if prepend items, startOutline is high, endOutline is low
  206. return {
  207. start: isAppend ? startOutline : endOutline,
  208. end: isAppend ? endOutline : startOutline,
  209. };
  210. }
  211. private _insert(
  212. items: IInfiniteGridItem[] = [],
  213. outline: number[] = [],
  214. isAppend?: boolean,
  215. cache?: boolean,
  216. ) {
  217. const clone = cache ? items : cloneItems(items);
  218. let startOutline = outline;
  219. if (!this._columnLength) {
  220. this.checkColumn(items[0]);
  221. }
  222. if (outline.length !== this._columnLength) {
  223. startOutline = fill(new Array(this._columnLength), outline.length ? (Math[isAppend ? "min" : "max"](...outline) || 0) : 0);
  224. }
  225. const result = this._layout(clone, startOutline, isAppend);
  226. return {
  227. items: clone,
  228. outlines: result,
  229. };
  230. }
  231. }
  232. export default GridLayout;
comments powered by Disqus