Add parent widget drag handle (#7792)

* Add parent widget drag handle

* Update parent.php

* Update css.php
This commit is contained in:
Alex
2026-03-17 01:08:52 +00:00
committed by GitHub
parent c7769b1dbd
commit f0377700c4
3 changed files with 90 additions and 49 deletions
+85 -45
View File
@@ -310,7 +310,7 @@ div.hud_chart {
padding-top: 7px; padding-top: 7px;
} }
/* dashboard settings */ /* Dashboard Settings */
<?php <?php
foreach ($widgets as $row) { foreach ($widgets as $row) {
$widget_id = 'id_'.md5(preg_replace('/[^-A-Fa-f0-9]/', '', $row['dashboard_widget_uuid'])); $widget_id = 'id_'.md5(preg_replace('/[^-A-Fa-f0-9]/', '', $row['dashboard_widget_uuid']));
@@ -484,7 +484,7 @@ foreach ($widgets as $row) {
} }
?> ?>
/* Screen smaller than 575px? 1 columns */ /* Screen smaller than 575px? 1 column */
@media (max-width: 575px) { @media (max-width: 575px) {
.widgets { grid-template-columns: repeat(1, minmax(100px, 1fr)); } .widgets { grid-template-columns: repeat(1, minmax(100px, 1fr)); }
.col-num { grid-column: span 1; } .col-num { grid-column: span 1; }
@@ -558,6 +558,7 @@ foreach ($widgets as $row) {
<script> <script>
// Widget details expand button
document.addEventListener('click', function(event) { document.addEventListener('click', function(event) {
let hud_content = event.target.closest('.hud_content'); let hud_content = event.target.closest('.hud_content');
let hud_expander = event.target.closest('.hud_expander'); let hud_expander = event.target.closest('.hud_expander');
@@ -589,7 +590,7 @@ function toggle_grid_row_span(widget_id) {
let first_toggle = false; let first_toggle = false;
function toggle_grid_row_span_all() { function toggle_grid_row_span_all() {
const widgets = document.querySelectorAll('div.widget, div.child_widget'); const widgets = document.querySelectorAll('div.widget');
widgets.forEach(widget => { widgets.forEach(widget => {
if (widget.classList.contains('details_disabled')) { if (widget.classList.contains('details_disabled')) {
@@ -613,14 +614,15 @@ function toggle_grid_row_span_all() {
first_toggle = true; first_toggle = true;
} }
// Automatically update parent widget height and row span based on content and window size
function update_parent_height() { function update_parent_height() {
const parent_widgets = document.querySelectorAll('.parent_widget'); const parent_widgets = document.querySelectorAll('.parent_widget > .hud_box > .hud_content');
parent_widgets.forEach(parent_widget => { parent_widgets.forEach(parent_widget => {
if (!parent_widget.dataset.originalHeight) { if (!parent_widget.dataset.originalHeight) {
parent_widget.dataset.originalHeight = parseFloat(window.getComputedStyle(parent_widget).height.replace('px', '')); parent_widget.dataset.originalHeight = parseFloat(window.getComputedStyle(parent_widget).height.replace('px', ''));
} }
const widget = parent_widget.closest('.widget'); const widget = parent_widget.closest('.parent_widget');
const row_gap = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--grid-gap').replace('px', '')); const row_gap = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--grid-gap').replace('px', ''));
const row_height = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--row-height').replace('px', '')); const row_height = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--row-height').replace('px', ''));
const original_row_span = parseInt(window.getComputedStyle(widget).getPropertyValue('--row-span').replace('span ', '')); const original_row_span = parseInt(window.getComputedStyle(widget).getPropertyValue('--row-span').replace('span ', ''));
@@ -647,7 +649,7 @@ window.addEventListener('resize', update_parent_height);
<?php <?php
//include the dashboards //include the widgets
echo "<div class='widgets' id='widgets' style='padding: 0 5px;'>\n"; echo "<div class='widgets' id='widgets' style='padding: 0 5px;'>\n";
$x = 0; $x = 0;
foreach ($widgets as $row) { foreach ($widgets as $row) {
@@ -707,7 +709,10 @@ window.addEventListener('resize', update_parent_height);
$widget_path_name = $widget_path_array[1]; $widget_path_name = $widget_path_array[1];
$path_array = glob(dirname(__DIR__, 2).'/*/'.$application_name.'/resources/dashboard/'.$widget_path_name.'.php'); $path_array = glob(dirname(__DIR__, 2).'/*/'.$application_name.'/resources/dashboard/'.$widget_path_name.'.php');
echo "<div class='widget details_".$widget_details_state."' id='".$widget_id."' ".($widget_path == 'dashboard/parent' ? "data-is-parent='true'" : null)." draggable='false'>\n"; // Widget class
$widget_class = ($widget_path == 'dashboard/parent' ? 'parent_widget' : 'widget');
echo "<div class='".$widget_class." details_".$widget_details_state." draggable' id='".$widget_id."' draggable='false'>\n";
if (file_exists($path_array[0])) { if (file_exists($path_array[0])) {
include $path_array[0]; include $path_array[0];
} }
@@ -730,22 +735,26 @@ window.addEventListener('resize', update_parent_height);
user-select: none; user-select: none;
} }
div.widget.editable { .editable {
cursor: move; position: relative;
cursor: grab;
} }
.hud_box.editable { .hud_box.editable {
transition: 0.2s; transition: 0.2s;
border: 1px dashed rgba(0,0,0,0.4); border: 1px dashed rgba(0,0,0,0.3);
} }
.hud_box.editable:hover { .hud_box.editable:hover {
box-shadow: 0 5px 10px rgba(0,0,0,0.2); box-shadow: 0 5px 10px rgba(0,0,0,0.2);
border: 1px dashed rgba(0,0,0,0.4); border: 1px dashed rgba(0,0,0,0.3);
transform: scale(1.03, 1.03);
transition: 0.2s; transition: 0.2s;
} }
.widget > .hud_box.editable:hover {
transform: scale(1.03, 1.03);
}
.hud_box .hud_box.editable:hover { .hud_box .hud_box.editable:hover {
box-shadow: none; box-shadow: none;
transform: none; transform: none;
@@ -760,57 +769,63 @@ window.addEventListener('resize', update_parent_height);
<?php unset($br); ?> <?php unset($br); ?>
opacity: 0.2; opacity: 0.2;
} }
.widget_edit_actions {
z-index: 999;
position: absolute;
top: 0;
right: 0;
background: #ffffff75;
backdrop-filter: blur(5px);
border-bottom-left-radius: 5px;
overflow: hidden;
color: #000000aa;
}
.widget_edit_actions i {
padding: 8px 12px;
background: #00000020;
font-size: 16px;
}
</style> </style>
<script> <script>
const widgets = document.getElementById('widgets'); const widgets = document.getElementById('widgets');
let sortable; let sortable;
//make widgets draggable
function edit_mode(state) { function edit_mode(state) {
if (state == 'on') { if (state == 'on') {
$('span#expand_contract, #btn_edit, #btn_add').hide(); $('span#expand_contract, #btn_edit, #btn_add').hide();
$('.hud_box').addClass('editable'); $('.hud_box').addClass('editable');
$('#btn_back, #btn_save').show(); $('#btn_back, #btn_save').show();
$('div.widget').attr('draggable',true).addClass('editable'); $('div.widget, div.parent_widget').attr('draggable',true).addClass('editable');
$('div.child_widget').attr('draggable',true).addClass('editable'); $('div.child_widget').attr('draggable',true).addClass('editable');
function update_widget_order() { // Widget edit actions
let widget_ids_list = []; document.querySelectorAll('.parent_widget > .hud_box').forEach(widget => {
let order = 10; const container = document.createElement('div');
container.className = 'widget_edit_actions';
widgets.querySelectorAll(':scope > div.widget[id]').forEach(widget => { // Drag handle
const widget_id = widget.id; const grip_icon = document.createElement('i');
grip_icon.className = 'fa-solid fa-grip-lines';
//add the widgets to the list container.appendChild(grip_icon);
widget_ids_list.push(`${widget_id}|null|${order}`); widget.prepend(container);
order += 10; });
//add the nested widgets to the list
const nested_container = widget.querySelector('.parent_widget');
if (nested_container) {
nested_container.querySelectorAll(':scope > div.child_widget[id]').forEach(nested => {
const child_id = nested.id;
widget_ids_list.push(`${child_id}|${widget_id}|${order}`);
order += 10;
});
}
});
document.getElementById('widget_order').value = widget_ids_list;
}
// Make widgets draggable
sortable = Sortable.create(widgets, { sortable = Sortable.create(widgets, {
group: { group: {
name: 'shared', name: 'shared',
pull: function(to, from, dragEl) { pull: function(to, from, dragEl) {
return !dragEl.hasAttribute('data-is-parent'); return !dragEl.classList.contains('parent_widget');
}, },
put: true, put: true,
}, },
animation: 150, animation: 150,
draggable: '.widget', draggable: '.draggable',
preventOnFilter: true, preventOnFilter: true,
ghostClass: 'ghost', ghostClass: 'ghost',
onSort: update_widget_order, onSort: update_widget_order,
@@ -831,7 +846,7 @@ window.addEventListener('resize', update_parent_height);
}, },
}); });
document.querySelectorAll('.parent_widget').forEach(function(container) { document.querySelectorAll('.parent_widget > .hud_box > .hud_content').forEach(function(container) {
Sortable.create(container, { Sortable.create(container, {
group: { group: {
name: 'shared', name: 'shared',
@@ -839,7 +854,7 @@ window.addEventListener('resize', update_parent_height);
return true; return true;
}, },
put: function(to, from, dragEl) { put: function(to, from, dragEl) {
return !dragEl.hasAttribute('data-is-parent'); return !dragEl.classList.contains('parent_widget');
}, },
}, },
animation: 150, animation: 150,
@@ -870,18 +885,43 @@ window.addEventListener('resize', update_parent_height);
}); });
}); });
function update_widget_order() {
let widget_ids_list = [];
let order = 10;
widgets.querySelectorAll(':scope > div.widget[id]').forEach(widget => {
const widget_id = widget.id;
//add the widgets to the list
widget_ids_list.push(`${widget_id}|null|${order}`);
order += 10;
//add the nested widgets to the list
const nested_container = widget.querySelector('.parent_widget > .hud_box > .hud_content');
if (nested_container) {
nested_container.querySelectorAll(':scope > div.child_widget[id]').forEach(nested => {
const child_id = nested.id;
widget_ids_list.push(`${child_id}|${widget_id}|${order}`);
order += 10;
});
}
});
document.getElementById('widget_order').value = widget_ids_list;
}
} }
else { // off else { // off
$('div.widget, div.parent_widget, div.child_widget').attr('draggable',false).removeClass('editable');
$('div.widget').attr('draggable',false).removeClass('editable');
$('div.child_widget').attr('draggable',false).removeClass('editable');
$('.hud_box').removeClass('editable'); $('.hud_box').removeClass('editable');
$('#btn_back, #btn_save').hide(); $('#btn_back, #btn_save').hide();
$('span#expand_contract, #btn_edit, #btn_add').show(); $('span#expand_contract, #btn_edit, #btn_add').show();
document.querySelectorAll('.widget_edit_actions').forEach(element => element.remove());
sortable.option('disabled', true); sortable.option('disabled', true);
document.querySelectorAll('.parent_widget').forEach(el => { document.querySelectorAll('.parent_widget > .hud_box > .hud_content').forEach(element => {
const nested_sortable = Sortable.get(el); const nested_sortable = Sortable.get(element);
if (nested_sortable) { if (nested_sortable) {
nested_sortable.option('disabled', true); nested_sortable.option('disabled', true);
} }
@@ -69,7 +69,7 @@
<style> <style>
div.parent_widget { .parent_widget > .hud_box > .hud_content {
max-width: 100%; max-width: 100%;
margin: 0 auto; margin: 0 auto;
display: grid; display: grid;
@@ -124,9 +124,9 @@ foreach ($child_widgets as $row) {
<?php <?php
//include the dashboards //include the widgets
echo "<div class='hud_box'>\n"; echo "<div class='hud_box'>\n";
echo " <div class='hud_content parent_widget'>\n"; echo " <div class='hud_content'>\n";
$x = 0; $x = 0;
foreach ($child_widgets as $row) { foreach ($child_widgets as $row) {
+2 -1
View File
@@ -3004,7 +3004,8 @@ else { //default: white
border: 1px solid <?=$dashboard_border_color_hover?>; border: 1px solid <?=$dashboard_border_color_hover?>;
} }
div.widget div.hud_box:first-of-type { div.widget div.hud_box:first-of-type,
div.parent_widget div.hud_box:first-of-type {
<?php <?php
echo "background: ".($dashboard_background_color[0] ?? '#ffffff').";\n"; echo "background: ".($dashboard_background_color[0] ?? '#ffffff').";\n";
if (!empty($dashboard_background_color) && is_array($dashboard_background_color) && sizeof($dashboard_background_color) > 1) { if (!empty($dashboard_background_color) && is_array($dashboard_background_color) && sizeof($dashboard_background_color) > 1) {