Basic VUE Components & Test WidgetController

This commit is contained in:
cgfhtw
2022-10-05 10:43:43 +02:00
parent 90e006feb5
commit 4f3e8dcb3c
9 changed files with 959 additions and 1 deletions
+1 -1
View File
@@ -130,7 +130,7 @@ class Config extends Auth_Controller
$this->terminateWithJsonError('override could not be saved');
}
$this->outputJsonSuccess(array('msg' => 'override successfully stored.'));
$this->outputJsonSuccess(array('msg' => 'override successfully stored.', 'data' => $override_decoded));
}
public function removeWidgetFromUserOverride()
@@ -0,0 +1,162 @@
<?php
defined('BASEPATH') || exit('No direct script access allowed');
/**
* Description of Widget
*
* @author chris
*/
class Widget extends Auth_Controller
{
private $demoData = [
[
"id" => 1,
"name" => 'Widget 1',
"description" => 'Das ist ein Test Widget',
"icon" => 'https://upload.wikimedia.org/wikipedia/commons/8/8a/Farben-Testbild.svg',
"file" => 'DashboardWidget/Widget1.js',
"arguments" => [
"test" => 2
],
"size" => [
"width" => [ "max" => 3 ],
"height" => [ "max" => 3 ]
]
]
];
public function __construct()
{
parent::__construct(
array(
'index' => 'dashboard/benutzer:r',
'getWidgetsForDashboard' => 'dashboard/benutzer:rw',
'addWidgetToUserOverride' => 'dashboard/benutzer:rw',
'updateWidgetsToUserOverride' => 'dashboard/benutzer:rw',
'removeWidgetFromUserOverride' => 'dashboard/benutzer:rw'
)
);
$this->load->library('dashboard/DashboardLib', null, 'DashboardLib');
}
public function index()
{
$widget_id = $this->input->get('id');
foreach ($this->demoData as $widget) {
if ($widget["id"] == $widget_id)
return $this->outputJsonSuccess($widget);
}
return $this->outputJsonSuccess([
"id" => 0,
"name" => 'Widget Not Found',
"file" => 'DashboardWidget/Default.js',
"arguments" => [
"className" => 'alert-danger',
"title" => 'Widget Not Found',
"msg" => 'The widget with the id ' . $widget_id . ' could not be found'
],
"size" => [
"width" => 1,
"height" => 1
]
]);
}
public function getWidgetsForDashboard()
{
$db = $this->input->get('db');
$this->outputJsonSuccess($this->demoData);
}
public function addWidgetToUserOverride()
{
$input = json_decode($this->input->raw_input_stream);
$dashboard_kurzbz = $input->db;
$uid = $input->uid;
$funktion = $input->funktion;
$override = $this->DashboardLib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
$override_decoded = json_decode($override->override, true);
$widgetid = isset($input->widgetid) ? array('widgetid' => $input->widgetid)
: $this->DashboardLib->generateWidgetId($dashboard_kurzbz);
$override_decoded[$funktion][$widgetid['widgetid']] = $input->widget;
$override->override = json_encode($override_decoded);
$result = $this->DashboardLib->insertOrUpdateOverride($override);
if( isError($result) ) {
http_response_code(500);
$this->terminateWithJsonError('override could not be saved');
}
$this->outputJsonSuccess($widgetid['widgetid']);
}
public function updateWidgetsToUserOverride()
{
$input = json_decode($this->input->raw_input_stream, true);
$dashboard_kurzbz = $input['db'];
$uid = $input['uid'];
$funktion = $input['funktion'];
$override = $this->DashboardLib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
$override_decoded = json_decode($override->override, true);
if ($override_decoded[$funktion])
$override_decoded[$funktion] = array_replace_recursive($override_decoded[$funktion], $input['widgets']);
else
$override_decoded[$funktion] = $input['widgets'];
$override->override = json_encode($override_decoded);
$result = $this->DashboardLib->insertOrUpdateOverride($override);
if( isError($result) ) {
http_response_code(500);
$this->terminateWithJsonError('override could not be saved');
}
$this->outputJsonSuccess(['msg' => 'override successfully updated.']);
}
public function removeWidgetFromUserOverride()
{
$input = json_decode($this->input->raw_input_stream);
$dashboard_kurzbz = $input->db;
$uid = $input->uid;
$funktion = $input->funktion;
$widgetid = $input->widgetid;
$override = $this->DashboardLib->getOverride($dashboard_kurzbz, $uid);
if( empty($override) ) {
http_response_code(404);
$this->terminateWithJsonError('userconfig for dashboard '
. $dashboard_kurzbz . ' not found.');
}
$override_decoded = json_decode($override->override, true);
if( array_key_exists($widgetid, $override_decoded[$funktion]) )
{
unset($override_decoded[$funktion][$widgetid]);
}
else
{
http_response_code(404);
$this->terminateWithJsonError('widgetid ' . $widgetid . ' not found in funktion ' . $funktion);
}
$override->override = json_encode($override_decoded);
$result = $this->DashboardLib->insertOrUpdateOverride($override, $uid);
if( isError($result) )
{
http_response_code(500);
$this->terminateWithJsonError('failed to remove widget');
}
$this->outputJsonSuccess(array('msg' => 'override successfully updated.'));
}
}
+16
View File
@@ -0,0 +1,16 @@
.alert-danger .form-check-input:checked {
border-color: #842029;
background-color: #842029;
}
.draganddropcontainer {
grid-template-columns:repeat(4,1fr);
gap: 1rem;
place-items: stretch;
place-content: stretch;
}
@media(max-width: 577px) {
.draganddropcontainer {
grid-template-columns:repeat(2,1fr);
}
}
+170
View File
@@ -0,0 +1,170 @@
import DashboardSection from "./Section.js";
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
export default {
props: [
"dashboard",
"apiurl"
],
data: () => ({
sections: [],
widgets: [],
isLoading: 0,
tmpCreate: null,
}),
components: {
DashboardSection
},
computed: {
listReady() {
return this.widgets.length && !this.isLoading;
}
},
methods: {
widgetAdd(section_name, widget) {
if (!this.widgets.length) {
axios.get(this.apiurl + '/Widget/getWidgetsForDashboard', {params:{
db: this.dashboard
}}).then(res => {
//console.log(res.data.retval);
this.widgets = res.data.retval;
}).catch(err => console.error('ERROR:', err));
}
this.tmpCreate = {section_name,widget};
this.listModal.show();
},
widgetCreate(widget) {
this.isLoading = 1;
this.tmpCreate.widget.widget = widget;
axios.post(this.apiurl + '/Config/addWidgetsToUserOverride', {
db: this.dashboard,
uid: 'ma0168',
funktion_kurzbz: this.tmpCreate.section_name,
widgets: [this.tmpCreate.widget]
}).then(result => {
let newId = 0;
let sec = result.data.retval.data.widgets[this.tmpCreate.section_name];
for (var i in sec) {
newId = i;
break;
}
console.log(newId);
this.tmpCreate.widget.id = newId;
this.sections.forEach(section => {
if (section.name == this.tmpCreate.section_name)
section.widgets.push(this.tmpCreate.widget);
});
}).catch(error => {
console.error('ERROR: ', error);
alert('ERROR: ' + error.response.data.retval);
}).finally(() => {
this.listModal.hide();
this.isLoading = 0;
});
},
widgetUpdate(section_name, payload) {
payload = payload[section_name];
for (var k in payload) {
for (var i in this.sections) {
if (this.sections[i].name == section_name) {
for (var wid in this.sections[i].widgets) {
if (this.sections[i].widgets[wid].id == k) {
payload[k] = {...this.sections[i].widgets[wid], ...payload[k]};
break;
}
}
break;
}
}
payload[k].widgetid = k;
}
return axios.post(this.apiurl + '/Config/addWidgetsToUserOverride', {
db: this.dashboard,
uid: 'ma0168',
funktion_kurzbz: section_name,
widgets: payload
}).then(result => {
this.sections.forEach(section => {
if (section.name == section_name) {
section.widgets.forEach((widget, i) => {
if (payload[widget.id]) {
// TODO(chris): revert placement on failure
delete payload[widget.id].place; // TODO(chris): find out why overwriting place bugs out
section.widgets[i] = {...widget, ...payload[widget.id]};
}
});
}
});
}).catch(error => {
console.error('ERROR: ', error);
alert('ERROR: ' + error.response.data.retval);
});
},
widgetRemove(section_name, id) {
axios.post(this.apiurl + '/Config/removeWidgetFromUserOverride', {
db: this.dashboard,
uid: 'ma0168',
funktion_kurzbz: section_name,
widgetid: id
}).then(result => {
this.sections.forEach(section => {
if (section.name == section_name)
section.widgets = section.widgets.filter(widget => widget.id != id);
});
}).catch(error => {
console.error('ERROR: ', error);
alert('ERROR: ' + error.response.data.retval);
});
}
},
created() {
CachedWidgetLoader.setPath(this.apiurl + '/Widget');
axios.get(this.apiurl + '/Config', {params:{
db: this.dashboard,
uid: 'ma0168'
}}).then(res => {
//console.log(res.data.retval);
for (var name in res.data.retval.widgets) {
let widgets = [];
for (var wid in res.data.retval.widgets[name]) {
res.data.retval.widgets[name][wid].id = wid;
widgets.push(res.data.retval.widgets[name][wid]);
}
this.sections.push({
name: name,
widgets: widgets
});
}
}).catch(err => console.error('ERROR:', err));
},
mounted() {
this.listModal = new bootstrap.Modal(this.$refs.widgetlist);
},
template: `<div class="core-dashboard">
<dashboard-section v-for="section in sections" :key="section.name" :name="section.name" :widgets="section.widgets" @widgetAdd="widgetAdd" @widgetUpdate="widgetUpdate" @widgetRemove="widgetRemove"></dashboard-section>
<div ref="widgetlist" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create new widget</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div v-if="listReady" class="row">
<div v-for="widget in widgets" :v-key="widget.id" class="col">
<div class="card h-100" @click="widgetCreate(widget.id)">
<img class="card-img-top" :src="widget.icon" :alt="'pictogram for ' + widget.name">
<div class="card-body">
<h5 class="card-title">{{ widget.name }}</h5>
<p class="card-text">{{ widget.description }}</p>
</div>
</div>
</div>
</div>
<div v-else class="text-center"><i class="fa-solid fa-spinner fa-pulse fa-3x"></i></div>
</div>
</div>
</div>
</div>
</div>`
}
+138
View File
@@ -0,0 +1,138 @@
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
export default {
components: {},
data: () => ({
component: '',
arguments: null,
configModal: null,
target: false,
widget: null,
tmpConfig: {},
isLoading: false,
hasConfig: true
}),
emits: [
"change",
"remove",
"dragstart",
"resizestart",
],
props: [
"id",
"config",
"width",
"height",
"custom",
"hidden",
"editMode"
],
computed: {
isResizeable() {
if (!this.widget)
return false;
return this.widget.size.width.max || this.widget.size.height.max;
},
ready() {
return this.component && this.arguments !== null
}
},
methods: {
mouseDown(e) {
this.target = e.target;
},
startDrag(e) {
if (this.$refs.dragHandle.contains(this.target)) {
this.$emit('dragstart', e);
} else if (this.isResizeable && this.$refs.resizeHandle.contains(this.target)) {
if (this.isResizeable)
this.$emit('resizestart', e);
else
e.preventDefault();
} else {
e.preventDefault();
}
},
openConfig() {
this.tmpConfig = {...this.arguments};
this.configModal.show();
},
setConfig(hasConfig) {
this.hasConfig = hasConfig;
},
changeConfig() {
this.isLoading = true;
let config = {...this.tmpConfig};
this.sendChangeConfig(config);
},
changeConfigManually() {
let config = {...this.arguments};
this.sendChangeConfig(config);
},
sendChangeConfig(config) {
for (var k in config) {
if (this.widget.arguments[k] == config[k]) {
delete config[k];
}
}
this.$emit('change', config);
}
},
watch: {
config() {
this.arguments = {...this.widget.arguments, ...this.config};
this.tmpConfig = {...this.arguments};
this.configModal.hide();
this.isLoading = false;
}
},
async created() {
this.widget = await CachedWidgetLoader.loadWidget(this.id);
let component = (await import('../' + this.widget.file)).default;
this.$options.components['widget' + this.widget.id] = component;
this.component = 'widget' + this.widget.id;
this.arguments = {...this.widget.arguments, ...this.config};
this.tmpConfig = {...this.arguments};
},
mounted() {
this.configModal = new bootstrap.Modal(this.$refs.config);
},
template: `<div v-if="!hidden || editMode" :class="'dashboard-item card overflow-hidden ' + (arguments ? arguments.className : '')" @mousedown="mouseDown($event)" @dragstart="startDrag($event)" :draggable="!!editMode">
<div v-if="editMode && widget" class="card-header d-flex">
<span ref="dragHandle" class="col-auto pe-3"><i class="fa-solid fa-grip-vertical"></i></span>
<span class="col">{{ widget.name }}</span>
<a v-if="hasConfig" class="col-auto ps-1" href="#" @click.prevent="openConfig"><i class="fa-solid fa-gear"></i></a>
<a v-if="custom" class="col-auto ps-1" href="#" @click.prevent="$emit('remove')">
<i class="fa-solid fa-trash"></i>
</a>
<div v-else class="col-auto ps-1 form-switch">
<input class="form-check-input ms-0" type="checkbox" role="switch" id="flexSwitchCheckChecked" :checked="!hidden" @input="$emit('remove', hidden)">
</div>
</div>
<div v-if="ready" class="card-body overflow-hidden">
<component :is="component" :config="arguments" :width="width" :height="height" @setConfig="setConfig" @change="changeConfigManually"></component>
</div>
<div v-else class="card-body overflow-hidden text-center d-flex flex-column justify-content-center"><i class="fa-solid fa-spinner fa-pulse fa-3x"></i></div>
<div ref="config" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 v-if="widget" class="modal-title">Config for {{ widget.name }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<component v-if="ready && !isLoading" :is="component" :config="tmpConfig" @change="changeConfig" :configMode="true"></component>
<div v-else class="text-center"><i class="fa-solid fa-spinner fa-pulse fa-3x"></i></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" @click="changeConfig">Save changes</button>
</div>
</div>
</div>
</div>
<div v-if="editMode && isResizeable" class="card-footer d-flex justify-content-end p-0">
<span ref="resizeHandle" class="col-auto ps-1" @dragstart.prevent="$emit('resize')"><i class="fa-solid fa-up-right-and-down-left-from-center"></i></span>
</div>
</div>`
}
+417
View File
@@ -0,0 +1,417 @@
import DashboardItem from "./Item.js";
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
// TODO(chris): handle overflow (moving outside the box)
export default {
data: () => ({
gridWidth: 0,
containerRect: {top:0,left:0},
changeHeight: 1,
movedObjects: [],
editMode: 0,
gridXLast: 0,
gridYLast: 0,
}),
props: [
"name",
"widgets"
],
emits: [
"widgetAdd",
"widgetUpdate",
"widgetRemove"
],
components: {
DashboardItem
},
computed: {
items() {
this.widgets.forEach((item,i) => item.index = i);
return this.widgets;
},
itemCoords() {
if (!this.gridWidth)
return [];
let itemCoords = this.items.map(item => item.place[this.gridWidth] || this.createItemPlacement(item));
// TODO(chris): verify positions & sizes
let occupiers = [];
let wrongPlacedItems = [];
let gridWidth = this.gridWidth;
this.items.forEach(item => {
let x = item._x !== undefined ? item._x : itemCoords[item.index].x;
let y = item._y !== undefined ? item._y : itemCoords[item.index].y;
let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
// TODO(chris): check with and height params here?
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
var c = (y+j-1) * gridWidth + (x+i-1);
// NOTE(chris): check for overlaping items
if (occupiers[c] !== undefined) {
//console.log('try to add ' + item.index + ' to ' + x + '/' + y + ', but ' + occupiers[c] + ' is already there');
// NOTE(chris): remove possible other entries of this item
for (var c2 = c; c2; c2--)
if (occupiers[c2] == item.index)
occupiers[c2] = undefined;
wrongPlacedItems.push(item);
return;
}
occupiers[c] = item.index;
}
}
});
wrongPlacedItems.forEach(item => {
let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
for (var c = 0; c < occupiers.length + gridWidth; c++) {
if (occupiers[c] === undefined) {
var occupied = false;
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
if (occupiers[c + i + j * gridWidth] !== undefined) {
i = w;
occupied = true;
break;
}
}
}
if (!occupied) {
item.place[gridWidth].x = c%gridWidth + 1;
item.place[gridWidth].y = Math.floor(c/gridWidth) + 1;
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
occupiers[c + i + j * gridWidth] = item.index;
}
}
return;
}
}
}
});
return itemCoords;
},
gridHeight() {
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));
// TODO(chris): the extraline should only be present if all slots are occupied
return minH + this.editMode;
},
gridOccupiers() {
let occupiers = [];
let gridWidth = this.gridWidth;
this.items.forEach(item => {
let x = item._x !== undefined ? item._x : this.itemCoords[item.index].x;
let y = item._y !== undefined ? item._y : this.itemCoords[item.index].y;
let w = item._w !== undefined ? item._w : this.itemCoords[item.index].w;
let h = item._h !== undefined ? item._h : this.itemCoords[item.index].h;
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
var c = (y+j-1) * gridWidth + (x+i-1);
occupiers[c] = item.index;
}
}
});
return occupiers;
}
},
methods: {
addWidget(evt) {
if (evt.target != this.$refs.container || !this.editMode)
return;
const rect = this.containerRect;
const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth) + 1;
const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight) + 1;
if (this.gridOccupiers[gridY * this.gridWidth + gridX] === undefined) {
let widget = { widget: 1, config: {}, place: {}, custom: 1 };
widget.place[this.gridWidth] = {
x: gridX,
y: gridY,
w: 1,
h: 1
};
this.$emit('widgetAdd', this.name, widget);
}
},
createItemPlacement(item) {
// TODO(chris): create correct default placement if it is not there
item.place[this.gridWidth] = {x:1,y:1,w:1,h:1};
/*var freeList = [], nextId = 0;
this.items.forEach(item => {
if (!item.place[this.gridWidth]) {
if (!this.gridWidth) {
item.place[this.gridWidth] = {x:1,y:nextId++,w:1,h:1};
} else {
// TODO(chris): IMPLEMENT widths & heights
if (freeList[nextId])
while (freeList[++nextId]);
freeList[nextId] = 1;
item.place[this.gridWidth] = {x:(nextId%this.gridWidth)+1,y:Math.floor(nextId/this.gridWidth)+1,w:1,h:1};
}
}
});*/
return item.place[this.gridWidth];
},
startDrag(evt, item) {
this.gridXLast = -1;
this.gridYLast = -1;
item._x = this.itemCoords[item.index].x;
item._y = this.itemCoords[item.index].y;
evt.dataTransfer.dropEffect = 'move';
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData('itemAction', 'm');
evt.dataTransfer.setData('itemId', item.index);
evt.dataTransfer.setData('itemW', this.itemCoords[item.index].w);
evt.dataTransfer.setData('itemH', this.itemCoords[item.index].h);
},
startResize(evt, item) {
this.gridXLast = -1;
this.gridYLast = -1;
item._w = this.itemCoords[item.index].w;
item._h = this.itemCoords[item.index].h;
evt.dataTransfer.setDragImage(evt.target, -99999, -99999);
evt.dataTransfer.dropEffect = 'move';
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData('itemAction', 'r');
evt.dataTransfer.setData('itemId', item.index);
evt.dataTransfer.setData('itemX', this.itemCoords[item.index].x);
evt.dataTransfer.setData('itemY', this.itemCoords[item.index].y);
},
occupyFields(id, x, y, w, h) {
var c;
while ((c = this.movedObjects.pop())) {
if (this.items[c]._y !== undefined) {
this.items[c].place[this.gridWidth].y = this.items[c]._y;
this.items[c]._y = undefined;
}
}
var move = {};
move[id] = this.items[id];
this.getOccupiedItems(x,y,w,h,move);
h = y + h;
y = 0;
for (x in move) {
if (x != id) {
c = move[x]._y !== undefined ? move[x]._y : this.itemCoords[x].y;
if (c < h)
y = Math.max(h-c, y);
}
}
for (x in move) {
if (x != id) {
this.movedObjects.push(x);
if (move[x]._y === undefined) {
move[x]._y = this.itemCoords[x].y;
}
move[x].place[this.gridWidth].y = move[x]._y + y;
}
}
},
getOccupiedItems(x, y, w, h, move) {
var i, j, c;
for (i = 0; i < w; i++) {
for (j = 0; j < h; j++) {
c = (y+j-1) * this.gridWidth + (x+i-1);
if (this.gridOccupiers[c] !== undefined && !move[this.gridOccupiers[c]]) {
move[this.gridOccupiers[c]] = this.items[this.gridOccupiers[c]];
c = this.itemCoords[this.gridOccupiers[c]];
this.getOccupiedItems(c.x, c.y + 1, c.w, c.h, move);
}
}
}
},
onDragOver(evt) {
let id, x, y, w, h;
const action = evt.dataTransfer.getData('itemAction');
const rect = this.containerRect;
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.gridXLast == gridX && this.gridYLast == gridY)
return;
this.gridXLast = gridX;
this.gridYLast = gridY;
if (action == 'm') {
x = gridX + 1;
y = gridY + 1;
w = parseInt(evt.dataTransfer.getData('itemW'));
h = parseInt(evt.dataTransfer.getData('itemH'));
if (x + w > this.gridWidth + 1)
x = this.gridWidth + 1 - w;
id = evt.dataTransfer.getData('itemID');
this.occupyFields(id, x, y, w, h);
this.itemCoords[id].x = x;
this.itemCoords[id].y = y;
} else if (action == 'r') {
x = parseInt(evt.dataTransfer.getData('itemX'));
y = parseInt(evt.dataTransfer.getData('itemY'));
w = gridX + 2 - x;
h = gridY + 2 - y;
w = Math.max(1, w);
h = Math.max(1, h);
if (x + w > this.gridWidth + 1)
w = this.gridWidth + 1 - x;
id = evt.dataTransfer.getData('itemID');
let widget = CachedWidgetLoader.getWidget(this.items[id].widget);
if (widget) {
let minmaxW = widget.size.width;
if (minmaxW.max)
minmaxW.min = minmaxW.min || 1;
else
minmaxW = {min:minmaxW,max:minmaxW};
if (w < minmaxW.min)
w = minmaxW.min;
if (w > minmaxW.max)
w = minmaxW.max;
let minmaxH = widget.size.height;
if (minmaxH.max)
minmaxH.min = minmaxH.min || 1;
else
minmaxH = {min:minmaxH,max:minmaxH};
if (h < minmaxH.min)
h = minmaxH.min;
if (h > minmaxH.max)
h = minmaxH.max;
}
this.occupyFields(id, x, y, w, h);
this.itemCoords[id].w = w;
this.itemCoords[id].h = h;
}
},
onDrop(evt) {
let id = 0;
let update = {};
while ((id = this.movedObjects.pop())) {
if (this.items[id]._y !== undefined) {
if (this.itemCoords[id].y != this.items[id]._y) {
update[this.items[id].id] = {place:{}};
update[this.items[id].id].place[this.gridWidth] = {y:this.itemCoords[id].y};
}
this.items[id]._y = undefined;
}
}
id = evt.dataTransfer.getData('itemId');
const action = evt.dataTransfer.getData('itemAction');
update[this.items[id].id] = {place:{}};
update[this.items[id].id].place[this.gridWidth] = {};
if (action == 'm') {
if (this.items[id]._x !== undefined) {
if (this.itemCoords[id].x != this.items[id]._x) {
update[this.items[id].id].place[this.gridWidth].x = this.itemCoords[id].x;
}
this.items[id]._x = undefined;
}
if (this.items[id]._y !== undefined) {
if (this.itemCoords[id].y != this.items[id]._y) {
update[this.items[id].id].place[this.gridWidth].y = this.itemCoords[id].y;
}
this.items[id]._y = undefined;
}
} else if (action == 'r') {
update[this.items[id].id].place[this.gridWidth].w = this.itemCoords[id].w;
update[this.items[id].id].place[this.gridWidth].h = this.itemCoords[id].h;
}
if (update[this.items[id].id].place[this.gridWidth].x === undefined &&
update[this.items[id].id].place[this.gridWidth].y === undefined &&
update[this.items[id].id].place[this.gridWidth].w === undefined &&
update[this.items[id].id].place[this.gridWidth].h === undefined) {
delete update[this.items[id].id].place[this.gridWidth];
}
this.updatePreset(update);
// TODO(chris): find better way to trigger change for gridHeight
this.changeHeight++
},
removeWidget(item, revert) {
if (item.custom) {
if (confirm('Are you sure you want to delete this widget?')) {
this.$emit('widgetRemove', this.name, item.id);
}
} else {
let update = {};
update[item.id] = { hidden: !revert };
this.updatePreset(update);
}
},
saveConfig(config, item) {
let payload = {};
payload[item.id] = { config };console.log(payload);
this.updatePreset(payload);
},
updatePreset(update) {
let payload = {};
payload[this.name] = update;
this.$emit('widgetUpdate', this.name, payload);
}
},
mounted() {
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 = '';
}
});
},
template: `<div class="dashboard-section">
<h3 class="d-flex">
<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 ref="container"
class="position-absolute top-0 left-0 w-100 h-100 draganddropcontainer"
:style="'display:grid;grid-template-rows:repeat('+gridHeight+',1fr)'"
@click="addWidget($event)"
@drop="onDrop($event, 1)"
@dragover.prevent="onDragOver"
@dragenter.prevent>
<dashboard-item
v-for="item in items"
:key="item.id"
:id="item.widget"
:config="item.config"
:custom="item.custom"
:hidden="item.hidden"
:editMode="editMode"
:x="itemCoords[item.index] ? itemCoords[item.index].x : -1"
:y="itemCoords[item.index] ? itemCoords[item.index].y : -1"
:style="itemCoords[item.index] ? {'grid-column-start':itemCoords[item.index].x,'grid-column-end':itemCoords[item.index].x+itemCoords[item.index].w,'grid-row-start':itemCoords[item.index].y,'grid-row-end':itemCoords[item.index].y+itemCoords[item.index].h} : {}"
:width="itemCoords[item.index] ? itemCoords[item.index].w : 0"
:height="itemCoords[item.index] ? itemCoords[item.index].h : 0"
@dragstart="startDrag($event, item)"
@resizestart="startResize($event, item)"
@change="saveConfig($event, item)"
@remove="removeWidget(item, $event)">
</dashboard-item>
</div>
</div>
</div>`
}
@@ -0,0 +1,12 @@
export default {
props: [
"config",
"width",
"height",
"setConfig",
"configMode"
],
emits: [
"change" // TODO(chris): do we need this?
]
}
@@ -0,0 +1,14 @@
import AbstractWidget from './Abstract';
export default {
mixins: [
AbstractWidget
],
created() {
this.$emit('setConfig', false)
},
template: `<div class="dashboard-widget-default">
<h5 class="card-title">{{ config.title }}</h5>
<p class="card-text">{{ config.msg }}</p>
</div>`
}
@@ -0,0 +1,29 @@
let __widgets = {};
let __widgetsStarted = {};
let __path = '';
export default {
getWidget(id) {
return __widgets[id];
},
loadWidget(id) {
if (__widgets[id])
return Promise.resolve(__widgets[id]);
if (__widgetsStarted[id])
return __widgetsStarted[id];
if (!__path)
return Promise.reject('Widget could not be loaded because there is no path yet!');
__widgetsStarted[id] = new Promise((resolve, reject) => {
axios.get(__path, {params:{id}}).then(res => {
__widgets[id] = res.data.retval;
__widgetsStarted[id] = undefined;
resolve(__widgets[id]);
}).catch(error => reject(error.response.data.retval.error));
});
return __widgetsStarted[id];
},
setPath(path) {
__path = path;
}
}