diff --git a/data/configs/default.json b/data/configs/default.json index 6e713cc61..660013340 100644 --- a/data/configs/default.json +++ b/data/configs/default.json @@ -17,156 +17,6 @@ } ], "apps": [ - { - "id": "5df743d9-5cb1-457c-85d2-64ff86855652", - "name": "Your app", - "url": "https://homarr.dev", - "appearance": { - "iconUrl": "/imgs/logo/logo.png" - }, - "network": { - "enabledStatusChecker": false, - "okStatus": [] - }, - "behaviour": { - "isOpeningNewTab": true, - "externalUrl": "https://homarr.dev" - }, - "area": { - "type": "wrapper", - "properties": { - "id": "default" - } - }, - "shape": { - "location": { - "x": 0, - "y": 7 - }, - "size": { - "width": 6, - "height": 4 - } - }, - "integration": { - "type": null, - "properties": [] - } - }, - { - "id": "76217a87-7151-42d0-b0cf-1b72aef63f83", - "name": "Small app", - "url": "https://homarr.dev", - "appearance": { - "iconUrl": "/imgs/logo/logo.png" - }, - "network": { - "enabledStatusChecker": false, - "okStatus": [] - }, - "behaviour": { - "isOpeningNewTab": true, - "externalUrl": "https://homarr.dev" - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "location": { - "x": 4, - "y": 0 - }, - "size": { - "width": 1, - "height": 1 - } - }, - "integration": { - "type": null, - "properties": [] - } - }, - { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330", - "name": "Contribute", - "url": "https://github.com/ajnart/homarr", - "behaviour": { - "onClickUrl": "https://github.com/ajnart/homarr", - "externalUrl": "https://github.com/ajnart/homarr", - "isOpeningNewTab": true - }, - "network": { - "enabledStatusChecker": false, - "okStatus": [ - 200 - ] - }, - "appearance": { - "iconUrl": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/github.png" - }, - "integration": { - "type": null, - "properties": [] - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "location": { - "x": 7, - "y": 0 - }, - "size": { - "width": 1, - "height": 1 - } - } - }, - { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337", - "name": "Discord", - "url": "https://discord.com/invite/aCsmEV5RgA", - "behaviour": { - "onClickUrl": "https://discord.com/invite/aCsmEV5RgA", - "isOpeningNewTab": true, - "externalUrl": "https://discord.com/invite/aCsmEV5RgA" - }, - "network": { - "enabledStatusChecker": false, - "okStatus": [ - 200 - ] - }, - "appearance": { - "iconUrl": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/discord.png" - }, - "integration": { - "type": null, - "properties": [] - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "location": { - "x": 6, - "y": 0 - }, - "size": { - "width": 1, - "height": 1 - } - } - }, { "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33a", "name": "Documentation", @@ -236,13 +86,163 @@ }, "shape": { "location": { - "x": 5, + "x": 6, + "y": 1 + }, + "size": { + "width": 1, + "height": 2 + } + } + }, + { + "id": "76217a87-7151-42d0-b0cf-1b72aef63f83", + "name": "Small app", + "url": "https://homarr.dev", + "appearance": { + "iconUrl": "/imgs/logo/logo.png" + }, + "network": { + "enabledStatusChecker": false, + "okStatus": [] + }, + "behaviour": { + "isOpeningNewTab": true, + "externalUrl": "https://homarr.dev" + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "location": { + "x": 4, "y": 0 }, "size": { "width": 1, "height": 1 } + }, + "integration": { + "type": null, + "properties": [] + } + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337", + "name": "Discord", + "url": "https://discord.com/invite/aCsmEV5RgA", + "behaviour": { + "onClickUrl": "https://discord.com/invite/aCsmEV5RgA", + "isOpeningNewTab": true, + "externalUrl": "https://discord.com/invite/aCsmEV5RgA" + }, + "network": { + "enabledStatusChecker": false, + "okStatus": [ + 200 + ] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/discord.png" + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "location": { + "x": 5, + "y": 2 + }, + "size": { + "width": 1, + "height": 2 + } + } + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330", + "name": "Contribute", + "url": "https://github.com/ajnart/homarr", + "behaviour": { + "onClickUrl": "https://github.com/ajnart/homarr", + "externalUrl": "https://github.com/ajnart/homarr", + "isOpeningNewTab": true + }, + "network": { + "enabledStatusChecker": false, + "okStatus": [ + 200 + ] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/github.png" + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "location": { + "x": 7, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + } + }, + { + "id": "5df743d9-5cb1-457c-85d2-64ff86855652", + "name": "Your app", + "url": "https://homarr.dev", + "appearance": { + "iconUrl": "/imgs/logo/logo.png" + }, + "network": { + "enabledStatusChecker": false, + "okStatus": [] + }, + "behaviour": { + "isOpeningNewTab": true, + "externalUrl": "https://homarr.dev" + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "location": { + "x": 0, + "y": 7 + }, + "size": { + "width": 6, + "height": 4 + } + }, + "integration": { + "type": null, + "properties": [] } }, { @@ -269,7 +269,7 @@ "shape": { "location": { "x": 8, - "y": 10 + "y": 9 }, "size": { "width": 4, @@ -297,10 +297,10 @@ "shape": { "location": { "x": 0, - "y": 2 + "y": 0 }, "size": { - "width": 5, + "width": 3, "height": 5 } } @@ -323,8 +323,8 @@ "y": 0 }, "size": { - "width": 4, - "height": 1 + "width": 3, + "height": 2 } } }, @@ -341,12 +341,12 @@ }, "shape": { "location": { - "x": 8, + "x": 9, "y": 0 }, "size": { - "width": 4, - "height": 1 + "width": 3, + "height": 2 } } } diff --git a/src/components/Dashboard/Wrappers/gridstack/column-sorting.ts b/src/components/Dashboard/Wrappers/gridstack/column-sorting.ts new file mode 100644 index 000000000..659fa4928 --- /dev/null +++ b/src/components/Dashboard/Wrappers/gridstack/column-sorting.ts @@ -0,0 +1,88 @@ +import { GridStackNode } from 'fily-publish-gridstack'; + +type GridstackColumnSortingFn = ( + column: number, + prevColumn: number, + newNodes: GridStackNode[], + nodes: GridStackNode[] +) => void; + +const getGridstackAttribute = (node: GridStackNode, path: 'x' | 'y' | 'w' | 'h'): number => + parseInt(node.el!.getAttribute(`data-gridstack-${path}`)!, 10); + +const getGridstackAttributes = (node: GridStackNode) => ({ + width: getGridstackAttribute(node, 'w'), + height: getGridstackAttribute(node, 'h'), + x: getGridstackAttribute(node, 'x'), + y: getGridstackAttribute(node, 'y'), +}); + +type Type = ReturnType & { node: GridStackNode }; + +const nextItem = (start: number, end: number, nodes: Type[]): number => { + const next = nodes + .filter((x) => x.y <= end && x.y + x.height - 1 > end) + .sort((a, b) => a.y + a.height - (b.y + b.height)) + .at(0); + if (!next) return end; + return nextItem(start, next.height - 1 + next.y, nodes); +}; + +const nextRowHeight = (nodes: Type[], values: number[], current = 0) => { + const item = nodes.find((x) => x.y >= current); + if (!item) return; + const next = nextItem(item.y, item.y + item.height - 1, nodes); + values.push(next + 1 - item.y); + nextRowHeight(nodes, values, next); +}; + +const getRowHeights = (nodes: Type[]) => { + const rowHeights: number[] = []; + nextRowHeight( + nodes, + rowHeights + ); + return rowHeights; +}; + +const sortNodesByYAndX = (a: GridStackNode, b: GridStackNode) => { + const aAttributes = getGridstackAttributes(a); + const bAttributes = getGridstackAttributes(b); + + const differenceY = aAttributes.y - bAttributes.y; + + return differenceY !== 0 ? differenceY : aAttributes.x - bAttributes.x; +}; + +export const commonColumnSorting: GridstackColumnSortingFn = ( + column, + prevColumn, + newNodes, + nodes +) => { + if (column === prevColumn) { + newNodes.concat(nodes); + return; + } + + let nextRow = 0; + let available = column; + + const sortedNodes = nodes.sort(sortNodesByYAndX); + const mappedNodes = sortedNodes.map((node) => ({ + ...getGridstackAttributes(node), + node, + })); + const rowHeights = getRowHeights(mappedNodes); + + const rowItems: Type[][] = []; + + // TODO: fix issue with spaces between. + let rowTotal = 0; + rowHeights.forEach(height => { + rowItems.push(mappedNodes.filter(node => node.y >= rowTotal && node.y < rowTotal + height)); + rowTotal += height; + }); + + console.log(rowItems); +}; diff --git a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts index 7b9cfc8fe..2d05233eb 100644 --- a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts +++ b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts @@ -16,8 +16,46 @@ import { AppType } from '../../../../types/app'; import { AreaType } from '../../../../types/area'; import { IWidget } from '../../../../widgets/widgets'; import { useEditModeStore } from '../../Views/useEditModeStore'; +import { commonColumnSorting } from './column-sorting'; import { initializeGridstack } from './init-gridstack'; +const getGridstackAttribute = (node: GridStackNode, path: 'x' | 'y' | 'w' | 'h'): number => parseInt(node.el!.getAttribute(`data-gridstack-${path}`)!, 10); + + const getGridstackAttributes = (node: GridStackNode) => ({ + width: getGridstackAttribute(node, 'w'), + height: getGridstackAttribute(node, 'h'), + x: getGridstackAttribute(node, 'x'), + y: getGridstackAttribute(node, 'y'), + }); + +type Type = (ReturnType & { node: GridStackNode }); + +const nextItem = (start: number, end: number, nodes: Type[]): number => { + const next = nodes + .filter(x => x.y <= end && x.y + x.height - 1 > end) + .sort((a, b) => (a.y + a.height) - (b.y + b.height)) + .at(0); +if (!next) return end; +return nextItem(start, next.height - 1 + next.y, nodes); +}; + +const nextRowHeight = (nodes: Type[], values: number[], current = 0) => { + const item = nodes.find(x => x.y >= current); + if (!item) return; + const next = nextItem(item.y, item.y + item.height - 1, nodes); + values.push(next + 1 - item.y); + nextRowHeight(nodes, values, next); +}; + +const getRowHeights = (nodes: GridStackNode[]) => { + const rowHeights: number[] = []; + nextRowHeight(nodes.map((node) => ({ + ...getGridstackAttributes(node), + node, + })), rowHeights); + return rowHeights; +}; + interface UseGristackReturnType { apps: AppType[]; widgets: IWidget[]; @@ -90,24 +128,16 @@ export const useGridstack = ( if (areaType === 'sidebar') return; gridRef.current?.column( wrapperColumnCount, - (column, prevColumn, newNodes, nodes) => { + /*(column, prevColumn, newNodes, nodes) => { let nextRow = 0; let available = column; + let maxHeightInRow = 1; if (column === prevColumn) { newNodes.concat(nodes); return; } - const getGridstackAttribute = (node: GridStackNode, path: 'x' | 'y' | 'w' | 'h'): number => parseInt(node.el!.getAttribute(`data-gridstack-${path}`)!, 10); - - const getGridstackAttributes = (node: GridStackNode) => ({ - width: getGridstackAttribute(node, 'w'), - height: getGridstackAttribute(node, 'h'), - x: getGridstackAttribute(node, 'x'), - y: getGridstackAttribute(node, 'y'), - }); - const sortNodes = (a: GridStackNode, b: GridStackNode) => { const aAttributes = getGridstackAttributes(a); const bAttributes = getGridstackAttributes(b); @@ -117,36 +147,39 @@ export const useGridstack = ( return differenceY !== 0 ? differenceY : aAttributes.x - bAttributes.x; }; - const sorted = nodes.sort(sortNodes); + const sortedNodes = nodes.sort(sortNodes); + const rowHeights = getRowHeights(sortedNodes); - console.log(sorted); - - sorted.forEach((node) => { + sortedNodes.forEach((node) => { const newnode = node; const width = parseInt(newnode.el!.getAttribute('data-gridstack-w')!, 10); const height = parseInt(newnode.el!.getAttribute('data-gridstack-h')!, 10); const x = parseInt(newnode.el!.getAttribute('data-gridstack-x')!, 10); const y = parseInt(newnode.el!.getAttribute('data-gridstack-y')!, 10); - const moveYDown = 1; + maxHeightInRow = height > maxHeightInRow ? height : maxHeightInRow; + + const continueInNextRow = () => { + nextRow += maxHeightInRow; + maxHeightInRow = 1; + available = column; + return nextRow; + }; if (column === 3) { newnode.x = available >= width ? 3 - available : 0; - newnode.y = available === 3 || available >= width ? nextRow : nextRow += moveYDown; + newnode.y = available === 3 || available >= width ? nextRow : continueInNextRow(); if (width > 3) { newnode.w = 3; - nextRow += moveYDown; - available = 3; + continueInNextRow(); } else if (available >= width) { available -= width; if (available === 0) { - nextRow += moveYDown; - available = 3; + continueInNextRow(); } } else if (available < width) { - newnode.y = newnode.y! + moveYDown; + newnode.y = continueInNextRow(); available = 3 - width; - nextRow += moveYDown; } } else if (column === 6) { newnode.x = available >= width ? 6 - available : 0; @@ -154,29 +187,25 @@ export const useGridstack = ( if (width > 6) { newnode.w = 6; - nextRow += moveYDown; - available = 6; + continueInNextRow(); } else if (available >= width) { available -= width; if (available === 0) { - nextRow += moveYDown; - available = 6; + continueInNextRow(); } } else if (available < width) { - newnode.y = newnode.y! + moveYDown; + newnode.y = continueInNextRow(); available = 6 - width; - nextRow += moveYDown; } } else { newnode.x = y % 2 === 1 ? x + 6 : x; newnode.y = Math.floor(y / 2); } - console.log(newnode); - newNodes.push(newnode); }); - } + }*/ + commonColumnSorting ); }, [wrapperColumnCount]);