react-native-draggable-masonry-grid
Version:
React native flatlist based implementation of masonary grid with drag/drop functionality
203 lines (198 loc) • 7.86 kB
JavaScript
;
import { useRef, useState } from 'react';
export function asyncWaitForMS(waitForMS) {
return new Promise(res => setTimeout(res, waitForMS));
}
/**
* @param min
* @param max
* @returns {number} random integer between min(inclusive) and max(inclusive)
*/
export const generateRandomInteger = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
/**
*
* @param array
* @param index
* @returns new array with item at added at given index
*/
export function arrayAddItemAtIndex(array, index, item) {
return [...array.slice(0, index), item, ...array.slice(index)];
}
/**
*
* @param array
* @param index
* @returns new array with item removed at given index
*/
export function arrayRemoveItemAtIndex(array, index) {
return [...array.slice(0, index), ...array.slice(index + 1)];
}
/**
*
* @param array
* @param indexFrom
* @param indexTo
* @returns new array with item that was in indexFrom moved to indexTo
*/
export function arrayMoveFromIndexToIndex(array, indexFrom, indexTo) {
if (indexFrom < indexTo && array[indexFrom]) {
return [...array.slice(0, indexFrom), ...array.slice(indexFrom + 1, indexTo + 1), array[indexFrom], ...array.slice(indexTo + 1)];
} else if (indexFrom > indexTo && array[indexFrom]) {
return [...array.slice(0, indexTo), array[indexFrom], ...array.slice(indexTo, indexFrom), ...array.slice(indexFrom + 1)];
} else {
return [...array];
}
}
export function getGridColumns(data, numberOfColumns, columnWidth) {
const columnHeightsTracker = new Array(numberOfColumns);
const gridDataInSequence = new Array(data.length);
const columnsForGrid = new Array(numberOfColumns);
for (let i = 0; i < numberOfColumns; i++) {
columnHeightsTracker[i] = 0;
columnsForGrid[i] = [];
}
if (!data.length) {
return {
columnsForGrid,
gridDataInSequence,
columnHeights: columnHeightsTracker
};
}
data.forEach((item, index) => {
if (item.type === 'HEIGHT_EQUILIZER') {
// adds empty space to compensate for unaligned extra height of last columns item
const maxColumnHeight = Math.max(...columnHeightsTracker);
for (let i = 0; i < columnHeightsTracker.length; i++) {
const currentColumnHeight = columnHeightsTracker[i];
const height = maxColumnHeight - (currentColumnHeight ?? 0);
const data = {
columnIndex: i,
height,
isDraggable: false,
index: columnsForGrid[i]?.length ?? 0,
offsetY: columnHeightsTracker[i] ?? 0,
originalIndex: index,
position: {
x: i * columnWidth,
y: columnHeightsTracker[i] ?? 0
},
type: 'empty_space'
};
columnsForGrid[i]?.push(data);
gridDataInSequence[index] = data;
columnHeightsTracker[i] = (columnHeightsTracker[i] ?? 0) + height;
}
} else {
const minColumnHeight = Math.min(...columnHeightsTracker);
// add to earliest column if height with 10px of minColumnHeight
const columnIndexToAddTo = columnHeightsTracker.findIndex(height => height < minColumnHeight + 10);
const data = {
columnIndex: columnIndexToAddTo,
height: Math.round(item.height),
isDraggable: item.isDraggable,
index: columnsForGrid[columnIndexToAddTo]?.length ?? 0,
item: item.item,
offsetY: columnHeightsTracker[columnIndexToAddTo] ?? 0,
verticeoffsets: item.verticeoffsets,
originalIndex: index,
position: {
x: columnIndexToAddTo * columnWidth,
y: columnHeightsTracker[columnIndexToAddTo] ?? 0
},
type: 'item'
};
columnsForGrid[columnIndexToAddTo]?.push(data);
gridDataInSequence[index] = data;
columnHeightsTracker[columnIndexToAddTo] = (columnHeightsTracker[columnIndexToAddTo] ?? 0) + data.height;
}
});
return {
columnsForGrid,
gridDataInSequence,
columnHeights: columnHeightsTracker
};
}
export function getRawDataFromGridColumns(columnsForGrid, columnHeights) {
const totalItems = columnsForGrid.reduce((length, column) => length += column.length, 0);
const rawDataInSequence = new Array(totalItems);
const columnHeightsTracker = new Array(...columnHeights);
const columnIndexTrackers = columnsForGrid.map(column => column.length - 1);
let lastItem = undefined;
for (let i = totalItems - 1; i >= 0; i--) {
const maxColumnHeight = Math.max(...columnHeightsTracker);
const columnIndexToExtractFrom = columnHeightsTracker.findIndex(height => height === maxColumnHeight);
// extract last item in the column
const item = columnsForGrid[columnIndexToExtractFrom]?.[columnIndexTrackers[columnIndexToExtractFrom] ?? 0];
columnHeightsTracker[columnIndexToExtractFrom] = (columnHeightsTracker[columnIndexToExtractFrom] ?? 0) - (item?.height ?? 0);
columnIndexTrackers[columnIndexToExtractFrom] = (columnIndexTrackers[columnIndexToExtractFrom] ?? 0) - 1;
if (item?.type === 'empty_space') {
if (lastItem?.type !== 'empty_space') {
rawDataInSequence[i] = {
type: 'HEIGHT_EQUILIZER'
};
}
} else if (item) {
rawDataInSequence[i] = {
height: item.height,
isDraggable: item.isDraggable,
item: item.item,
type: 'ITEM'
};
}
lastItem = item;
}
return {
rawDataInSequence: rawDataInSequence.filter(Boolean)
};
}
export const checkIfContainsCoordinates = (coordinates, vertices) => coordinates.x > vertices.topLeft.x && coordinates.y > vertices.topLeft.y && coordinates.x < vertices.bottomRight.x && coordinates.y < vertices.bottomRight.y;
export const checkForOverlap = (verticesA, verticesB) => checkIfContainsCoordinates(verticesA.center, verticesB) || checkIfContainsCoordinates(verticesB.center, verticesA);
export const getDistanceBetweenVertices = (coordinatesA, coordinatesB) => Math.sqrt(Math.pow(coordinatesA.x - coordinatesB.x, 2) + Math.pow(coordinatesA.y - coordinatesB.y, 2));
const getViewMeasurements = cardRef => new Promise(res => cardRef?.current?.measure((...values) => res(values)));
export const getViewVertices = async (cardRef, options = {}) => {
const {
scale = 1,
offsets
} = options;
const cornerScale = (scale - 1) / 2 + 1;
const [_x, _y, width, height, pageX, pageY] = await getViewMeasurements(cardRef);
return {
topCenter: {
x: Math.round(pageX + width / 2 + (offsets?.center?.x ?? 0)),
y: Math.round(pageY * cornerScale + (offsets?.topLeft?.y ?? 0))
},
topLeft: {
x: Math.round(pageX * cornerScale + (offsets?.topLeft?.x ?? 0)),
y: Math.round(pageY * cornerScale + (offsets?.topLeft?.y ?? 0))
},
topRight: {
x: Math.round((pageX + width) / cornerScale + (offsets?.topRight?.x ?? 0)),
y: Math.round(pageY * cornerScale + (offsets?.topRight?.y ?? 0))
},
bottomCenter: {
x: Math.round(pageX + width / 2 + (offsets?.center?.x ?? 0)),
y: Math.round((pageY + height) / cornerScale + (offsets?.bottomRight?.y ?? 0))
},
bottomRight: {
x: Math.round((pageX + width) / cornerScale + (offsets?.bottomRight?.x ?? 0)),
y: Math.round((pageY + height) / cornerScale + (offsets?.bottomRight?.y ?? 0))
},
bottomLeft: {
x: Math.round(pageX * cornerScale + (offsets?.bottomLeft?.x ?? 0)),
y: Math.round((pageY + height) / cornerScale + (offsets?.bottomLeft?.y ?? 0))
},
center: {
x: Math.round(pageX + width / 2 + (offsets?.center?.x ?? 0)),
y: Math.round(pageY + height / 2 + (offsets?.center?.y ?? 0))
}
};
};
export function useStateRef(initialValue) {
const [state, setState] = useState(initialValue);
const stateRef = useRef(initialValue);
stateRef.current = state;
return [stateRef, setState];
}
//# sourceMappingURL=utils.js.map