Dashboard Test V1

This commit is contained in:
Cris
2022-09-06 15:29:42 +02:00
parent 7f64f5c29f
commit 522e341d3d
13 changed files with 788 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
import CoreDashboard from '../components/CoreDashboard.js';
Vue.createApp({
data: () => ({
appSideMenuEntries: {}
}),
components: {
CoreNavigationCmpt,
CoreDashboard/*,
"CoreFilterCmpt": CoreFilterCmpt,
"verticalsplit": verticalsplit,
"searchbar": searchbar*/
}
}).mount('#main');
+206
View File
@@ -0,0 +1,206 @@
import CoreDashboardItem from './Dashboard/Item.js';
import DragAndDrop from './Dashboard/DragAndDrop.js';
export default {
components: {
CoreDashboardItem,
DragAndDrop
},
data: () => ({
widgetCache: {},
componentCache: {},
widgetWizard: null,
allowedWidgets: [],
widgets: [],
name: '',
newMode: false,
editMode: false
}),
computed: {
loaded: function() {
return this.widgets && this.name;
}
},
props: [
"dashboard"
],
methods: {
saveConfig() {
// TODO(chris): SAVE!
console.log('SAVE', this.widgets);
this.editMode = false;
},
startWidgetWizard() {
// TODO(chris): load widgets!
let self = this;
axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/Dashboard/Widgets', {
headers: {
'FHC-API-KEY':'[email protected]'
},
params: {
dashboard: this.dashboard
}
}).then(function(result) {console.log(result);
self.allowedWidgets = result.data;
});
this.widgetWizard.show();
},
newWidget(widget) {
let self = this;
let newWidget = [widget.widget_id, this.widgets.length, this.widgets.length, this.widgets.length, []];
// TODO(chris): loadingscreen?
(new Promise(function (resolve, reject) {
if (self.widgetCache[widget.widget_id]) {
resolve(self.widgetCache[widget.widget_id]);
} else {
this.extendWidgetAndCache(widget).then(widget => {
resolve(widget);
});
}
})).then(widget => {
newWidget[5] = widget;
self.widgets.push(newWidget);
self.widgetWizard.hide();
});
},
removeWidget(id) {
if (confirm('Are you sure you want to delete this widget?'))
this.widgets = this.widgets.filter(widget => widget[0] != id);
},
getWidget(widget_id) {
let self = this;
return new Promise(function(resolve, reject) {
if (self.widgetCache[widget_id])
return resolve(self.widgetCache[widget_id]);
axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/User/Widget', {
headers: {
'FHC-API-KEY':'[email protected]'
},
params: {
widget_id: widget_id
}
})
.then(result => self.extendWidgetAndCache(result.data))
.then(widget => resolve(widget));
});
},
extendWidgetAndCache(widget) {
let self = this;
return new Promise(function(resolve, reject) {
let name = widget.component_name;
widget.arguments = JSON.parse(widget.arguments);
widget.component_name = widget.component_name.replace(/[A-Z]/g, (m,o) => (o > 0 ? "-" : "") + m.toLowerCase());
self.getComponent(name, widget.component_path).then(component => {
widget.component = component;
return widget;
}).then(() => {
self.widgetCache[widget.widget_id] = widget;
resolve(self.widgetCache[widget.widget_id]);
});
});
},
getComponent(name, path) {
let self = this;
return new Promise(async function(resolve, reject) {
if (self.componentCache[name])
return resolve(self.componentCache[name]);
self.componentCache[name] = (await import(path)).default;
resolve(self.componentCache[name]);
});
}
},
mounted() {
let self = this;
this.widgetWizard = new bootstrap.Modal(this.$refs.widgetWizard);
axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/User/AuthObj', {
headers: {
'FHC-API-KEY':'[email protected]'
}
}).then(function(result) {
self.name = result.data.name;
});
axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/User/Widgets', {
headers: {
'FHC-API-KEY':'[email protected]'
},
params: {
dashboard: this.dashboard
}
}).then(function(result) {
let promises = [];
result.data.forEach(function(item) {
promises.push(new Promise(function(resolve, reject) {
self.getWidget(item[0]).then(function(widget) {
item[5] = widget;
resolve();
});
}));
});
Promise.all(promises).then(function() {
self.widgets = result.data;
})
});
},
template: `<div class="core-dashboard">
<div style="width:500px">
<drag-and-drop width="4" height="3" :items="[{id:0,c:'blue',x:1,y:1,w:1,h:1},{id:1,c:'red',x:2,y:1,w:1,h:2},{id:2,c:'green',x:1,y:3,w:2,h:1}]"></drag-and-drop>
</div>
<h3 v-if="loaded" class="d-flex">
<span class="col">Hallo {{name}}!</span>
<!--div class="dropstart">
<button type="button" class="btn btn-secondary" data-bs-display="static" data-bs-auto-close="outside" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-gear"></i>
</button>
<ul class="dropdown-menu">
<li><span class="dropdown-item-text">Meine Widgets</span></li>
<li v-for="widget in widgets" :key="widget[0]">
<button class="dropdown-item" type="button" data-bs-toggle="collapse" :data-bs-target="'#settings-' + widget[0]">Action</button>
<core-dashboard-item :id="'settings-' + widget[0]" class="collapse w-100" editMode="true" :config="widget[4]" @change="v => widget[4] = v" :widget="widget[5]" @remove="removeWidget(widget[0])"></core-dashboard-item>
</li>
</ul>
</div-->
<a href="#" class="link-secondary" v-if="editMode" @click.prevent="saveConfig"><i class="fa-solid fa-floppy-disk"></i></a>
<a href="#" class="link-secondary" v-else @click.prevent="editMode = true"><i class="fa-solid fa-gear"></i></a>
</h3>
<div v-else class="fetch-loader">Loading...</div>
<div v-if="loaded" class="core-dashboard-list row">
<core-dashboard-item v-for="widget in widgets" :key="widget[0]" :editMode="editMode" :config="widget[4]" @change="v => widget[4] = v" :widget="widget[5]" :style="{'--core-dashboard-order-sm':widget[1],'--core-dashboard-order-md':widget[2],'--core-dashboard-order-lg':widget[3]}" @remove="removeWidget(widget[0])"></core-dashboard-item>
<div v-if="editMode" class="core-dashboard-item-add col-sm-6 col-md-3">
<div class="fixed-h fixed-h-1">
<div class="card d-flex justify-content-center align-items-center" @click="startWidgetWizard">
<i class="fa-solid fa-plus h1"></i>
</div>
</div>
</div>
</div>
<div ref="widgetWizard" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add new widget</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<template v-if="allowedWidgets.length">
<div v-for="widget in allowedWidgets" :v-key="widget.dashboard_widget_id" class="col">
<div class="card" @click="newWidget(widget)">
<img src="..." class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title text-center">{{widget.name}}</h5>
</div>
</div>
</div>
</template>
<div v-else class="fetch-loader">Loading...</div>
</div>
</div>
</div>
</div>
</div>
</div>`
}
+26
View File
@@ -0,0 +1,26 @@
export default {
props: [
"configMode",
"config"
],
methods: {
changeConfig() {
this.config.display = parseInt(this.$refs.display.value);
this.$emit('config', this.config);
}
},
template: `<div class="dbw-kpi">
<div>KPI Widget</div>
<div v-if="configMode">
<select ref="display" class="form-control">
<option value="0" :selected="config.display == 0">H1</option>
<option value="1" :selected="config.display == 1">H2</option>
<option value="2" :selected="config.display == 2">H3</option>
</select>
<button class="btn btn-default" @click="changeConfig">Save</button>
</div>
<template v-else>
<span v-for="val in config.data" :class="'h' + (1 + parseInt(config.display))">{{val}}</span>
</template>
</div>`
}
@@ -0,0 +1,139 @@
export default {
data: () => ({
isMounted: 0,
movedObjects: [],
tmpStyle: {
display: 'none',
background: 'gray',
'grid-column-start': 0,
'grid-column-end': 0,
'grid-row-start': 0,
'grid-row-end': 0,
}
}),
props: [
"width",
"height",
"items"
],
computed: {
gridWidth() {
if(!this.isMounted)
return 0;
return window.getComputedStyle(this.$refs.container).getPropertyValue('grid-template-columns').split(" ").length;
},
gridHeight() {
if (!this.gridWidth)
return 0;
let minH = 0;
this.items.forEach(item => {
// TODO(chris): item change is not detected?
minH = Math.max(minH, item.y + item.h - 1);
});
return Math.max(1, minH);
},
gridOccupiers() {
let occupiers = [];
let gridWidth = this.gridWidth;
this.items.forEach(item => {
for (var i = 0; i < item.w; i++)
for (var j = 0; j < item.h; j++)
occupiers[(item.y+j) * gridWidth + (item.x+i)] = item.id;
});
return occupiers;
}
},
methods: {
startDrag(evt, item) {
evt.dataTransfer.dropEffect = 'move';
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData('itemId', item.id)
evt.dataTransfer.setData('itemW', item.w)
evt.dataTransfer.setData('itemH', item.h)
console.log(evt.target.style.display);
},
moveItem(item) {
// TODO(chris): IMPLEMENT
if (!item._x)
item._x = item.x;
item.x++;
},
moveItemBack(item) {
item.x = item._x;
},
onDragOver(evt) {
this.tmpStyle.display = 'block';
let x = Math.floor(this.gridWidth * (evt.layerX / this.$refs.container.clientWidth)) + 1;
let y = Math.floor(this.gridHeight * (evt.layerY / this.$refs.container.clientHeight)) + 1;
let w = parseInt(evt.dataTransfer.getData('itemW'));
let h = parseInt(evt.dataTransfer.getData('itemH'));
while (x + w > this.gridWidth + 1)
x--;
// TODO(chris): start
let id = 0;
while (id = this.movedObjects.pop())
/*this.items[id].c = this.items[id]._c;*/
this.moveItemBack(this.items[id]);
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
let id = (y+j) * this.gridWidth + (x+i);
if (this.gridOccupiers[id] && this.gridOccupiers[id] != evt.dataTransfer.getData('itemID')) {
/*if (!this.items[this.gridOccupiers[id]]._c)
this.items[this.gridOccupiers[id]]._c = this.items[this.gridOccupiers[id]].c;
this.items[this.gridOccupiers[id]].c = 'grey';*/
this.moveItem(this.items[this.gridOccupiers[id]]);
this.movedObjects.push(this.gridOccupiers[id]);
}
}
}
// TODO(chris): end
this.tmpStyle['grid-column-start'] = x;
this.tmpStyle['grid-column-end'] = x + w;
this.tmpStyle['grid-row-start'] = y;
this.tmpStyle['grid-row-end'] = y + h;
},
onDrop(evt, list) {
this.tmpStyle.display = 'none';
let id = evt.dataTransfer.getData('itemId');
let x = Math.floor(this.gridWidth * (evt.layerX / this.$refs.container.clientWidth)) + 1;
let y = Math.floor(this.gridHeight * (evt.layerY / this.$refs.container.clientHeight)) + 1;
let w = parseInt(evt.dataTransfer.getData('itemW'));
while (x + w > this.gridWidth + 1)
x--;
this.items.forEach(item => {
if (id == item.id) {
item.x = x;
item.y = y;
console.log(item, id);
}
});
// TODO(chris): find better way to trigger change for gridHeight
this.isMounted++
}
},
watchers: {
items() {
console.log(this.items);
}
},
mounted() {
this.isMounted = 1;
window.addEventListener('resize', e => { this.isMounted ? this.isMounted++ : 0 })
},
template: `<div class="drag-and-drop 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)'" @drop="onDrop($event, 1)" @dragover.prevent="onDragOver" @dragenter.prevent>
<div v-for="item in items" :key="item.id" :style="{'grid-column-start':item.x,'grid-column-end':item.x+item.w,'grid-row-start':item.y,'grid-row-end':item.y+item.h,background:item.c}" @dragstart="startDrag($event, item)" draggable="true">
</div>{{gridWidth2}}
<div :style="tmpStyle"></div>
</div>
</div>`
}
+52
View File
@@ -0,0 +1,52 @@
export default {
components: {},
data: () => ({
configMode: false,
name: '',
component: '',
arguments: null
}),
props: [
"widget",
"config",
"editMode"
],
computed: {
ready() {
return this.name && this.component && this.arguments !== null;
}
},
methods: {
changeConfig(v) {
this.arguments = v;
this.configMode = false;
// TODO(chris): diff arguments widget.arguments
this.$emit('change', v);
}
},
mounted() {
let self = this;
if (!this.isPlaceholder) {
this.$options.components[this.widget.component_name] = this.widget.component;
this.name = this.widget.name;
this.component = this.widget.component_name;
this.arguments = {...this.widget.arguments, ...this.config};
}
},
template: `<div class="core-dashboard-item col-sm-6 col-md-3">
<div class="fixed-h fixed-h-1">
<div v-if="ready" class="card">
<div class="card-header d-flex">
<span v-if="editMode" class="col-auto pe-3"><i class="fa-solid fa-grip-lines-vertical"></i></span>
<span class="col">{{name}}</span>
<a v-if="editMode" class="col-auto ps-1" href="#" @click.prevent="configMode = !configMode"><i class="fa-solid fa-gear"></i></a>
<a v-if="editMode" class="col-auto ps-1" href="#" @click.prevent="$emit('remove')"><i class="fa-solid fa-trash-can"></i></a>
</div>
<div class="card-body">
<component :is="component" :config="arguments" @config="changeConfig" :configMode="configMode"></component>
</div>
</div>
</div>
</div>`
}