micro-sidebar 1.2.2__py3-none-any.whl → 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {micro_sidebar-1.2.2.dist-info → micro_sidebar-2.2.0.dist-info}/METADATA +117 -1
- micro_sidebar-2.2.0.dist-info/RECORD +31 -0
- sidebar/apps.py +10 -0
- sidebar/context_processors.py +127 -0
- sidebar/discovery.py +151 -0
- sidebar/static/sidebar/css/reorder.css +91 -0
- sidebar/static/sidebar/css/theme_picker.css +165 -0
- sidebar/static/sidebar/js/reorder.js +267 -0
- sidebar/static/sidebar/js/theme_picker.js +58 -0
- sidebar/static/sidebar/sidebar.css +34 -8
- sidebar/static/sidebar/sidebar.js +15 -40
- sidebar/static/themes/blue.css +51 -0
- sidebar/static/themes/dark.css +501 -0
- sidebar/static/themes/gold.css +51 -0
- sidebar/static/themes/green.css +63 -0
- sidebar/static/themes/light.css +51 -0
- sidebar/static/themes/main.css +6 -0
- sidebar/static/themes/main.js +41 -0
- sidebar/static/themes/red.css +51 -0
- sidebar/templates/sidebar/auto.html +15 -0
- sidebar/templates/sidebar/extra_groups.html +34 -0
- sidebar/templates/sidebar/main.html +94 -5
- sidebar/templatetags/__init__.py +1 -0
- sidebar/templatetags/sidebar_tags.py +74 -0
- micro_sidebar-1.2.2.dist-info/RECORD +0 -13
- {micro_sidebar-1.2.2.dist-info → micro_sidebar-2.2.0.dist-info}/LICENSE +0 -0
- {micro_sidebar-1.2.2.dist-info → micro_sidebar-2.2.0.dist-info}/WHEEL +0 -0
- {micro_sidebar-1.2.2.dist-info → micro_sidebar-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
|
|
2
|
+
.current-theme-indicator {
|
|
3
|
+
width: 25px;
|
|
4
|
+
height: 25px;
|
|
5
|
+
border-radius: 50%;
|
|
6
|
+
cursor: pointer;
|
|
7
|
+
border: 2px solid white;
|
|
8
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
9
|
+
transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
10
|
+
margin: 5px; /* Spacing from bottom-left corner */
|
|
11
|
+
justify-self: end;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
pointer-events: auto; /* Enable clicks since parent toolbar is none */
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.theme-arrow {
|
|
19
|
+
position: absolute;
|
|
20
|
+
bottom: 36px; /* Space above indicator */
|
|
21
|
+
left: 50%;
|
|
22
|
+
transform: translateX(-50%);
|
|
23
|
+
color: var(--primal);
|
|
24
|
+
font-size: 1.3rem; /* Even bigger arrow */
|
|
25
|
+
opacity: 0;
|
|
26
|
+
visibility: hidden;
|
|
27
|
+
transition: all 0.2s ease;
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
z-index: 10;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.sidebar.collapsed .theme-arrow.visible {
|
|
33
|
+
opacity: 1;
|
|
34
|
+
visibility: visible;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sidebar.collapsed .theme-arrow {
|
|
38
|
+
bottom: 36px;
|
|
39
|
+
left: 50%;
|
|
40
|
+
transform: translateX(-50%);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sidebar.collapsed .current-theme-indicator {
|
|
44
|
+
margin: 5px auto; /* Centered in narrow sidebar */
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.current-theme-indicator:hover {
|
|
48
|
+
transform: scale(1.15);
|
|
49
|
+
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Theme Popup Menu */
|
|
53
|
+
.theme-popup {
|
|
54
|
+
display: none;
|
|
55
|
+
position: absolute;
|
|
56
|
+
bottom: 45px;
|
|
57
|
+
left: 10px; /* Fixed to left in RTL too for specific bottom-left request */
|
|
58
|
+
background: white;
|
|
59
|
+
border-radius: 12px;
|
|
60
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
|
61
|
+
padding: 10px;
|
|
62
|
+
z-index: 1100;
|
|
63
|
+
min-width: 110px;
|
|
64
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
65
|
+
animation: popupIn 0.2s ease-out;
|
|
66
|
+
pointer-events: auto;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sidebar.collapsed .theme-popup {
|
|
70
|
+
left: 5px;
|
|
71
|
+
right: 5px;
|
|
72
|
+
bottom: 65px; /* More space to accommodate larger arrow and breathing room */
|
|
73
|
+
min-width: auto;
|
|
74
|
+
width: auto;
|
|
75
|
+
background: transparent !important;
|
|
76
|
+
border: none !important;
|
|
77
|
+
box-shadow: none !important;
|
|
78
|
+
padding: 0 !important;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.sidebar.collapsed .theme-popup .small {
|
|
82
|
+
display: none; /* Hide "Select Color" text in narrow view */
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.sidebar.collapsed .theme-options-grid {
|
|
86
|
+
grid-template-columns: 1fr; /* Vertical column */
|
|
87
|
+
gap: 8px;
|
|
88
|
+
justify-items: center;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Staggered Animations for items */
|
|
92
|
+
.theme-option-circle {
|
|
93
|
+
opacity: 0;
|
|
94
|
+
transform: translateY(10px);
|
|
95
|
+
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.theme-popup.show .theme-option-circle {
|
|
99
|
+
opacity: 1;
|
|
100
|
+
transform: translateY(0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Staggered delays */
|
|
104
|
+
.theme-popup.show .theme-option-circle:nth-child(1) { transition-delay: 0.05s; }
|
|
105
|
+
.theme-popup.show .theme-option-circle:nth-child(2) { transition-delay: 0.1s; }
|
|
106
|
+
.theme-popup.show .theme-option-circle:nth-child(3) { transition-delay: 0.15s; }
|
|
107
|
+
.theme-popup.show .theme-option-circle:nth-child(4) { transition-delay: 0.2s; }
|
|
108
|
+
.theme-popup.show .theme-option-circle:nth-child(5) { transition-delay: 0.25s; }
|
|
109
|
+
.theme-popup.show .theme-option-circle:nth-child(6) { transition-delay: 0.3s; }
|
|
110
|
+
|
|
111
|
+
@keyframes popupIn {
|
|
112
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
113
|
+
to { opacity: 1; transform: translateY(0); }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.theme-popup.show {
|
|
117
|
+
display: block;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.theme-options-grid {
|
|
121
|
+
display: grid;
|
|
122
|
+
grid-template-columns: repeat(3, 1fr);
|
|
123
|
+
gap: 12px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.theme-option-circle {
|
|
127
|
+
width: 25px;
|
|
128
|
+
height: 25px;
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
border: 2px solid transparent;
|
|
132
|
+
transition: all 0.2s ease;
|
|
133
|
+
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1);
|
|
134
|
+
pointer-events: auto;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.theme-option-circle:hover {
|
|
138
|
+
transform: scale(1.2);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.theme-option-circle.active {
|
|
142
|
+
border-color: #3b82f6;
|
|
143
|
+
box-shadow: 0 0 0 2px white, 0 0 0 4px #3b82f6 !important;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Theme Colors for circles */
|
|
147
|
+
.theme-circle-light { background: #f6f7f9; }
|
|
148
|
+
.theme-circle-blue { background: #2363c3; }
|
|
149
|
+
.theme-circle-gold { background: #d97706; }
|
|
150
|
+
.theme-circle-green { background: #166534; }
|
|
151
|
+
.theme-circle-red { background: #7f1d1d; }
|
|
152
|
+
.theme-circle-dark { background: #0f172a; }
|
|
153
|
+
|
|
154
|
+
/* Dark Mode Overrides for the Popup */
|
|
155
|
+
:root.theme-dark .theme-popup {
|
|
156
|
+
background: #1e293b;
|
|
157
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
158
|
+
color: white;
|
|
159
|
+
}
|
|
160
|
+
:root.theme-dark .sidebar-toolbar {
|
|
161
|
+
border-top-color: rgba(255, 255, 255, 0.05);
|
|
162
|
+
}
|
|
163
|
+
:root.theme-dark .theme-option-circle.active {
|
|
164
|
+
box-shadow: 0 0 0 2px #1e293b, 0 0 0 4px #3b82f6 !important;
|
|
165
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY_AUTO = 'sidebar_auto_order';
|
|
5
|
+
const STORAGE_KEY_PREFIX_EXTRA = 'sidebar_extra_';
|
|
6
|
+
|
|
7
|
+
let isReorderMode = false;
|
|
8
|
+
let draggedElement = null;
|
|
9
|
+
let dropIndicator = null;
|
|
10
|
+
|
|
11
|
+
// Expose restore function globally for immediate FOUC fix
|
|
12
|
+
window.restoreSidebarOrder = restoreOrder;
|
|
13
|
+
|
|
14
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
15
|
+
const sidebar = document.getElementById('sidebar');
|
|
16
|
+
const reorderToggle = document.getElementById('sidebarReorderToggle');
|
|
17
|
+
|
|
18
|
+
if (!sidebar || !reorderToggle) return;
|
|
19
|
+
|
|
20
|
+
// Create drop indicator element
|
|
21
|
+
dropIndicator = document.createElement('div');
|
|
22
|
+
dropIndicator.className = 'drop-indicator';
|
|
23
|
+
dropIndicator.style.display = 'none';
|
|
24
|
+
|
|
25
|
+
// Restore is now called immediately via inline script for FOUC prevention
|
|
26
|
+
// But call again here as fallback if inline script didn't run
|
|
27
|
+
if (!window._sidebarOrderRestored) {
|
|
28
|
+
restoreOrder();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Toggle reorder mode
|
|
32
|
+
reorderToggle.addEventListener('click', (e) => {
|
|
33
|
+
e.stopPropagation();
|
|
34
|
+
isReorderMode = !isReorderMode;
|
|
35
|
+
reorderToggle.classList.toggle('active', isReorderMode);
|
|
36
|
+
sidebar.classList.toggle('reorder-mode', isReorderMode);
|
|
37
|
+
|
|
38
|
+
if (isReorderMode) {
|
|
39
|
+
enableDragAndDrop();
|
|
40
|
+
} else {
|
|
41
|
+
disableDragAndDrop();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Close reorder mode when clicking outside
|
|
46
|
+
document.addEventListener('click', (e) => {
|
|
47
|
+
if (isReorderMode && !sidebar.contains(e.target)) {
|
|
48
|
+
isReorderMode = false;
|
|
49
|
+
reorderToggle.classList.remove('active');
|
|
50
|
+
sidebar.classList.remove('reorder-mode');
|
|
51
|
+
disableDragAndDrop();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function enableDragAndDrop() {
|
|
57
|
+
// Auto items in .sidebar-auto-items or direct .list-group children
|
|
58
|
+
const autoContainer = document.getElementById('sidebarAutoItems') ||
|
|
59
|
+
document.querySelector('.sidebar .list-group');
|
|
60
|
+
if (autoContainer) {
|
|
61
|
+
setupDraggableContainer(autoContainer, STORAGE_KEY_AUTO);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Extra group items in accordion bodies
|
|
65
|
+
const accordionBodies = document.querySelectorAll('.sidebar .accordion-body');
|
|
66
|
+
accordionBodies.forEach(body => {
|
|
67
|
+
const groupName = body.dataset.groupName || body.closest('.accordion-item')?.querySelector('.accordion-button span')?.textContent?.trim();
|
|
68
|
+
if (groupName) {
|
|
69
|
+
const key = STORAGE_KEY_PREFIX_EXTRA + slugify(groupName);
|
|
70
|
+
setupDraggableContainer(body, key);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function disableDragAndDrop() {
|
|
76
|
+
const items = document.querySelectorAll('.sidebar .list-group-item[draggable="true"]');
|
|
77
|
+
items.forEach(item => {
|
|
78
|
+
item.removeAttribute('draggable');
|
|
79
|
+
item.removeEventListener('dragstart', handleDragStart);
|
|
80
|
+
item.removeEventListener('dragend', handleDragEnd);
|
|
81
|
+
item.removeEventListener('dragover', handleDragOver);
|
|
82
|
+
item.removeEventListener('drop', handleDrop);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Hide drop indicator
|
|
86
|
+
if (dropIndicator) {
|
|
87
|
+
dropIndicator.style.display = 'none';
|
|
88
|
+
if (dropIndicator.parentNode) {
|
|
89
|
+
dropIndicator.parentNode.removeChild(dropIndicator);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setupDraggableContainer(container, storageKey) {
|
|
95
|
+
const items = container.querySelectorAll(':scope > .list-group-item');
|
|
96
|
+
|
|
97
|
+
items.forEach(item => {
|
|
98
|
+
// Skip accordion buttons - they're not reorderable
|
|
99
|
+
if (item.classList.contains('accordion-button')) return;
|
|
100
|
+
|
|
101
|
+
item.setAttribute('draggable', 'true');
|
|
102
|
+
item.dataset.storageKey = storageKey;
|
|
103
|
+
|
|
104
|
+
item.addEventListener('dragstart', handleDragStart);
|
|
105
|
+
item.addEventListener('dragend', handleDragEnd);
|
|
106
|
+
item.addEventListener('dragover', handleDragOver);
|
|
107
|
+
item.addEventListener('drop', handleDrop);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Add event listeners to container for drag events
|
|
111
|
+
container.addEventListener('dragover', handleContainerDragOver);
|
|
112
|
+
container.addEventListener('drop', handleContainerDrop);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleDragStart(e) {
|
|
116
|
+
draggedElement = this;
|
|
117
|
+
this.classList.add('dragging');
|
|
118
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
119
|
+
e.dataTransfer.setData('text/plain', ''); // Required for Firefox
|
|
120
|
+
|
|
121
|
+
// Add drop indicator to DOM
|
|
122
|
+
if (dropIndicator && this.parentNode) {
|
|
123
|
+
this.parentNode.appendChild(dropIndicator);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function handleDragEnd(e) {
|
|
128
|
+
this.classList.remove('dragging');
|
|
129
|
+
|
|
130
|
+
// Hide drop indicator
|
|
131
|
+
if (dropIndicator) {
|
|
132
|
+
dropIndicator.style.display = 'none';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Save new order
|
|
136
|
+
if (draggedElement) {
|
|
137
|
+
saveOrder(draggedElement.parentNode, draggedElement.dataset.storageKey);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
draggedElement = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function handleDragOver(e) {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
e.dataTransfer.dropEffect = 'move';
|
|
146
|
+
|
|
147
|
+
if (!draggedElement || draggedElement === this) return;
|
|
148
|
+
if (draggedElement.dataset.storageKey !== this.dataset.storageKey) return;
|
|
149
|
+
|
|
150
|
+
const rect = this.getBoundingClientRect();
|
|
151
|
+
const midY = rect.top + rect.height / 2;
|
|
152
|
+
|
|
153
|
+
// Show drop indicator
|
|
154
|
+
if (dropIndicator) {
|
|
155
|
+
dropIndicator.style.display = 'block';
|
|
156
|
+
if (e.clientY < midY) {
|
|
157
|
+
this.parentNode.insertBefore(dropIndicator, this);
|
|
158
|
+
} else {
|
|
159
|
+
this.parentNode.insertBefore(dropIndicator, this.nextSibling);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handleContainerDragOver(e) {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleDrop(e) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
e.stopPropagation();
|
|
171
|
+
|
|
172
|
+
if (!draggedElement || draggedElement === this) return;
|
|
173
|
+
if (draggedElement.dataset.storageKey !== this.dataset.storageKey) return;
|
|
174
|
+
|
|
175
|
+
const rect = this.getBoundingClientRect();
|
|
176
|
+
const midY = rect.top + rect.height / 2;
|
|
177
|
+
|
|
178
|
+
if (e.clientY < midY) {
|
|
179
|
+
this.parentNode.insertBefore(draggedElement, this);
|
|
180
|
+
} else {
|
|
181
|
+
this.parentNode.insertBefore(draggedElement, this.nextSibling);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleContainerDrop(e) {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
// Item drops are handled by individual items
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function saveOrder(container, storageKey) {
|
|
191
|
+
if (!container || !storageKey) return;
|
|
192
|
+
|
|
193
|
+
const items = container.querySelectorAll(':scope > .list-group-item[data-url-name]');
|
|
194
|
+
const order = Array.from(items).map(item => item.dataset.urlName);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
localStorage.setItem(storageKey, JSON.stringify(order));
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.warn('Could not save sidebar order:', e);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function restoreOrder() {
|
|
204
|
+
// Restore auto items order
|
|
205
|
+
const autoContainer = document.getElementById('sidebarAutoItems') ||
|
|
206
|
+
document.querySelector('.sidebar .list-group');
|
|
207
|
+
if (autoContainer) {
|
|
208
|
+
restoreContainerOrder(autoContainer, STORAGE_KEY_AUTO);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Restore extra group items order
|
|
212
|
+
const accordionBodies = document.querySelectorAll('.sidebar .accordion-body');
|
|
213
|
+
accordionBodies.forEach(body => {
|
|
214
|
+
const groupName = body.dataset.groupName || body.closest('.accordion-item')?.querySelector('.accordion-button span')?.textContent?.trim();
|
|
215
|
+
if (groupName) {
|
|
216
|
+
const key = STORAGE_KEY_PREFIX_EXTRA + slugify(groupName);
|
|
217
|
+
restoreContainerOrder(body, key);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Mark as restored
|
|
222
|
+
window._sidebarOrderRestored = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function restoreContainerOrder(container, storageKey) {
|
|
226
|
+
let savedOrder;
|
|
227
|
+
try {
|
|
228
|
+
const saved = localStorage.getItem(storageKey);
|
|
229
|
+
if (!saved) return; // No saved order, use default
|
|
230
|
+
savedOrder = JSON.parse(saved);
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return; // Invalid JSON, use default
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!Array.isArray(savedOrder) || savedOrder.length === 0) return;
|
|
236
|
+
|
|
237
|
+
const items = container.querySelectorAll(':scope > .list-group-item[data-url-name]');
|
|
238
|
+
const itemMap = new Map();
|
|
239
|
+
items.forEach(item => {
|
|
240
|
+
itemMap.set(item.dataset.urlName, item);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Reorder based on saved order
|
|
244
|
+
savedOrder.forEach(urlName => {
|
|
245
|
+
const item = itemMap.get(urlName);
|
|
246
|
+
if (item) {
|
|
247
|
+
container.appendChild(item);
|
|
248
|
+
itemMap.delete(urlName);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Append any remaining items (new items not in saved order)
|
|
253
|
+
itemMap.forEach(item => {
|
|
254
|
+
container.appendChild(item);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function slugify(text) {
|
|
259
|
+
return text
|
|
260
|
+
.toString()
|
|
261
|
+
.toLowerCase()
|
|
262
|
+
.trim()
|
|
263
|
+
.replace(/\s+/g, '-')
|
|
264
|
+
.replace(/[^\w\-]+/g, '')
|
|
265
|
+
.replace(/\-\-+/g, '-');
|
|
266
|
+
}
|
|
267
|
+
})();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
3
|
+
const indicator = document.getElementById('sidebarThemeIndicator');
|
|
4
|
+
const popup = document.getElementById('sidebarThemePopup');
|
|
5
|
+
const options = document.querySelectorAll('.theme-option-circle');
|
|
6
|
+
const arrow = document.getElementById('sidebarThemeArrow');
|
|
7
|
+
|
|
8
|
+
if (!indicator || !popup) return;
|
|
9
|
+
|
|
10
|
+
// Toggle Popup
|
|
11
|
+
indicator.addEventListener('click', (e) => {
|
|
12
|
+
e.stopPropagation();
|
|
13
|
+
const isOpen = popup.classList.toggle('show');
|
|
14
|
+
indicator.classList.toggle('open', isOpen);
|
|
15
|
+
if (arrow) arrow.classList.toggle('visible', isOpen);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Close when clicking outside
|
|
19
|
+
document.addEventListener('click', (e) => {
|
|
20
|
+
if (!popup.contains(e.target) && e.target !== indicator) {
|
|
21
|
+
popup.classList.remove('show');
|
|
22
|
+
indicator.classList.remove('open');
|
|
23
|
+
if (arrow) arrow.classList.remove('visible');
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Theme selection
|
|
28
|
+
options.forEach(opt => {
|
|
29
|
+
opt.addEventListener('click', () => {
|
|
30
|
+
const theme = opt.getAttribute('data-theme');
|
|
31
|
+
if (window.setTheme) {
|
|
32
|
+
window.setTheme(theme);
|
|
33
|
+
updateCurrentThemeIndicator(theme);
|
|
34
|
+
popup.classList.remove('show');
|
|
35
|
+
indicator.classList.remove('open');
|
|
36
|
+
if (arrow) arrow.classList.remove('visible');
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function updateCurrentThemeIndicator(theme) {
|
|
42
|
+
// Update the main indicator circle's color class
|
|
43
|
+
indicator.className = 'current-theme-indicator theme-circle-' + (theme || 'light');
|
|
44
|
+
|
|
45
|
+
// Highlight active option in popup
|
|
46
|
+
options.forEach(opt => {
|
|
47
|
+
opt.classList.remove('active');
|
|
48
|
+
if (opt.getAttribute('data-theme') === (theme || 'light')) {
|
|
49
|
+
opt.classList.add('active');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Initialize indicator color
|
|
55
|
+
const savedTheme = localStorage.getItem('appTheme') || 'light';
|
|
56
|
+
updateCurrentThemeIndicator(savedTheme);
|
|
57
|
+
});
|
|
58
|
+
})();
|
|
@@ -47,20 +47,31 @@
|
|
|
47
47
|
white-space: nowrap;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
.sidebar.collapsed .accordion-button::after {
|
|
50
|
+
/* .sidebar.collapsed .accordion-button::after {
|
|
51
51
|
display: none !important;
|
|
52
|
+
} */
|
|
53
|
+
|
|
54
|
+
/* Hide accordion body content when sidebar is collapsed */
|
|
55
|
+
/* .sidebar.collapsed .accordion-collapse {
|
|
56
|
+
display: none !important;
|
|
57
|
+
} */
|
|
58
|
+
|
|
59
|
+
/* Also hide any expanded accordion body items text */
|
|
60
|
+
.sidebar.collapsed .accordion-body .list-group-item span {
|
|
61
|
+
opacity: 0;
|
|
62
|
+
visibility: hidden;
|
|
63
|
+
width: 0;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
.sidebar-ghost {
|
|
66
|
+
/* .sidebar-ghost {
|
|
55
67
|
display: none;
|
|
56
|
-
width: 52px;
|
|
57
68
|
flex-shrink: 0;
|
|
58
|
-
}
|
|
69
|
+
} */
|
|
59
70
|
|
|
60
71
|
@media (max-width: 1100px) {
|
|
61
|
-
.sidebar-ghost {
|
|
62
|
-
display:
|
|
63
|
-
}
|
|
72
|
+
/* .sidebar-ghost {
|
|
73
|
+
display: none !important;
|
|
74
|
+
} */
|
|
64
75
|
|
|
65
76
|
.sidebar {
|
|
66
77
|
position: fixed;
|
|
@@ -74,7 +85,9 @@
|
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
.sidebar.collapsed {
|
|
77
|
-
width:
|
|
88
|
+
width: 0 !important; /* Completely hide when collapsed on mobile */
|
|
89
|
+
border: none !important;
|
|
90
|
+
overflow: hidden !important;
|
|
78
91
|
right: 0;
|
|
79
92
|
position: fixed;
|
|
80
93
|
}
|
|
@@ -108,6 +121,7 @@
|
|
|
108
121
|
color: var(--primal) !important;
|
|
109
122
|
font-weight: 700 !important;
|
|
110
123
|
box-shadow: 0 4px 12px rgba(35, 99, 195, 0.08) !important;
|
|
124
|
+
transform: translateX(-4px);
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
.sidebar .list-group-item i,
|
|
@@ -203,4 +217,16 @@
|
|
|
203
217
|
color: white;
|
|
204
218
|
padding: 8px 12px;
|
|
205
219
|
border-radius: 5px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.sidebar-toolbar {
|
|
223
|
+
padding: 10px;
|
|
224
|
+
position: absolute;
|
|
225
|
+
bottom: 0;
|
|
226
|
+
left: 0;
|
|
227
|
+
width: 100%;
|
|
228
|
+
background: transparent;
|
|
229
|
+
pointer-events: none; /* Let clicks pass through to content if needed, but we override for items */
|
|
230
|
+
display: flex;
|
|
231
|
+
justify-content: flex-end
|
|
206
232
|
}
|
|
@@ -66,40 +66,24 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
|
66
66
|
deinitializeTooltips();
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}).catch(error => console.error("Error updating sidebar state:", error));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
setTimeout(triggerAutoscale, 250);
|
|
69
|
+
// Update session
|
|
70
|
+
fetch(toggleUrl, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
"X-CSRFToken": csrfToken,
|
|
74
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
75
|
+
},
|
|
76
|
+
body: `collapsed=${isCollapsed}`
|
|
77
|
+
}).then(response => response.json())
|
|
78
|
+
.then(data => {
|
|
79
|
+
if (data.status === "success") {
|
|
80
|
+
isSessionCollapsed = isCollapsed; // Update local state
|
|
81
|
+
}
|
|
82
|
+
}).catch(error => console.error("Error updating sidebar state:", error));
|
|
87
83
|
});
|
|
88
84
|
}
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
// This fixes the "sticking to top of screen" glitch during transitions
|
|
92
|
-
sidebar.addEventListener("click", function (event) {
|
|
93
|
-
// Find all tooltips in the sidebar and dispose them immediately
|
|
94
|
-
// This ensures they are completely removed from the DOM before any transition
|
|
95
|
-
const sidebarItems = sidebar.querySelectorAll(".list-group-item, .accordion-button");
|
|
96
|
-
sidebarItems.forEach(item => {
|
|
97
|
-
if (item._tooltip) {
|
|
98
|
-
item._tooltip.dispose();
|
|
99
|
-
delete item._tooltip;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
86
|
+
|
|
103
87
|
});
|
|
104
88
|
|
|
105
89
|
// Close sidebar when clicking outside (only for small screens)
|
|
@@ -154,12 +138,3 @@ function deinitializeTooltips() {
|
|
|
154
138
|
}
|
|
155
139
|
});
|
|
156
140
|
}
|
|
157
|
-
|
|
158
|
-
function triggerAutoscale() {
|
|
159
|
-
if (window.innerWidth > 1100) {
|
|
160
|
-
const autoscaleButton = document.querySelector('.modebar-btn[data-title="Reset axes"]');
|
|
161
|
-
if (autoscaleButton) {
|
|
162
|
-
autoscaleButton.click();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* --- Blue Theme (Aqua - Formerly Light) --- */
|
|
2
|
+
:root.theme-blue {
|
|
3
|
+
--title: #1e4f99;
|
|
4
|
+
--body: #f6f7f98b;
|
|
5
|
+
--htitle: #256cb2;
|
|
6
|
+
--hbody: #dee8ffdb;
|
|
7
|
+
--table-row: #e7f1fb;
|
|
8
|
+
--table-row-hover: #dfeaf5;
|
|
9
|
+
--primal: #2363c3;
|
|
10
|
+
--primal_dark: #1e4f99;
|
|
11
|
+
--primal-rgb: 35, 99, 195;
|
|
12
|
+
--btn-primary-shadow: rgba(35, 99, 195, 0.4);
|
|
13
|
+
|
|
14
|
+
/* Login Theme Variables */
|
|
15
|
+
--bg-gradient: linear-gradient(135deg, #f6f7f9 0%, #dee8ff 100%);
|
|
16
|
+
--right-bg: var(--title);
|
|
17
|
+
--primary-color: var(--primal);
|
|
18
|
+
|
|
19
|
+
/* Bootstrap Overrides */
|
|
20
|
+
--bs-primary: var(--primal);
|
|
21
|
+
--bs-primary-rgb: var(--primal-rgb);
|
|
22
|
+
--bs-btn-bg: var(--primal);
|
|
23
|
+
--bs-btn-border-color: var(--primal);
|
|
24
|
+
--bs-btn-hover-bg: var(--primal_dark);
|
|
25
|
+
--bs-btn-hover-border-color: var(--primal_dark);
|
|
26
|
+
--bs-link-color: var(--primal);
|
|
27
|
+
--bs-link-hover-color: var(--primal_dark);
|
|
28
|
+
}
|
|
29
|
+
:root.theme-blue .titlebar {
|
|
30
|
+
background: linear-gradient(90deg, #ffffff 10%, #e7f1fb 90%) !important;
|
|
31
|
+
}
|
|
32
|
+
:root.theme-blue #sidebar {
|
|
33
|
+
background: linear-gradient(180deg, #ffffff 10%, #e7f1fb 100%) !important;
|
|
34
|
+
border-left: 1px solid #dfeaf5 !important;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
:root.theme-blue .page .right {
|
|
38
|
+
background: var(--htitle) !important; /* Darker logo container */
|
|
39
|
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3) !important;
|
|
40
|
+
color: #e2e8f0 !important;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Dropdown & Button Overrides for Blue */
|
|
44
|
+
:root.theme-blue .dropdown-item:hover {
|
|
45
|
+
background-color: rgba(35, 99, 195, 0.08) !important;
|
|
46
|
+
color: #1e4f99 !important;
|
|
47
|
+
}
|
|
48
|
+
:root.theme-blue .titlebar .btn-light:hover {
|
|
49
|
+
background-color: rgba(35, 99, 195, 0.08) !important;
|
|
50
|
+
color: #1e4f99 !important;
|
|
51
|
+
}
|