Merge branch 'feature-24647/Konfigurierbare_Dashboards' into feature-25999/C4

This commit is contained in:
cgfhtw
2023-02-09 16:12:36 +01:00
10 changed files with 231 additions and 28 deletions
+19
View File
@@ -0,0 +1,19 @@
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
import CoreDashboard from '../components/Dashboard/Dashboard.js';
import DashboardAdmin from '../components/Dashboard/Admin.js';
Vue.createApp({
data: () => ({
appSideMenuEntries: {}
}),
components: {
CoreNavigationCmpt,
DashboardAdmin,
CoreDashboard/*,
"CoreFilterCmpt": CoreFilterCmpt,
"verticalsplit": verticalsplit,
"searchbar": searchbar*/
},
mounted() {
}
}).mount('#main');
@@ -14,6 +14,7 @@ export default {
data: () => ({
funktionen: {},
sections: [],
tmpLoading: ''
}),
computed: {
apiurl() {
@@ -117,10 +118,14 @@ export default {
loadSections(evt) {
let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value);
this.sections = [];
this.tmpLoading = funktionen.join('###');
axios.get(this.apiurl + '/Config/PresetBatch', {params: {
db: this.dashboard,
funktionen
}}).then(res => {
if (this.tmpLoading !== funktionen.join('###'))
return; // NOTE(chris): prevent race condition
for (var section in res.data.retval) {
let widgets = [];
for (var wid in res.data.retval[section]) {
@@ -145,10 +150,16 @@ export default {
});
}).catch(err => console.error('ERROR:', err));
},
watch: {
dashboard() {
// TODO(chris): this should be done without a watcher
this.loadSections({target:this.$refs.funktionenList});
}
},
template: `<div class="dashboard-admin-presets">
<div class="row">
<div class="col-3">
<select class="form-control" multiple @input="loadSections">
<select ref="funktionenList" style="height:30em" class="form-control" multiple @input="loadSections">
<option v-for="name,id in funktionen" :key="id" :value="id">{{ name }}</option>
</select>
</div>
+54 -17
View File
@@ -24,13 +24,16 @@ export default {
data() {
return {
gridWidth: 0,
containerRect: {top:0,left:0},
changeHeight: 1,
movedObjects: [],
editMode: this.adminMode ? 1 : 0,
gridXLast: 0,
gridYLast: 0,
dataTransfer: {}
gridXLast: -1, // NOTE(chris): 0 based
gridYLast: -1,
dragging: 0,
dataTransfer: {},
gridAddFound: false,
gridXAdd: -1, // NOTE(chris): 0 based
gridYAdd: -1
}
},
computed: {
@@ -38,7 +41,7 @@ export default {
this.widgets.forEach((item,i) => item.index = i);
return this.widgets;
},
itemCoords() {
itemCoords() { // NOTE(chris): 1 based
if (!this.gridWidth)
return [];
let itemCoords = this.items.map(item => item.place[this.gridWidth] || this.createItemPlacement(item));
@@ -103,11 +106,13 @@ export default {
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 + item.h - 1));
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
return minH + this.editMode;
if (minH == 0 && this.editMode)
return 1;
return minH + this.editMode*this.dragging;
},
gridOccupiers() {
gridOccupiers() { // NOTE(chris): 0 based
let occupiers = [];
let gridWidth = this.gridWidth;
this.items.forEach(item => {
@@ -123,20 +128,26 @@ export default {
}
});
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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-500 -500 1448 1512"><path fill="lightgray" d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM200 344V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H248v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>')` + (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.containerRect;
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,
y: gridY,
x: gridX+1,
y: gridY+1,
w: 1,
h: 1
};
@@ -162,7 +173,29 @@ export default {
});*/
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;
@@ -178,6 +211,7 @@ export default {
}
},
startResize(evt, item) {
this.dragging = 1;
this.gridXLast = -1;
this.gridYLast = -1;
item._w = this.itemCoords[item.index].w;
@@ -239,7 +273,7 @@ export default {
onDragOver(evt) {
let id, x, y, w, h;
const action = this.dataTransfer.action;
const rect = this.containerRect;
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);
@@ -304,6 +338,9 @@ export default {
}
},
onDrop() {
this.dragging = 0;
this.gridXLast = -1;
this.gridYLast = -1;
let id = 0;
let update = {};
while ((id = this.movedObjects.pop())) {
@@ -376,14 +413,12 @@ export default {
let self = this;
let cont = self.$refs.container;
self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
self.containerRect = cont.getBoundingClientRect();
window.addEventListener('resize', () => {
for (const child of cont.children) {
child.style.display = 'none';
}
self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
self.containerRect = cont.getBoundingClientRect();
for (const child of cont.children) {
child.style.display = '';
}
@@ -394,14 +429,16 @@ export default {
<span class="col">{{name}}</span>
<button class="col-auto btn" @click.prevent="editMode = editMode ? 0 : 1"><i class="fa-solid fa-gear"></i></button>
</h3>
<div class="position-relative" :style="'height:0;padding-bottom:' + (gridHeight * 100/gridWidth) + '%'">
<div :h="gridHeight" :w="gridWidth" class="position-relative" :style="'height:0;padding-bottom:' + (gridHeight * 100/gridWidth) + '%'">
<div ref="container"
class="position-absolute top-0 left-0 w-100 h-100 draganddropcontainer"
:style="'display:grid;grid-template-rows:repeat('+gridHeight+',1fr)'"
:style="'display:grid;grid-template-rows:repeat('+gridHeight+',1fr);background:'+cssBg"
@click="addWidget($event)"
@drop="onDrop($event, 1)"
@dragover.prevent="onDragOver"
@dragenter.prevent>
@dragenter.prevent
@mousemove="onMouseMove"
@mouseleave="onMouseLeave">
<dashboard-item
v-for="item in items"
+8 -5
View File
@@ -22,7 +22,10 @@ export default {
}
return this.allNewsList.slice(0, quantity);
}
},
placeHolderImgURL: function() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'skin/images/fh_technikum_wien_illustration_klein.png';
}
},
created(){
axios
@@ -69,7 +72,7 @@ export default {
<div v-else class="h-100" :class="'row row-cols-' + width">
<div v-for="news in newsList" :key="news.id">
<div class="card h-100">
<img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="card-img-top">
<img :src="placeHolderImgURL" class="card-img-top">
<div class="card-footer"><span class="card-subtitle small text-muted">{{ formatDateTime(news.insertamum) }}</span></div>
<div class="card-body">
<a href="#newsModal" class="card-title h5 stretched-link" @click="setSingleNews(news)">{{ news.betreff }}</a><br>
@@ -85,7 +88,7 @@ export default {
<BsModal ref="newsModal" id="newsModal" dialog-class="modal-lg">
<template #title>
<div class="row">
<div class="col-5"><img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="img-fluid rounded-start"></div>
<div class="col-5"><img :src="placeHolderImgURL" class="img-fluid rounded-start"></div>
<div class="col-7 d-flex align-items-end">
<p>{{ singleNews.betreff }}<br><small class="text-muted">{{ formatDateTime(singleNews.insertamum) }}</small></p>
</div>
@@ -101,7 +104,7 @@ export default {
<div class="row row-cols-5 g-4 h-100 px-5">
<div v-for="news in allNewsList" :key="news.id">
<div class="card h-100">
<img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="card-img-top">
<img :src="placeHolderImgURL" class="card-img-top">
<div class="card-footer"><span class="card-subtitle small">{{ formatDateTime(news.insertamum) }}</span></div>
<div class="card-body">
<a href="" class="card-title h5 stretched-link" @click="setSingleNews1(news)">{{ news.betreff }}</a><br>
@@ -112,4 +115,4 @@ export default {
</div>
</template>
</BsModal>`
}
}