refactor(ui): unify spinner system and improve logging in config validation

- Consolidate spinner styles into unified system in main.css
- Remove duplicate spinner definitions from component CSS
- Update spinner class usage across JavaScript components
- Add logging separators for configuration validation steps
- Remove unnecessary logging separator in torrent list retrieval
This commit is contained in:
bobokun 2025-08-28 10:51:12 -04:00
parent 52cb4f1c50
commit b2ee109dfe
No known key found for this signature in database
GPG key ID: B73932169607D927
8 changed files with 126 additions and 43 deletions

View file

@ -1 +1 @@
4.5.6-develop4
4.5.6-develop5

View file

@ -85,7 +85,6 @@ class Qbt:
except Exception as exc:
self.config.notify(exc, "Qbittorrent")
raise Failed(exc)
logger.separator("Getting Torrent List", space=False, border=False)
self.torrent_list = self.get_torrents({"sort": "added_on"})
self.torrentfiles = {} # a map of torrent files to track cross-seeds

View file

@ -781,6 +781,7 @@ class WebAPI:
# Write temporary config file for validation
temp_config_path = self.config_path / f".temp_{filename}"
try:
logger.separator("Configuration Validation Check", space=False, border=False)
self._write_yaml_config(temp_config_path, request.data)
# Try to load config using existing validation logic
@ -788,13 +789,17 @@ class WebAPI:
Config(self.default_dir, temp_args)
except Exception as e:
errors.append(str(e))
logger.separator("Configuration Validation Failed", space=False, border=False)
valid = len(errors) == 0
if valid:
logger.separator("Configuration Valid", space=False, border=False)
finally:
# Clean up temporary file
if temp_config_path.exists():
temp_config_path.unlink()
return ValidationResponse(valid=len(errors) == 0, errors=errors, warnings=warnings)
return ValidationResponse(valid=valid, errors=errors, warnings=warnings)
except Exception as e:
logger.error(f"Error validating config '{filename}': {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

View file

@ -337,18 +337,7 @@
gap: var(--spacing-sm);
}
.loading-spinner {
width: 14px;
height: 14px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Loading spinner now uses unified system from main.css */
/* Preset Buttons */
.preset-buttons {

View file

@ -252,7 +252,114 @@ body {
transition: all var(--transition-fast);
}
/* Animation for loading states */
/* Unified Spinner System */
@keyframes modern-spin {
0% {
transform: rotate(0deg) scale(1);
opacity: 1;
}
50% {
transform: rotate(180deg) scale(1.1);
opacity: 0.7;
}
100% {
transform: rotate(360deg) scale(1);
opacity: 1;
}
}
/* Unified Spinner System */
@keyframes smooth-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Base spinner class - can be used anywhere */
.spinner {
position: relative;
border-radius: 50%;
animation: smooth-rotate 1s linear infinite;
flex-shrink: 0;
border: 2px solid transparent;
border-top: 2px solid var(--primary-color);
}
/* Size variants */
.spinner-sm {
width: 16px !important;
height: 16px !important;
}
.spinner-md {
width: 24px !important;
height: 24px !important;
}
.spinner-lg {
width: 48px !important;
height: 48px !important;
}
/* Context-specific spinner styles */
.spinner.spinner-inline {
display: inline-block;
vertical-align: middle;
margin-right: var(--spacing-xs);
}
.spinner.spinner-button {
border-top-color: currentColor;
}
/* Loading overlay specific styles */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: var(--z-modal-backdrop);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: var(--text-inverse);
padding: var(--spacing-xl);
border-radius: var(--border-radius-lg);
background-color: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
/* Dark mode: lighter text for better readability */
[data-theme="dark"] .loading-spinner {
color: var(--text-secondary);
}
/* Auto dark mode support */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .loading-spinner {
color: var(--text-secondary);
}
}
.loading-spinner .spinner {
margin-bottom: var(--spacing-md);
}
/* Legacy support - keep for backward compatibility */
@keyframes rotate {
from {
transform: rotate(0deg);
@ -693,37 +800,20 @@ body {
border: 0;
}\n
.loading-overlay {
position: absolute;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: var(--z-modal-backdrop); /* Use a high z-index to ensure it's on top */
z-index: var(--z-modal-backdrop);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}
.loading-spinner {
text-align: center;
color: var(--text-inverse); /* Use text-inverse for better contrast on dark overlay */
}
.spinner {
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid var(--primary-color);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Password Input Group */
.password-input-group {

View file

@ -133,7 +133,7 @@ class SchedulerControl {
<button type="submit" class="btn btn-primary" id="update-schedule-btn" disabled aria-describedby="update-help">
<span class="btn-text">Save Schedule</span>
<span class="btn-loading" style="display: none;" aria-hidden="true">
<span class="loading-spinner" aria-hidden="true"></span>
<span class="spinner spinner-sm spinner-button" aria-hidden="true"></span>
Saving...
</span>
</button>

View file

@ -93,7 +93,7 @@ export class ShareLimitsComponent {
addButton.disabled = loading;
const uiTexts = shareLimitsSchema.ui?.texts || {};
addButton.innerHTML = loading
? `<span class="loading-spinner"></span> ${uiTexts.addGroupLoadingText || 'Creating...'}`
? `<span class="spinner spinner-sm spinner-button"></span> ${uiTexts.addGroupLoadingText || 'Creating...'}`
: (uiTexts.addGroupButtonText || 'Add New Group');
}
}

View file

@ -65,7 +65,7 @@ export function showLoading(container, message = 'Loading...') {
loadingOverlay.className = 'loading-overlay'; // Add a class for styling
loadingOverlay.innerHTML = `
<div class="loading-spinner">
<div class="spinner"></div>
<div class="spinner spinner-lg"></div>
<p>${message}</p>
</div>
`;