Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 170 additions & 97 deletions projects/angular-grid-layout/src/lib/grid.component.ts

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions projects/angular-grid-layout/src/lib/grid.definitions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { InjectionToken } from '@angular/core';
import { CompactType } from './utils/react-grid-layout.utils';
import { KtdClientRect } from './utils/client-rect';
import { KtdDictionary } from '../types';

export interface KtdGridLayoutItem {
id: string;
Expand Down Expand Up @@ -67,3 +68,11 @@ export interface KtdDraggingData {
dragElemClientRect: KtdClientRect;
scrollDifference: { top: number, left: number };
}

export interface KtdDraggingMultipleData {
pointerDownEvent: MouseEvent | TouchEvent;
pointerDragEvent: MouseEvent | TouchEvent;
gridElemClientRect: KtdClientRect;
dragElementsClientRect: KtdDictionary<KtdClientRect>;
scrollDifference: { top: number, left: number };
}
133 changes: 131 additions & 2 deletions projects/angular-grid-layout/src/lib/utils/grid.utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { compact, CompactType, getFirstCollision, Layout, LayoutItem, moveElement } from './react-grid-layout.utils';
import { compact, CompactType, getFirstCollision, Layout, LayoutItem, moveElement, sortLayoutItems } from './react-grid-layout.utils';
import {
KtdDraggingData, KtdGridCfg, KtdGridCompactType, KtdGridItemRect, KtdGridItemRenderData, KtdGridLayout, KtdGridLayoutItem
KtdDraggingData, KtdDraggingMultipleData, KtdGridCfg, KtdGridCompactType, KtdGridItemRect, KtdGridItemRenderData, KtdGridLayout, KtdGridLayoutItem
} from '../grid.definitions';
import { ktdPointerClientX, ktdPointerClientY } from './pointer.utils';
import { KtdDictionary } from '../../types';
import { KtdGridItemComponent } from '../grid-item/grid-item.component';
import { moveElements } from './react-grid-layout-multiple.utils';

/** Tracks items by id. This function is mean to be used in conjunction with the ngFor that renders the 'ktd-grid-items' */
export function ktdTrackById(index: number, item: {id: string}) {
Expand All @@ -31,6 +32,19 @@ export function ktdGridCompact(layout: KtdGridLayout, compactType: KtdGridCompac
.map(item => ({ id: item.id, x: item.x, y: item.y, w: item.w, h: item.h, minW: item.minW, minH: item.minH, maxW: item.maxW, maxH: item.maxH }));
}

/**
* Call react-grid-layout utils 'sortLayoutItems()' function to return the 'layout' sorted by 'compactType'
* @param {Layout} layout
* @param {CompactType} compactType
* @returns {Layout}
*/
export function ktdGridSortLayoutItems(
layout: Layout,
compactType: CompactType,
): Layout {
return sortLayoutItems(layout,compactType)
}

function screenXToGridX(screenXPos: number, cols: number, width: number, gap: number): number {
if (cols <= 1) {
return 0;
Expand Down Expand Up @@ -152,6 +166,121 @@ export function ktdGridItemDragging(gridItem: KtdGridItemComponent, config: KtdG
};
}



/**
* Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position
* @param gridItem grid item that is been dragged
* @param config current grid configuration
* @param compactionType type of compaction that will be performed
* @param draggingData contains all the information about the drag
*/
export function ktdGridItemsDragging(gridItems: KtdGridItemComponent[], config: KtdGridCfg, compactionType: CompactType, draggingData: KtdDraggingMultipleData): { layout: KtdGridLayoutItem[]; draggedItemPos: KtdDictionary<KtdGridItemRect> } {
const {pointerDownEvent, pointerDragEvent, gridElemClientRect, dragElementsClientRect, scrollDifference} = draggingData;

const draggingElemPrevItem: KtdDictionary<KtdGridLayoutItem> = {}
gridItems.forEach(gridItem=> {
draggingElemPrevItem[gridItem.id] = config.layout.find(item => item.id === gridItem.id)!
});

const clientStartX = ktdPointerClientX(pointerDownEvent);
const clientStartY = ktdPointerClientY(pointerDownEvent);
const clientX = ktdPointerClientX(pointerDragEvent);
const clientY = ktdPointerClientY(pointerDragEvent);

// Grid element positions taking into account the possible scroll total difference from the beginning.
const gridElementLeftPosition = gridElemClientRect.left + scrollDifference.left;
const gridElementTopPosition = gridElemClientRect.top + scrollDifference.top;

const rowHeightInPixels = config.rowHeight === 'fit'
? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)
: config.rowHeight;

const layoutItemsToMove: KtdDictionary<KtdGridLayoutItem>={};
const gridRelPos: KtdDictionary<{x:number,y:number}>={}
let maxXMove: number = 0;
let maxYMove: number = 0;
gridItems.forEach((gridItem: KtdGridItemComponent)=>{
const offsetX = clientStartX - dragElementsClientRect[gridItem.id].left;
const offsetY = clientStartY - dragElementsClientRect[gridItem.id].top;
// Calculate position relative to the grid element.
gridRelPos[gridItem.id]={
x: clientX - gridElementLeftPosition - offsetX,
y: clientY - gridElementTopPosition - offsetY
};
// Get layout item position
layoutItemsToMove[gridItem.id] = {
...draggingElemPrevItem[gridItem.id],
x: screenXToGridX(gridRelPos[gridItem.id].x , config.cols, gridElemClientRect.width, config.gap),
y: screenYToGridY(gridRelPos[gridItem.id].y, rowHeightInPixels, gridElemClientRect.height, config.gap)
};
// Determine the maximum X and Y displacement where an item has gone outside the grid
if(0>layoutItemsToMove[gridItem.id].x && maxXMove>layoutItemsToMove[gridItem.id].x){
maxXMove = layoutItemsToMove[gridItem.id].x;
}
if(0>layoutItemsToMove[gridItem.id].y && maxYMove>layoutItemsToMove[gridItem.id].y){
maxYMove = layoutItemsToMove[gridItem.id].y;
}
if(layoutItemsToMove[gridItem.id].x + layoutItemsToMove[gridItem.id].w > config.cols && maxXMove<layoutItemsToMove[gridItem.id].w + layoutItemsToMove[gridItem.id].x - config.cols){
maxXMove = layoutItemsToMove[gridItem.id].w + layoutItemsToMove[gridItem.id].x - config.cols
}
})
// Correct all the x and y position of the group decreasing/increasing the maximum overflow of an item, to maintain the structure
Object.entries(layoutItemsToMove).forEach(([key, item]) => {
layoutItemsToMove[key] = {
...item,
x: item.x - maxXMove,
y: item.y - maxYMove
};
})

// Parse to LayoutItem array data in order to use 'react.grid-layout' utils
const layoutItems: LayoutItem[] = config.layout;
const draggedLayoutItem: {
l: LayoutItem,
x: number | null | undefined,
y: number | null | undefined
}[] = gridItems.map((gridItem:KtdGridItemComponent)=>{
const draggedLayoutItem: LayoutItem = layoutItems.find(item => item.id === gridItem.id)!;
draggedLayoutItem.static = true;
return {
l: draggedLayoutItem,
x: layoutItemsToMove[gridItem.id].x,
y: layoutItemsToMove[gridItem.id].y
}
});

let newLayoutItems: LayoutItem[] = moveElements(
layoutItems,
draggedLayoutItem,
true,
config.preventCollision,
compactionType,
config.cols,
);

// Compact with selected items as static to preserve the structure of the selected items group
newLayoutItems = compact(newLayoutItems, compactionType, config.cols);
gridItems.forEach(gridItem=>newLayoutItems.find(layoutItem=>layoutItem.id === gridItem.id)!.static = false);
// Compact normal to display the layout correctly
newLayoutItems = compact(newLayoutItems, compactionType, config.cols);

const draggedItemPos: KtdDictionary<KtdGridItemRect>={};
gridItems.forEach(gridItem=>
draggedItemPos[gridItem.id]={
left: gridRelPos[gridItem.id].x,
top: gridRelPos[gridItem.id].y,
width: dragElementsClientRect[gridItem.id].width,
height: dragElementsClientRect[gridItem.id].height,
}
);

return {
layout: newLayoutItems,
draggedItemPos
};
}

/**
* Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position
* @param gridItem grid item that is been dragged
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* IMPORTANT:
* This utils are taken from the project: https://github.com/STRML/react-grid-layout.
* The code should be as less modified as possible for easy maintenance.
*/

import { CompactType, getAllCollisions, getFirstCollision, Layout, LayoutItem, sortLayoutItems } from "./react-grid-layout.utils";

const DEBUG = false;

// Disable lint since we don't want to modify this code
/* eslint-disable */

/**
* Move an element. Responsible for doing cascading movements of other elements.
*
* @param {Array} layout Full layout to modify.
* @param {LayoutItem} l element to move.
* @param {Number} [x] X position in grid units.
* @param {Number} [y] Y position in grid units.
*/
export function moveElements(
layout: Layout,
items: {
l: LayoutItem,
x: number | null | undefined,
y: number | null | undefined
}[],
isUserAction: boolean | null | undefined,
preventCollision: boolean | null | undefined,
compactType: CompactType,
cols: number
): Layout {
// Short-circuit if nothing to do.
if(items.every((item)=>item.l.y === item.y && item.l.x === item.x)){
return layout;
}

const oldX = items[0].l.x;
const oldY = items[0].l.y;
const oldCoord = {}

items.forEach((item)=>{
oldCoord[item.l.id]={
x: item.l.x,
y: item.l.y
}
if (typeof item.x === 'number') {
item.l.x = item.x;
}
if (typeof item.y === 'number') {
item.l.y = item.y;
}
item.l.moved = true;
})

let sorted = sortLayoutItems(layout, compactType);
// If this collides with anything, move it.
// When doing this comparison, we have to sort the items we compare with
// to ensure, in the case of multiple collisions, that we're getting the
// nearest collision.
const movingUp =
compactType === 'vertical' && typeof items[0].y === 'number'
? oldY >= items[0].y
: compactType === 'horizontal' && typeof items[0].x === 'number'
? oldX >= items[0].x
: false;
if (movingUp) {
sorted = sorted.reverse();
}

items.forEach((item)=>{
const collisions: LayoutItem[] = getAllCollisions(sorted, item.l);
if (preventCollision && collisions.length) {
item.l.x = oldCoord[item.l.id].x;
item.l.y = oldCoord[item.l.id].y;
item.l.moved = false;
} else {
// Move each item that collides away from this element.
for (let i = 0, len = collisions.length; i < len; i++) {
const collision = collisions[i];
logMulti(
`Resolving collision between ${items}] and ${
collision.id
} at [${collision.x},${collision.y}]`,
);
// Short circuit so we can't infinite loop
if (collision.moved) {
continue;
}
// Don't move static items - we have to move *this* element away
if (collision.static) {
layout = moveElementsAwayFromCollision(
layout,
collision,
item.l,
isUserAction,
compactType,
cols
);
} else {
layout = moveElementsAwayFromCollision(
layout,
item.l,
collision,
isUserAction,
compactType,
cols
);
}
}
}
});

return layout;
}

export function moveElementsAwayFromCollision(
layout: Layout,
collidesWith: LayoutItem,
itemToMove: LayoutItem,
isUserAction: boolean | null | undefined,
compactType: CompactType,
cols: number,
): Layout {
const compactH = compactType === 'horizontal';
// Compact vertically if not set to horizontal
const compactV = compactType !== 'horizontal';
const preventCollision = collidesWith.static; // we're already colliding (not for static items)

// If there is enough space above the collision to put this element, move it there.
// We only do this on the main collision as this can get funky in cascades and cause
// unwanted swapping behavior.
if (isUserAction) {
// Reset isUserAction flag because we're not in the main collision anymore.
isUserAction = false;

// Make a mock item so we don't modify the item here, only modify in moveElement.
const fakeItem: LayoutItem = {
x: compactH
? Math.max(collidesWith.x - itemToMove.w, 0)
: itemToMove.x,
y: compactV
? Math.max(collidesWith.y - itemToMove.h, 0)
: itemToMove.y,
w: itemToMove.w,
h: itemToMove.h,
id: '-1',
};

// No collision? If so, we can go up there; otherwise, we'll end up moving down as normal
if (!getFirstCollision(layout, fakeItem)) {
logMulti(
`Doing reverse collision on ${itemToMove.id} up to [${
fakeItem.x
},${fakeItem.y}].`,
);
return moveElements(
layout,
[{
l: itemToMove,
x: compactH ? fakeItem.x : undefined,
y: compactV ? fakeItem.y : undefined,
}],
isUserAction,
preventCollision,
compactType,
cols
);
}
}

return moveElements(
layout,
[{
l: itemToMove,
x: compactH ? itemToMove.x+1 : undefined,
y: compactV ? itemToMove.y+1 : undefined,
}],
isUserAction,
preventCollision,
compactType,
cols
);
}

function logMulti(...args) {
if (!DEBUG) {
return;
}
// eslint-disable-next-line no-console
console.log(...args);
}

Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ export function compact(
const sorted = sortLayoutItems(layout, compactType);
// Holding for new items.
const out = Array(layout.length);

for (let i = 0, len = sorted.length; i < len; i++) {
let l = cloneLayoutItem(sorted[i]);

Expand Down Expand Up @@ -217,10 +216,10 @@ function resolveCompactionCollision(

// Optimization: we can break early if we know we're past this el
// We can do this b/c it's a sorted layout
if (otherItem.y > item.y + item.h) {
if (otherItem[axis] > moveToCoord+item[sizeProp]) {
break;
}

}
if (collides(item, otherItem)) {
resolveCompactionCollision(
layout,
Expand Down Expand Up @@ -281,7 +280,6 @@ export function compactItem(
}
}
}

// Ensure that there are no negative positions
l.y = Math.max(l.y, 0);
l.x = Math.max(l.x, 0);
Expand Down
Loading