mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 08:07:03 +00:00
feat(frontend): improve drag-n-drop behavior
This commit is contained in:
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user