I’m trying to make online version of Windows XP, and I’m currently on making moving multiple selected apps on desktop grid and I encounter some error in my functions’ logic however I do not know why it occurs. So the problem only appears when I move at least 3 items at the same time. When I move more than 3 apps to the place when other apps exists, it just overrides one of the apps. I’ve tried to debug it and I find the moment when it fails however I don’t know why because it don’t enter one of my if statements however is I print the condition after statement it returns true. So to visualize it better I will first show this issue on the UI.
So if we have 5 apps on desktop
and I will select 3 of them and then move it to column one and rows 0,1,2 one of the apps will disappear
So looking at the code first thing you need to know is that all of the frontend code is written in vue, i represent grid as 2 dimensional array and store it in data. if the grid slot is empty i represent it as null and if not it contains object with informations like app title, icon, selcted and id.
<div class="desktop-row" v-for="(row, rowIndex) in desktopGridLayout" :key="rowIndex">
<div class="desktop-slot" v-for="(slot, slotIndex) in row" :key="slotIndex" ref="appSlots">
<div class="app-wrapper" v-if="slot" @mousedown="registerGrab($event, slot)" @touchstart="registerGrab($event, slot)" @click="selectApp($event, slot)" :class="{
selected: slot.selected
}" :id="`slot${slot.id}`" @dblclick="slot.edit = true">
<div class="image-container">
<img :src="slot.icon" :alt="slot.title">
<div class="select-overlay" :style="{
maskImage: `url(${slot.icon})`
}">
</div>
</div>
<input type="text" v-if="slot.edit" v-model="slot.title">
<h3 v-else>{{slot.title}}</h3>
</div>
</div>
</div>
I also have array of object containg apps inforamtions (title, id, icon, row, col, selected)
apps: [
{
id: 0,
title: "Kosz bardzo długa nazwa",
icon: "icons/Recycle Bin (empty).png",
row: 0,
col: 0,
selected: false,
edit: false,
},
{
id: 1,
title: "Mój komputer",
icon: "icons/My Computer.png",
row: 1,
col: 0,
selected: false,
edit: false,
}
],
So I can use it to re-render grid to resize to make this responsive. To move and re-render grid, I use update grid function which gets the number of columns and rows as arguments and then re-render the grid.
updateGrid(numOfRows = this.desktopGridLayout.length, numOfCols = this.desktopGridLayout[0].length) {
// console.error("new grid update");
const newGrid = []
for (let i = 0; i < numOfRows; i++) {
newGrid.push([])
for (let j = 0; j < numOfCols; j++) {
newGrid[i].push(null)
}
}
this.apps.sort((a,b) => {
let rowDiff = a.row - b.row
let colDiff = a.col - b.col
if (colDiff === 0) {
return rowDiff
}
return colDiff
}).sort(function (x, y) {
return (x === y) ? 0 : x ? 1 : -1;
}).forEach(app => {
let row = app.row
let col = app.col
if (row >= newGrid.length ) {
row = newGrid.length - 1
} else if (row < 0) {
row = 0
}
if (col >= newGrid[0].length ) {
col = newGrid[0].length - 1
} else if (col < 0) {
col = 0
}
// console.log("Moving app: ", app.title, " from: ", app.row, app.col, " to: ", row, col)
if (newGrid[row][col] !== null) {
// console.log("Slot is occupied moving occupied app down")
this.moveAppDown(row, col, newGrid)
}
console.log(`Selected app finished on (${row}, ${col}) ${newGrid[row][col]}`)
newGrid[row][col] = {
title: app.title,
icon: app.icon,
id: app.id,
selected: app.selected
}
this.apps[this.apps.indexOf(app)] = {
...app,
row: row,
col: col
}
})
this.desktopGridLayout = newGrid
}
If the app will be placed on the place of other app, it will trigger the move app down function. However, here is the problem because after watching consol log some apps don’t enter the if even is the slot is not empty, and I don’t have idea why.
moveAppDown(rowIndex, colIndex, grid = this.desktopGridLayout) {
let newColIndex = colIndex;
let newRowIndex = rowIndex + 1;
if (newRowIndex >= grid.length) {
newRowIndex = 0
newColIndex = colIndex + 1
if (newColIndex >= grid[0].length) {
const emptySlotRow = grid.find(row => row.some(col => col === null))
newRowIndex = grid.indexOf(emptySlotRow)
newColIndex = grid[newRowIndex].indexOf(null)
}
}
// console.log("Moving", grid[rowIndex][colIndex].title, "from", rowIndex, colIndex, "occupied slot to", newRowIndex, newColIndex)
if (grid[newRowIndex][newColIndex] !== null) {
console.log("entering reccurention")
this.moveAppDown(newRowIndex, newColIndex)
}
const app = this.apps.filter(app => app ? app.id === grid[rowIndex][colIndex].id : false)[0]
console.log(`Moved app finished on (${newRowIndex}, ${newColIndex})`, grid[newRowIndex][newColIndex], grid[newRowIndex][newColIndex] !== null)
grid[newRowIndex][newColIndex] = grid[rowIndex][colIndex]
grid[rowIndex][colIndex] = null
this.apps[this.apps.indexOf(app)] = {
...app,
row: newRowIndex,
col: newColIndex
}
}
I will also provide you function that handle app movement on grab, but there are still some parts of the code missing because this component is huge, and I don’t want to make this text so long. That’s why you can also see a full component as well as a project on this GitHub repo. https://github.com/kremobil/Portfolio/blob/main/resources/js/Components/Desktop.vue
Here are also handle Grab function I talked about
handleGrab(mouseX, mouseY) {
const app = this.apps.filter(app => app.id === this.grabActive)[0]
const appWrapper = app.element.querySelector(".app-wrapper")
appWrapper.style.position = "absolute";
appWrapper.style.top = mouseY + this.grabOffset.y + "px"
appWrapper.style.left = mouseX + this.grabOffset.x + "px"
appWrapper.style.zIndex = 500
// also move selected objects
this.apps.filter(app => app.selected && app.id !== this.grabActive).forEach(selectedApp => {
const yOffset = selectedApp.element.getBoundingClientRect().y - app.element.getBoundingClientRect().y
const xOffset = selectedApp.element.getBoundingClientRect().x - app.element.getBoundingClientRect().x
const selectedAppWrapper = selectedApp.element.querySelector(".app-wrapper")
selectedAppWrapper.style.position = "absolute";
selectedAppWrapper.style.top = mouseY + this.grabOffset.y + yOffset + "px"
selectedAppWrapper.style.left = mouseX + this.grabOffset.x + xOffset + "px"
selectedAppWrapper.style.zIndex = 500
})
const xDiff = mouseX - this.mouseStartingPosition.x
const yDiff = mouseY - this.mouseStartingPosition.y
if (Math.abs(xDiff) > 10 || Math.abs(yDiff) > 10) {
this.blockNextClick = true
let nextRowIndex = app.row
let nextColIndex = app.col
if (xDiff < -((this.iconSize.width / 5) * 3 + this.iconSize.gap)) {
this.mouseStartingPosition.x -= this.iconSize.width + this.iconSize.gap
nextColIndex = nextColIndex - 1 > 0 ? nextColIndex - 1 : 0
} else if (xDiff > ((this.iconSize.width / 5) * 3 + this.iconSize.gap)) {
this.mouseStartingPosition.x += this.iconSize.width + this.iconSize.gap
nextColIndex = nextColIndex + 1 >= this.desktopGridLayout[0].length ? this.desktopGridLayout[0].length - 1 : nextColIndex + 1
}
if (yDiff < -((this.iconSize.height / 5) * 3 + this.iconSize.gap)) {
this.mouseStartingPosition.y -= this.iconSize.height + this.iconSize.gap
nextRowIndex = nextRowIndex - 1 > 0 ? nextRowIndex - 1 : 0
} else if (yDiff > ((this.iconSize.height / 5) * 3 + this.iconSize.gap)) {
this.mouseStartingPosition.y += this.iconSize.height + this.iconSize.gap
nextRowIndex = nextRowIndex + 1 >= this.desktopGridLayout.length ? this.desktopGridLayout.length - 1 : nextRowIndex + 1
}
if(app.row !== nextRowIndex || app.col !== nextColIndex) {
// update other selected apps
this.apps = this.apps.map(selectedApp => {
let nextSelectedRowIndex = nextRowIndex - app.row
let nextSelectedColIndex = nextColIndex - app.col
return {
...selectedApp,
row: selectedApp.selected || selectedApp.id === app.id ? selectedApp.row + nextSelectedRowIndex : selectedApp.row,
col: selectedApp.selected || selectedApp.id === app.id ? selectedApp.col + nextSelectedColIndex : selectedApp.col
}
})
}
}
}
and also here are the screen of console log when the error occures