mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-25 08:34:29 +00:00
Dashboard Test V1
This commit is contained in:
@@ -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');
|
||||
@@ -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>`
|
||||
}
|
||||
@@ -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>`
|
||||
}
|
||||
@@ -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>`
|
||||
}
|
||||
Reference in New Issue
Block a user