Commit f39069af authored by Kent Nielsen's avatar Kent Nielsen
Browse files

Merge branch 'develop' into 'feature/mobile-layout'

# Conflicts:
#   website/studerende-dk/src/views/Home.vue
parents d4030c31 848114c2
Pipeline #52372 passed with stage
in 39 seconds
<template>
<BlockTemplate heading="Aktive kurser">
<BlockTemplate heading="Aktive kurser" :row="row" :col="col">
<ul>
<li v-for="(course, index) in courses" :key="index"><a href="/about">{{ course }}</a></li>
</ul>
......@@ -9,6 +9,9 @@
<script>
export default {
name: 'ActiveCourses',
props: ['row', 'col'],
data() {
return {
courses: ['Calculus Beta', 'Databaser', 'etc.']
......
<template>
<div class="block">
<div class="heading">
<div class="heading" :draggable="dragEnabled.value" @dragstart="startDrag($event, row, col)" @dragend.prevent="stopDrag()">
<h2>{{ heading }}</h2>
</div>
<div class="content">
......@@ -11,7 +11,9 @@
<script>
export default {
props: [ 'heading' ]
props: [ 'heading', 'row', 'col' ],
inject: [ 'dragEnabled', 'startDrag', 'stopDrag' ]
}
</script>
......@@ -30,6 +32,7 @@ export default {
.heading {
color: $lightest-theme-color;
background-color: $theme-color;
cursor: move;
h2 {
padding: 10px $padding;
......
<template>
<div id="calendar">
<table>
<thead>
<thead :draggable="dragEnabled.value" @dragstart="startDrag($event, row, col)" @dragend.prevent="stopDrag()">
<!-- Generating first header of schema -->
<tr>
<th>
......@@ -52,6 +52,11 @@
<script>
export default {
name: 'Cal',
props: ['row', 'col'],
inject: [ 'dragEnabled', 'startDrag', 'stopDrag' ],
data() {
return {
// Currently showing schema data
......@@ -185,6 +190,7 @@ export default {
border: 0;
padding: 0;
height: 100%;
cursor: move;
tr{
height: 37px;
......
<template>
<BlockTemplate heading="Deadlines">
<BlockTemplate heading="Deadlines" :row="row" :col="col">
<ul>
<li v-for="(deadline, index) in deadlines" :key="index">
<span class="course-name">{{ deadline.course }}</span>
......@@ -15,13 +15,28 @@
</template>
<script>
Date.prototype.addDays = function(days) {
const date = new Date(this.valueOf());
date.setDate(date.getDate() + days);
return date;
}
Date.prototype.toMidnight = function() {
const date = new Date(this.valueOf());
date.setHours(0, 0, 0, 0);
return date;
}
export default {
name: 'Deadlines',
props: ['row', 'col'],
data() {
return {
deadlines: [{ course: "Calculus Beta", name: "Aflevering 1", date: new Date() },
{ course: "Databaser", name: "SQL", date: new Date("10 Apr 2021") },
{ course: "Numerisk Lineær Algebra", name: "Python for dummies", date: new Date("12 Apr 2021") }]
{ course: "Databaser", name: "SQL", date: new Date().addDays(2).toMidnight() },
{ course: "Numerisk Lineær Algebra", name: "Python for dummies", date: new Date().addDays(3).toMidnight() }]
}
}
}
......
<template>
<BlockTemplate heading="Feed">
<BlockTemplate heading="Feed" :row="row" :col="col">
<ul>
<li v-for="(message, index) in messages" :key="index" @click="$router.push('/about')" @keypress.enter="$router.push('/about')" tabindex=0>
<a href="/about">{{ message.course }}</a>
......@@ -13,6 +13,9 @@
<script>
export default {
name: 'Feed',
props: ['row', 'col'],
data() {
return {
messages: [
......
<template>
<BlockTemplate heading="Post">
<BlockTemplate heading="Post" :row="row" :col="col">
<ul>
<li v-for="(mail, index) in mails" :key="index">
<span style="width: 50%;">{{ mail.sender }}</span>
......@@ -15,6 +15,9 @@
<script>
export default {
name: 'Mail',
props: ['row', 'col'],
data() {
return {
mails: [
......
<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 }">
<component :is="colBlock.type"></component>
<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()" @drop="drop($event, rowIndex)">
<span class="col-preview">
<component :is="previewType"></component>
</span>
<div @mousedown="resizeHorizontal($event, rowIndex, colIndex)" :id="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 }">
<component :is="colBlock.type" :row="rowIndex" :col="colIndex"></component>
</div>
</div>
<button class="add-block" @click="addBlock()">
......@@ -14,6 +20,7 @@
</template>
<script>
import { computed } from 'vue';
import ActiveCourses from "@/components/blocks/ActiveCourses";
import Deadlines from "@/components/blocks/Deadlines";
import Feed from "@/components/blocks/Feed";
......@@ -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',
......@@ -52,7 +62,10 @@ export default {
mouseDragPos: {x: 0, y: 0},
verticalResizeObject: {index: 0},
horizontalResizeObject: {row: 0, col: 0},
prevWinWidth: 1000
prevWinWidth: 1000,
draggedBlock: null,
activePreview: null,
dragEnabled: true
}
},
......@@ -102,13 +115,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;
......@@ -116,24 +129,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;
},
mobileLayoutResize(){
......@@ -172,6 +188,163 @@ export default {
this[side].splice(row, 1);
},
startDrag(event, row, col) {
event.dataTransfer.effectAllowed = 'move';
const colElement = document.getElementById(row + ',' + col);
event.dataTransfer.setDragImage(colElement, colElement.offsetWidth/2, 20);
// 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 (const 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) {
if (this.activePreview !== null)
this.hidePreview();
this.activePreview = row;
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) {
// If events get processed in an unfortunate order
if (this.draggedBlock === null)
return;
this.hidePreview();
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 (const block of origBlocks) {
block.width *= origScaleFactor;
}
const destTotalWidth = destBlocks.reduce((total, block) => total + block.width, 0);
const destScaleFactor = 100 / destTotalWidth;
for (const block of destBlocks) {
block.width *= destScaleFactor;
}
}
this.draggedBlock = null;
},
hidePreview() {
if (this.activePreview === null)
return;
const targetRow = document.getElementById(this.activePreview.toString());
const preview = targetRow.getElementsByClassName('col-preview')[0];
preview.style.display = 'none';
this.activePreview = null;
},
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);
......@@ -194,7 +367,6 @@ export default {
}
},
},
mounted(){
this.mobileLayoutResize();
this.prevWinWidth = window.innerWidth;
......@@ -205,6 +377,26 @@ export default {
this.mobileLayoutResize();
},
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;
}
},
provide() {
return {
dragEnabled: computed(() => this.dragEnabled),
startDrag: this.startDrag,
stopDrag: this.stopDrag
}
},
components: {
ActiveCourses,
Deadlines,
......@@ -220,7 +412,7 @@ export default {
#grid {
width: 100%;
padding: $gap/2;
padding: $gap $gap / 2;
}
.row {
......@@ -228,7 +420,11 @@ export default {
margin-bottom: $gap/2;
flex-direction: column;
position: relative;
min-height: 150px
min-height: 150px;
&.drag * {
pointer-events: none;
}
}
// Must be outside ".row" or else after affect children
......@@ -242,18 +438,37 @@ 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;
&:not(:last-child) {
margin-bottom: $gap / 2;
}
&: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;
}
}
.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