dbviewer 0.7.1 → 0.7.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ae5e2964635ef24eaa04e213e09c6ec284b2361d4b23d648297b92b9c276474
4
- data.tar.gz: f205bbb6df4cf8bbc5b748ae91b9843e8704f26d16d37337747ad8023f5825db
3
+ metadata.gz: f1d51130bb3ed8d46ab3602679788ee98812bce9dbab988e32999c3b50f445fe
4
+ data.tar.gz: 53aa2f65c0dafce3d118de8801a69cd233b4c437eaccc9974b609dce12591148
5
5
  SHA512:
6
- metadata.gz: 5541be4f54077ebd58091153cbeb53464e4f32469e8a10220f823d1528e59a90e6edfeba26e910f4362528e69013e537619a3d6c79938024dbca5be3fee490e3
7
- data.tar.gz: d68cb03d0f721cc96561c809ede600b99f702a69c9607719916a60edb431659e9a6eb41abd6b3330de56a0fe48bf48d9d06d3d88a0d9f1b567378330beca1a9e
6
+ metadata.gz: c1566aef41e200d6b1e61cdb665381480eb592e3e738cf3f74c2c8be2cdc9cf8f616ed63b2e9883a286fd41b1f0e8708bf05875e0a6beaea796e22bc40d9afeb
7
+ data.tar.gz: 1b84dfca22a4421f437e02f85d5c290d37f9a48dedd4e0f5f309c4e64a3d1f06d3de916a043cc448061f320fc31899d4c9346252e6ac907680ef7499c6e49c9f
@@ -100,37 +100,35 @@ document.addEventListener("DOMContentLoaded", function () {
100
100
  let relationshipsLoaded = false;
101
101
 
102
102
  // Function to fetch relationships asynchronously
103
- function fetchRelationships() {
103
+ async function fetchRelationships() {
104
104
  const apiPath = document.getElementById("relationships_api_path").value;
105
- return fetch(apiPath, {
106
- headers: {
107
- Accept: "application/json",
108
- "X-Requested-With": "XMLHttpRequest",
109
- },
110
- })
111
- .then((response) => {
112
- if (!response.ok) {
113
- throw new Error(`HTTP error! status: ${response.status}`);
114
- }
115
- return response.json();
116
- })
117
- .then((data) => {
118
- relationships = data.relationships || [];
119
- relationshipsLoaded = true;
120
- updateRelationshipsStatus(true);
121
- return relationships;
122
- })
123
- .catch((error) => {
124
- console.error("Error fetching relationships:", error);
125
- relationshipsLoaded = true; // Mark as loaded even on error to prevent infinite loading
126
- updateRelationshipsStatus(true);
127
- return [];
105
+ try {
106
+ const response = await fetch(apiPath, {
107
+ headers: {
108
+ Accept: "application/json",
109
+ "X-Requested-With": "XMLHttpRequest",
110
+ },
128
111
  });
112
+
113
+ if (!response.ok) {
114
+ throw new Error(`HTTP error! status: ${response.status}`);
115
+ }
116
+
117
+ const data = await response.json();
118
+ relationships = data.relationships || [];
119
+ relationshipsLoaded = true;
120
+ updateRelationshipsStatus(true);
121
+ return relationships;
122
+ } catch (error) {
123
+ console.error("Error fetching relationships:", error);
124
+ relationshipsLoaded = true; // Mark as loaded even on error to prevent infinite loading
125
+ updateRelationshipsStatus(true);
126
+ return [];
127
+ }
129
128
  }
130
129
 
131
130
  // Function to update loading status
132
131
  function updateLoadingStatus(message) {
133
- const loadingElement = document.getElementById("erd-loading");
134
132
  const loadingPhase = document.getElementById("loading-phase");
135
133
  if (loadingPhase) {
136
134
  loadingPhase.textContent = message;
@@ -196,6 +194,35 @@ document.addEventListener("DOMContentLoaded", function () {
196
194
  const tablePath = document.getElementById("tables_path").value;
197
195
 
198
196
  // First pass: add all tables with minimal info and start loading columns
197
+ // Function to fetch column data for a table
198
+ async function fetchTableColumns(tableName) {
199
+ try {
200
+ const response = await fetch(`${tablePath}/${tableName}?format=json`, {
201
+ headers: {
202
+ Accept: "application/json",
203
+ "X-Requested-With": "XMLHttpRequest",
204
+ },
205
+ });
206
+
207
+ const data = await response.json();
208
+
209
+ if (data && data.columns) {
210
+ tableColumns[tableName] = data.columns;
211
+ columnsLoadedCount++;
212
+
213
+ // Update progress bar
214
+ updateTableProgress(columnsLoadedCount, totalTables);
215
+
216
+ checkIfReadyToUpdate();
217
+ }
218
+ } catch (error) {
219
+ console.error(`Error fetching columns for table ${tableName}:`, error);
220
+ columnsLoadedCount++;
221
+ updateTableProgress(columnsLoadedCount, totalTables);
222
+ checkIfReadyToUpdate();
223
+ }
224
+ }
225
+
199
226
  tables.forEach(function (table) {
200
227
  const tableName = table.name;
201
228
  mermaidDefinition += ` ${tableName} {\n`;
@@ -203,30 +230,7 @@ document.addEventListener("DOMContentLoaded", function () {
203
230
  mermaidDefinition += " }\n";
204
231
 
205
232
  // Start loading column data asynchronously
206
- fetch(`${tablePath}/${tableName}?format=json`, {
207
- headers: {
208
- Accept: "application/json",
209
- "X-Requested-With": "XMLHttpRequest",
210
- },
211
- })
212
- .then((response) => response.json())
213
- .then((data) => {
214
- if (data && data.columns) {
215
- tableColumns[tableName] = data.columns;
216
- columnsLoadedCount++;
217
-
218
- // Update progress bar
219
- updateTableProgress(columnsLoadedCount, totalTables);
220
-
221
- checkIfReadyToUpdate();
222
- }
223
- })
224
- .catch((error) => {
225
- console.error(`Error fetching columns for table ${tableName}:`, error);
226
- columnsLoadedCount++;
227
- updateTableProgress(columnsLoadedCount, totalTables);
228
- checkIfReadyToUpdate();
229
- });
233
+ fetchTableColumns(tableName);
230
234
  });
231
235
 
232
236
  // Function to check if we're ready to update the diagram with full data
@@ -0,0 +1,210 @@
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ const searchInput = document.getElementById("tableSearch");
3
+ const sidebarContent = document.querySelector(".dbviewer-sidebar-content");
4
+
5
+ // Storage keys for persistence
6
+ const STORAGE_KEYS = {
7
+ searchFilter: "dbviewer_sidebar_search_filter",
8
+ scrollPosition: "dbviewer_sidebar_scroll_position",
9
+ };
10
+
11
+ if (searchInput) {
12
+ // Debounce function to limit how often the filter runs
13
+ function debounce(func, wait) {
14
+ let timeout;
15
+ return function () {
16
+ const context = this;
17
+ const args = arguments;
18
+ clearTimeout(timeout);
19
+ timeout = setTimeout(function () {
20
+ func.apply(context, args);
21
+ }, wait);
22
+ };
23
+ }
24
+
25
+ // Filter function
26
+ const filterTables = debounce(function () {
27
+ const query = searchInput.value.toLowerCase();
28
+ const tableItems = document.querySelectorAll(
29
+ "#tablesList .list-group-item-action"
30
+ );
31
+ let visibleCount = 0;
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
+ }
58
+
59
+ // Show/hide no results message
60
+ let noResultsEl = document.getElementById("dbviewer-no-filter-results");
61
+ if (visibleCount === 0 && query !== "") {
62
+ if (!noResultsEl) {
63
+ noResultsEl = document.createElement("div");
64
+ noResultsEl.id = "dbviewer-no-filter-results";
65
+ noResultsEl.className = "list-group-item text-muted text-center py-3";
66
+ noResultsEl.innerHTML =
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";
74
+ }
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
+ });
99
+
100
+ const filterContainer = document.querySelector(
101
+ ".dbviewer-table-filter-container"
102
+ );
103
+ if (filterContainer) {
104
+ filterContainer.style.position = "relative";
105
+ filterContainer.appendChild(clearButton);
106
+
107
+ searchInput.addEventListener("input", function () {
108
+ clearButton.style.display = this.value ? "block" : "none";
109
+ });
110
+ }
111
+
112
+ // Restore saved search filter on page load and apply it immediately
113
+ const savedFilter = localStorage.getItem(STORAGE_KEYS.searchFilter);
114
+ if (savedFilter) {
115
+ searchInput.value = savedFilter;
116
+ // Show clear button immediately when filter is restored
117
+ clearButton.style.display = "block";
118
+ // Apply filter immediately without debouncing to prevent blinking
119
+ const query = savedFilter.toLowerCase();
120
+ const tableItems = document.querySelectorAll(
121
+ "#tablesList .list-group-item-action"
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";
160
+ }
161
+ }
162
+
163
+ // Restore saved scroll position on page load
164
+ if (sidebarContent) {
165
+ const savedScrollPosition = localStorage.getItem(
166
+ STORAGE_KEYS.scrollPosition
167
+ );
168
+ if (savedScrollPosition) {
169
+ // Use requestAnimationFrame to ensure DOM is fully rendered
170
+ requestAnimationFrame(() => {
171
+ sidebarContent.scrollTop = parseInt(savedScrollPosition, 10);
172
+ });
173
+ }
174
+
175
+ // Save scroll position on scroll
176
+ const saveScrollPosition = debounce(function () {
177
+ localStorage.setItem(
178
+ STORAGE_KEYS.scrollPosition,
179
+ sidebarContent.scrollTop
180
+ );
181
+ }, 100);
182
+
183
+ sidebarContent.addEventListener("scroll", saveScrollPosition);
184
+ }
185
+
186
+ // Set up event listeners for the search input
187
+ searchInput.addEventListener("input", filterTables);
188
+ searchInput.addEventListener("keyup", function (e) {
189
+ filterTables();
190
+
191
+ // Add keyboard navigation for the filtered list
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
+ }
207
+ });
208
+ searchInput.addEventListener("search", filterTables); // For clearing via the "x" in some browsers
209
+ }
210
+ });
@@ -11,23 +11,7 @@
11
11
  title: table[:name],
12
12
  class: "list-group-item list-group-item-action d-flex align-items-center #{'active' if current_table?(table[:name])}",
13
13
  tabindex: "0",
14
- data: { table_name: table[:name] },
15
- onkeydown: "
16
- if(event.key === 'ArrowDown') {
17
- event.preventDefault();
18
- let next = this.nextElementSibling;
19
- while(next && next.classList.contains('d-none')) {
20
- next = next.nextElementSibling;
21
- }
22
- if(next) next.focus();
23
- } else if(event.key === 'ArrowUp') {
24
- event.preventDefault();
25
- let prev = this.previousElementSibling;
26
- while(prev && prev.classList.contains('d-none')) {
27
- prev = prev.previousElementSibling;
28
- }
29
- if(prev) prev.focus();
30
- }" do %>
14
+ data: { table_name: table[:name] } do %>
31
15
  <div class="d-flex justify-content-between align-items-center w-100">
32
16
  <div class="text-truncate">
33
17
  <i class="bi bi-table me-2 small"></i>
@@ -38,6 +38,7 @@
38
38
  <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
39
39
 
40
40
  <%= javascript_include_tag "dbviewer/layout", "data-turbo-track": "reload" %>
41
+ <%= javascript_include_tag "dbviewer/sidebar", "data-turbo-track": "reload" %>
41
42
  <%= stylesheet_link_tag "dbviewer/application", "data-turbo-track": "reload" %>
42
43
 
43
44
  <%= yield :head %>
@@ -3,58 +3,6 @@
3
3
  <input type="text" class="form-control form-control-sm dbviewer-table-filter mb-0"
4
4
  id="tableSearch" placeholder="Filter tables..." aria-label="Filter tables">
5
5
  </div>
6
-
7
-
8
-
9
- <!-- Add custom styling for datetime inputs -->
10
- <style>
11
- /* Better datetime input styling */
12
- input[type="datetime-local"] {
13
- padding-right: 0.5rem;
14
- }
15
-
16
- /* Dark mode support for datetime inputs */
17
- [data-bs-theme="dark"] input[type="datetime-local"] {
18
- background-color: rgba(255,255,255,0.1);
19
- color: #fff;
20
- border-color: rgba(255,255,255,0.15);
21
- }
22
-
23
- [data-bs-theme="dark"] input[type="datetime-local"]::-webkit-calendar-picker-indicator {
24
- filter: invert(1);
25
- }
26
- </style>
27
-
28
- <script>
29
- // Set default values for datetime inputs when empty
30
- document.addEventListener('DOMContentLoaded', function() {
31
- const startInput = document.getElementById('creationFilterStart');
32
- const endInput = document.getElementById('creationFilterEnd');
33
-
34
- // When applying filter with empty start date, default to beginning of current month
35
- if (startInput) {
36
- startInput.addEventListener('click', function() {
37
- if (!this.value) {
38
- const now = new Date();
39
- const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
40
- const formattedDate = firstDay.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:MM
41
- this.value = formattedDate;
42
- }
43
- });
44
- }
45
-
46
- // When applying filter with empty end date, default to current datetime
47
- if (endInput) {
48
- endInput.addEventListener('click', function() {
49
- if (!this.value) {
50
- const now = new Date();
51
- const formattedDate = now.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:MM
52
- this.value = formattedDate;
53
- }
54
- });
55
- }
56
- });
57
- </script>
58
6
  </div>
59
7
 
60
8
  <div class="dbviewer-sidebar-content">
@@ -115,194 +63,3 @@
115
63
  <i class="bi bi-gem me-1"></i>
116
64
  DBViewer v<%= Dbviewer::VERSION %>
117
65
  </div>
118
-
119
- <script>
120
- document.addEventListener('DOMContentLoaded', function() {
121
- const searchInput = document.getElementById('tableSearch');
122
- const sidebarContent = document.querySelector('.dbviewer-sidebar-content');
123
-
124
- // Storage keys for persistence
125
- const STORAGE_KEYS = {
126
- searchFilter: 'dbviewer_sidebar_search_filter',
127
- scrollPosition: 'dbviewer_sidebar_scroll_position'
128
- };
129
-
130
- if (searchInput) {
131
- // Debounce function to limit how often the filter runs
132
- function debounce(func, wait) {
133
- let timeout;
134
- return function() {
135
- const context = this;
136
- const args = arguments;
137
- clearTimeout(timeout);
138
- timeout = setTimeout(function() {
139
- func.apply(context, args);
140
- }, wait);
141
- };
142
- }
143
-
144
- // Filter function
145
- const filterTables = debounce(function() {
146
- const query = searchInput.value.toLowerCase();
147
- const tableItems = document.querySelectorAll('#tablesList .list-group-item-action');
148
- let visibleCount = 0;
149
-
150
- // Save the current search filter to localStorage
151
- localStorage.setItem(STORAGE_KEYS.searchFilter, searchInput.value);
152
-
153
- tableItems.forEach(function(item) {
154
- // Get the table name from the title attribute for more accurate matching
155
- const tableName = (item.getAttribute('title') || item.textContent).trim().toLowerCase();
156
-
157
- // Also get the displayed text content for a broader match
158
- const displayedText = item.textContent.trim().toLowerCase();
159
-
160
- if (tableName.includes(query) || displayedText.includes(query)) {
161
- item.classList.remove('d-none');
162
- visibleCount++;
163
- } else {
164
- item.classList.add('d-none');
165
- }
166
- });
167
-
168
- // Update the tables count in the sidebar
169
- const tableCountElement = document.getElementById('table-count');
170
- if (tableCountElement) {
171
- tableCountElement.textContent = visibleCount;
172
- }
173
-
174
- // Show/hide no results message
175
- let noResultsEl = document.getElementById('dbviewer-no-filter-results');
176
- if (visibleCount === 0 && query !== '') {
177
- if (!noResultsEl) {
178
- noResultsEl = document.createElement('div');
179
- noResultsEl.id = 'dbviewer-no-filter-results';
180
- noResultsEl.className = 'list-group-item text-muted text-center py-3';
181
- noResultsEl.innerHTML = '<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
182
- document.getElementById('tablesList').appendChild(noResultsEl);
183
- }
184
- noResultsEl.querySelector('.fw-bold').textContent = query;
185
- noResultsEl.style.display = 'block';
186
- } else if (noResultsEl) {
187
- noResultsEl.style.display = 'none';
188
- }
189
- }, 150); // Debounce for 150ms
190
-
191
- // Set up clear button first
192
- const clearButton = document.createElement('button');
193
- clearButton.type = 'button';
194
- clearButton.className = 'btn btn-sm btn-link position-absolute';
195
- clearButton.style.right = '15px';
196
- clearButton.style.top = '50%';
197
- clearButton.style.transform = 'translateY(-50%)';
198
- clearButton.style.display = 'none';
199
- clearButton.style.color = '#6c757d';
200
- clearButton.style.fontSize = '0.85rem';
201
- clearButton.style.padding = '0.25rem';
202
- clearButton.style.width = '1.5rem';
203
- clearButton.style.textAlign = 'center';
204
- clearButton.innerHTML = '<i class="bi bi-x-circle"></i>';
205
- clearButton.addEventListener('click', function() {
206
- searchInput.value = '';
207
- // Clear the saved filter from localStorage
208
- localStorage.removeItem(STORAGE_KEYS.searchFilter);
209
- // Call filter directly without debouncing for immediate feedback
210
- filterTables();
211
- this.style.display = 'none';
212
- });
213
-
214
- const filterContainer = document.querySelector('.dbviewer-table-filter-container');
215
- if (filterContainer) {
216
- filterContainer.style.position = 'relative';
217
- filterContainer.appendChild(clearButton);
218
-
219
- searchInput.addEventListener('input', function() {
220
- clearButton.style.display = this.value ? 'block' : 'none';
221
- });
222
- }
223
-
224
- // Restore saved search filter on page load and apply it immediately
225
- const savedFilter = localStorage.getItem(STORAGE_KEYS.searchFilter);
226
- if (savedFilter) {
227
- searchInput.value = savedFilter;
228
- // Show clear button immediately when filter is restored
229
- clearButton.style.display = 'block';
230
- // Apply filter immediately without debouncing to prevent blinking
231
- const query = savedFilter.toLowerCase();
232
- const tableItems = document.querySelectorAll('#tablesList .list-group-item-action');
233
- let visibleCount = 0;
234
-
235
- tableItems.forEach(function(item) {
236
- const tableName = (item.getAttribute('title') || item.textContent).trim().toLowerCase();
237
- const displayedText = item.textContent.trim().toLowerCase();
238
-
239
- if (tableName.includes(query) || displayedText.includes(query)) {
240
- item.classList.remove('d-none');
241
- visibleCount++;
242
- } else {
243
- item.classList.add('d-none');
244
- }
245
- });
246
-
247
- // Update the tables count immediately
248
- const tableCountElement = document.getElementById('table-count');
249
- if (tableCountElement) {
250
- tableCountElement.textContent = visibleCount;
251
- }
252
-
253
- // Handle no results message immediately
254
- let noResultsEl = document.getElementById('dbviewer-no-filter-results');
255
- if (visibleCount === 0 && query !== '') {
256
- if (!noResultsEl) {
257
- noResultsEl = document.createElement('div');
258
- noResultsEl.id = 'dbviewer-no-filter-results';
259
- noResultsEl.className = 'list-group-item text-muted text-center py-3';
260
- noResultsEl.innerHTML = '<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
261
- document.getElementById('tablesList').appendChild(noResultsEl);
262
- }
263
- noResultsEl.querySelector('.fw-bold').textContent = query;
264
- noResultsEl.style.display = 'block';
265
- } else if (noResultsEl) {
266
- noResultsEl.style.display = 'none';
267
- }
268
- }
269
-
270
- // Restore saved scroll position on page load
271
- if (sidebarContent) {
272
- const savedScrollPosition = localStorage.getItem(STORAGE_KEYS.scrollPosition);
273
- if (savedScrollPosition) {
274
- // Use requestAnimationFrame to ensure DOM is fully rendered
275
- requestAnimationFrame(() => {
276
- sidebarContent.scrollTop = parseInt(savedScrollPosition, 10);
277
- });
278
- }
279
-
280
- // Save scroll position on scroll
281
- const saveScrollPosition = debounce(function() {
282
- localStorage.setItem(STORAGE_KEYS.scrollPosition, sidebarContent.scrollTop);
283
- }, 100);
284
-
285
- sidebarContent.addEventListener('scroll', saveScrollPosition);
286
- }
287
-
288
- // Set up event listeners for the search input
289
- searchInput.addEventListener('input', filterTables);
290
- searchInput.addEventListener('keyup', function(e) {
291
- filterTables();
292
-
293
- // Add keyboard navigation for the filtered list
294
- if (e.key === 'Enter' || e.key === 'ArrowDown') {
295
- e.preventDefault();
296
- // Focus the first visible table item (not having d-none class)
297
- const firstVisibleItem = document.querySelector('#tablesList .list-group-item-action:not(.d-none)');
298
- if (firstVisibleItem) {
299
- firstVisibleItem.focus();
300
- // Make sure the item is visible in the scrollable area
301
- firstVisibleItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
302
- }
303
- }
304
- });
305
- searchInput.addEventListener('search', filterTables); // For clearing via the "x" in some browsers
306
- }
307
- });
308
- </script>
@@ -1,3 +1,3 @@
1
1
  module Dbviewer
2
- VERSION = "0.7.1"
2
+ VERSION = "0.7.2"
3
3
  end
data/lib/dbviewer.rb CHANGED
@@ -21,6 +21,8 @@ require "dbviewer/database/metadata_manager"
21
21
  require "dbviewer/datatable/query_operations"
22
22
  require "dbviewer/datatable/query_params"
23
23
 
24
+ require "propshaft"
25
+
24
26
  module Dbviewer
25
27
  # Main module for the database viewer
26
28
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbviewer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wailan Tirajoh
@@ -67,6 +67,7 @@ files:
67
67
  - app/assets/javascripts/dbviewer/home.js
68
68
  - app/assets/javascripts/dbviewer/layout.js
69
69
  - app/assets/javascripts/dbviewer/query.js
70
+ - app/assets/javascripts/dbviewer/sidebar.js
70
71
  - app/assets/javascripts/dbviewer/table.js
71
72
  - app/assets/stylesheets/dbviewer/application.css
72
73
  - app/assets/stylesheets/dbviewer/entity_relationship_diagram.css