mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
Common graph drilldown hamburger toggle across all themes (#8308)
* Graph drilldown: common hamburger toggle across all themes The graph action icons were revealed by hovering to the right of the graph, implemented per-theme (dark/midwinter/sunrise main.js) and always-on elsewhere. Hide the icons by default behind a small fa-bars hamburger overlaid on the graph's top-right. Clicking it opens the icon strip, which stays until a second click, leaving the strip, or an outside click. The behaviour is now centralized in include/layout.js (initGraphDrilldown) and include/css/graph_drilldown.css, and the per-theme hover JS is removed. * changelog: 8308 feature fragment * Graph drilldown: address review (a11y, positioning, spikekill, POT) - Anchor the toggle to the shrink-wrapped graph holder (width:fit-content) so the hamburger sits on the graph's top-right, not the full-width column edge. - Keep the hamburger focusable while visually hidden (opacity instead of display:none); reveal on hover, keyboard focus, open, and hover-less devices. - Expose aria-expanded on the toggle and update it in every open/close path; add aria-haspopup/aria-label. - Close any open SpikeKill submenu (spikeKillClose) when the strip closes or switches, matching the removed per-theme handlers. - Remove the now-duplicate Midwinter top-right ellipsis pseudo-element. - Add the new 'Graph Actions' msgid to locales/po/cacti.pot.
This commit is contained in:
1 parent
06c32ab13f
commit
e25a67c31f
9 files changed
+154
-135
No files matched your search
@@ -0,0 +1 @@
|
||||
Make the graph drilldown action icons a common hamburger toggle across all themes instead of per-theme hover reveal
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
+-------------------------------------------------------------------------+
|
||||
| Copyright (C) 2004-2026 The Cacti Group |
|
||||
| |
|
||||
| This program is free software; you can redistribute it and/or |
|
||||
| modify it under the terms of the GNU General Public License |
|
||||
| as published by the Free Software Foundation; either version 2 |
|
||||
| of the License, or (at your option) any later version. |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common, theme-agnostic drilldown behaviour: the action icons are hidden
|
||||
* behind a hamburger glyph overlaid on the top-right of the graph. Clicking
|
||||
* the glyph opens the icon strip; it closes on a second click, on leaving the
|
||||
* strip, or on an outside click (see initGraphDrilldown() in layout.js).
|
||||
* Loaded after each theme's main.css so these rules win.
|
||||
*/
|
||||
|
||||
.graphWrapperOuter > div:first-child {
|
||||
position: relative;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.graphDrillDownToggle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.graphDrillDownToggle .graphDrillDownMenu {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
margin: 3px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
line-height: 1;
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
transition: opacity 0.12s ease-in-out;
|
||||
}
|
||||
|
||||
/* Keep the toggle focusable (never display:none) and reveal it on hover,
|
||||
keyboard focus, the open state, and on devices that cannot hover. */
|
||||
.graphWrapperOuter:hover .graphDrillDownToggle .graphDrillDownMenu,
|
||||
.graphDrillDownToggle .graphDrillDownMenu:focus,
|
||||
.graphDrillDownToggle.open .graphDrillDownMenu {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.graphDrillDownToggle .graphDrillDownMenu {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.graphDrillDownToggle.open .graphDrillDownMenu {
|
||||
background: rgba(0, 0, 0, 0.82);
|
||||
}
|
||||
|
||||
.graphDrillDownToggle .iconWrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.graphDrillDownToggle.open .iconWrapper {
|
||||
display: inline-block;
|
||||
margin: 0 3px 3px 3px;
|
||||
padding: 5px 7px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, 0.82);
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.graphDrillDownToggle.open .iconWrapper .iconLink {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -3847,6 +3847,8 @@ function cactiReady() {
|
||||
|
||||
applySkin();
|
||||
|
||||
initGraphDrilldown();
|
||||
|
||||
setupEllipsis();
|
||||
|
||||
handleUserMenu();
|
||||
@@ -4847,6 +4849,62 @@ function refreshGraphs() {
|
||||
});
|
||||
}
|
||||
|
||||
function initGraphDrilldown() {
|
||||
// Common graph drilldown behaviour for every theme (styled in graph_drilldown.css).
|
||||
// A hamburger glyph over the graph's top-right toggles the action icons; they
|
||||
// close on a second click, on leaving the strip, or on an outside click.
|
||||
$(document).off('.graphdd');
|
||||
|
||||
function closeGraphDrilldown(container) {
|
||||
container.removeClass('open');
|
||||
container.find('.graphDrillDownMenu').attr('aria-expanded', 'false');
|
||||
|
||||
// Close an open SpikeKill submenu so it is not orphaned with the strip.
|
||||
if (typeof spikeKillClose == 'function') {
|
||||
spikeKillClose();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on('click.graphdd', '.graphDrillDownToggle .graphDrillDownMenu', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var menu = $(this);
|
||||
var container = menu.closest('.graphDrillDownToggle');
|
||||
var willOpen = !container.hasClass('open');
|
||||
|
||||
$('.graphDrillDownToggle.open').not(container).each(function () {
|
||||
closeGraphDrilldown($(this));
|
||||
});
|
||||
|
||||
if (willOpen) {
|
||||
container.addClass('open');
|
||||
menu.attr('aria-expanded', 'true');
|
||||
} else {
|
||||
closeGraphDrilldown(container);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('keydown.graphdd', '.graphDrillDownToggle .graphDrillDownMenu', function (event) {
|
||||
if (event.keyCode == 13 || event.keyCode == 32) {
|
||||
event.preventDefault();
|
||||
$(this).trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('mouseleave.graphdd', '.graphDrillDownToggle.open', function () {
|
||||
closeGraphDrilldown($(this));
|
||||
});
|
||||
|
||||
$(document).on('click.graphdd', function (event) {
|
||||
if ($(event.target).closest('.graphDrillDownToggle').length == 0) {
|
||||
$('.graphDrillDownToggle.open').each(function () {
|
||||
closeGraphDrilldown($(this));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeGraphs(disable_cache) {
|
||||
disable_cache = (typeof disable_cache == 'undefined') ? false : true;
|
||||
|
||||
|
||||
@@ -92,43 +92,6 @@ function themeReady() {
|
||||
}
|
||||
});
|
||||
|
||||
// Hide the graph icons until you hover
|
||||
$('.graphDrillDown').on('mouseenter', function() {
|
||||
element = $(this);
|
||||
|
||||
// hide the previously shown element
|
||||
if (element.attr('id').replace('dd', '') != graphMenuElement && graphMenuElement > 0) {
|
||||
$('#dd'+graphMenuElement).find('.iconWrapper:first').hide(300);
|
||||
}
|
||||
|
||||
clearTimeout(graphMenuTimer);
|
||||
graphMenuTimer = setTimeout(function() { showGraphMenu(element); }, 400);
|
||||
}).on('mouseleave', function() {
|
||||
element = $(this);
|
||||
clearTimeout(graphMenuTimer);
|
||||
graphMenuTimer = setTimeout(function() { hideGraphMenu(element); }, 400);
|
||||
|
||||
if (typeof spikeKillClose == 'function') {
|
||||
spikeKillClose();
|
||||
}
|
||||
});
|
||||
|
||||
function showGraphMenu(element) {
|
||||
element.find('.spikekillMenu').menu('disable');
|
||||
element.find('.iconWrapper').show(300, function() {
|
||||
graphMenuElement = element.attr('id').replace('dd', '');;
|
||||
$(this).find('.spikekillMenu').menu('enable');
|
||||
$(this).css('display', 'block');
|
||||
});
|
||||
}
|
||||
|
||||
function hideGraphMenu(element) {
|
||||
element.find('.spikekillMenu').menu('disable');
|
||||
element.find('.iconWrapper').hide(300, function() {
|
||||
$(this).find('.spikekillMenu').menu('enable');
|
||||
});
|
||||
}
|
||||
|
||||
setNavigationScroll();
|
||||
}
|
||||
|
||||
|
||||
@@ -1428,24 +1428,6 @@ legend::before {
|
||||
.graphWrapper {
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
&:hover {
|
||||
&::after {
|
||||
color: var(--text-color-normal);
|
||||
background: var(--background-content);
|
||||
content: "\22EE";
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
opacity: 0.98;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.graphimage {
|
||||
|
||||
@@ -840,47 +840,6 @@ function setupDefaultElements() {
|
||||
$('.import_text').text(fileText);
|
||||
}
|
||||
|
||||
// Hide the graph icons until you hover
|
||||
$('.graphDrillDown').on('mouseenter', function () {
|
||||
element = $(this);
|
||||
|
||||
// hide the previously shown element
|
||||
if (element.attr('id').replace('dd', '') != graphMenuElement && graphMenuElement > 0) {
|
||||
$('#dd' + graphMenuElement).find('.iconWrapper:first').hide(300);
|
||||
}
|
||||
|
||||
clearTimeout(graphMenuTimer);
|
||||
graphMenuTimer = setTimeout(function () {
|
||||
showGraphMenu(element);
|
||||
}, 400);
|
||||
}).on('mouseleave', function () {
|
||||
element = $(this);
|
||||
clearTimeout(graphMenuTimer);
|
||||
graphMenuTimer = setTimeout(function () {
|
||||
hideGraphMenu(element);
|
||||
}, 400);
|
||||
|
||||
if (typeof spikeKillClose == 'function') {
|
||||
spikeKillClose();
|
||||
}
|
||||
});
|
||||
|
||||
function showGraphMenu(element) {
|
||||
element.find('.spikekillMenu').menu('disable');
|
||||
element.find('.iconWrapper').show(300, function () {
|
||||
graphMenuElement = element.attr('id').replace('dd', '');
|
||||
$(this).find('.spikekillMenu').menu('enable');
|
||||
$(this).css('display', 'block');
|
||||
});
|
||||
}
|
||||
|
||||
function hideGraphMenu(element) {
|
||||
element.find('.spikekillMenu').menu('disable');
|
||||
element.find('.iconWrapper').hide(300, function () {
|
||||
$(this).find('.spikekillMenu').menu('enable');
|
||||
});
|
||||
}
|
||||
|
||||
setNavigationScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,43 +154,6 @@ function themeReady() {
|
||||
$('.import_text').text(fileText);
|
||||
}
|
||||
|
||||
// Hide the graph icons until you hover
|
||||
$('.graphDrillDown').on('mouseenter', function() {
|
||||
element = $(this);
|
||||
|
||||
// hide the previously shown element
|
||||
if (element.attr('id').replace('dd', '') != graphMenuElement && graphMenuElement > 0) {
|
||||
$('#dd'+graphMenuElement).find('.iconWrapper:first').hide(300);
|
||||
}
|
||||
|
||||
clearTimeout(graphMenuTimer);
|
||||
graphMenuTimer = setTimeout(function() { showGraphMenu(element); }, 400);
|
||||
}).on('mouseleave', function() {
|
||||
element = $(this);
|
||||
clearTimeout(graphMenuTimer);
|
||||
graphMenuTimer = setTimeout(function() { hideGraphMenu(element); }, 400);
|
||||
|
||||
if (typeof spikeKillClose == 'function') {
|
||||
spikeKillClose();
|
||||
}
|
||||
});
|
||||
|
||||
function showGraphMenu(element) {
|
||||
element.find('.spikekillMenu').menu('disable');
|
||||
element.find('.iconWrapper').show(300, function() {
|
||||
graphMenuElement = element.attr('id').replace('dd', '');;
|
||||
$(this).find('.spikekillMenu').menu('enable');
|
||||
$(this).css('display', 'block');
|
||||
});
|
||||
}
|
||||
|
||||
function hideGraphMenu(element) {
|
||||
element.find('.spikekillMenu').menu('disable');
|
||||
element.find('.iconWrapper').hide(300, function() {
|
||||
$(this).find('.spikekillMenu').menu('enable');
|
||||
});
|
||||
}
|
||||
|
||||
setNavigationScroll();
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -380,7 +380,7 @@ function html_graph_area(array &$graph_array, string $no_graphs_message = '', st
|
||||
<div>
|
||||
<div class='graphWrapper' id='wrapper_<?php print $graph['local_graph_id']?>' graph_width='<?php print $graph['width']; ?>' graph_height='<?php print $graph['height']; ?>' title_font_size='<?php print((read_user_setting('custom_fonts') == 'on') ? read_user_setting('title_size') : read_config_option('title_size')); ?>'></div>
|
||||
<?php if (is_realm_allowed(27)) { ?>
|
||||
<div id='dd<?php print $graph['local_graph_id']; ?>' class='noprint graphDrillDown'>
|
||||
<div id='dd<?php print $graph['local_graph_id']; ?>' class='noprint graphDrillDown graphDrillDownToggle'>
|
||||
<?php graph_drilldown_icons($graph['local_graph_id'], 'graph_buttons', $tree_id, $branch_id); ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
@@ -499,7 +499,7 @@ function html_graph_thumbnail_area(array &$graph_array, string $no_graphs_messag
|
||||
<div>
|
||||
<div class='graphWrapper' id='wrapper_<?php print $graph['local_graph_id']?>' graph_width='<?php print read_user_setting('default_width'); ?>' graph_height='<?php print read_user_setting('default_height'); ?>'></div>
|
||||
<?php if (is_realm_allowed(27)) { ?>
|
||||
<div id='dd<?php print $graph['local_graph_id']; ?>' class='noprint graphDrillDown'>
|
||||
<div id='dd<?php print $graph['local_graph_id']; ?>' class='noprint graphDrillDown graphDrillDownToggle'>
|
||||
<?php graph_drilldown_icons($graph['local_graph_id'], 'graph_buttons_thumbnails', $tree_id, $branch_id); ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
@@ -542,6 +542,7 @@ function graph_drilldown_icons(int $local_graph_id, string $type = 'graph_button
|
||||
WHERE id = ?',
|
||||
[$local_graph_id]);
|
||||
|
||||
print "<span class='graphDrillDownMenu noprint' role='button' tabindex='0' aria-haspopup='true' aria-expanded='false' aria-label='" . __esc('Graph Actions') . "' title='" . __esc('Graph Actions') . "'><i class='fa fa-bars'></i></span>";
|
||||
print "<div class='iconWrapper'>";
|
||||
print "<a class='iconLink utils' href='#' role='link' id='graph_" . $local_graph_id . "_util'><i class='drillDown ti ti-settings-filled actionCog' title='" . __esc('Graph Details, Zooming and Debugging Utilities') . "'></i></a><br>";
|
||||
print "<a class='iconLink csvexport' href='#' role='link' id='graph_" . $local_graph_id . "_csv'><i class='drillDown ti ti-file-type-csv fileCSV' title='" . __esc('CSV Export of Graph Data') . "'></i></a><br>";
|
||||
@@ -3438,6 +3439,7 @@ function html_common_header(string $title, string $selectedTheme = '') : void {
|
||||
// Global styles
|
||||
print get_md5_include_css('include/themes/' . $selectedTheme . '/main.css');
|
||||
print get_md5_include_css('include/themes/print.css');
|
||||
print get_md5_include_css('include/css/graph_drilldown.css');
|
||||
|
||||
// Global scripts
|
||||
print get_md5_include_js('include/js/screenfull.js', true);
|
||||
|
||||
@@ -15553,6 +15553,10 @@ msgstr ""
|
||||
msgid "Data Query:"
|
||||
msgstr ""
|
||||
|
||||
#: lib/html.php
|
||||
msgid "Graph Actions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/html.php lib/html_graph.php
|
||||
msgid "Graph Details, Zooming and Debugging Utilities"
|
||||
msgstr ""
|
||||
|
||||
Reference in new issue
Block a user