From cfd489a55059e7b6843f99bab261c70b3852e6a2 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 13 Mar 2023 12:44:39 +0100 Subject: [PATCH] feat(frontend): improve drag-n-drop behavior --- .../components/apps/svelte-grid/Grid.svelte | 21 +- .../components/apps/svelte-grid/utils/item.ts | 337 ++++++++++-------- .../apps/svelte-grid/utils/matrix.ts | 8 +- 3 files changed, 210 insertions(+), 156 deletions(-) diff --git a/frontend/src/lib/components/apps/svelte-grid/Grid.svelte b/frontend/src/lib/components/apps/svelte-grid/Grid.svelte index 9684b9ca83..56f564f406 100644 --- a/frontend/src/lib/components/apps/svelte-grid/Grid.svelte +++ b/frontend/src/lib/components/apps/svelte-grid/Grid.svelte @@ -87,8 +87,21 @@ return () => sizeObserver.disconnect() }) + let initItems: FilledItem[] | undefined = undefined const updateMatrix = ({ detail }) => { - let activeItem = getItemById(detail.id, items) + let isPointerUp = detail.isPointerUp + let citems: FilledItem[] + 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() diff --git a/frontend/src/lib/components/apps/svelte-grid/utils/item.ts b/frontend/src/lib/components/apps/svelte-grid/utils/item.ts index 5c25f342c8..555b4e4827 100644 --- a/frontend/src/lib/components/apps/svelte-grid/utils/item.ts +++ b/frontend/src/lib/components/apps/svelte-grid/utils/item.ts @@ -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(matrix: FilledItem[][], 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 - const closeBlocks = findCloseBlocks(matrix, item); - // Getting the objects of items under active Array - 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 + const closeBlocks = findCloseBlocks(matrix, item) + // Getting the objects of items under active Array + 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(items: FilledItem[], col) { - let matrix = makeMatrix(getRowsCount(items, col), col); + let matrix = makeMatrix(getRowsCount(items, col), col) - let res: FilledItem[] = []; + let res: FilledItem[] = [] - 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 } diff --git a/frontend/src/lib/components/apps/svelte-grid/utils/matrix.ts b/frontend/src/lib/components/apps/svelte-grid/utils/matrix.ts index ec91b3d553..410b6027f6 100644 --- a/frontend/src/lib/components/apps/svelte-grid/utils/matrix.ts +++ b/frontend/src/lib/components/apps/svelte-grid/utils/matrix.ts @@ -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(items: FilledItem[], _row, _col): FilledItem[][] { - let matrix = makeMatrix(_row, _col); +export function makeMatrixFromItems(items: FilledItem[], row: number, col: number): FilledItem[][] { + 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];