dbviewer 0.5.6 → 0.5.8

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.
@@ -23,6 +23,8 @@
23
23
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
24
24
  <!-- Bootstrap Icons -->
25
25
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
26
+ <!-- Flatpickr CSS for Date Range Picker -->
27
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
26
28
  <!-- Google Fonts -->
27
29
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
28
30
  <!-- Chart.js for Data Visualization -->
@@ -1407,6 +1409,8 @@
1407
1409
 
1408
1410
  <!-- Bootstrap JS -->
1409
1411
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
1412
+ <!-- Flatpickr JS for Date Range Picker -->
1413
+ <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
1410
1414
 
1411
1415
  <!-- Dark Mode Toggle Script -->
1412
1416
  <script>
@@ -4,95 +4,7 @@
4
4
  id="tableSearch" placeholder="Filter tables..." aria-label="Filter tables">
5
5
  </div>
6
6
 
7
- <div class="p-2">
8
- <div class="accordion accordion-flush" id="creationFilterAccordion">
9
- <div class="accordion-item border-0">
10
- <h2 class="accordion-header" id="creationFilterHeading">
11
- <button class="accordion-button p-2 collapsed" type="button" data-bs-toggle="collapse"
12
- data-bs-target="#creationFilterCollapse" aria-expanded="false"
13
- aria-controls="creationFilterCollapse">
14
- <i class="bi bi-calendar-range me-2"></i> Creation Filter
15
- <% if @creation_filter_start.present? || @creation_filter_end.present? %>
16
- <% if @table_name.present? && has_timestamp_column?(@table_name) %>
17
- <span class="badge bg-success ms-2">Active</span>
18
- <% else %>
19
- <span class="badge bg-secondary ms-2">Set</span>
20
- <% end %>
21
- <% end %>
22
- </button>
23
- </h2>
24
- <div id="creationFilterCollapse" class="accordion-collapse collapse" aria-labelledby="creationFilterHeading">
25
- <div class="accordion-body p-2">
26
- <form id="creationFilterForm" action="<%= request.path %>" method="get" class="mb-0">
27
- <!-- Preserve existing query parameters -->
28
- <input type="hidden" name="page" value="<%= @current_page %>">
29
- <input type="hidden" name="per_page" value="<%= @per_page %>">
30
- <input type="hidden" name="order_by" value="<%= @order_by %>">
31
- <input type="hidden" name="order_direction" value="<%= @order_direction %>">
32
-
33
- <!-- Datetime range fields -->
34
- <div class="mb-2">
35
- <label for="creationFilterStart" class="form-label mb-1 small">Start Date/Time</label>
36
- <input type="datetime-local" id="creationFilterStart" name="creation_filter_start"
37
- class="form-control form-control-sm" value="<%= @creation_filter_start %>">
38
- </div>
39
- <div class="mb-2">
40
- <label for="creationFilterEnd" class="form-label mb-1 small">End Date/Time</label>
41
- <input type="datetime-local" id="creationFilterEnd" name="creation_filter_end"
42
- class="form-control form-control-sm" value="<%= @creation_filter_end %>">
43
- </div>
44
-
45
- <div class="d-flex justify-content-between">
46
- <button type="submit" class="btn btn-primary btn-sm">Apply</button>
47
- <% if @creation_filter_start.present? || @creation_filter_end.present? %>
48
- <%
49
- # Preserve other query params when clearing creation filter
50
- clear_params = {
51
- clear_creation_filter: true,
52
- page: @current_page,
53
- per_page: @per_page,
54
- order_by: @order_by,
55
- order_direction: @order_direction
56
- }
57
- %>
58
- <a href="<%= request.path %>?<%= clear_params.to_query %>" class="btn btn-outline-secondary btn-sm">Clear</a>
59
- <% end %>
60
- </div>
61
- <div class="mt-2 small">
62
- <% if @table_name.present? && has_timestamp_column?(@table_name) && (@creation_filter_start.present? || @creation_filter_end.present?) %>
63
- <div class="text-success">
64
- <i class="bi bi-check-circle-fill"></i>
65
- Filter active on this table.
66
- <% if @current_page == 1 && @records && @records.rows && @records.rows.empty? %>
67
- <div class="alert alert-warning p-1 mt-2 small">
68
- <i class="bi bi-exclamation-triangle-fill"></i>
69
- No records match the filter criteria.
70
- </div>
71
- <% end %>
72
- </div>
73
- <% elsif @table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
74
- <div class="text-warning">
75
- <i class="bi bi-exclamation-circle-fill"></i>
76
- This table has no <code>created_at</code> column.
77
- </div>
78
- <% elsif !@table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
79
- <div class="text-info">
80
- <i class="bi bi-info-circle-fill"></i>
81
- Filter will be applied on tables with <code>created_at</code> column.
82
- </div>
83
- <% else %>
84
- <div class="text-muted">
85
- <i class="bi bi-info-circle-fill"></i>
86
- Filters apply to tables with a <code>created_at</code> column.
87
- </div>
88
- <% end %>
89
- </div>
90
- </form>
91
- </div>
92
- </div>
93
- </div>
94
- </div>
95
- </div>
7
+
96
8
 
97
9
  <!-- Add custom styling for datetime inputs -->
98
10
  <style>
@@ -207,6 +119,13 @@
207
119
  <script>
208
120
  document.addEventListener('DOMContentLoaded', function() {
209
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
+ };
210
129
 
211
130
  if (searchInput) {
212
131
  // Debounce function to limit how often the filter runs
@@ -228,6 +147,9 @@
228
147
  const tableItems = document.querySelectorAll('#tablesList .list-group-item-action');
229
148
  let visibleCount = 0;
230
149
 
150
+ // Save the current search filter to localStorage
151
+ localStorage.setItem(STORAGE_KEYS.searchFilter, searchInput.value);
152
+
231
153
  tableItems.forEach(function(item) {
232
154
  // Get the table name from the title attribute for more accurate matching
233
155
  const tableName = (item.getAttribute('title') || item.textContent).trim().toLowerCase();
@@ -266,26 +188,7 @@
266
188
  }
267
189
  }, 150); // Debounce for 150ms
268
190
 
269
- // Set up event listeners for the search input
270
- searchInput.addEventListener('input', filterTables);
271
- searchInput.addEventListener('keyup', function(e) {
272
- filterTables();
273
-
274
- // Add keyboard navigation for the filtered list
275
- if (e.key === 'Enter' || e.key === 'ArrowDown') {
276
- e.preventDefault();
277
- // Focus the first visible table item (not having d-none class)
278
- const firstVisibleItem = document.querySelector('#tablesList .list-group-item-action:not(.d-none)');
279
- if (firstVisibleItem) {
280
- firstVisibleItem.focus();
281
- // Make sure the item is visible in the scrollable area
282
- firstVisibleItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
283
- }
284
- }
285
- });
286
- searchInput.addEventListener('search', filterTables); // For clearing via the "x" in some browsers
287
-
288
- // Clear the search box when clicking the X button
191
+ // Set up clear button first
289
192
  const clearButton = document.createElement('button');
290
193
  clearButton.type = 'button';
291
194
  clearButton.className = 'btn btn-sm btn-link position-absolute';
@@ -301,6 +204,8 @@
301
204
  clearButton.innerHTML = '<i class="bi bi-x-circle"></i>';
302
205
  clearButton.addEventListener('click', function() {
303
206
  searchInput.value = '';
207
+ // Clear the saved filter from localStorage
208
+ localStorage.removeItem(STORAGE_KEYS.searchFilter);
304
209
  // Call filter directly without debouncing for immediate feedback
305
210
  filterTables();
306
211
  this.style.display = 'none';
@@ -314,13 +219,90 @@
314
219
  searchInput.addEventListener('input', function() {
315
220
  clearButton.style.display = this.value ? 'block' : 'none';
316
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;
317
234
 
318
- // Apply filter initially in case there's a value already (e.g., from browser autofill)
319
- if (searchInput.value) {
320
- filterTables();
321
- clearButton.style.display = 'block';
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';
322
267
  }
323
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
324
306
  }
325
307
  });
326
308
  </script>
data/config/routes.rb CHANGED
@@ -25,6 +25,9 @@ Dbviewer::Engine.routes.draw do
25
25
  get "records"
26
26
  get "relationships_count"
27
27
  end
28
+ member do
29
+ get "relationship_counts"
30
+ end
28
31
  end
29
32
 
30
33
  resources :entity_relationship_diagrams, only: [] do
@@ -1,3 +1,3 @@
1
1
  module Dbviewer
2
- VERSION = "0.5.6"
2
+ VERSION = "0.5.8"
3
3
  end
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.5.6
4
+ version: 0.5.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wailan Tirajoh