From 4f3e8dcb3c8ccfceac18b3eac4f7297c5b25f20b Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Wed, 5 Oct 2022 10:43:43 +0200 Subject: [PATCH] Basic VUE Components & Test WidgetController --- application/controllers/dashboard/Config.php | 2 +- application/controllers/dashboard/Widget.php | 162 +++++++ public/css/components/dashboard.css | 16 + public/js/components/Dashboard/Dashboard.js | 170 +++++++ public/js/components/Dashboard/Item.js | 138 ++++++ public/js/components/Dashboard/Section.js | 417 ++++++++++++++++++ .../js/components/DashboardWidget/Abstract.js | 12 + .../js/components/DashboardWidget/Default.js | 14 + .../Dashboard/CachedWidgetLoader.js | 29 ++ 9 files changed, 959 insertions(+), 1 deletion(-) create mode 100644 application/controllers/dashboard/Widget.php create mode 100644 public/css/components/dashboard.css create mode 100644 public/js/components/Dashboard/Dashboard.js create mode 100644 public/js/components/Dashboard/Item.js create mode 100644 public/js/components/Dashboard/Section.js create mode 100644 public/js/components/DashboardWidget/Abstract.js create mode 100644 public/js/components/DashboardWidget/Default.js create mode 100644 public/js/composables/Dashboard/CachedWidgetLoader.js diff --git a/application/controllers/dashboard/Config.php b/application/controllers/dashboard/Config.php index 60225fa11..ae177091e 100644 --- a/application/controllers/dashboard/Config.php +++ b/application/controllers/dashboard/Config.php @@ -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() diff --git a/application/controllers/dashboard/Widget.php b/application/controllers/dashboard/Widget.php new file mode 100644 index 000000000..f1f0614d7 --- /dev/null +++ b/application/controllers/dashboard/Widget.php @@ -0,0 +1,162 @@ + 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.')); + } + +} diff --git a/public/css/components/dashboard.css b/public/css/components/dashboard.css new file mode 100644 index 000000000..91379200c --- /dev/null +++ b/public/css/components/dashboard.css @@ -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); + } +} diff --git a/public/js/components/Dashboard/Dashboard.js b/public/js/components/Dashboard/Dashboard.js new file mode 100644 index 000000000..04976da9c --- /dev/null +++ b/public/js/components/Dashboard/Dashboard.js @@ -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: `
+ + +
` +} \ No newline at end of file diff --git a/public/js/components/Dashboard/Item.js b/public/js/components/Dashboard/Item.js new file mode 100644 index 000000000..4434311ff --- /dev/null +++ b/public/js/components/Dashboard/Item.js @@ -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: `
+
+ + {{ widget.name }} + + + + +
+ +
+
+
+ +
+
+ + +
` +} \ No newline at end of file diff --git a/public/js/components/Dashboard/Section.js b/public/js/components/Dashboard/Section.js new file mode 100644 index 000000000..9ea40fdc1 --- /dev/null +++ b/public/js/components/Dashboard/Section.js @@ -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: `
+

+ {{name}} + +

+
+
+ + + + +
+
+
` +} \ No newline at end of file diff --git a/public/js/components/DashboardWidget/Abstract.js b/public/js/components/DashboardWidget/Abstract.js new file mode 100644 index 000000000..8d0416e28 --- /dev/null +++ b/public/js/components/DashboardWidget/Abstract.js @@ -0,0 +1,12 @@ +export default { + props: [ + "config", + "width", + "height", + "setConfig", + "configMode" + ], + emits: [ + "change" // TODO(chris): do we need this? + ] +} diff --git a/public/js/components/DashboardWidget/Default.js b/public/js/components/DashboardWidget/Default.js new file mode 100644 index 000000000..29e3880dd --- /dev/null +++ b/public/js/components/DashboardWidget/Default.js @@ -0,0 +1,14 @@ +import AbstractWidget from './Abstract'; + +export default { + mixins: [ + AbstractWidget + ], + created() { + this.$emit('setConfig', false) + }, + template: `
+
{{ config.title }}
+

{{ config.msg }}

+
` +} \ No newline at end of file diff --git a/public/js/composables/Dashboard/CachedWidgetLoader.js b/public/js/composables/Dashboard/CachedWidgetLoader.js new file mode 100644 index 000000000..df9ff6abe --- /dev/null +++ b/public/js/composables/Dashboard/CachedWidgetLoader.js @@ -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; + } +} \ No newline at end of file