feat: Implement settings loading skeleton and enhance UI elements

This commit is contained in:
Jokob @NetAlertX
2026-06-20 00:40:01 +00:00
parent a9b2f4b256
commit 8cb466e722
8 changed files with 338 additions and 26 deletions

View File

@@ -1083,6 +1083,7 @@ height: 50px;
.settingswrap
{
margin-bottom: 100px;
padding-top: 0px;
}
.settingswrap .metadata
@@ -1178,8 +1179,10 @@ height: 50px;
/* Settings */
#settingsPage .overview-setting-value{
display:unset;
display: block;
overflow: hidden;
text-overflow: ellipsis;
font-size: smaller;
}
.overview-setting-value-wrap
@@ -1228,7 +1231,7 @@ height: 50px;
}
#settingsPage .panel-heading:hover{
background-color: #272c30;
background-color: #e8e8e8;
}
.settings-expand-icon {
@@ -2256,7 +2259,7 @@ textarea[readonly],
----------------------------------------------------------------------------- */
#loadingSpinner {
position: fixed;
z-index: 1000;
z-index: 9999;
/* top: 0; */
/* left: 0; */
/* width: 100%; */
@@ -2265,7 +2268,6 @@ textarea[readonly],
transition: opacity 0.3s ease-in-out;
pointer-events: none;
display: block;
z-index: 800;
}
.fa-spinner
@@ -2587,4 +2589,170 @@ table.dataTable tbody > tr.selected
.input-group-addon.text-muted {
color: #8c8c8c;
background-color: rgba(140, 140, 140, 0.05);
}
}
/* ===== Settings Page Loading Skeleton ===== */
:root {
--skel-base: #e2e2e2;
--skel-shine: #f0f0f0;
--skel-section: #d4d4d4;
--skel-panel-bg: #f5f5f5;
--skel-border: #ddd;
--skel-bg: #ecf0f5;
}
@keyframes settingsShimmer {
0% { background-position: -600px 0; }
100% { background-position: 600px 0; }
}
#settingsPage {
position: relative;
}
#settings-skeleton {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 50;
background-color: var(--skel-bg);
padding-top: 50px;
padding-left: 20px;
padding-right: 20px;
min-height: 500px;
}
.skel-shimmer {
background: linear-gradient(
90deg,
var(--skel-base) 25%,
var(--skel-shine) 50%,
var(--skel-base) 75%
);
background-size: 600px 100%;
animation: settingsShimmer 1.5s infinite linear;
border-radius: 3px;
display: inline-block;
}
/* Overview panel skeleton */
.skel-overview-panel {
margin-bottom: 10px;
border-radius: 4px;
overflow: hidden;
border: 1px solid var(--skel-border);
}
.skel-overview-heading {
height: 44px;
background: var(--skel-section);
display: flex;
align-items: center;
padding: 0 15px;
gap: 12px;
}
.skel-overview-body {
padding: 12px;
background: var(--skel-panel-bg);
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.skel-overview-card {
min-width: 100px;
height: 76px;
border-radius: 4px;
}
/* Section accordion skeleton */
.skel-section {
margin-bottom: 8px;
border-radius: 4px;
overflow: hidden;
border: 1px solid var(--skel-border);
}
.skel-overview-header {
height: 44px;
padding: 0 15px;
display: flex;
align-items: center;
gap: 14px;
background: var(--skel-panel-bg);
}
.skel-section-header {
height: 44px;
padding: 0 15px;
display: flex;
align-items: center;
gap: 14px;
background: var(--skel-section);
}
/* Plugin block inside an open section */
.skel-plugin-block {
margin: 8px;
border-radius: 4px;
overflow: hidden;
border: 1px solid var(--skel-border);
}
.skel-plugin-header {
height: 48px;
padding: 0 15px;
display: flex;
align-items: center;
gap: 14px;
background: var(--skel-section);
}
.skel-plugin-body {
background: var(--skel-panel-bg);
}
.skel-setting-row {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 15px;
border-bottom: 1px solid var(--skel-border);
}
.skel-setting-row:last-child {
border-bottom: none;
}
/* Skeleton line / cell primitives */
.skel-line {
height: 14px;
flex-shrink: 0;
}
.skel-icon-block {
width: 22px;
height: 16px;
flex-shrink: 0;
}
.skel-cell-name {
width: 20%;
height: 14px;
flex-shrink: 0;
}
.skel-cell-desc {
width: 36%;
height: 14px;
flex-shrink: 0;
}
.skel-cell-input {
flex: 1;
height: 32px;
}
/* ===== /Settings Page Loading Skeleton ===== */

View File

@@ -516,6 +516,9 @@ textarea[readonly],
border: 1px solid #353c42;
color: #bec5cb;
}
#settingsPage .panel-heading:hover {
background-color: #272c30;
}
.box.box-solid.box-info,
.box.box-solid.box-info > .box-header {
color: #bec5cb;
@@ -754,4 +757,14 @@ table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
font-family: 'Courier New', monospace;
font-size: .85em;
cursor: pointer;
}
/* Settings skeleton - dark theme */
:root {
--skel-base: #2e3540;
--skel-shine: #3d4555;
--skel-section: #252c38;
--skel-panel-bg: #1e242e;
--skel-border: #2a323e;
--skel-bg: #353c42;
}

View File

@@ -512,6 +512,9 @@
border: 1px solid #353c42;
color: #bec5cb;
}
#settingsPage .panel-heading:hover {
background-color: #272c30;
}
.box.box-solid.box-info,
.box.box-solid.box-info > .box-header {
color: #bec5cb;
@@ -731,4 +734,14 @@
font-family: 'Courier New', monospace;
font-size: .85em;
cursor: pointer;
}
}
/* Settings skeleton - dark theme */
:root {
--skel-base: #2e3540;
--skel-shine: #3d4555;
--skel-section: #252c38;
--skel-panel-bg: #1e242e;
--skel-border: #2a323e;
--skel-bg: #353c42;
}

View File

@@ -957,8 +957,8 @@ let animationTime = 300
function showSpinner(stringKey = 'Loading') {
let text = isEmpty(stringKey) ? "Loading..." : getString(stringKey || "Loading");
if (text == ""){
text = "Loading"
if (!text || !text.trim()) {
text = "Loading..."
}
const spinner = $("#loadingSpinner");
@@ -978,7 +978,7 @@ function showSpinner(stringKey = 'Loading') {
left: offset.left,
width: width,
height: height,
zIndex: 800
zIndex: 9999
});
} else {
// Fullscreen fallback
@@ -988,7 +988,7 @@ function showSpinner(stringKey = 'Loading') {
left: 0,
width: "100%",
height: "100%",
zIndex: 800
zIndex: 9999
});
}
@@ -1018,7 +1018,7 @@ function hideSpinner() {
left: offset.left,
width: width,
height: height,
zIndex: 800
zIndex: 9999
});
} else {
// Fullscreen fallback
@@ -1028,7 +1028,7 @@ function hideSpinner() {
left: 0,
width: "100%",
height: "100%",
zIndex: 800
zIndex: 9999
});
}

View File

@@ -99,7 +99,7 @@ function pluginCards(prefixesOfEnabledPlugins, includeSettings) {
});
html += `
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px">
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 padding-5px">
<div class="small-box bg-green col-sm-12 " >
<div class="inner col-sm-12">
<a href="#${prefix}_header" onclick="toggleAllSettings('open')">

View File

@@ -151,7 +151,7 @@
<div class="nax_semitransparent-panel"></div>
<div class="panel panel-default nax_spinner">
<table>
<td id="loadingSpinnerText" width="130px" ></td>
<td id="loadingSpinnerText" width="130px">Loading...</td>
<td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td>
</table>
</div>

View File

@@ -0,0 +1,106 @@
<!-- Settings Loading Skeleton --------------------------------------------- -->
<div id="settings-skeleton">
<!-- Overview panel skeleton -->
<div class="skel-overview-panel">
<div class="skel-overview-heading">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:180px"></span>
</div>
<div class="skel-overview-body">
<div class="skel-overview-card skel-shimmer col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px"></div>
<div class="skel-overview-card skel-shimmer col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px"></div>
<div class="skel-overview-card skel-shimmer col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px"></div>
</div>
<div class="skel-overview-heading">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:180px"></span>
</div>
<div class="skel-overview-body">
<div class="skel-overview-card skel-shimmer col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px"></div>
<div class="skel-overview-card skel-shimmer col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px"></div>
<div class="skel-overview-card skel-shimmer col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px"></div>
</div>
</div>
<!-- Core section (open, with a plugin block) -->
<div class="skel-section">
<div class="skel-section-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:60px"></span>
</div>
<div class="skel-plugin-block">
<div class="skel-plugin-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:75px"></span>
<span class="skel-line skel-shimmer" style="width:210px"></span>
</div>
<div class="skel-plugin-body">
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer"></span><span class="skel-cell-desc skel-shimmer"></span><span class="skel-cell-input skel-shimmer" style="height:75px"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer" ></span><span class="skel-cell-desc skel-shimmer" ></span><span class="skel-cell-input skel-shimmer" style="max-width:35px"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer" ></span><span class="skel-cell-desc skel-shimmer" ></span><span class="skel-cell-input skel-shimmer" style="max-width:35px"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer"></span><span class="skel-cell-desc skel-shimmer" ></span><span class="skel-cell-input skel-shimmer" style="height:75px"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer" ></span><span class="skel-cell-desc skel-shimmer" ></span><span class="skel-cell-input skel-shimmer"></span></div>
</div>
</div>
</div>
<!-- System section (open) -->
<div class="skel-section">
<div class="skel-section-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:80px"></span>
</div>
<div class="skel-plugin-block">
<div class="skel-plugin-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:90px"></span>
<span class="skel-line skel-shimmer" style="width:185px"></span>
</div>
<div class="skel-plugin-body">
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer"></span><span class="skel-cell-desc skel-shimmer"></span><span class="skel-cell-input skel-shimmer"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer"></span><span class="skel-cell-desc skel-shimmer"></span><span class="skel-cell-input skel-shimmer"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer"></span><span class="skel-cell-desc skel-shimmer"></span><span class="skel-cell-input skel-shimmer"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer"></span><span class="skel-cell-desc skel-shimmer"></span><span class="skel-cell-input skel-shimmer"></span></div>
</div>
</div>
</div>
<!-- Device Scanners section (closed) -->
<div class="skel-section">
<div class="skel-section-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:140px"></span>
</div>
</div>
<!-- Other Scanners section (open) -->
<div class="skel-section">
<div class="skel-section-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:120px"></span>
</div>
<div class="skel-plugin-block">
<div class="skel-plugin-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:65px"></span>
<span class="skel-line skel-shimmer" style="width:200px"></span>
</div>
<div class="skel-plugin-body">
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer" style="width:16%"></span><span class="skel-cell-desc skel-shimmer" style="width:40%"></span><span class="skel-cell-input skel-shimmer"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer"></span><span class="skel-cell-desc skel-shimmer" style="width:33%"></span><span class="skel-cell-input skel-shimmer"></span></div>
<div class="skel-setting-row"><span class="skel-cell-name skel-shimmer" style="width:19%"></span><span class="skel-cell-desc skel-shimmer" style="width:42%"></span><span class="skel-cell-input skel-shimmer"></span></div>
</div>
</div>
</div>
<!-- Publishers section (closed) -->
<div class="skel-section">
<div class="skel-section-header">
<span class="skel-icon-block skel-shimmer"></span>
<span class="skel-line skel-shimmer" style="width:100px"></span>
</div>
</div>
</div>
<!-- /Settings Loading Skeleton -------------------------------------------- -->

View File

@@ -69,20 +69,17 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
<div id="settingsPage" class="content-wrapper">
<a style="cursor:pointer">
<span>
<i id='toggleSettings' onclick="toggleAllSettings()" class="settings-expand-icon fa fa-angle-double-down"></i>
</span>
</a>
<?php require 'php/templates/settings_skeleton.php'; ?>
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<div class ="bg-white color-palette box box-solid box-primary col-sm-12 panel panel-default panel-title" >
<div class ="bg-white color-palette box box-solid col-sm-12 panel panel-default panel-title" >
<a data-toggle="collapse" href="#settingsOverview">
<div class ="settings-group col-sm-12 panel-heading panel-title">
<div class ="settings-group col-sm-12 ">
<i class="<?= lang("settings_enabled_icon");?>"></i> <?= lang("settings_enabled");?>
</div>
</a>
@@ -284,7 +281,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
},
error: function (xhr, status, error) {
console.error("Error:", error);
// Handle any errors
hideSettingsSkeleton();
}
});
}
@@ -328,7 +325,10 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
overviewSections.forEach((section) => {
overviewSections_html += `<div class="overview-section col-sm-12" id="${section}">
const sectionHtml = overviewSectionsHtml[index];
if (sectionHtml.trim()) {
overviewSections_html += `<div class="overview-section col-sm-12" id="${section}">
<div class="col-sm-12 " title="${getString("settings_"+section)}">
<a href="#${section}_content_header">
<div class="overview-group col-sm-12 col-xs-12">
@@ -338,9 +338,10 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
</a>
</div>
<div class="col-sm-12">
${overviewSectionsHtml[index]}
${sectionHtml}
</div>
</div>`
}
index++;
});
@@ -535,9 +536,18 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
initSelect2();
initHoverNodeInfo();
hideSpinner();
hideSettingsSkeleton();
}
// ----------------------------------------------------------------
function hideSettingsSkeleton() {
var $skel = $('#settings-skeleton');
if (!$skel.length) return;
$('#settingsPage').removeClass('settings-loading');
$skel.fadeOut(250, function() { $(this).remove(); });
}
// display the name of the first person
// echo $settingsJson[0]->name;
var settingsNumberDB = <?php echo count($settings)?>;
@@ -652,6 +662,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
{
showMessage (getString("settings_readonly"), 10000, "modal_red");
console.log(`app.conf seems to be read only (canRWConfig: ${canReadAndWriteConfig})`);
hideSettingsSkeleton();
} else
{
// check if config file has been updated
@@ -706,9 +717,10 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
}
}
showSpinner()
handleLoadingDialog()
// Fallback: hide skeleton after 15s in case of unexpected error
setTimeout(hideSettingsSkeleton, 15000)