dbviewer 0.7.10 → 0.7.11
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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/app/assets/images/dbviewer/emoji-favicon.txt +1 -0
- data/app/assets/images/dbviewer/favicon.ico +4 -0
- data/app/assets/images/dbviewer/favicon.png +4 -0
- data/app/assets/images/dbviewer/favicon.svg +10 -0
- data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +38 -42
- data/app/assets/javascripts/dbviewer/error_handler.js +58 -0
- data/app/assets/javascripts/dbviewer/home.js +25 -34
- data/app/assets/javascripts/dbviewer/layout.js +100 -129
- data/app/assets/javascripts/dbviewer/query.js +309 -246
- data/app/assets/javascripts/dbviewer/sidebar.js +170 -183
- data/app/assets/javascripts/dbviewer/utility.js +124 -0
- data/app/assets/stylesheets/dbviewer/application.css +8 -146
- data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +0 -34
- data/app/assets/stylesheets/dbviewer/logs.css +0 -11
- data/app/assets/stylesheets/dbviewer/query.css +21 -9
- data/app/assets/stylesheets/dbviewer/table.css +49 -131
- data/app/controllers/concerns/dbviewer/database_operations/connection_management.rb +90 -0
- data/app/controllers/concerns/dbviewer/database_operations/data_export.rb +31 -0
- data/app/controllers/concerns/dbviewer/database_operations/database_information.rb +54 -0
- data/app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb +37 -0
- data/app/controllers/concerns/dbviewer/database_operations/query_operations.rb +37 -0
- data/app/controllers/concerns/dbviewer/database_operations/relationship_management.rb +175 -0
- data/app/controllers/concerns/dbviewer/database_operations/table_operations.rb +46 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +4 -9
- data/app/controllers/dbviewer/api/tables_controller.rb +12 -0
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +0 -15
- data/app/controllers/dbviewer/tables_controller.rb +4 -33
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +1 -1
- data/app/views/dbviewer/tables/query.html.erb +21 -6
- data/app/views/dbviewer/tables/show.html.erb +2 -2
- data/app/views/layouts/dbviewer/application.html.erb +12 -3
- data/config/routes.rb +2 -2
- data/lib/dbviewer/database/manager.rb +2 -2
- data/lib/dbviewer/datatable/query_operations.rb +1 -17
- data/lib/dbviewer/engine.rb +29 -0
- data/lib/dbviewer/version.rb +1 -1
- metadata +15 -10
- data/app/controllers/concerns/dbviewer/connection_management.rb +0 -88
- data/app/controllers/concerns/dbviewer/data_export.rb +0 -32
- data/app/controllers/concerns/dbviewer/database_information.rb +0 -62
- data/app/controllers/concerns/dbviewer/datatable_support.rb +0 -47
- data/app/controllers/concerns/dbviewer/pagination_concern.rb +0 -34
- data/app/controllers/concerns/dbviewer/query_operations.rb +0 -28
- data/app/controllers/concerns/dbviewer/relationship_management.rb +0 -173
- data/app/controllers/concerns/dbviewer/table_operations.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b357ba9437acaa807f1b31562bb7a1b76bfaefc7c3e2e6ccb3be4cfdbd627a0a
|
4
|
+
data.tar.gz: 2adbc0f217154bad12cb3c7bfd711a7daa7dcbfa5b01538563ea49010cd6d2f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c24e471a8d6d3248ef88040cff072a7868b8e936bdb535888882497b9ac64396017bb42f7c7f100b68240c8a927e45b573f679e47bd6573c832fcd2e6adc837a
|
7
|
+
data.tar.gz: a7be084a91cb9d090544d7a770ba3f1cf18e512a56a216b431a58672c09c4fd9b682cb6df768708c5fcc1bf85494a9738643e42f488103abe1aefc945ef2eda6
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|

|
2
2
|
|
3
3
|
# 👁️ DBViewer
|
4
|
+
> **The fastest way to visualize and explore your database**
|
4
5
|
|
5
6
|
DBViewer is a powerful Rails engine that provides a comprehensive interface to view and explore database tables, records, and schema.
|
6
7
|
It's designed for development, debugging, and database analysis, offering a clean and intuitive way to interact with your application's database.
|
@@ -0,0 +1 @@
|
|
1
|
+
data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👁️</text></svg>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1"
|
3
|
+
xmlns="http://www.w3.org/2000/svg">
|
4
|
+
<title>DB Viewer Favicon</title>
|
5
|
+
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
6
|
+
<circle fill="#0d6efd" cx="50" cy="50" r="50" />
|
7
|
+
<text x="50" y="70" font-family="sans-serif" font-size="60" text-anchor="middle"
|
8
|
+
fill="white">👁️</text>
|
9
|
+
</g>
|
10
|
+
</svg>
|
@@ -1,30 +1,30 @@
|
|
1
1
|
document.addEventListener("DOMContentLoaded", function () {
|
2
|
+
// Validate that required utility scripts have loaded
|
3
|
+
if (!window.DBViewer || !DBViewer.Utility || !DBViewer.ErrorHandler) {
|
4
|
+
console.error(
|
5
|
+
"Required DBViewer scripts not loaded. Please check utility.js and error_handler.js."
|
6
|
+
);
|
7
|
+
return;
|
8
|
+
}
|
9
|
+
|
10
|
+
// Destructure the needed functions for easier access
|
11
|
+
const { debounce, ThemeManager } = DBViewer.Utility;
|
12
|
+
const { displayError } = DBViewer.ErrorHandler;
|
2
13
|
// Check if mermaid is loaded first
|
3
14
|
if (typeof mermaid === "undefined") {
|
4
15
|
console.error("Mermaid library not loaded!");
|
5
|
-
|
6
|
-
"
|
16
|
+
displayError(
|
17
|
+
"erd-container",
|
18
|
+
"Mermaid Library Not Loaded",
|
7
19
|
"The diagram library could not be loaded. Please check your internet connection and try again."
|
8
20
|
);
|
9
21
|
return;
|
10
22
|
}
|
11
23
|
|
12
|
-
// Helper function to debounce rapid function calls
|
13
|
-
function debounce(func, wait) {
|
14
|
-
let timeout;
|
15
|
-
return function (...args) {
|
16
|
-
clearTimeout(timeout);
|
17
|
-
timeout = setTimeout(() => func.apply(this, args), wait);
|
18
|
-
};
|
19
|
-
}
|
20
|
-
|
21
24
|
// Initialize mermaid with theme detection like mini ERD
|
22
25
|
mermaid.initialize({
|
23
26
|
startOnLoad: true,
|
24
|
-
theme:
|
25
|
-
document.documentElement.getAttribute("data-bs-theme") === "dark"
|
26
|
-
? "dark"
|
27
|
-
: "default",
|
27
|
+
theme: ThemeManager.getCurrentTheme() === "dark" ? "dark" : "default",
|
28
28
|
securityLevel: "loose",
|
29
29
|
er: {
|
30
30
|
diagramPadding: 20,
|
@@ -38,7 +38,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
38
38
|
},
|
39
39
|
});
|
40
40
|
|
41
|
-
// Function to show error messages
|
41
|
+
// Function to show error messages - using our custom error handler with specific UI adjustments
|
42
42
|
function showError(title, message, details = "") {
|
43
43
|
const errorContainer = document.getElementById("erd-error");
|
44
44
|
const errorMessage = document.getElementById("erd-error-message");
|
@@ -656,34 +656,30 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
656
656
|
|
657
657
|
// Add theme observer to update diagram when theme changes
|
658
658
|
function setupThemeObserver() {
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
fontSize: 20,
|
677
|
-
},
|
678
|
-
});
|
679
|
-
// Trigger redraw if diagram is already displayed
|
680
|
-
if (diagramReady) {
|
681
|
-
updateDiagramWithFullData();
|
682
|
-
}
|
683
|
-
}
|
659
|
+
// Listen for our custom theme change event
|
660
|
+
document.addEventListener("dbviewerThemeChanged", (event) => {
|
661
|
+
const newTheme = event.detail.theme;
|
662
|
+
mermaid.initialize({
|
663
|
+
theme: newTheme === "dark" ? "dark" : "default",
|
664
|
+
// Keep other settings
|
665
|
+
securityLevel: "loose",
|
666
|
+
er: {
|
667
|
+
diagramPadding: 20,
|
668
|
+
layoutDirection: "TB",
|
669
|
+
minEntityWidth: 100,
|
670
|
+
minEntityHeight: 75,
|
671
|
+
entityPadding: 15,
|
672
|
+
stroke: "gray",
|
673
|
+
fill: "honeydew",
|
674
|
+
fontSize: 20,
|
675
|
+
},
|
684
676
|
});
|
677
|
+
|
678
|
+
// Trigger redraw if diagram is already displayed
|
679
|
+
if (diagramReady) {
|
680
|
+
updateDiagramWithFullData();
|
681
|
+
}
|
685
682
|
});
|
686
|
-
observer.observe(document.documentElement, { attributes: true });
|
687
683
|
}
|
688
684
|
|
689
685
|
setupThemeObserver();
|
@@ -0,0 +1,58 @@
|
|
1
|
+
/**
|
2
|
+
* DBViewer Error Handler
|
3
|
+
* Provides consistent error handling across the application
|
4
|
+
*/
|
5
|
+
|
6
|
+
// Create a global namespace for DBViewer if it doesn't exist yet
|
7
|
+
window.DBViewer = window.DBViewer || {};
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Display an error message in a container
|
11
|
+
* @param {string} containerId - The ID of the container to show the error in
|
12
|
+
* @param {string} title - Error title
|
13
|
+
* @param {string} message - Error message
|
14
|
+
* @param {string} details - Optional details about the error
|
15
|
+
*/
|
16
|
+
function displayError(containerId, title, message, details = "") {
|
17
|
+
const container = document.getElementById(containerId);
|
18
|
+
if (!container) {
|
19
|
+
console.error(`Error container ${containerId} not found`);
|
20
|
+
return;
|
21
|
+
}
|
22
|
+
|
23
|
+
let detailsHtml = "";
|
24
|
+
if (details) {
|
25
|
+
detailsHtml = `<small class="text-muted">${details}</small>`;
|
26
|
+
}
|
27
|
+
|
28
|
+
container.innerHTML = `
|
29
|
+
<div class="text-center my-4 text-danger">
|
30
|
+
<i class="bi bi-exclamation-triangle fs-2 d-block mb-2"></i>
|
31
|
+
<p class="mb-1">${title}</p>
|
32
|
+
<div>${message}</div>
|
33
|
+
${detailsHtml}
|
34
|
+
</div>
|
35
|
+
`;
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Handle API fetch errors with consistent error handling and logging
|
40
|
+
* @param {string} endpoint - The API endpoint being accessed
|
41
|
+
* @param {Error} error - The error that occurred
|
42
|
+
* @param {Function} errorCallback - Callback function to handle UI updates on error
|
43
|
+
*/
|
44
|
+
async function handleApiError(endpoint, error, errorCallback) {
|
45
|
+
console.error(`Error fetching from ${endpoint}:`, error);
|
46
|
+
|
47
|
+
if (typeof errorCallback === "function") {
|
48
|
+
errorCallback(error);
|
49
|
+
}
|
50
|
+
|
51
|
+
// You could add additional error tracking here, like sending to a monitoring service
|
52
|
+
}
|
53
|
+
|
54
|
+
// Expose error handling functions globally
|
55
|
+
DBViewer.ErrorHandler = {
|
56
|
+
displayError,
|
57
|
+
handleApiError,
|
58
|
+
};
|
@@ -1,21 +1,16 @@
|
|
1
|
+
// Use DBViewer namespace for utility functions and error handling
|
1
2
|
document.addEventListener("DOMContentLoaded", async function () {
|
2
|
-
//
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
function numberToHumanSize(bytes) {
|
9
|
-
if (bytes === null || bytes === undefined) return "N/A";
|
10
|
-
if (bytes === 0) return "0 Bytes";
|
11
|
-
|
12
|
-
const k = 1024;
|
13
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
14
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
15
|
-
|
16
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
3
|
+
// Validate that required utility scripts have loaded
|
4
|
+
if (!window.DBViewer || !DBViewer.Utility || !DBViewer.ErrorHandler) {
|
5
|
+
console.error(
|
6
|
+
"Required DBViewer scripts not loaded. Please check utility.js and error_handler.js."
|
7
|
+
);
|
8
|
+
return;
|
17
9
|
}
|
18
10
|
|
11
|
+
// Destructure the needed functions for easier access
|
12
|
+
const { numberWithDelimiter, numberToHumanSize } = DBViewer.Utility;
|
13
|
+
const { displayError, handleApiError } = DBViewer.ErrorHandler;
|
19
14
|
// Function to update analytics cards
|
20
15
|
function updateTablesCount(data) {
|
21
16
|
document.getElementById("tables-loading").classList.add("d-none");
|
@@ -167,17 +162,7 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
167
162
|
}
|
168
163
|
}
|
169
164
|
|
170
|
-
//
|
171
|
-
function showError(containerId, message) {
|
172
|
-
const container = document.getElementById(containerId);
|
173
|
-
container.innerHTML = `
|
174
|
-
<div class="text-center my-4 text-danger">
|
175
|
-
<i class="bi bi-exclamation-triangle fs-2 d-block mb-2"></i>
|
176
|
-
<p>Error loading data</p>
|
177
|
-
<small>${message}</small>
|
178
|
-
</div>
|
179
|
-
`;
|
180
|
-
}
|
165
|
+
// Using error handler from imported module
|
181
166
|
|
182
167
|
async function fetchTableCount() {
|
183
168
|
try {
|
@@ -196,12 +181,13 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
196
181
|
const data = await response.json();
|
197
182
|
updateTablesCount(data);
|
198
183
|
} catch (error) {
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
184
|
+
await handleApiError("tables count", error, () => {
|
185
|
+
const loading = document.getElementById("tables-loading");
|
186
|
+
const count = document.getElementById("tables-count");
|
187
|
+
loading.classList.add("d-none");
|
188
|
+
count.classList.remove("d-none");
|
189
|
+
count.innerHTML = '<span class="text-danger">Error</span>';
|
190
|
+
});
|
205
191
|
}
|
206
192
|
}
|
207
193
|
|
@@ -274,8 +260,13 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
274
260
|
const data = await response.json();
|
275
261
|
updateRecentQueries(data);
|
276
262
|
} catch (error) {
|
277
|
-
|
278
|
-
|
263
|
+
await handleApiError("recent queries", error, () => {
|
264
|
+
displayError(
|
265
|
+
"recent-queries-container",
|
266
|
+
"Error Loading Queries",
|
267
|
+
error.message
|
268
|
+
);
|
269
|
+
});
|
279
270
|
}
|
280
271
|
}
|
281
272
|
// Load database size data (Loading records first to prevent race condition on dbviewer model constant creation)
|
@@ -1,52 +1,28 @@
|
|
1
1
|
document.addEventListener("DOMContentLoaded", function () {
|
2
|
+
// Validate that required utility scripts have loaded
|
3
|
+
if (!window.DBViewer || !DBViewer.Utility) {
|
4
|
+
console.error(
|
5
|
+
"Required DBViewer utility scripts not loaded. Please check utility.js."
|
6
|
+
);
|
7
|
+
return;
|
8
|
+
}
|
9
|
+
|
10
|
+
// Get ThemeManager from the global namespace
|
11
|
+
const { ThemeManager } = DBViewer.Utility;
|
12
|
+
|
2
13
|
// Theme toggle functionality
|
3
14
|
const themeToggleBtn = document.querySelector(".theme-toggle");
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
const prefersDarkMode = window.matchMedia(
|
8
|
-
"(prefers-color-scheme: dark)"
|
9
|
-
).matches;
|
10
|
-
const savedTheme = localStorage.getItem("dbviewerTheme");
|
11
|
-
|
12
|
-
// Set initial theme
|
13
|
-
if (savedTheme) {
|
14
|
-
htmlElement.setAttribute("data-bs-theme", savedTheme);
|
15
|
-
} else if (prefersDarkMode) {
|
16
|
-
htmlElement.setAttribute("data-bs-theme", "dark");
|
17
|
-
localStorage.setItem("dbviewerTheme", "dark");
|
18
|
-
}
|
15
|
+
|
16
|
+
// Initialize theme based on saved preference or OS preference
|
17
|
+
ThemeManager.initialize();
|
19
18
|
|
20
19
|
// Toggle theme when button is clicked
|
21
20
|
if (themeToggleBtn) {
|
22
21
|
themeToggleBtn.addEventListener("click", function () {
|
23
|
-
|
24
|
-
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
25
|
-
|
26
|
-
// Update theme
|
27
|
-
htmlElement.setAttribute("data-bs-theme", newTheme);
|
28
|
-
localStorage.setItem("dbviewerTheme", newTheme);
|
29
|
-
|
30
|
-
// Dispatch event for other components to respond to theme change (Monaco editor)
|
31
|
-
const themeChangeEvent = new CustomEvent("dbviewerThemeChanged", {
|
32
|
-
detail: { theme: newTheme },
|
33
|
-
});
|
34
|
-
document.dispatchEvent(themeChangeEvent);
|
22
|
+
ThemeManager.toggleTheme();
|
35
23
|
});
|
36
24
|
}
|
37
25
|
|
38
|
-
// Check if styles are loaded properly
|
39
|
-
const styleCheck = getComputedStyle(
|
40
|
-
document.documentElement
|
41
|
-
).getPropertyValue("--dbviewer-styles-loaded");
|
42
|
-
if (!styleCheck) {
|
43
|
-
console.log(
|
44
|
-
"DBViewer: Using fallback inline styles (asset pipeline may not be available)"
|
45
|
-
);
|
46
|
-
} else {
|
47
|
-
console.log("DBViewer: External CSS loaded successfully");
|
48
|
-
}
|
49
|
-
|
50
26
|
const toggleBtn = document.querySelector(".dbviewer-sidebar-toggle");
|
51
27
|
const closeBtn = document.querySelector(".dbviewer-sidebar-close");
|
52
28
|
const sidebar = document.querySelector(".dbviewer-sidebar");
|
@@ -72,26 +48,22 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
72
48
|
}, 300);
|
73
49
|
}
|
74
50
|
|
75
|
-
|
76
|
-
|
77
|
-
if (sidebar.classList.contains("active")) {
|
78
|
-
hideSidebar();
|
79
|
-
} else {
|
80
|
-
showSidebar();
|
81
|
-
// Focus the search input when sidebar becomes visible
|
82
|
-
setTimeout(() => {
|
83
|
-
const searchInput = document.getElementById("tableSearch");
|
84
|
-
if (searchInput) searchInput.focus();
|
85
|
-
}, 300); // Small delay to allow for animation
|
86
|
-
}
|
87
|
-
});
|
88
|
-
}
|
89
|
-
|
90
|
-
if (closeBtn) {
|
91
|
-
closeBtn.addEventListener("click", function () {
|
51
|
+
toggleBtn.addEventListener("click", function () {
|
52
|
+
if (sidebar.classList.contains("active")) {
|
92
53
|
hideSidebar();
|
93
|
-
}
|
94
|
-
|
54
|
+
} else {
|
55
|
+
showSidebar();
|
56
|
+
// Focus the search input when sidebar becomes visible
|
57
|
+
setTimeout(() => {
|
58
|
+
const searchInput = document.getElementById("tableSearch");
|
59
|
+
if (searchInput) searchInput.focus();
|
60
|
+
}, 300); // Small delay to allow for animation
|
61
|
+
}
|
62
|
+
});
|
63
|
+
|
64
|
+
closeBtn.addEventListener("click", function () {
|
65
|
+
hideSidebar();
|
66
|
+
});
|
95
67
|
|
96
68
|
overlay.addEventListener("click", function () {
|
97
69
|
hideSidebar();
|
@@ -110,85 +82,84 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
110
82
|
|
111
83
|
// Offcanvas enhancement for theme synchronization
|
112
84
|
const offcanvasElement = document.getElementById("navbarOffcanvas");
|
113
|
-
if (offcanvasElement) {
|
114
|
-
// Get all theme toggles
|
115
|
-
const allThemeToggles = document.querySelectorAll(".theme-toggle");
|
116
|
-
|
117
|
-
// Handle theme change from any toggle button
|
118
|
-
allThemeToggles.forEach((toggleBtn) => {
|
119
|
-
toggleBtn.addEventListener("click", function () {
|
120
|
-
const currentTheme =
|
121
|
-
document.documentElement.getAttribute("data-bs-theme") || "light";
|
122
|
-
|
123
|
-
// Update all theme toggle buttons to maintain consistency
|
124
|
-
allThemeToggles.forEach((btn) => {
|
125
|
-
// Update icon in all theme toggle buttons
|
126
|
-
if (currentTheme === "dark") {
|
127
|
-
btn.querySelector("span").innerHTML =
|
128
|
-
'<i class="bi bi-sun-fill"></i>';
|
129
|
-
btn.setAttribute("aria-label", "Switch to light mode");
|
130
|
-
} else {
|
131
|
-
btn.querySelector("span").innerHTML =
|
132
|
-
'<i class="bi bi-moon-fill"></i>';
|
133
|
-
btn.setAttribute("aria-label", "Switch to dark mode");
|
134
|
-
}
|
135
|
-
});
|
136
|
-
});
|
137
|
-
});
|
138
85
|
|
139
|
-
|
140
|
-
|
86
|
+
// Get all theme toggles
|
87
|
+
const allThemeToggles = document.querySelectorAll(".theme-toggle");
|
88
|
+
|
89
|
+
// Handle theme change from any toggle button
|
90
|
+
allThemeToggles.forEach((toggleBtn) => {
|
91
|
+
toggleBtn.addEventListener("click", function () {
|
141
92
|
const currentTheme =
|
142
93
|
document.documentElement.getAttribute("data-bs-theme") || "light";
|
143
|
-
if (currentTheme === "dark") {
|
144
|
-
offcanvasElement
|
145
|
-
.querySelector(".offcanvas-header")
|
146
|
-
.classList.remove("bg-light-subtle");
|
147
|
-
offcanvasElement
|
148
|
-
.querySelector(".offcanvas-header")
|
149
|
-
.classList.add("bg-dark-subtle");
|
150
|
-
} else {
|
151
|
-
offcanvasElement
|
152
|
-
.querySelector(".offcanvas-header")
|
153
|
-
.classList.remove("bg-dark-subtle");
|
154
|
-
offcanvasElement
|
155
|
-
.querySelector(".offcanvas-header")
|
156
|
-
.classList.add("bg-light-subtle");
|
157
|
-
}
|
158
|
-
}
|
159
|
-
|
160
|
-
// Sync on page load
|
161
|
-
document.addEventListener("DOMContentLoaded", syncOffcanvasWithTheme);
|
162
94
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
95
|
+
// Update all theme toggle buttons to maintain consistency
|
96
|
+
allThemeToggles.forEach((btn) => {
|
97
|
+
// Update icon in all theme toggle buttons
|
98
|
+
if (currentTheme === "dark") {
|
99
|
+
btn.querySelector("span").innerHTML =
|
100
|
+
'<i class="bi bi-sun-fill"></i>';
|
101
|
+
btn.setAttribute("aria-label", "Switch to light mode");
|
102
|
+
} else {
|
103
|
+
btn.querySelector("span").innerHTML =
|
104
|
+
'<i class="bi bi-moon-fill"></i>';
|
105
|
+
btn.setAttribute("aria-label", "Switch to dark mode");
|
174
106
|
}
|
175
107
|
});
|
176
108
|
});
|
109
|
+
});
|
177
110
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
111
|
+
// Function to sync offcanvas colors with current theme
|
112
|
+
function syncOffcanvasWithTheme() {
|
113
|
+
const currentTheme =
|
114
|
+
document.documentElement.getAttribute("data-bs-theme") || "light";
|
115
|
+
if (currentTheme === "dark") {
|
116
|
+
offcanvasElement
|
117
|
+
.querySelector(".offcanvas-header")
|
118
|
+
.classList.remove("bg-light-subtle");
|
119
|
+
offcanvasElement
|
120
|
+
.querySelector(".offcanvas-header")
|
121
|
+
.classList.add("bg-dark-subtle");
|
122
|
+
} else {
|
123
|
+
offcanvasElement
|
124
|
+
.querySelector(".offcanvas-header")
|
125
|
+
.classList.remove("bg-dark-subtle");
|
126
|
+
offcanvasElement
|
127
|
+
.querySelector(".offcanvas-header")
|
128
|
+
.classList.add("bg-light-subtle");
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
// Sync on page load
|
133
|
+
document.addEventListener("DOMContentLoaded", syncOffcanvasWithTheme);
|
134
|
+
|
135
|
+
// Listen for theme changes
|
136
|
+
document.addEventListener("dbviewerThemeChanged", syncOffcanvasWithTheme);
|
137
|
+
|
138
|
+
// Handle link click in offcanvas (auto-close on mobile)
|
139
|
+
const offcanvasLinks = offcanvasElement.querySelectorAll(
|
140
|
+
".nav-link:not(.dropdown-toggle)"
|
141
|
+
);
|
142
|
+
offcanvasLinks.forEach((link) => {
|
143
|
+
link.addEventListener("click", function () {
|
144
|
+
if (window.innerWidth < 992) {
|
145
|
+
bootstrap.Offcanvas.getInstance(offcanvasElement).hide();
|
191
146
|
}
|
192
147
|
});
|
193
|
-
}
|
148
|
+
});
|
149
|
+
|
150
|
+
// Fix offcanvas backdrop on desktop
|
151
|
+
window.addEventListener("resize", function () {
|
152
|
+
if (window.innerWidth >= 992) {
|
153
|
+
const offcanvasInstance =
|
154
|
+
bootstrap.Offcanvas.getInstance(offcanvasElement);
|
155
|
+
if (offcanvasInstance) {
|
156
|
+
offcanvasInstance.hide();
|
157
|
+
}
|
158
|
+
// Also remove any backdrop
|
159
|
+
const backdrop = document.querySelector(".offcanvas-backdrop");
|
160
|
+
if (backdrop) {
|
161
|
+
backdrop.remove();
|
162
|
+
}
|
163
|
+
}
|
164
|
+
});
|
194
165
|
});
|