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
@@ -1,4 +1,14 @@
|
|
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 debounce from the global namespace
|
11
|
+
const { debounce } = DBViewer.Utility;
|
2
12
|
const searchInput = document.getElementById("tableSearch");
|
3
13
|
const sidebarContent = document.querySelector(".dbviewer-sidebar-content");
|
4
14
|
|
@@ -8,203 +18,180 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
8
18
|
scrollPosition: "dbviewer_sidebar_scroll_position",
|
9
19
|
};
|
10
20
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
clearTimeout(timeout);
|
19
|
-
timeout = setTimeout(function () {
|
20
|
-
func.apply(context, args);
|
21
|
-
}, wait);
|
22
|
-
};
|
23
|
-
}
|
21
|
+
// Filter function
|
22
|
+
const filterTables = debounce(function () {
|
23
|
+
const query = searchInput.value.toLowerCase();
|
24
|
+
const tableItems = document.querySelectorAll(
|
25
|
+
"#tablesList .list-group-item-action"
|
26
|
+
);
|
27
|
+
let visibleCount = 0;
|
24
28
|
|
25
|
-
//
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
)
|
31
|
-
|
32
|
-
|
33
|
-
// Save the current search filter to localStorage
|
34
|
-
localStorage.setItem(STORAGE_KEYS.searchFilter, searchInput.value);
|
35
|
-
|
36
|
-
tableItems.forEach(function (item) {
|
37
|
-
// Get the table name from the title attribute for more accurate matching
|
38
|
-
const tableName = (item.getAttribute("title") || item.textContent)
|
39
|
-
.trim()
|
40
|
-
.toLowerCase();
|
41
|
-
|
42
|
-
// Also get the displayed text content for a broader match
|
43
|
-
const displayedText = item.textContent.trim().toLowerCase();
|
44
|
-
|
45
|
-
if (tableName.includes(query) || displayedText.includes(query)) {
|
46
|
-
item.classList.remove("d-none");
|
47
|
-
visibleCount++;
|
48
|
-
} else {
|
49
|
-
item.classList.add("d-none");
|
50
|
-
}
|
51
|
-
});
|
52
|
-
|
53
|
-
// Update the tables count in the sidebar
|
54
|
-
const tableCountElement = document.getElementById("table-count");
|
55
|
-
if (tableCountElement) {
|
56
|
-
tableCountElement.textContent = visibleCount;
|
57
|
-
}
|
29
|
+
// Save the current search filter to localStorage
|
30
|
+
localStorage.setItem(STORAGE_KEYS.searchFilter, searchInput.value);
|
31
|
+
|
32
|
+
tableItems.forEach(function (item) {
|
33
|
+
// Get the table name from the title attribute for more accurate matching
|
34
|
+
const tableName = (item.getAttribute("title") || item.textContent)
|
35
|
+
.trim()
|
36
|
+
.toLowerCase();
|
58
37
|
|
59
|
-
//
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
'<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
|
68
|
-
document.getElementById("tablesList").appendChild(noResultsEl);
|
69
|
-
}
|
70
|
-
noResultsEl.querySelector(".fw-bold").textContent = query;
|
71
|
-
noResultsEl.style.display = "block";
|
72
|
-
} else if (noResultsEl) {
|
73
|
-
noResultsEl.style.display = "none";
|
38
|
+
// Also get the displayed text content for a broader match
|
39
|
+
const displayedText = item.textContent.trim().toLowerCase();
|
40
|
+
|
41
|
+
if (tableName.includes(query) || displayedText.includes(query)) {
|
42
|
+
item.classList.remove("d-none");
|
43
|
+
visibleCount++;
|
44
|
+
} else {
|
45
|
+
item.classList.add("d-none");
|
74
46
|
}
|
75
|
-
}, 150); // Debounce for 150ms
|
76
|
-
|
77
|
-
// Set up clear button first
|
78
|
-
const clearButton = document.createElement("button");
|
79
|
-
clearButton.type = "button";
|
80
|
-
clearButton.className = "btn btn-sm btn-link position-absolute";
|
81
|
-
clearButton.style.right = "15px";
|
82
|
-
clearButton.style.top = "50%";
|
83
|
-
clearButton.style.transform = "translateY(-50%)";
|
84
|
-
clearButton.style.display = "none";
|
85
|
-
clearButton.style.color = "#6c757d";
|
86
|
-
clearButton.style.fontSize = "0.85rem";
|
87
|
-
clearButton.style.padding = "0.25rem";
|
88
|
-
clearButton.style.width = "1.5rem";
|
89
|
-
clearButton.style.textAlign = "center";
|
90
|
-
clearButton.innerHTML = '<i class="bi bi-x-circle"></i>';
|
91
|
-
clearButton.addEventListener("click", function () {
|
92
|
-
searchInput.value = "";
|
93
|
-
// Clear the saved filter from localStorage
|
94
|
-
localStorage.removeItem(STORAGE_KEYS.searchFilter);
|
95
|
-
// Call filter directly without debouncing for immediate feedback
|
96
|
-
filterTables();
|
97
|
-
this.style.display = "none";
|
98
47
|
});
|
99
48
|
|
100
|
-
|
101
|
-
|
102
|
-
)
|
103
|
-
|
104
|
-
filterContainer.style.position = "relative";
|
105
|
-
filterContainer.appendChild(clearButton);
|
106
|
-
|
107
|
-
searchInput.addEventListener("input", function () {
|
108
|
-
clearButton.style.display = this.value ? "block" : "none";
|
109
|
-
});
|
49
|
+
// Update the tables count in the sidebar
|
50
|
+
const tableCountElement = document.getElementById("table-count");
|
51
|
+
if (tableCountElement) {
|
52
|
+
tableCountElement.textContent = visibleCount;
|
110
53
|
}
|
111
54
|
|
112
|
-
//
|
113
|
-
|
114
|
-
if (
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
"
|
122
|
-
);
|
123
|
-
let visibleCount = 0;
|
124
|
-
|
125
|
-
tableItems.forEach(function (item) {
|
126
|
-
const tableName = (item.getAttribute("title") || item.textContent)
|
127
|
-
.trim()
|
128
|
-
.toLowerCase();
|
129
|
-
const displayedText = item.textContent.trim().toLowerCase();
|
130
|
-
|
131
|
-
if (tableName.includes(query) || displayedText.includes(query)) {
|
132
|
-
item.classList.remove("d-none");
|
133
|
-
visibleCount++;
|
134
|
-
} else {
|
135
|
-
item.classList.add("d-none");
|
136
|
-
}
|
137
|
-
});
|
138
|
-
|
139
|
-
// Update the tables count immediately
|
140
|
-
const tableCountElement = document.getElementById("table-count");
|
141
|
-
if (tableCountElement) {
|
142
|
-
tableCountElement.textContent = visibleCount;
|
143
|
-
}
|
144
|
-
|
145
|
-
// Handle no results message immediately
|
146
|
-
let noResultsEl = document.getElementById("dbviewer-no-filter-results");
|
147
|
-
if (visibleCount === 0 && query !== "") {
|
148
|
-
if (!noResultsEl) {
|
149
|
-
noResultsEl = document.createElement("div");
|
150
|
-
noResultsEl.id = "dbviewer-no-filter-results";
|
151
|
-
noResultsEl.className = "list-group-item text-muted text-center py-3";
|
152
|
-
noResultsEl.innerHTML =
|
153
|
-
'<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
|
154
|
-
document.getElementById("tablesList").appendChild(noResultsEl);
|
155
|
-
}
|
156
|
-
noResultsEl.querySelector(".fw-bold").textContent = query;
|
157
|
-
noResultsEl.style.display = "block";
|
158
|
-
} else if (noResultsEl) {
|
159
|
-
noResultsEl.style.display = "none";
|
55
|
+
// Show/hide no results message
|
56
|
+
let noResultsEl = document.getElementById("dbviewer-no-filter-results");
|
57
|
+
if (visibleCount === 0 && query !== "") {
|
58
|
+
if (!noResultsEl) {
|
59
|
+
noResultsEl = document.createElement("div");
|
60
|
+
noResultsEl.id = "dbviewer-no-filter-results";
|
61
|
+
noResultsEl.className = "list-group-item text-muted text-center py-3";
|
62
|
+
noResultsEl.innerHTML =
|
63
|
+
'<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
|
64
|
+
document.getElementById("tablesList").appendChild(noResultsEl);
|
160
65
|
}
|
66
|
+
noResultsEl.querySelector(".fw-bold").textContent = query;
|
67
|
+
noResultsEl.style.display = "block";
|
68
|
+
} else if (noResultsEl) {
|
69
|
+
noResultsEl.style.display = "none";
|
161
70
|
}
|
71
|
+
}, 150); // Debounce for 150ms
|
72
|
+
|
73
|
+
// Set up clear button first
|
74
|
+
const clearButton = document.createElement("button");
|
75
|
+
clearButton.type = "button";
|
76
|
+
clearButton.className = "btn btn-sm btn-link position-absolute";
|
77
|
+
clearButton.style.right = "15px";
|
78
|
+
clearButton.style.top = "50%";
|
79
|
+
clearButton.style.transform = "translateY(-50%)";
|
80
|
+
clearButton.style.display = "none";
|
81
|
+
clearButton.style.color = "#6c757d";
|
82
|
+
clearButton.style.fontSize = "0.85rem";
|
83
|
+
clearButton.style.padding = "0.25rem";
|
84
|
+
clearButton.style.width = "1.5rem";
|
85
|
+
clearButton.style.textAlign = "center";
|
86
|
+
clearButton.innerHTML = '<i class="bi bi-x-circle"></i>';
|
87
|
+
clearButton.addEventListener("click", function () {
|
88
|
+
searchInput.value = "";
|
89
|
+
// Clear the saved filter from localStorage
|
90
|
+
localStorage.removeItem(STORAGE_KEYS.searchFilter);
|
91
|
+
// Call filter directly without debouncing for immediate feedback
|
92
|
+
filterTables();
|
93
|
+
this.style.display = "none";
|
94
|
+
});
|
95
|
+
|
96
|
+
const filterContainer = document.querySelector(
|
97
|
+
".dbviewer-table-filter-container"
|
98
|
+
);
|
99
|
+
if (filterContainer) {
|
100
|
+
filterContainer.style.position = "relative";
|
101
|
+
filterContainer.appendChild(clearButton);
|
102
|
+
|
103
|
+
searchInput.addEventListener("input", function () {
|
104
|
+
clearButton.style.display = this.value ? "block" : "none";
|
105
|
+
});
|
106
|
+
}
|
162
107
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
108
|
+
// Restore saved search filter on page load and apply it immediately
|
109
|
+
const savedFilter = localStorage.getItem(STORAGE_KEYS.searchFilter);
|
110
|
+
searchInput.value = savedFilter;
|
111
|
+
// Show clear button immediately when filter is restored
|
112
|
+
clearButton.style.display = "block";
|
113
|
+
// Apply filter immediately without debouncing to prevent blinking
|
114
|
+
const query = savedFilter.toLowerCase();
|
115
|
+
const tableItems = document.querySelectorAll(
|
116
|
+
"#tablesList .list-group-item-action"
|
117
|
+
);
|
118
|
+
let visibleCount = 0;
|
119
|
+
|
120
|
+
tableItems.forEach(function (item) {
|
121
|
+
const tableName = (item.getAttribute("title") || item.textContent)
|
122
|
+
.trim()
|
123
|
+
.toLowerCase();
|
124
|
+
const displayedText = item.textContent.trim().toLowerCase();
|
125
|
+
|
126
|
+
if (tableName.includes(query) || displayedText.includes(query)) {
|
127
|
+
item.classList.remove("d-none");
|
128
|
+
visibleCount++;
|
129
|
+
} else {
|
130
|
+
item.classList.add("d-none");
|
131
|
+
}
|
132
|
+
});
|
174
133
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
);
|
181
|
-
}, 100);
|
134
|
+
// Update the tables count immediately
|
135
|
+
const tableCountElement = document.getElementById("table-count");
|
136
|
+
if (tableCountElement) {
|
137
|
+
tableCountElement.textContent = visibleCount;
|
138
|
+
}
|
182
139
|
|
183
|
-
|
140
|
+
// Handle no results message immediately
|
141
|
+
let noResultsEl = document.getElementById("dbviewer-no-filter-results");
|
142
|
+
if (visibleCount === 0 && query !== "") {
|
143
|
+
if (!noResultsEl) {
|
144
|
+
noResultsEl = document.createElement("div");
|
145
|
+
noResultsEl.id = "dbviewer-no-filter-results";
|
146
|
+
noResultsEl.className = "list-group-item text-muted text-center py-3";
|
147
|
+
noResultsEl.innerHTML =
|
148
|
+
'<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
|
149
|
+
document.getElementById("tablesList").appendChild(noResultsEl);
|
184
150
|
}
|
151
|
+
noResultsEl.querySelector(".fw-bold").textContent = query;
|
152
|
+
noResultsEl.style.display = "block";
|
153
|
+
} else if (noResultsEl) {
|
154
|
+
noResultsEl.style.display = "none";
|
155
|
+
}
|
185
156
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
if (e.key === "Enter" || e.key === "ArrowDown") {
|
193
|
-
e.preventDefault();
|
194
|
-
// Focus the first visible table item (not having d-none class)
|
195
|
-
const firstVisibleItem = document.querySelector(
|
196
|
-
"#tablesList .list-group-item-action:not(.d-none)"
|
197
|
-
);
|
198
|
-
if (firstVisibleItem) {
|
199
|
-
firstVisibleItem.focus();
|
200
|
-
// Make sure the item is visible in the scrollable area
|
201
|
-
firstVisibleItem.scrollIntoView({
|
202
|
-
behavior: "smooth",
|
203
|
-
block: "nearest",
|
204
|
-
});
|
205
|
-
}
|
206
|
-
}
|
157
|
+
// Restore saved scroll position on page load
|
158
|
+
const savedScrollPosition = localStorage.getItem(STORAGE_KEYS.scrollPosition);
|
159
|
+
if (savedScrollPosition) {
|
160
|
+
// Use requestAnimationFrame to ensure DOM is fully rendered
|
161
|
+
requestAnimationFrame(() => {
|
162
|
+
sidebarContent.scrollTop = parseInt(savedScrollPosition, 10);
|
207
163
|
});
|
208
|
-
searchInput.addEventListener("search", filterTables); // For clearing via the "x" in some browsers
|
209
164
|
}
|
165
|
+
|
166
|
+
// Save scroll position on scroll
|
167
|
+
const saveScrollPosition = debounce(function () {
|
168
|
+
localStorage.setItem(STORAGE_KEYS.scrollPosition, sidebarContent.scrollTop);
|
169
|
+
}, 100);
|
170
|
+
|
171
|
+
sidebarContent.addEventListener("scroll", saveScrollPosition);
|
172
|
+
|
173
|
+
// Set up event listeners for the search input
|
174
|
+
searchInput.addEventListener("input", filterTables);
|
175
|
+
searchInput.addEventListener("keyup", function (e) {
|
176
|
+
filterTables();
|
177
|
+
|
178
|
+
// Add keyboard navigation for the filtered list
|
179
|
+
if (e.key === "Enter" || e.key === "ArrowDown") {
|
180
|
+
e.preventDefault();
|
181
|
+
// Focus the first visible table item (not having d-none class)
|
182
|
+
const firstVisibleItem = document.querySelector(
|
183
|
+
"#tablesList .list-group-item-action:not(.d-none)"
|
184
|
+
);
|
185
|
+
if (firstVisibleItem) {
|
186
|
+
firstVisibleItem.focus();
|
187
|
+
// Make sure the item is visible in the scrollable area
|
188
|
+
firstVisibleItem.scrollIntoView({
|
189
|
+
behavior: "smooth",
|
190
|
+
block: "nearest",
|
191
|
+
});
|
192
|
+
}
|
193
|
+
}
|
194
|
+
});
|
195
|
+
|
196
|
+
searchInput.addEventListener("search", filterTables); // For clearing via the "x" in some browsers
|
210
197
|
});
|
@@ -0,0 +1,124 @@
|
|
1
|
+
/**
|
2
|
+
* DBViewer Utility Functions
|
3
|
+
* Shared utilities for DBViewer JavaScript modules
|
4
|
+
*/
|
5
|
+
|
6
|
+
// Create a global namespace for DBViewer
|
7
|
+
window.DBViewer = window.DBViewer || {};
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Debounces a function call to limit how often it runs
|
11
|
+
* @param {Function} func - The function to debounce
|
12
|
+
* @param {number} wait - Milliseconds to wait between calls
|
13
|
+
* @returns {Function} Debounced function
|
14
|
+
*/
|
15
|
+
function debounce(func, wait) {
|
16
|
+
let timeout;
|
17
|
+
return function (...args) {
|
18
|
+
const context = this;
|
19
|
+
clearTimeout(timeout);
|
20
|
+
timeout = setTimeout(() => func.apply(context, args), wait);
|
21
|
+
};
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Decodes HTML entities in a string
|
26
|
+
* @param {string} text - Text with HTML entities
|
27
|
+
* @returns {string} Decoded text
|
28
|
+
*/
|
29
|
+
function decodeHTMLEntities(text) {
|
30
|
+
const textarea = document.createElement("textarea");
|
31
|
+
textarea.innerHTML = text;
|
32
|
+
return textarea.value;
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Format a number with thousands separators
|
37
|
+
* @param {number} number - Number to format
|
38
|
+
* @returns {string} Formatted number with commas
|
39
|
+
*/
|
40
|
+
function numberWithDelimiter(number) {
|
41
|
+
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
42
|
+
}
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Convert bytes to human-readable file size
|
46
|
+
* @param {number} bytes - Size in bytes
|
47
|
+
* @returns {string} Human readable size (e.g., "4.2 MB")
|
48
|
+
*/
|
49
|
+
function numberToHumanSize(bytes) {
|
50
|
+
if (bytes === null || bytes === undefined) return "N/A";
|
51
|
+
if (bytes === 0) return "0 Bytes";
|
52
|
+
|
53
|
+
const k = 1024;
|
54
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
55
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
56
|
+
|
57
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Helper function to manage theme changes across the application
|
62
|
+
*/
|
63
|
+
const ThemeManager = {
|
64
|
+
/**
|
65
|
+
* Get the current theme
|
66
|
+
* @returns {string} 'dark' or 'light'
|
67
|
+
*/
|
68
|
+
getCurrentTheme() {
|
69
|
+
return document.documentElement.getAttribute("data-bs-theme") || "light";
|
70
|
+
},
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Set the theme and save to local storage
|
74
|
+
* @param {string} theme - 'dark' or 'light'
|
75
|
+
*/
|
76
|
+
setTheme(theme) {
|
77
|
+
if (theme !== "dark" && theme !== "light") {
|
78
|
+
console.error("Invalid theme value:", theme);
|
79
|
+
return;
|
80
|
+
}
|
81
|
+
|
82
|
+
document.documentElement.setAttribute("data-bs-theme", theme);
|
83
|
+
localStorage.setItem("dbviewerTheme", theme);
|
84
|
+
|
85
|
+
// Notify all components about theme change
|
86
|
+
const themeChangeEvent = new CustomEvent("dbviewerThemeChanged", {
|
87
|
+
detail: { theme },
|
88
|
+
});
|
89
|
+
document.dispatchEvent(themeChangeEvent);
|
90
|
+
},
|
91
|
+
|
92
|
+
/**
|
93
|
+
* Toggle between dark and light themes
|
94
|
+
*/
|
95
|
+
toggleTheme() {
|
96
|
+
const currentTheme = this.getCurrentTheme();
|
97
|
+
this.setTheme(currentTheme === "dark" ? "light" : "dark");
|
98
|
+
},
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Initialize theme based on saved preference or OS preference
|
102
|
+
*/
|
103
|
+
initialize() {
|
104
|
+
const prefersDarkMode = window.matchMedia(
|
105
|
+
"(prefers-color-scheme: dark)"
|
106
|
+
).matches;
|
107
|
+
const savedTheme = localStorage.getItem("dbviewerTheme");
|
108
|
+
|
109
|
+
if (savedTheme) {
|
110
|
+
this.setTheme(savedTheme);
|
111
|
+
} else if (prefersDarkMode) {
|
112
|
+
this.setTheme("dark");
|
113
|
+
}
|
114
|
+
},
|
115
|
+
};
|
116
|
+
|
117
|
+
// Expose utilities to global namespace
|
118
|
+
DBViewer.Utility = {
|
119
|
+
debounce,
|
120
|
+
decodeHTMLEntities,
|
121
|
+
numberWithDelimiter,
|
122
|
+
numberToHumanSize,
|
123
|
+
ThemeManager,
|
124
|
+
};
|