Commit 840f4420 authored by Lasse Overgaard Møldrup's avatar Lasse Overgaard Møldrup
Browse files

Implemented moving blocks

parent 1e0fe6b1
<template>
<div class="home">
<div id="grid">
<div @mousedown="resizeVertical($event, rowIndex)" class="row" v-for="(rowBlock, rowIndex) in layout" :key="rowIndex" :style="{ height: rowBlock.height + 'px' }">
<div @mousedown="resizeHorizontal($event, rowIndex, colIndex)" class="col" v-for="(colBlock, colIndex) in rowBlock.blocks" :key="colIndex" :style="{ 'flex-grow': colBlock.width }">
<div @mousedown="resizeVertical($event, rowIndex)" :id="rowIndex" class="row" :class="{ drag: draggedBlock !== null }"
v-for="(rowBlock, rowIndex) in layout" :key="rowIndex" :style="{ height: rowBlock.height + 'px' }"
@dragover="dragHover($event, rowIndex)" @dragleave="hidePreview(rowIndex)" @drop="drop($event, rowIndex)">
<span class="col-preview">
<component :is="previewType"></component>
</span>
<div @mousedown="resizeHorizontal($event, rowIndex, colIndex)" class="col" v-show="!isDragged(rowIndex, colIndex)"
v-for="(colBlock, colIndex) in rowBlock.blocks" :key="colIndex" :style="{ 'flex-grow': colBlock.width, 'order': colIndex }"
:draggable="dragEnabled" @dragstart="startDrag($event, rowIndex, colIndex)" @dragend.prevent="stopDrag()">
<component :is="colBlock.type"></component>
</div>
</div>
......@@ -30,6 +37,9 @@ const sendServerRequest = (type, payload) => {
return xhr;
}
// Maximum number of blocks allowed in a row
const maxBlocks = 3;
export default {
name: 'Home',
......@@ -51,7 +61,9 @@ export default {
],
mouseDragPos: {x: 0, y: 0},
verticalResizeObject: {index: 0},
horizontalResizeObject: {row: 0, col: 0}
horizontalResizeObject: {row: 0, col: 0},
draggedBlock: null,
dragEnabled: true
}
},
......@@ -101,13 +113,13 @@ export default {
this.layout[row].blocks[col].width = newFlexRight;
},
resizeVerticalMouseMove(event){
resizeVerticalMouseMove(event) {
const deltaY = this.mouseDragPos.y - event.y;
this.mouseDragPos.y = event.y;
this.layout[this.verticalResizeObject.index].height -= deltaY
},
resizeHorizontal(event, row, col){
resizeHorizontal(event, row, col) {
if(event.offsetX < 0){
this.mouseDragPos.x = event.x;
this.horizontalResizeObject.row = row;
......@@ -115,24 +127,27 @@ export default {
document.addEventListener("mousemove", this.resizeHorizontalMouseMove, false);
document.addEventListener("mouseup", this.eventRemover, false);
document.getElementsByTagName("html")[0].classList.add("no-select");
this.dragEnabled = false;
}
},
resizeVertical(event, index){
if(event.offsetY > this.layout[index].height){
resizeVertical(event, index) {
if(event.offsetY > this.layout[index].height) {
this.mouseDragPos.y = event.y;
this.verticalResizeObject.index = index;
document.addEventListener("mousemove", this.resizeVerticalMouseMove, false);
document.addEventListener("mouseup", this.eventRemover, false);
document.getElementsByTagName("html")[0].classList.add("no-select");
this.dragEnabled = false;
}
},
eventRemover(){
eventRemover() {
document.removeEventListener("mousemove", this.resizeHorizontalMouseMove, false);
document.removeEventListener("mousemove", this.resizeVerticalMouseMove, false);
document.removeEventListener("mouseup", this.eventRemover, false);
document.getElementsByTagName("html")[0].classList.remove("no-select");
this.dragEnabled = true;
},
removeBlock(side, row, col) {
......@@ -141,6 +156,148 @@ export default {
this[side].splice(row, 1);
},
startDrag(event, row, col) {
event.dataTransfer.effectAllowed = 'move';
// Workaround for a bug in Chrome causing dragend
// to be fired immediately if the timeout is not used
setTimeout(() => {
this.draggedBlock = { row, col };
this.showDropPreview(row, col);
}, 10);
},
stopDrag() {
this.draggedBlock = null;
},
getDropPosition(event, row) {
// Copy blocks
const blocks = JSON.parse(JSON.stringify(this.layout[row].blocks));
const origRow = this.draggedBlock.row;
const origCol = this.draggedBlock.col;
const origBlock = this.layout[origRow].blocks[origCol];
// Don't take into account the block we are dragging when calculating the preview
if (origRow === row) {
const missingWidth = origBlock.width / (blocks.length - 1);
blocks.splice(origCol, 1);
for (let block of blocks)
block.width += missingWidth;
}
// No drop position if row is full
if (blocks.length === maxBlocks)
return null;
const targetRow = document.getElementById(row.toString());
const rowWidth = targetRow.offsetWidth;
const x = event.x - targetRow.offsetLeft;
let position = blocks.length;
let cutoff = 0;
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i];
cutoff += block.width / 2;
if (x <= rowWidth * cutoff / 100) {
position = i;
break;
}
cutoff += block.width / 2;
}
return position;
},
// Shows a preview upon dragging a block over a row
dragHover(event, row) {
event.dataTransfer.dropEffect = 'move';
let position = this.getDropPosition(event,row);
// Don't show preview if row is full
if (position === null)
return;
this.showDropPreview(row, position);
event.preventDefault();
},
showDropPreview(row, position) {
const origRow = this.draggedBlock.row;
const origCol = this.draggedBlock.col;
const origBlock = this.layout[origRow].blocks[origCol];
// Fix cases when dragging to own row
if (origRow === row && position >= origCol) {
position++;
}
const targetRow = document.getElementById(row.toString());
const preview = targetRow.getElementsByClassName('col-preview')[0];
preview.style.order = position;
preview.style.flexGrow = origBlock.width;
preview.style.display = 'block';
},
drop(event, row) {
this.hidePreview(row);
const position = this.getDropPosition(event, row);
// Do nothing if row full
if (position == null)
return;
const origRow = this.draggedBlock.row;
const origCol = this.draggedBlock.col;
const origBlock = this.layout[origRow].blocks[origCol];
// Delete at origin
const origBlocks = this.layout[origRow].blocks;
origBlocks.splice(origCol, 1);
// Insert at destination
const destBlocks = this.layout[row].blocks;
destBlocks.splice(position, 0, origBlock);
if (origBlocks.length == 0)
this.layout.splice(origRow, 1);
// Normalize rows to 100 total width
if (origRow !== row) {
const origTotalWidth = origBlocks.reduce((total, block) => total + block.width, 0);
const origScaleFactor = 100 / origTotalWidth;
for (let block of origBlocks) {
block.width *= origScaleFactor;
}
const destTotalWidth = destBlocks.reduce((total, block) => total + block.width, 0);
const destScaleFactor = 100 / destTotalWidth;
for (let block of destBlocks) {
block.width *= destScaleFactor;
}
}
this.draggedBlock = null;
},
hidePreview(row) {
const targetRow = document.getElementById(row.toString());
const preview = targetRow.getElementsByClassName('col-preview')[0];
preview.style.display = 'none';
},
isDragged(row, col) {
return row === this.draggedBlock?.row && col === this.draggedBlock?.col;
},
saveLayout(id) {
const type = "save"
const payload = "id=" + id + "&" + "data=" + JSON.stringify(this.$data);
......@@ -164,6 +321,18 @@ export default {
},
},
computed: {
previewType() {
if (this.draggedBlock == null)
return null;
const origRow = this.draggedBlock.row;
const origCol = this.draggedBlock.col;
return this.layout[origRow].blocks[origCol].type;
}
},
components: {
ActiveCourses,
Deadlines,
......@@ -179,13 +348,17 @@ export default {
#grid {
width: 100%;
padding: $gap;
padding: $gap $gap / 2;
}
.row {
display: flex;
margin-bottom: $gap;
position: relative;
&.drag * {
pointer-events: none;
}
}
// Must be outside ".row" or else after affect children
......@@ -199,32 +372,34 @@ export default {
cursor: n-resize;
}
.col {
.col, .col-preview {
flex-basis: 0; // a basis of 0px. very important!
background-color: $light-green-theme-color;
border-radius: 4px;
border: 2px solid $theme-color;
margin: 0 $gap / 2;
}
.col {
position: relative;
cursor: move;
&:not(:first-child) {
margin-left: $gap / 2;
&::before{
content: " ";
background-color: rgba(0, 0, 0, 0);
position: absolute;
left: - $gap - 2px;
width: $gap;
height: 100%;
cursor: w-resize;
}
&:not(:first-of-type)::before{
content: " ";
background-color: rgba(0, 0, 0, 0);
position: absolute;
left: - $gap - 2px;
width: $gap;
height: 100%;
cursor: w-resize;
}
}
&:not(:last-child) {
margin-right: $gap / 2;
}
.col-preview {
display: none;
opacity: 0.5;
}
.add-block {
width: 50px;
height: 50px;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment