feat(frontend): improve drag-n-drop behavior

This commit is contained in:
Ruben Fiszel
2023-03-13 12:44:39 +01:00
parent 1f4ae53fb4
commit cfd489a550
3 changed files with 210 additions and 156 deletions
@@ -87,8 +87,21 @@
return () => sizeObserver.disconnect()
})
let initItems: FilledItem<T>[] | undefined = undefined
const updateMatrix = ({ detail }) => {
let activeItem = getItemById(detail.id, items)
let isPointerUp = detail.isPointerUp
let citems: FilledItem<T>[]
if (isPointerUp) {
citems = JSON.parse(JSON.stringify(initItems))
initItems = undefined
} else {
if (initItems == undefined) {
initItems = JSON.parse(JSON.stringify(items))
}
citems = JSON.parse(JSON.stringify(initItems))
}
let activeItem = getItemById(detail.id, citems)
if (activeItem) {
activeItem = {
@@ -102,12 +115,12 @@
if (fillSpace) {
items = moveItemsAroundItem(
activeItem,
items,
citems,
getComputedCols,
getItemById(detail.id, items)
getItemById(detail.id, citems)
)
} else {
items = moveItem(activeItem, items, getComputedCols, getItemById(detail.id, items))
items = moveItem(activeItem, citems, getComputedCols, getItemById(detail.id, citems))
}
if (detail.onUpdate) detail.onUpdate()
@@ -1,219 +1,260 @@
import type { FilledItem } from "../types";
import { makeMatrix, makeMatrixFromItemsIgnore, findCloseBlocks, findItemsById, makeMatrixFromItems } from "./matrix";
import { getRowsCount } from "./other";
import type { FilledItem, ItemLayout } from '../types'
import {
makeMatrix,
makeMatrixFromItemsIgnore,
findCloseBlocks,
findItemsById,
makeMatrixFromItems
} from './matrix'
import { getRowsCount } from './other'
export function getItemById(id, items) {
return items.find((value) => value.id === id);
return items.find((value) => value.id === id)
}
export function findFreeSpaceForItem(matrix, item) {
const cols = matrix[0].length;
const w = Math.min(cols, item.w);
let xNtime = cols - w;
let getMatrixRows = matrix.length;
export function isEmpty(matrix: any[][], x: number, y: number, w: number, h: number) {
for (var i = 0; i < h; i++) {
if (matrix[y + i]) {
for (var j = 0; j < w; j++) {
if (matrix[y + i][x + j] != undefined) {
return false
}
}
}
}
return true
}
for (var i = 0; i < getMatrixRows; i++) {
const row = matrix[i];
for (var j = 0; j < xNtime + 1; j++) {
const sliceA = row.slice(j, j + w);
const empty = sliceA.every((val) => val === undefined);
if (empty) {
const isEmpty = matrix.slice(i, i + item.h).every((a) => a.slice(j, j + w).every((n) => n === undefined));
function distance(a, b): number {
return Math.abs(a.x - b.x + 0.25) + Math.abs(a.y - b.y + 0.25)
}
if (isEmpty) {
return { y: i, x: j };
}
}
}
}
export function findFreeSpaceForItem<T>(matrix: FilledItem<T>[][], item: ItemLayout) {
const cols = matrix[0].length
const w = Math.min(cols, item.w)
const h = item.h
let xNtime = cols - w
let getMatrixRows = matrix.length
return {
y: getMatrixRows,
x: 0,
};
const range = Array.from({ length: getMatrixRows }, (_, y) =>
Array.from({ length: xNtime }, (_, x) => ({ x, y }))
)
.flat(1)
.sort((a, b) => {
let dst1 = distance(a, item)
let dst2 = distance(b, item)
if (dst1 > dst2) {
return 1
} else if (dst1 < dst2) {
return -1
} else {
return 0
}
})
for (const { x, y } of range.values()) {
if (isEmpty(matrix, x, y, w, h)) {
return { x, y }
}
}
return {
y: getMatrixRows,
x: 0
}
}
const getItem = (item, col) => {
return { ...item[col], id: item.id };
};
return { ...item[col], id: item.id }
}
const updateItem = (elements, active, position, col) => {
return elements.map((value) => {
if (value.id === active.id) {
return { ...value, [col]: { ...value[col], ...position } };
}
return value;
});
};
return elements.map((value) => {
if (value.id === active.id) {
return { ...value, [col]: { ...value[col], ...position } }
}
return value
})
}
export function moveItemsAroundItem(active, items, cols, original) {
// Get current item from the breakpoint
const activeItem = getItem(active, cols);
const ids = items.map((value) => value.id).filter((value) => value !== activeItem.id);
// Get current item from the breakpoint
const activeItem = getItem(active, cols)
const ids = items.map((value) => value.id).filter((value) => value !== activeItem.id)
const els = items.filter((value) => value.id !== activeItem.id);
const els = items.filter((value) => value.id !== activeItem.id)
// Update items
let newItems = updateItem(items, active, activeItem, cols);
// Update items
let newItems = updateItem(items, active, activeItem, cols)
let matrix = makeMatrixFromItemsIgnore(newItems, ids, getRowsCount(newItems, cols), cols);
let tempItems = newItems;
let matrix = makeMatrixFromItemsIgnore(newItems, ids, getRowsCount(newItems, cols), cols)
let tempItems = newItems
// Exclude resolved elements ids in array
let exclude: string[] = [];
// Exclude resolved elements ids in array
let exclude: string[] = []
els.forEach((item) => {
// Find position for element
let position = findFreeSpaceForItem(matrix, item[cols]);
// Exclude item
exclude.push(item.id);
els.forEach((item) => {
// Find position for element
let position = findFreeSpaceForItem(matrix, item[cols])
// Exclude item
exclude.push(item.id)
tempItems = updateItem(tempItems, item, position, cols);
tempItems = updateItem(tempItems, item, position, cols)
// Recreate ids of elements
let getIgnoreItems = ids.filter((value) => exclude.indexOf(value) === -1);
// Recreate ids of elements
let getIgnoreItems = ids.filter((value) => exclude.indexOf(value) === -1)
// Update matrix for next iteration
matrix = makeMatrixFromItemsIgnore(tempItems, getIgnoreItems, getRowsCount(tempItems, cols), cols);
});
// Update matrix for next iteration
matrix = makeMatrixFromItemsIgnore(
tempItems,
getIgnoreItems,
getRowsCount(tempItems, cols),
cols
)
})
// Return result
return tempItems;
// Return result
return tempItems
}
export function moveItem(active, items, cols, original) {
// Get current item from the breakpoint
const item = getItem(active, cols);
// Get current item from the breakpoint
const item = getItem(active, cols)
// Create matrix from the items expect the active
let matrix = makeMatrixFromItemsIgnore(items, [item.id], getRowsCount(items, cols), cols);
// Getting the ids of items under active Array<String>
const closeBlocks = findCloseBlocks(matrix, item);
// Getting the objects of items under active Array<Object>
let closeObj = findItemsById(closeBlocks, items);
// Getting whenever of these items is fixed
const fixed = closeObj.find((value) => value[cols].fixed);
// Create matrix from the items expect the active
let matrix = makeMatrixFromItemsIgnore(items, [item.id], getRowsCount(items, cols), cols)
// Getting the ids of items under active Array<String>
const closeBlocks = findCloseBlocks(matrix, item)
// Getting the objects of items under active Array<Object>
let closeObj = findItemsById(closeBlocks, items)
// Getting whenever of these items is fixed
const fixed = closeObj.find((value) => value[cols].fixed)
// If found fixed, reset the active to its original position
if (fixed) return items;
// If found fixed, reset the active to its original position
if (fixed) return items
// Update items
items = updateItem(items, active, item, cols);
// Update items
items = updateItem(items, active, item, cols)
// Create matrix of items expect close elements
matrix = makeMatrixFromItemsIgnore(items, closeBlocks, getRowsCount(items, cols), cols);
// Create matrix of items expect close elements
matrix = makeMatrixFromItemsIgnore(items, closeBlocks, getRowsCount(items, cols), cols)
// Create temp vars
let tempItems = items;
let tempCloseBlocks = closeBlocks;
// Create temp vars
let tempItems = items
let tempCloseBlocks = closeBlocks
// Exclude resolved elements ids in array
let exclude: string[] = [];
// Exclude resolved elements ids in array
let exclude: string[] = []
// Iterate over close elements under active item
closeObj.forEach((item) => {
// Find position for element
let position = findFreeSpaceForItem(matrix, item[cols]);
// Exclude item
exclude.push(item.id);
// Iterate over close elements under active item
closeObj.forEach((item) => {
// Find position for element
let position = findFreeSpaceForItem(matrix, item[cols])
// Exclude item
exclude.push(item.id)
// Assign the position to the element in the column
tempItems = updateItem(tempItems, item, position, cols);
// Assign the position to the element in the column
tempItems = updateItem(tempItems, item, position, cols)
// Recreate ids of elements
let getIgnoreItems = tempCloseBlocks.filter((value) => exclude.indexOf(value) === -1);
// Recreate ids of elements
let getIgnoreItems = tempCloseBlocks.filter((value) => exclude.indexOf(value) === -1)
// Update matrix for next iteration
matrix = makeMatrixFromItemsIgnore(tempItems, getIgnoreItems, getRowsCount(tempItems, cols), cols);
});
// Update matrix for next iteration
matrix = makeMatrixFromItemsIgnore(
tempItems,
getIgnoreItems,
getRowsCount(tempItems, cols),
cols
)
})
// Return result
return tempItems;
// Return result
return tempItems
}
// Helper function
export function normalize(items, col) {
let result = items.slice();
let result = items.slice()
result.forEach((value) => {
const getItem = value[col];
if (!getItem.static) {
result = moveItem(getItem, result, col, { ...getItem });
}
});
result.forEach((value) => {
const getItem = value[col]
if (!getItem.static) {
result = moveItem(getItem, result, col, { ...getItem })
}
})
return result;
return result
}
// Helper function
export function adjust<T>(items: FilledItem<T>[], col) {
let matrix = makeMatrix(getRowsCount(items, col), col);
let matrix = makeMatrix(getRowsCount(items, col), col)
let res: FilledItem<T>[] = [];
let res: FilledItem<T>[] = []
items.forEach((item) => {
let position = findFreeSpaceForItem(matrix, item[col]);
items.forEach((item) => {
let position = findFreeSpaceForItem(matrix, item[col])
res.push({
...item,
[col]: {
...item[col],
...position,
},
});
res.push({
...item,
[col]: {
...item[col],
...position
}
})
matrix = makeMatrixFromItems(res, getRowsCount(res, col), col);
});
matrix = makeMatrixFromItems(res, getRowsCount(res, col), col)
})
return res;
return res
}
export function getUndefinedItems(items, col, breakpoints) {
return items
.map((value) => {
if (!value[col]) {
return value.id;
}
})
.filter(Boolean);
return items
.map((value) => {
if (!value[col]) {
return value.id
}
})
.filter(Boolean)
}
export function getClosestColumn(items, item, col, breakpoints) {
return breakpoints
.map(([_, column]) => item[column] && column)
.filter(Boolean)
.reduce(function (acc, value) {
const isLower = Math.abs(value - col) < Math.abs(acc - col);
return breakpoints
.map(([_, column]) => item[column] && column)
.filter(Boolean)
.reduce(function (acc, value) {
const isLower = Math.abs(value - col) < Math.abs(acc - col)
return isLower ? value : acc;
});
return isLower ? value : acc
})
}
export function specifyUndefinedColumns(items, col, breakpoints) {
let matrix = makeMatrixFromItems(items, getRowsCount(items, col), col);
let matrix = makeMatrixFromItems(items, getRowsCount(items, col), col)
const getUndefinedElements = getUndefinedItems(items, col, breakpoints);
const getUndefinedElements = getUndefinedItems(items, col, breakpoints)
let newItems = [...items];
let newItems = [...items]
getUndefinedElements.forEach((elementId) => {
const getElement = items.find((item) => item.id === elementId);
getUndefinedElements.forEach((elementId) => {
const getElement = items.find((item) => item.id === elementId)
const closestColumn = getClosestColumn(items, getElement, col, breakpoints);
const closestColumn = getClosestColumn(items, getElement, col, breakpoints)
const position = findFreeSpaceForItem(matrix, getElement[closestColumn]);
const position = findFreeSpaceForItem(matrix, getElement[closestColumn])
const newItem = {
...getElement,
[col]: {
...getElement[closestColumn],
...position,
},
};
const newItem = {
...getElement,
[col]: {
...getElement[closestColumn],
...position
}
}
newItems = newItems.map((value) => (value.id === elementId ? newItem : value));
newItems = newItems.map((value) => (value.id === elementId ? newItem : value))
matrix = makeMatrixFromItems(newItems, getRowsCount(newItems, col), col);
});
return newItems;
matrix = makeMatrixFromItems(newItems, getRowsCount(newItems, col), col)
})
return newItems
}
@@ -2,15 +2,15 @@ import type { FilledItem } from "../types";
export const makeMatrix: (w: number, h: number) => any[][] = (rows, cols) => Array.from(Array(rows), () => new Array(cols)); // make 2d array
export function makeMatrixFromItems<T>(items: FilledItem<T>[], _row, _col): FilledItem<T>[][] {
let matrix = makeMatrix(_row, _col);
export function makeMatrixFromItems<T>(items: FilledItem<T>[], row: number, col: number): FilledItem<T>[][] {
let matrix = makeMatrix(row, col);
for (var i = 0; i < items.length; i++) {
const value = items[i][_col];
const value = items[i][col];
if (value) {
const { x, y, h } = value;
const id = items[i].id;
const w = Math.min(_col, value.w);
const w = Math.min(col, value.w);
for (var j = y; j < y + h; j++) {
const row = matrix[j];