`
}
\ No newline at end of file
diff --git a/public/js/components/Dashboard/Section.js b/public/js/components/Dashboard/Section.js
index 84bcdd58e..868672a50 100644
--- a/public/js/components/Dashboard/Section.js
+++ b/public/js/components/Dashboard/Section.js
@@ -1,14 +1,16 @@
import BsConfirm from "../Bootstrap/Confirm.js";
+import DropGrid from '../Drop/Grid.js'
import DashboardItem from "./Item.js";
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
-// TODO(chris): handle overflow (moving outside the box)
export default {
components: {
+ DropGrid,
DashboardItem
},
inject: {
adminMode: {
+ type: Boolean,
default: false
}
},
@@ -23,371 +25,43 @@ export default {
],
data() {
return {
- gridWidth: 0,
- changeHeight: 1,
- movedObjects: [],
- editMode: this.adminMode ? 1 : 0,
- gridXLast: -1, // NOTE(chris): 0 based
- gridYLast: -1,
- dragging: 0,
- dataTransfer: {},
- gridAddFound: false,
- gridXAdd: -1, // NOTE(chris): 0 based
- gridYAdd: -1
+ gridWidth: 1,
+ editMode: this.adminMode
}
},
computed: {
items() {
- this.widgets.forEach((item,i) => item.index = i);
- return this.widgets;
- },
- itemCoords() { // NOTE(chris): 1 based
- if (!this.gridWidth)
- return [];
- let itemCoords = this.items.map(item => item.place[this.gridWidth] || this.createItemPlacement(item));
- // TODO(chris): verify positions & sizes
- let occupiers = [];
- let wrongPlacedItems = [];
- let gridWidth = this.gridWidth;
- this.items.forEach(item => {
- let x = item._x !== undefined ? item._x : itemCoords[item.index].x;
- let y = item._y !== undefined ? item._y : itemCoords[item.index].y;
- let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
- let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
- // TODO(chris): check with and height params here?
- for (var i = 0; i < w; i++) {
- for (var j = 0; j < h; j++) {
- var c = (y+j-1) * gridWidth + (x+i-1);
- // NOTE(chris): check for overlaping items
- if (occupiers[c] !== undefined) {
- //console.log('try to add ' + item.index + ' to ' + x + '/' + y + ', but ' + occupiers[c] + ' is already there');
- // NOTE(chris): remove possible other entries of this item
- for (var c2 = c; c2; c2--)
- if (occupiers[c2] == item.index)
- occupiers[c2] = undefined;
- wrongPlacedItems.push(item);
- return;
- }
- occupiers[c] = item.index;
- }
- }
+ return this.widgets.map(item => {
+ return {...item, ...(item.place[this.gridWidth] || {})};
});
- wrongPlacedItems.forEach(item => {
- let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
- let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
- for (var c = 0; c < occupiers.length + gridWidth; c++) {
- if (occupiers[c] === undefined) {
- var occupied = false, i, j;
- for (i = 0; i < w; i++) {
- for (j = 0; j < h; j++) {
- if (occupiers[c + i + j * gridWidth] !== undefined) {
- i = w;
- occupied = true;
- break;
- }
- }
- }
- if (!occupied) {
- item.place[gridWidth].x = c%gridWidth + 1;
- item.place[gridWidth].y = Math.floor(c/gridWidth) + 1;
- for (i = 0; i < w; i++) {
- for (j = 0; j < h; j++) {
- occupiers[c + i + j * gridWidth] = item.index;
- }
- }
- return;
- }
- }
- }
- });
- return itemCoords;
- },
- gridHeight() {
- if (!this.gridWidth || !this.changeHeight)
- return 0;
- let minH = 0;
- this.itemCoords.forEach((item,i) => minH = Math.max(minH, (!this.editMode && this.items[i].hidden) ? 0 : item.y - 1 + item.h));
- // TODO(chris): the extraline should only be present if all slots are occupied
- if (minH == 0 && this.editMode)
- return 1;
- return minH + this.editMode*this.dragging;
- },
- gridOccupiers() { // NOTE(chris): 0 based
- let occupiers = [];
- let gridWidth = this.gridWidth;
- this.items.forEach(item => {
- let x = item._x !== undefined ? item._x : this.itemCoords[item.index].x;
- let y = item._y !== undefined ? item._y : this.itemCoords[item.index].y;
- let w = item._w !== undefined ? item._w : this.itemCoords[item.index].w;
- let h = item._h !== undefined ? item._h : this.itemCoords[item.index].h;
- for (var i = 0; i < w; i++) {
- for (var j = 0; j < h; j++) {
- var c = (y+j-1) * gridWidth + (x+i-1);
- occupiers[c] = item.index;
- }
- }
- });
- return occupiers;
- },
- cssBg() {
- if (!this.editMode || this.dragging || !this.gridAddFound)
- return 'transparent';
- let x = this.gridXAdd, y = this.gridYAdd, h = this.gridHeight-1 || 1, w = this.gridWidth-1 || 1;
- return `url('data:image/svg+xml;utf8,
')` + (100 * x/w) + '% ' + (100 * y/h) + '%/' + (100/this.gridWidth) + '% ' + (100/this.gridHeight) + '% no-repeat;cursor:pointer';
}
},
methods: {
- addWidget(evt) {
- if (evt.target != this.$refs.container || !this.editMode)
- return;
- const rect = this.$refs.container.getBoundingClientRect();
- const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
- const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
- if (this.gridOccupiers[gridY * this.gridWidth + gridX] === undefined) {
- let widget = { widget: 1, config: {}, place: {}, custom: 1 };
- widget.place[this.gridWidth] = {
- x: gridX+1,
- y: gridY+1,
- w: 1,
- h: 1
- };
- this.$emit('widgetAdd', this.name, widget);
+ checkResizeLimit(item, w, h) {
+ // NOTE(chris): widgets needs to be loaded for this to work
+ let widget = CachedWidgetLoader.getWidget(item.widget);
+ if (widget) {
+ let minmaxW = widget.setup.width;
+ if (minmaxW.max)
+ minmaxW.min = minmaxW.min || 1;
+ else
+ minmaxW = {min:minmaxW,max:minmaxW};
+ if (w < minmaxW.min)
+ w = minmaxW.min;
+ if (w > minmaxW.max)
+ w = minmaxW.max;
+
+ let minmaxH = widget.setup.height;
+ if (minmaxH.max)
+ minmaxH.min = minmaxH.min || 1;
+ else
+ minmaxH = {min:minmaxH,max:minmaxH};
+ if (h < minmaxH.min)
+ h = minmaxH.min;
+ if (h > minmaxH.max)
+ h = minmaxH.max;
}
- },
- createItemPlacement(item) {
- // TODO(chris): create correct default placement if it is not there
- item.place[this.gridWidth] = {x:1,y:1,w:1,h:1};
- /*var freeList = [], nextId = 0;
- this.items.forEach(item => {
- if (!item.place[this.gridWidth]) {
- if (!this.gridWidth) {
- item.place[this.gridWidth] = {x:1,y:nextId++,w:1,h:1};
- } else {
- // TODO(chris): IMPLEMENT widths & heights
- if (freeList[nextId])
- while (freeList[++nextId]);
- freeList[nextId] = 1;
- item.place[this.gridWidth] = {x:(nextId%this.gridWidth)+1,y:Math.floor(nextId/this.gridWidth)+1,w:1,h:1};
- }
- }
- });*/
- return item.place[this.gridWidth];
- },
- onMouseMove(evt) {
- if (!this.editMode || this.dragging) {
- this.gridXAdd = this.gridYAdd = -1;
- return;
- }
-
- const rect = this.$refs.container.getBoundingClientRect();
- const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
- const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
-
- if (this.gridXAdd == gridX && this.gridYAdd == gridY)
- return;
- // TODO(chris): only mark it when its not occupied
- this.gridXAdd = gridX;
- this.gridYAdd = gridY;
- this.gridAddFound = (this.gridOccupiers[gridX + gridY * this.gridWidth] === undefined);
- },
- onMouseLeave() {
- this.gridXAdd = this.gridYAdd = -1;
- this.gridAddFound = false;
- },
- startDrag(evt, item) {
- this.dragging = 1;
- this.gridXLast = -1;
- this.gridYLast = -1;
- item._x = this.itemCoords[item.index].x;
- item._y = this.itemCoords[item.index].y;
-
- evt.dataTransfer.dropEffect = 'move';
- evt.dataTransfer.effectAllowed = 'move';
- this.dataTransfer = {
- action: 'm',
- id: item.index,
- w: this.itemCoords[item.index].w,
- h: this.itemCoords[item.index].h
- }
- },
- startResize(evt, item) {
- this.dragging = 1;
- this.gridXLast = -1;
- this.gridYLast = -1;
- item._w = this.itemCoords[item.index].w;
- item._h = this.itemCoords[item.index].h;
-
- evt.dataTransfer.setDragImage(evt.target, -99999, -99999);
- evt.dataTransfer.dropEffect = 'move';
- evt.dataTransfer.effectAllowed = 'move';
- this.dataTransfer = {
- action: 'r',
- id: item.index,
- x: this.itemCoords[item.index].x,
- y: this.itemCoords[item.index].y
- }
- },
- occupyFields(id, x, y, w, h) {
- var c;
- while ((c = this.movedObjects.pop())) {
- if (this.items[c]._y !== undefined) {
- this.items[c].place[this.gridWidth].y = this.items[c]._y;
- this.items[c]._y = undefined;
- }
- }
- var move = {};
- move[id] = this.items[id];
- this.getOccupiedItems(x,y,w,h,move);
- h = y + h;
- y = 0;
- for (x in move) {
- if (x != id) {
- c = move[x]._y !== undefined ? move[x]._y : this.itemCoords[x].y;
- if (c < h)
- y = Math.max(h-c, y);
- }
- }
- for (x in move) {
- if (x != id) {
- this.movedObjects.push(x);
- if (move[x]._y === undefined) {
- move[x]._y = this.itemCoords[x].y;
- }
- move[x].place[this.gridWidth].y = move[x]._y + y;
- }
- }
- },
- getOccupiedItems(x, y, w, h, move) {
- var i, j, c;
- for (i = 0; i < w; i++) {
- for (j = 0; j < h; j++) {
- c = (y+j-1) * this.gridWidth + (x+i-1);
- if (this.gridOccupiers[c] !== undefined && !move[this.gridOccupiers[c]]) {
- move[this.gridOccupiers[c]] = this.items[this.gridOccupiers[c]];
- c = this.itemCoords[this.gridOccupiers[c]];
- this.getOccupiedItems(c.x, c.y + 1, c.w, c.h, move);
- }
- }
- }
- },
- onDragOver(evt) {
- let id, x, y, w, h;
- const action = this.dataTransfer.action;
- const rect = this.$refs.container.getBoundingClientRect();
- const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
- const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
-
- if (this.gridXLast == gridX && this.gridYLast == gridY)
- return;
- this.gridXLast = gridX;
- this.gridYLast = gridY;
-
- if (action == 'm') {
- x = Math.max(gridX + 1, 1);
- y = Math.max(gridY + 1, 1);
- w = parseInt(this.dataTransfer.w);
- h = parseInt(this.dataTransfer.h);
-
- if (x + w > this.gridWidth + 1)
- x = this.gridWidth + 1 - w;
-
- id = this.dataTransfer.id;
- this.occupyFields(id, x, y, w, h);
-
- this.itemCoords[id].x = x;
- this.itemCoords[id].y = y;
- } else if (action == 'r') {
- x = parseInt(this.dataTransfer.x);
- y = parseInt(this.dataTransfer.y);
- w = gridX + 2 - x;
- h = gridY + 2 - y;
- w = Math.max(1, w);
- h = Math.max(1, h);
-
- if (x + w > this.gridWidth + 1)
- w = this.gridWidth + 1 - x;
-
- id = this.dataTransfer.id;
- let widget = CachedWidgetLoader.getWidget(this.items[id].widget);
- if (widget) {
- let minmaxW = widget.setup.width;
- if (minmaxW.max)
- minmaxW.min = minmaxW.min || 1;
- else
- minmaxW = {min:minmaxW,max:minmaxW};
- if (w < minmaxW.min)
- w = minmaxW.min;
- if (w > minmaxW.max)
- w = minmaxW.max;
-
- let minmaxH = widget.setup.height;
- if (minmaxH.max)
- minmaxH.min = minmaxH.min || 1;
- else
- minmaxH = {min:minmaxH,max:minmaxH};
- if (h < minmaxH.min)
- h = minmaxH.min;
- if (h > minmaxH.max)
- h = minmaxH.max;
- }
-
- this.occupyFields(id, x, y, w, h);
-
- this.itemCoords[id].w = w;
- this.itemCoords[id].h = h;
- }
- },
- onDrop() {
- this.dragging = 0;
- this.gridXLast = -1;
- this.gridYLast = -1;
- let id = 0;
- let update = {};
- while ((id = this.movedObjects.pop())) {
- if (this.items[id]._y !== undefined) {
- if (this.itemCoords[id].y != this.items[id]._y) {
- update[this.items[id].id] = {place:{}};
- update[this.items[id].id].place[this.gridWidth] = {y:this.itemCoords[id].y};
- }
- this.items[id]._y = undefined;
- }
- }
-
- id = this.dataTransfer.id;
-
- const action = this.dataTransfer.action;
- update[this.items[id].id] = {place:{}};
- update[this.items[id].id].place[this.gridWidth] = {};
-
- if (action == 'm') {
- if (this.items[id]._x !== undefined) {
- if (this.itemCoords[id].x != this.items[id]._x) {
- update[this.items[id].id].place[this.gridWidth].x = this.itemCoords[id].x;
- }
- this.items[id]._x = undefined;
- }
- if (this.items[id]._y !== undefined) {
- if (this.itemCoords[id].y != this.items[id]._y) {
- update[this.items[id].id].place[this.gridWidth].y = this.itemCoords[id].y;
- }
- this.items[id]._y = undefined;
- }
- } else if (action == 'r') {
- update[this.items[id].id].place[this.gridWidth].w = this.itemCoords[id].w;
- update[this.items[id].id].place[this.gridWidth].h = this.itemCoords[id].h;
- }
-
- if (update[this.items[id].id].place[this.gridWidth].x === undefined &&
- update[this.items[id].id].place[this.gridWidth].y === undefined &&
- update[this.items[id].id].place[this.gridWidth].w === undefined &&
- update[this.items[id].id].place[this.gridWidth].h === undefined) {
- delete update[this.items[id].id].place[this.gridWidth];
- }
-
- this.updatePreset(update);
-
- // TODO(chris): find better way to trigger change for gridHeight
- this.changeHeight++
+ return [w, h];
},
removeWidget(item, revert) {
if (item.custom) {
@@ -400,9 +74,32 @@ export default {
},
saveConfig(config, item) {
let payload = {};
- payload[item.id] = { config };console.log(payload);
+ payload[item.id] = { config };
this.updatePreset(payload);
},
+ updatePositions(updated) {
+ let result = {};
+ updated.forEach(update => {
+ let item = {...update.item};
+ if (!item.place[this.gridWidth])
+ item.place[this.gridWidth] = {x: 0, y: 0, w: 1, h: 1};
+ delete item.x;
+ delete item.y;
+ delete item.w;
+ delete item.h;
+ if (update.x !== undefined)
+ item.place[this.gridWidth].x = update.x;
+ if (update.y !== undefined)
+ item.place[this.gridWidth].y = update.y;
+ if (update.w !== undefined)
+ item.place[this.gridWidth].w = update.w;
+ if (update.h !== undefined)
+ item.place[this.gridWidth].h = update.h;
+
+ result[item.id] = item;
+ });
+ this.updatePreset(result);
+ },
updatePreset(update) {
let payload = {};
payload[this.name] = update;
@@ -412,55 +109,34 @@ export default {
mounted() {
let self = this;
let cont = self.$refs.container;
- self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
+ self.gridWidth = parseInt(window.getComputedStyle(cont).getPropertyValue('--fhc-dashboard-grid-size'));
window.addEventListener('resize', () => {
- for (const child of cont.children) {
- child.style.display = 'none';
- }
- self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
- for (const child of cont.children) {
- child.style.display = '';
- }
+ self.gridWidth = parseInt(window.getComputedStyle(cont).getPropertyValue('--fhc-dashboard-grid-size'));
});
},
- template: `
+ template: `
+
`
-}
\ No newline at end of file
+}
diff --git a/public/js/components/Drop/Grid.js b/public/js/components/Drop/Grid.js
new file mode 100644
index 000000000..62e4c15ea
--- /dev/null
+++ b/public/js/components/Drop/Grid.js
@@ -0,0 +1,377 @@
+// TODO(chris): Comments
+
+import GridItem from './Grid/Item.js';
+import GridLogic from '../../composables/GridLogic.js';
+
+const MODE_IDLE = 0;
+const MODE_MOVE = 1;
+const MODE_RESIZE = 2;
+
+export default {
+ components: {
+ GridItem
+ },
+ inject: {
+ },
+ props: {
+ cols: Number,
+ items: Array,
+ resizeLimit: Function,
+ active: {
+ type: Boolean,
+ default: true
+ },
+ marginForExtraRow: {
+ type: Number,
+ default: 0
+ }
+ },
+ emits: [
+ "rearrangeItems",
+ "newItem"
+ ],
+ data() {
+ return {
+ x: -1,
+ y: -1,
+ mode: MODE_IDLE,
+ grid: null,
+ dragGrid: null,
+ permUpdates: [],
+ positionUpdates: null,
+ fixedPositionUpdates: null,
+ draggedOffset: [0,0],
+ draggedItem: null,
+ additionalRow: null
+ }
+ },
+ computed: {
+ rows() {
+ if ((this.mode == MODE_MOVE || this.mode == MODE_RESIZE) && this.dragGrid)
+ return this.dragGrid.h;
+ return this.grid ? this.grid.h : 1;
+ },
+ gridStyle() {
+ const addH = this.active ? this.marginForExtraRow : 0;
+ return {
+ '--fhc-dg-row-height': 100/(this.rows + addH) + '%',
+ '--fhc-dg-col-width': 100/this.cols + '%',
+ 'padding-bottom': 100 * (this.rows + addH)/this.cols + '%'
+ }
+ },
+ indexedItems() {
+ return this.items.map(
+ (item, index) => {
+ return {
+ index: index,
+ x: item.x,
+ y: item.y,
+ w: item.w,
+ h: item.h,
+ weight: item.weight || 0,
+ data: item
+ }
+ }
+ );
+ },
+ prePlacedItems() {
+ if (!this.fixedPositionUpdates)
+ return this.indexedItems;
+ return this.indexedItems.map(item => {
+ if (!this.fixedPositionUpdates[item.index])
+ return item;
+ return {
+ index: item.index,
+ weight: item.weight,
+ data: item.data,
+ x: this.fixedPositionUpdates[item.index].x === undefined ? item.x : this.fixedPositionUpdates[item.index].x,
+ y: this.fixedPositionUpdates[item.index].y === undefined ? item.y : this.fixedPositionUpdates[item.index].y,
+ w: this.fixedPositionUpdates[item.index].w === undefined ? item.w : this.fixedPositionUpdates[item.index].w,
+ h: this.fixedPositionUpdates[item.index].h === undefined ? item.h : this.fixedPositionUpdates[item.index].h
+ };
+ });
+ },
+ placedItems() {
+ if (!this.positionUpdates)
+ return this.prePlacedItems;
+ return this.prePlacedItems.map(item => {
+ if (!this.positionUpdates[item.index])
+ return item;
+ return {
+ index: item.index,
+ weight: item.weight,
+ data: item.data,
+ x: this.positionUpdates[item.index].x === undefined ? item.x : this.positionUpdates[item.index].x,
+ y: this.positionUpdates[item.index].y === undefined ? item.y : this.positionUpdates[item.index].y,
+ w: this.positionUpdates[item.index].w === undefined ? item.w : this.positionUpdates[item.index].w,
+ h: this.positionUpdates[item.index].h === undefined ? item.h : this.positionUpdates[item.index].h
+ };
+ });
+ },
+ showEmptyTileHover() {
+ if (!this.active || !this.grid || this.mode != MODE_IDLE || this.x < 0 || this.y < 0 || this.x >= this.cols || this.y >= this.rows)
+ return false;
+ return this.grid.isFreeSlot(this.x, this.y);
+ }
+ },
+ watch: {
+ active(active) {
+ if (!active)
+ this.dragCancel();
+ },
+ cols() {
+ this.dragCancel();
+ },
+ indexedItems: {
+ handler(value) {
+ this.dragCancel();
+
+ const updated = this.createNewGrid(value);
+
+ this.fixedPositionUpdates = updated;
+ if (updated.length)
+ this.$emit('rearrangeItems', updated.filter(v => v));
+ },
+ immediate: true,
+ deep: true
+ }
+ },
+ methods: {
+ createNewGrid(items) {
+ this.grid = new GridLogic(this.cols);
+ const result = [];
+ [...items].sort((a, b) => a.weight > b.weight).forEach(item => {
+ if (item.x + item.w > this.cols) {
+ let targetW = this.cols-item.x,
+ targetX = undefined;
+ if (this.resizeLimit) {
+ [targetW] = this.resizeLimit(item.data, targetW, item.h);
+ }
+ if (targetW < 1)
+ targetW = 1;
+ if (targetW > this.cols)
+ targetW = this.cols;
+ if (item.x + targetW > this.cols) {
+ targetX = this.cols - targetW;
+ }
+ if (targetW == item.w)
+ targetW = undefined;
+ result[item.index] = {
+ item: item.data,
+ x: targetX,
+ w: targetW
+ };
+ }
+ item.frame = this.grid.getItemFrame(item);
+ this.convertGridResultToUpdate(this.grid.add(item), result, items);
+ });
+ this.grid.clearWeights();
+ return result;
+ },
+ convertGridResultToUpdate(input, output, baseArray) {
+ if (!input)
+ return;
+ if (!baseArray)
+ baseArray = this.indexedItems;
+ input.forEach(item => {
+ let result = {
+ item: baseArray[item.index].data
+ };
+ if (item.x !== undefined)
+ result.x = item.x;
+ if (item.y !== undefined)
+ result.y = item.y;
+ if (item.w !== undefined)
+ result.w = item.w;
+ if (item.h !== undefined)
+ result.h = item.h;
+ output[item.index] = result;
+ });
+ },
+ mouseLeave() {
+ if (this.mode == MODE_IDLE) {
+ this.x = -1;
+ this.y = -1;
+ if (this.additionalRow !== null) {
+ this.grid.h = this.additionalRow;
+ this.additionalRow = null;
+ }
+ }
+ },
+ updateCursor(evt) {
+ if (!this.active) {
+ this.x = this.y = -1;
+ return false;
+ }
+ const addH = this.active ? this.marginForExtraRow : 0;
+ const rect = this.$refs.container.getBoundingClientRect();
+
+ if (!evt.clientX && !evt.clientY && evt.touches) {
+ evt.clientX = evt.touches[0].clientX;
+ evt.clientY = evt.touches[0].clientY;
+ }
+
+ const gridX = Math.floor(this.cols * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
+ const gridY = Math.floor((this.rows + addH) * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
+ if (this.x == gridX && this.y == gridY)
+ return false;
+
+ if (this.mode == MODE_IDLE) {
+ if (this.additionalRow === null && this.y == this.rows-1 && gridY == this.rows) {
+ this.additionalRow = this.grid.h;
+ this.grid.h += 1;
+ } else if (this.additionalRow !== null && gridY != this.rows - 1) {
+ this.grid.h = this.additionalRow;
+ this.additionalRow = null;
+ }
+ }
+
+ this.x = gridX;
+ this.y = gridY;
+
+ return true;
+ },
+ _dragStart(evt) {
+ if (evt.dataTransfer) {
+ evt.dataTransfer.setDragImage(evt.target, -99999, -99999);
+ evt.dataTransfer.dropEffect = 'move';
+ evt.dataTransfer.effectAllowed = 'move';
+ }
+ },
+ startMove(evt, item) {
+ if (!this.active)
+ return;
+ this._dragStart(evt);
+ this.mode = MODE_MOVE;
+ this.updateCursor(evt);
+ this.draggedItem = item;
+ this.draggedOffset = [item.x - this.x, item.y - this.y];
+ },
+ startResize(evt, item) {
+ if (!this.active)
+ return;
+ this._dragStart(evt);
+ this.mode = MODE_RESIZE;
+ this.draggedItem = item;
+ },
+ dragOver(evt) {
+ if (!this.active)
+ return this.dragCancel();
+ if (this.updateCursor(evt)) {
+ switch(this.mode) {
+ case MODE_MOVE: {
+ evt.preventDefault();
+ this.dragGrid = new GridLogic(this.grid);
+ let x = this.x + this.draggedOffset[0];
+ let y = this.y + this.draggedOffset[1];
+ if (x < 0) {
+ this.draggedOffset[0] -= x;
+ x = 0;
+ } else if (x + this.draggedItem.w > this.cols) {
+ this.draggedOffset[0] += this.cols - this.draggedItem.w - x;
+ x = this.cols - this.draggedItem.w;
+ }
+ if (y < 0) {
+ this.draggedOffset[1] -= y;
+ y = 0;
+ }
+
+ this.positionUpdates = this.dragGrid.move(this.draggedItem, x, y);
+ break;
+ }
+ case MODE_RESIZE: {
+ evt.preventDefault();
+ this.dragGrid = new GridLogic(this.grid);
+ let w = Math.min(this.cols - this.draggedItem.x, Math.max(1, this.x - this.draggedItem.x + 1));
+ let h = Math.max(1, this.y - this.draggedItem.y + 1);
+ if (this.resizeLimit)
+ [w, h] = this.resizeLimit(this.draggedItem.data, w, h);
+ this.positionUpdates = this.dragGrid.resize(this.draggedItem, w, h);
+ break;
+ }
+ }
+ }
+ },
+ dragCancel() {
+ this.mode = MODE_IDLE;
+ this.positionUpdates = null;
+ this.draggedOffset = [0,0],
+ this.draggedItem = null;
+ },
+ dragEnd() {
+ if (this.mode == MODE_IDLE)
+ return;
+ if (!this.active || this.x < 0 || this.y < 0 || this.x >= this.cols)
+ return this.dragCancel();
+ this.mode = MODE_IDLE;
+ let updated = [];
+ this.convertGridResultToUpdate(this.positionUpdates, updated);
+ updated = this._updateFixedPositions(updated);
+ if (updated.length)
+ this.$emit('rearrangeItems', updated.filter(v => v));
+ },
+ _updateFixedPositions(updated) {
+ updated.forEach((item, index) => {
+ if (!this.fixedPositionUpdates[index])
+ this.fixedPositionUpdates[index] = item;
+ else
+ this.fixedPositionUpdates[index] = {...this.fixedPositionUpdates[index], ...item};
+ });
+ let additionalUpdates = this.createNewGrid(this.prePlacedItems);
+ if (additionalUpdates.length) {
+ // NOTE(chris): this should never happen but it's here for safety
+ additionalUpdates.forEach((item, index) => updated[index] = item);
+ return this._updateFixedPositions(updated);
+ }
+ return updated;
+ },
+ emptyTileClicked() {
+ this.$emit('newItem', this.x, this.y);
+ }
+ },
+ template: `
+
`
+}
diff --git a/public/js/components/Drop/Grid/Item.js b/public/js/components/Drop/Grid/Item.js
new file mode 100644
index 000000000..47eec85ee
--- /dev/null
+++ b/public/js/components/Drop/Grid/Item.js
@@ -0,0 +1,71 @@
+export default {
+ components: {
+ },
+ inject: {
+ },
+ props: {
+ item: Object,
+ active: Boolean
+ },
+ emits: [
+ "startMove",
+ "startResize",
+ "endDrag",
+ "dropDrag"
+ ],
+ data() {
+ return {
+ dragAction: '',
+ dragging: false
+ }
+ },
+ computed: {
+ },
+ methods: {
+ registerDragAction(evt) {
+ if (evt.target.hasAttribute('drag-action')) {
+ this.dragAction = evt.target.getAttribute('drag-action');
+ } else {
+ let parent = evt.target.closest('[drag-action]');
+ if (parent) {
+ this.dragAction = parent.getAttribute('drag-action');
+ } else {
+ this.dragAction = '';
+ }
+ }
+ },
+ tryDragStart(evt, item) {
+ let dragAction = this.dragAction || evt.target.getAttribute('drag-action');
+ if (dragAction) {
+ this.dragging = true;
+ if (dragAction == 'move')
+ return this.$emit('startMove', evt, item);
+ else if (dragAction == 'resize')
+ return this.$emit('startResize', evt, item);
+ }
+ evt.preventDefault();
+ },
+ touchDragEnd(evt) {
+ if (!this.dragging)
+ return evt.preventDefault();
+ this.dragging = false;
+ this.$emit('dropDrag', evt);
+ },
+ test(evt) {
+ let dragAction = this.dragAction || evt.target.getAttribute('drag-action');
+ if (dragAction) {
+ this.dragging = true;
+ }
+ }
+ },
+ template: `
+
+
+
`
+}
diff --git a/public/js/composables/GridLogic.js b/public/js/composables/GridLogic.js
new file mode 100644
index 000000000..10aa2909c
--- /dev/null
+++ b/public/js/composables/GridLogic.js
@@ -0,0 +1,208 @@
+// TODO(chris): Comments
+
+const DIR_UP = 0;
+const DIR_LEFT = 1;
+const DIR_RIGHT = 2;
+const DIR_DOWN = 3;
+
+class GridLogic {
+ constructor(w) {
+ if (w.w) {
+ this.w = w.w;
+ this.h = w.h;
+ this.data = [...w.data];
+ this.grid = [...w.grid];
+ } else {
+ this.w = w;
+ this.h = 1;
+ this.data = [];
+ this.grid = [];
+ }
+ }
+ isFreeSlot(x, y) {
+ const i = y*this.w + x;
+ return !this.grid[i] && this.grid[i] !== 0;
+ }
+ add(item, prefer) {
+ let occupiers = this.getItemsInFrame(item.frame);
+
+ if (!occupiers.length) {
+ item.frame.forEach(f => this.grid[f] = item.index);
+ this.data[item.index] = item;
+ this.h = Math.max(this.h, item.y + item.h);
+ return [];
+ } else {
+ const intermGrid = new GridLogic(this);
+
+ item.frame.forEach(f => intermGrid.grid[f] = -1);
+
+ const possiblities = intermGrid.tryMoving(occupiers, prefer);
+ if (possiblities.length) {
+ const bestOption = possiblities.sort((a,b) => {
+ if (a.cost === undefined)
+ a.cost = a.moves.reduce((a, v) => a * v.cost, 1);
+ if (b.cost === undefined)
+ b.cost = b.moves.reduce((a, v) => a * v.cost, 1);
+ return a.cost > b.cost;
+ });
+ const result = [];
+ bestOption[0].moves.forEach(move => {
+ const currItem = {...this.data[move.index]};
+ currItem.x += move.x;
+ currItem.y += move.y;
+ currItem.frame = this.getItemFrame(currItem);
+ this.h = Math.max(this.h, currItem.y + currItem.h);
+ this.remove(currItem);
+ currItem.frame.forEach(f => this.grid[f] = currItem.index);
+ this.data[move.index] = currItem;
+ result[move.index] = {
+ index: currItem.index,
+ x: currItem.x,
+ y: currItem.y
+ };
+ });
+ item.frame.forEach(f => this.grid[f] = item.index);
+ this.data[item.index] = item;
+ return result;
+ } else {
+ console.error('FATAL', "can't arrange item on grid");
+ }
+ }
+ }
+ move(item, x, y) {
+ if (item.x == x && item.y == y)
+ return [];
+ this.remove(item);
+
+ let prefer = undefined;
+ if (item.x == x) {
+ if (y-item.y > 0)
+ prefer = DIR_UP;
+ else
+ prefer = DIR_DOWN;
+ } else if (item.y == y) {
+ if (x-item.x > 0)
+ prefer = DIR_LEFT;
+ else
+ prefer = DIR_RIGHT;
+ }
+
+ const currItem = {...item};
+ currItem.x = x;
+ currItem.y = y;
+ currItem.frame = this.getItemFrame(currItem);
+
+ const updates = this.add(currItem, prefer);
+ updates[item.index] = {index: item.index, x, y};
+ return updates;
+ }
+ resize(item, w, h) {
+ if (item.w == w && item.h == h)
+ return [];
+ this.remove(item);
+
+ const currItem = {...item};
+ currItem.w = w;
+ currItem.h = h;
+ currItem.frame = this.getItemFrame(currItem);
+
+ const updates = this.add(currItem);
+ updates[item.index] = {index: item.index, w, h};
+ return updates;
+ }
+ tryMoving(index, prefer) {
+ if (Array.isArray(index)) {
+ index.forEach(i => this.remove({index:i}));
+ let possiblities = [{grid: this, moves: []}];
+ index.forEach(i => {
+ let newPoss = [];
+ possiblities.forEach(possiblity => {
+ possiblity.grid.tryMoving(i, prefer).forEach(p => {
+ possiblity.moves
+ p.moves = [...p.moves, ...possiblity.moves];
+ newPoss.push(p)
+ });
+ });
+ possiblities = newPoss;
+ });
+ return possiblities;
+ }
+ const directions = [DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT];
+
+ this.remove({index});
+
+ const weight = 1 + .2*(this.data[index].weight || 0);
+
+ return directions.reduce((result, dir) => {
+ let res = this.tryMovingInDirection(dir, index, 1, (prefer === dir ? .5 : 1 + dir*.1) * weight);
+ if (!res)
+ return result;
+ return [...result, ...res];
+ }, []).filter(p => p);
+ }
+ tryMovingInDirection(dir, index, amount, cost) {
+ const move = {index, x:0, y: 0, cost: cost};
+ let targetframe;
+ switch(dir) {
+ case DIR_UP:
+ if (this.data[index].y - amount < 0)
+ return false;
+ targetframe = this.data[index].frame.map(i => i-this.w*amount);
+ move.y = -amount;
+ break;
+ case DIR_DOWN:
+ if (this.data[index].y + this.data[index].h + amount > this.h)
+ cost += .4;
+ targetframe = this.data[index].frame.map(i => i+this.w*amount);
+ move.y = amount;
+ break;
+ case DIR_LEFT:
+ if (this.data[index].x - amount < 0)
+ return false;
+ targetframe = this.data[index].frame.map(i => i-amount);
+ move.x = -amount;
+ break;
+ case DIR_RIGHT:
+ if (this.data[index].x + this.data[index].w + amount > this.w)
+ return false;
+ targetframe = this.data[index].frame.map(i => i+amount);
+ move.x = amount;
+ break;
+ }
+
+ const occupiers = this.getItemsInFrame(targetframe);
+ if (occupiers.includes(-1)) {
+ return this.tryMovingInDirection(dir, index, amount+1, cost);
+ }
+
+ const intermGrid = new GridLogic(this);
+ targetframe.forEach(f => intermGrid.grid[f] = -1);
+
+ if (!occupiers.length) {
+ return [{grid: intermGrid, moves: [move]}];
+ }
+ const possiblities = intermGrid.tryMoving(occupiers).map(possiblity => possiblity.moves.unshift(move) && possiblity);
+ return possiblities.length ? possiblities : false;
+ }
+ clearWeights() {
+ this.data.forEach(item => item.weight = undefined);
+ }
+ getItemsInFrame(frame) {
+ return frame.map(i => this.grid[i]).filter((v,i,a) => (v || v === 0) && a.indexOf(v) === i);
+ }
+ remove(item) {
+ this.grid = this.grid.map(i => i != item.index ? i : undefined);
+ }
+ getItemFrame(item) {
+ const frame = [];
+ for (let i = 0; i < item.w; i++)
+ for (let j = 0; j < item.h; j++)
+ frame.push(i + item.x + (j + item.y) * this.w);
+ return frame;
+ }
+ debug() {
+ return this.grid;
+ }
+}
+
+export default GridLogic;
\ No newline at end of file