solidstats 1.0.0 → 2.0.0
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/CHANGELOG.md +85 -0
- data/README.md +35 -0
- data/app/assets/javascripts/solidstats/application.js +257 -0
- data/app/assets/javascripts/solidstats/dashboard.js +225 -0
- data/app/assets/javascripts/solidstats/gem_metadata.js +554 -0
- data/app/assets/stylesheets/solidstats/application.css +6 -1
- data/app/assets/stylesheets/solidstats/components/action_button.css +99 -0
- data/app/assets/stylesheets/solidstats/components/dashboard.css +151 -0
- data/app/assets/stylesheets/solidstats/components/dashboard_header.css +93 -0
- data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +97 -0
- data/app/assets/stylesheets/solidstats/components/gem_metadata.css +1403 -0
- data/app/assets/stylesheets/solidstats/components/navigation.css +80 -0
- data/app/assets/stylesheets/solidstats/components/quick_navigation.css +54 -0
- data/app/assets/stylesheets/solidstats/components/security.css +332 -0
- data/app/assets/stylesheets/solidstats/components/status_badge.css +58 -0
- data/app/assets/stylesheets/solidstats/components/summary_card.css +66 -0
- data/app/assets/stylesheets/solidstats/components/tab_navigation.css +95 -0
- data/app/components/solidstats/base_component.rb +88 -0
- data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
- data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
- data/app/components/solidstats/code_quality/section_component.html.erb +45 -0
- data/app/components/solidstats/code_quality/section_component.rb +34 -0
- data/app/components/solidstats/dashboard_header_component.html.erb +39 -0
- data/app/components/solidstats/dashboard_header_component.rb +33 -0
- data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +6 -0
- data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +6 -0
- data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +6 -0
- data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +6 -0
- data/app/components/solidstats/previews/action_button_component_preview.rb +64 -0
- data/app/components/solidstats/previews/navigation_component_preview.rb +74 -0
- data/app/components/solidstats/previews/stats_overview_component_preview.rb +100 -0
- data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +6 -0
- data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +6 -0
- data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +6 -0
- data/app/components/solidstats/previews/status_badge_component_preview.rb +49 -0
- data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +9 -0
- data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +9 -0
- data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +6 -0
- data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +6 -0
- data/app/components/solidstats/previews/summary_card_component_preview.rb +67 -0
- data/app/components/solidstats/quick_navigation_component.html.erb +8 -0
- data/app/components/solidstats/quick_navigation_component.rb +21 -0
- data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +44 -0
- data/app/components/solidstats/security/gem_impact_analysis_component.rb +45 -0
- data/app/components/solidstats/security/overview_component.html.erb +21 -0
- data/app/components/solidstats/security/overview_component.rb +104 -0
- data/app/components/solidstats/security/section_component.html.erb +26 -0
- data/app/components/solidstats/security/section_component.rb +52 -0
- data/app/components/solidstats/security/timeline_component.html.erb +39 -0
- data/app/components/solidstats/security/timeline_component.rb +43 -0
- data/app/components/solidstats/tasks_section_component.html.erb +17 -0
- data/app/components/solidstats/tasks_section_component.rb +22 -0
- data/app/components/solidstats/ui/action_button_component.html.erb +6 -0
- data/app/components/solidstats/ui/action_button_component.rb +71 -0
- data/app/components/solidstats/ui/dashboard_layout_component.html.erb +19 -0
- data/app/components/solidstats/ui/dashboard_layout_component.rb +85 -0
- data/app/components/solidstats/ui/navigation_component.html.erb +34 -0
- data/app/components/solidstats/ui/navigation_component.rb +72 -0
- data/app/components/solidstats/ui/stats_overview_component.html.erb +14 -0
- data/app/components/solidstats/ui/stats_overview_component.rb +78 -0
- data/app/components/solidstats/ui/status_badge_component.html.erb +6 -0
- data/app/components/solidstats/ui/status_badge_component.rb +42 -0
- data/app/components/solidstats/ui/summary_card_component.html.erb +12 -0
- data/app/components/solidstats/ui/summary_card_component.rb +63 -0
- data/app/components/solidstats/ui/tab_navigation_component.html.erb +22 -0
- data/app/components/solidstats/ui/tab_navigation_component.rb +79 -0
- data/app/controllers/solidstats/dashboard_controller.rb +22 -0
- data/app/controllers/solidstats/gem_metadata_controller.rb +12 -0
- data/app/helpers/solidstats/application_helper.rb +42 -0
- data/app/services/solidstats/gem_metadata/fetcher_service.rb +136 -0
- data/app/services/solidstats/log_size_monitor_service.rb +94 -0
- data/app/views/layouts/solidstats/application.html.erb +2 -1
- data/app/views/solidstats/dashboard/_log_monitor.html.erb +759 -0
- data/app/views/solidstats/dashboard/index.html.erb +67 -1323
- data/app/views/solidstats/gem_metadata/_panel.html.erb +419 -0
- data/config/routes.rb +7 -0
- data/lib/generators/solidstats/feature/feature_generator.rb +170 -0
- data/lib/generators/solidstats/feature/templates/component.html.erb +84 -0
- data/lib/generators/solidstats/feature/templates/component.rb.erb +103 -0
- data/lib/generators/solidstats/feature/templates/component.scss +243 -0
- data/lib/generators/solidstats/feature/templates/component_test.rb.erb +183 -0
- data/lib/generators/solidstats/feature/templates/controller.rb.erb +44 -0
- data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +111 -0
- data/lib/generators/solidstats/feature/templates/detail_view.html.erb +755 -0
- data/lib/generators/solidstats/feature/templates/preview.rb.erb +107 -0
- data/lib/generators/solidstats/feature/templates/service.rb.erb +132 -0
- data/lib/generators/solidstats/feature/templates/service_test.rb.erb +109 -0
- data/lib/generators/solidstats/install_generator.rb +109 -0
- data/lib/generators/solidstats/templates/initializer.rb +112 -0
- data/lib/solidstats/asset_compatibility.rb +238 -0
- data/lib/solidstats/asset_manifest.rb +205 -0
- data/lib/solidstats/engine.rb +114 -9
- data/lib/solidstats/version.rb +1 -1
- data/lib/solidstats.rb +299 -2
- data/lib/tasks/solidstats_install.rake +122 -2
- metadata +99 -2
@@ -0,0 +1,554 @@
|
|
1
|
+
// Enhanced Gem Metadata Interactive Features
|
2
|
+
document.addEventListener('DOMContentLoaded', function() {
|
3
|
+
// Enhanced search and filter functionality
|
4
|
+
const searchInput = document.getElementById('gem-search');
|
5
|
+
const clearSearchBtn = document.getElementById('clear-search');
|
6
|
+
const statusFilter = document.getElementById('status-filter');
|
7
|
+
const sortFilter = document.getElementById('sort-filter');
|
8
|
+
const resetFiltersBtn = document.getElementById('reset-filters');
|
9
|
+
const gemsGrid = document.getElementById('gems-grid');
|
10
|
+
const gemsTableContainer = document.getElementById('gems-table-container');
|
11
|
+
const gemsTable = document.getElementById('gems-table');
|
12
|
+
const gemsTableBody = document.getElementById('gems-table-body');
|
13
|
+
const resultsInfo = document.getElementById('results-info');
|
14
|
+
const resultsCount = document.getElementById('results-count');
|
15
|
+
|
16
|
+
// View toggle elements
|
17
|
+
const gridViewBtn = document.getElementById('grid-view-btn');
|
18
|
+
const tableViewBtn = document.getElementById('table-view-btn');
|
19
|
+
const exportGroup = document.getElementById('export-group');
|
20
|
+
const exportTableBtn = document.getElementById('export-table-btn');
|
21
|
+
|
22
|
+
// Export functionality
|
23
|
+
function exportTableToCSV() {
|
24
|
+
const csvData = [];
|
25
|
+
|
26
|
+
// Header row
|
27
|
+
csvData.push(['Gem Name', 'Status', 'Current Version', 'Latest Version', 'Released', 'Description', 'Dependencies']);
|
28
|
+
|
29
|
+
// Data rows from filtered results
|
30
|
+
filteredTableRows.forEach(row => {
|
31
|
+
const cells = row.element.querySelectorAll('td');
|
32
|
+
const rowData = [
|
33
|
+
cells[0]?.textContent?.trim() || '',
|
34
|
+
cells[1]?.textContent?.trim() || '',
|
35
|
+
cells[2]?.textContent?.trim() || '',
|
36
|
+
cells[3]?.textContent?.trim() || '',
|
37
|
+
cells[4]?.textContent?.trim() || '',
|
38
|
+
cells[5]?.textContent?.trim() || '',
|
39
|
+
cells[6]?.textContent?.trim() || ''
|
40
|
+
];
|
41
|
+
csvData.push(rowData);
|
42
|
+
});
|
43
|
+
|
44
|
+
// Convert to CSV format
|
45
|
+
const csvContent = csvData.map(row =>
|
46
|
+
row.map(field => `"${field.replace(/"/g, '""')}"`).join(',')
|
47
|
+
).join('\n');
|
48
|
+
|
49
|
+
// Create and download file
|
50
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
51
|
+
const link = document.createElement('a');
|
52
|
+
const url = URL.createObjectURL(blob);
|
53
|
+
link.setAttribute('href', url);
|
54
|
+
link.setAttribute('download', `gem-metadata-${new Date().toISOString().split('T')[0]}.csv`);
|
55
|
+
link.style.visibility = 'hidden';
|
56
|
+
document.body.appendChild(link);
|
57
|
+
link.click();
|
58
|
+
document.body.removeChild(link);
|
59
|
+
|
60
|
+
showToast('Table data exported successfully!', 'success');
|
61
|
+
}
|
62
|
+
|
63
|
+
// Loading state management
|
64
|
+
const loadingOverlay = document.getElementById('loading-overlay');
|
65
|
+
|
66
|
+
function showLoading() {
|
67
|
+
if (loadingOverlay) {
|
68
|
+
loadingOverlay.style.display = 'flex';
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
function hideLoading() {
|
73
|
+
if (loadingOverlay) {
|
74
|
+
loadingOverlay.style.display = 'none';
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
// Performance optimizations
|
79
|
+
let searchTimeout;
|
80
|
+
const SEARCH_DELAY = 300; // Debounce search for better performance
|
81
|
+
|
82
|
+
// Optimized search with debouncing
|
83
|
+
function debouncedSearch() {
|
84
|
+
clearTimeout(searchTimeout);
|
85
|
+
searchTimeout = setTimeout(() => {
|
86
|
+
performSearch();
|
87
|
+
}, SEARCH_DELAY);
|
88
|
+
}
|
89
|
+
|
90
|
+
let allGems = [];
|
91
|
+
let allTableRows = [];
|
92
|
+
let filteredGems = [];
|
93
|
+
let filteredTableRows = [];
|
94
|
+
let currentView = 'grid'; // Default to grid view
|
95
|
+
|
96
|
+
// Initialize gems data
|
97
|
+
function initializeGems() {
|
98
|
+
// Initialize grid cards
|
99
|
+
allGems = Array.from(document.querySelectorAll('.gem-card-full')).map(card => {
|
100
|
+
return {
|
101
|
+
element: card,
|
102
|
+
name: card.dataset.gemName,
|
103
|
+
status: card.dataset.status,
|
104
|
+
releaseDate: parseInt(card.dataset.releaseDate) || 0
|
105
|
+
};
|
106
|
+
});
|
107
|
+
|
108
|
+
// Initialize table rows
|
109
|
+
allTableRows = Array.from(document.querySelectorAll('.gem-table-row')).map(row => {
|
110
|
+
return {
|
111
|
+
element: row,
|
112
|
+
name: row.dataset.gemName,
|
113
|
+
status: row.dataset.status,
|
114
|
+
releaseDate: parseInt(row.dataset.releaseDate) || 0
|
115
|
+
};
|
116
|
+
});
|
117
|
+
|
118
|
+
filteredGems = [...allGems];
|
119
|
+
filteredTableRows = [...allTableRows];
|
120
|
+
updateResults();
|
121
|
+
}
|
122
|
+
|
123
|
+
// View toggle functionality
|
124
|
+
function switchView(view) {
|
125
|
+
currentView = view;
|
126
|
+
|
127
|
+
if (view === 'grid') {
|
128
|
+
// Show grid, hide table
|
129
|
+
gemsGrid.style.display = 'grid';
|
130
|
+
gemsTableContainer.style.display = 'none';
|
131
|
+
gridViewBtn.classList.add('active');
|
132
|
+
tableViewBtn.classList.remove('active');
|
133
|
+
if (exportGroup) exportGroup.style.display = 'none';
|
134
|
+
|
135
|
+
// Save preference
|
136
|
+
localStorage.setItem('gems-view-preference', 'grid');
|
137
|
+
} else {
|
138
|
+
// Hide grid, show table
|
139
|
+
gemsGrid.style.display = 'none';
|
140
|
+
gemsTableContainer.style.display = 'block';
|
141
|
+
tableViewBtn.classList.add('active');
|
142
|
+
gridViewBtn.classList.remove('active');
|
143
|
+
if (exportGroup) exportGroup.style.display = 'flex';
|
144
|
+
|
145
|
+
// Save preference
|
146
|
+
localStorage.setItem('gems-view-preference', 'table');
|
147
|
+
}
|
148
|
+
|
149
|
+
// Update results for current view
|
150
|
+
updateResults();
|
151
|
+
}
|
152
|
+
|
153
|
+
// Load saved view preference
|
154
|
+
function loadViewPreference() {
|
155
|
+
const savedView = localStorage.getItem('gems-view-preference') || 'grid';
|
156
|
+
switchView(savedView);
|
157
|
+
}
|
158
|
+
|
159
|
+
// Search functionality
|
160
|
+
function performSearch() {
|
161
|
+
const searchTerm = searchInput.value.toLowerCase().trim();
|
162
|
+
const statusValue = statusFilter.value;
|
163
|
+
|
164
|
+
// Filter grid cards
|
165
|
+
filteredGems = allGems.filter(gem => {
|
166
|
+
const matchesSearch = !searchTerm || gem.name.includes(searchTerm);
|
167
|
+
const matchesStatus = !statusValue || gem.status === statusValue;
|
168
|
+
return matchesSearch && matchesStatus;
|
169
|
+
});
|
170
|
+
|
171
|
+
// Filter table rows
|
172
|
+
filteredTableRows = allTableRows.filter(row => {
|
173
|
+
const matchesSearch = !searchTerm || row.name.includes(searchTerm);
|
174
|
+
const matchesStatus = !statusValue || row.status === statusValue;
|
175
|
+
return matchesSearch && matchesStatus;
|
176
|
+
});
|
177
|
+
|
178
|
+
applySorting();
|
179
|
+
updateResults();
|
180
|
+
updateClearButton();
|
181
|
+
}
|
182
|
+
|
183
|
+
// Sorting functionality
|
184
|
+
function applySorting() {
|
185
|
+
const sortValue = sortFilter.value;
|
186
|
+
|
187
|
+
const sortFunction = (a, b) => {
|
188
|
+
switch (sortValue) {
|
189
|
+
case 'name-asc':
|
190
|
+
return a.name.localeCompare(b.name);
|
191
|
+
case 'name-desc':
|
192
|
+
return b.name.localeCompare(a.name);
|
193
|
+
case 'status-desc':
|
194
|
+
const statusOrder = { 'outdated': 0, 'unavailable': 1, 'up-to-date': 2 };
|
195
|
+
return (statusOrder[a.status] || 3) - (statusOrder[b.status] || 3);
|
196
|
+
case 'release-desc':
|
197
|
+
return b.releaseDate - a.releaseDate;
|
198
|
+
case 'release-asc':
|
199
|
+
return a.releaseDate - b.releaseDate;
|
200
|
+
default:
|
201
|
+
return 0;
|
202
|
+
}
|
203
|
+
};
|
204
|
+
|
205
|
+
filteredGems.sort(sortFunction);
|
206
|
+
filteredTableRows.sort(sortFunction);
|
207
|
+
}
|
208
|
+
|
209
|
+
// Update display
|
210
|
+
function updateResults() {
|
211
|
+
if (currentView === 'grid') {
|
212
|
+
updateGridView();
|
213
|
+
} else {
|
214
|
+
updateTableView();
|
215
|
+
}
|
216
|
+
|
217
|
+
// Update results count
|
218
|
+
const count = currentView === 'grid' ? filteredGems.length : filteredTableRows.length;
|
219
|
+
if (resultsCount) {
|
220
|
+
resultsCount.textContent = count;
|
221
|
+
}
|
222
|
+
|
223
|
+
// Show/hide empty state
|
224
|
+
const isEmpty = count === 0;
|
225
|
+
if (isEmpty && !document.querySelector('.filter-empty-state')) {
|
226
|
+
showFilterEmptyState();
|
227
|
+
} else if (!isEmpty) {
|
228
|
+
hideFilterEmptyState();
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
// Update grid view
|
233
|
+
function updateGridView() {
|
234
|
+
// Hide all gems first
|
235
|
+
allGems.forEach(gem => {
|
236
|
+
gem.element.style.display = 'none';
|
237
|
+
gem.element.style.order = '';
|
238
|
+
});
|
239
|
+
|
240
|
+
// Show and order filtered gems
|
241
|
+
filteredGems.forEach((gem, index) => {
|
242
|
+
gem.element.style.display = 'block';
|
243
|
+
gem.element.style.order = index;
|
244
|
+
|
245
|
+
// Reset animation delay for visible items
|
246
|
+
gem.element.style.animationDelay = `${index * 0.05}s`;
|
247
|
+
});
|
248
|
+
}
|
249
|
+
|
250
|
+
// Update table view
|
251
|
+
function updateTableView() {
|
252
|
+
// Hide all table rows first
|
253
|
+
allTableRows.forEach(row => {
|
254
|
+
row.element.style.display = 'none';
|
255
|
+
});
|
256
|
+
|
257
|
+
// Show filtered rows
|
258
|
+
filteredTableRows.forEach((row, index) => {
|
259
|
+
row.element.style.display = '';
|
260
|
+
});
|
261
|
+
}
|
262
|
+
|
263
|
+
// Show empty state for filters
|
264
|
+
function showFilterEmptyState() {
|
265
|
+
const emptyState = document.createElement('div');
|
266
|
+
emptyState.className = 'filter-empty-state';
|
267
|
+
emptyState.innerHTML = `
|
268
|
+
<div class="empty-icon">🔍</div>
|
269
|
+
<div class="empty-title">No matching gems found</div>
|
270
|
+
<div class="empty-description">
|
271
|
+
Try adjusting your search terms or filters to find what you're looking for.
|
272
|
+
</div>
|
273
|
+
<button class="action-btn refresh-btn" onclick="resetAllFilters()">
|
274
|
+
<i class="fas fa-undo"></i>
|
275
|
+
Clear Filters
|
276
|
+
</button>
|
277
|
+
`;
|
278
|
+
|
279
|
+
if (currentView === 'grid') {
|
280
|
+
emptyState.style.cssText = `
|
281
|
+
grid-column: 1 / -1;
|
282
|
+
`;
|
283
|
+
gemsGrid.appendChild(emptyState);
|
284
|
+
} else {
|
285
|
+
// Add fallback class for better browser support
|
286
|
+
gemsTableContainer.classList.add('has-empty-state');
|
287
|
+
|
288
|
+
// For table view, create a properly centered empty state
|
289
|
+
const emptyRow = document.createElement('tr');
|
290
|
+
const emptyCell = document.createElement('td');
|
291
|
+
emptyCell.colSpan = 7; // Span all columns
|
292
|
+
emptyCell.className = 'empty-state-cell';
|
293
|
+
emptyCell.style.height = '400px'; // Set fixed height for better centering
|
294
|
+
emptyCell.appendChild(emptyState);
|
295
|
+
emptyRow.appendChild(emptyCell);
|
296
|
+
emptyRow.className = 'filter-empty-state-row';
|
297
|
+
gemsTableBody.appendChild(emptyRow);
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
// Hide filter empty state
|
302
|
+
function hideFilterEmptyState() {
|
303
|
+
const emptyState = document.querySelector('.filter-empty-state');
|
304
|
+
const emptyRow = document.querySelector('.filter-empty-state-row');
|
305
|
+
|
306
|
+
if (emptyState) {
|
307
|
+
emptyState.remove();
|
308
|
+
}
|
309
|
+
|
310
|
+
if (emptyRow) {
|
311
|
+
emptyRow.remove();
|
312
|
+
}
|
313
|
+
|
314
|
+
// Remove fallback class
|
315
|
+
if (gemsTableContainer) {
|
316
|
+
gemsTableContainer.classList.remove('has-empty-state');
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
// Update clear search button visibility
|
321
|
+
function updateClearButton() {
|
322
|
+
const hasValue = searchInput.value.trim().length > 0;
|
323
|
+
clearSearchBtn.style.display = hasValue ? 'block' : 'none';
|
324
|
+
}
|
325
|
+
|
326
|
+
// Reset all filters
|
327
|
+
function resetAllFilters() {
|
328
|
+
searchInput.value = '';
|
329
|
+
statusFilter.value = '';
|
330
|
+
sortFilter.value = 'name-asc';
|
331
|
+
performSearch();
|
332
|
+
updateClearButton();
|
333
|
+
}
|
334
|
+
|
335
|
+
// Global function for empty state button
|
336
|
+
window.resetAllFilters = resetAllFilters;
|
337
|
+
|
338
|
+
// Event listeners
|
339
|
+
if (searchInput) {
|
340
|
+
searchInput.addEventListener('input', debouncedSearch);
|
341
|
+
searchInput.addEventListener('input', updateClearButton);
|
342
|
+
}
|
343
|
+
|
344
|
+
if (clearSearchBtn) {
|
345
|
+
clearSearchBtn.addEventListener('click', function() {
|
346
|
+
searchInput.value = '';
|
347
|
+
performSearch();
|
348
|
+
updateClearButton();
|
349
|
+
searchInput.focus();
|
350
|
+
});
|
351
|
+
}
|
352
|
+
|
353
|
+
if (statusFilter) {
|
354
|
+
statusFilter.addEventListener('change', performSearch);
|
355
|
+
}
|
356
|
+
|
357
|
+
if (sortFilter) {
|
358
|
+
sortFilter.addEventListener('change', function() {
|
359
|
+
applySorting();
|
360
|
+
updateResults();
|
361
|
+
});
|
362
|
+
}
|
363
|
+
|
364
|
+
if (resetFiltersBtn) {
|
365
|
+
resetFiltersBtn.addEventListener('click', resetAllFilters);
|
366
|
+
}
|
367
|
+
|
368
|
+
// View toggle event listeners
|
369
|
+
if (gridViewBtn) {
|
370
|
+
gridViewBtn.addEventListener('click', function() {
|
371
|
+
switchView('grid');
|
372
|
+
});
|
373
|
+
}
|
374
|
+
|
375
|
+
if (tableViewBtn) {
|
376
|
+
tableViewBtn.addEventListener('click', function() {
|
377
|
+
switchView('table');
|
378
|
+
});
|
379
|
+
}
|
380
|
+
|
381
|
+
// Export functionality event listener
|
382
|
+
if (exportTableBtn) {
|
383
|
+
exportTableBtn.addEventListener('click', exportTableToCSV);
|
384
|
+
}
|
385
|
+
|
386
|
+
// Table sorting functionality
|
387
|
+
if (gemsTable) {
|
388
|
+
const sortableHeaders = gemsTable.querySelectorAll('th.sortable');
|
389
|
+
sortableHeaders.forEach(header => {
|
390
|
+
header.addEventListener('click', function() {
|
391
|
+
const sortType = this.dataset.sort;
|
392
|
+
const currentSort = sortFilter.value;
|
393
|
+
|
394
|
+
// Toggle between asc/desc for the same column
|
395
|
+
if (currentSort.startsWith(sortType)) {
|
396
|
+
const isAsc = currentSort.endsWith('-asc');
|
397
|
+
sortFilter.value = `${sortType}-${isAsc ? 'desc' : 'asc'}`;
|
398
|
+
} else {
|
399
|
+
sortFilter.value = `${sortType}-asc`;
|
400
|
+
}
|
401
|
+
|
402
|
+
// Update visual indicators
|
403
|
+
sortableHeaders.forEach(h => h.classList.remove('sorted'));
|
404
|
+
this.classList.add('sorted');
|
405
|
+
|
406
|
+
applySorting();
|
407
|
+
updateResults();
|
408
|
+
});
|
409
|
+
});
|
410
|
+
}
|
411
|
+
|
412
|
+
// Refresh button functionality with loading state
|
413
|
+
const refreshBtn = document.querySelector('.refresh-btn');
|
414
|
+
if (refreshBtn) {
|
415
|
+
refreshBtn.addEventListener('click', function(e) {
|
416
|
+
this.classList.add('loading');
|
417
|
+
this.setAttribute('aria-busy', 'true');
|
418
|
+
|
419
|
+
// Reset loading state after a delay (or when page reloads)
|
420
|
+
setTimeout(() => {
|
421
|
+
this.classList.remove('loading');
|
422
|
+
this.removeAttribute('aria-busy');
|
423
|
+
}, 3000);
|
424
|
+
});
|
425
|
+
}
|
426
|
+
|
427
|
+
// Keyboard shortcuts
|
428
|
+
document.addEventListener('keydown', function(e) {
|
429
|
+
// Ctrl/Cmd + K to focus search
|
430
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
431
|
+
e.preventDefault();
|
432
|
+
searchInput.focus();
|
433
|
+
searchInput.select();
|
434
|
+
}
|
435
|
+
|
436
|
+
// Escape to clear search when focused
|
437
|
+
if (e.key === 'Escape' && document.activeElement === searchInput) {
|
438
|
+
resetAllFilters();
|
439
|
+
searchInput.blur();
|
440
|
+
}
|
441
|
+
});
|
442
|
+
|
443
|
+
// Intersection Observer for staggered animations
|
444
|
+
if ('IntersectionObserver' in window) {
|
445
|
+
const observer = new IntersectionObserver((entries) => {
|
446
|
+
entries.forEach((entry, index) => {
|
447
|
+
if (entry.isIntersecting) {
|
448
|
+
entry.target.style.animationPlayState = 'running';
|
449
|
+
observer.unobserve(entry.target);
|
450
|
+
}
|
451
|
+
});
|
452
|
+
}, {
|
453
|
+
threshold: 0.1,
|
454
|
+
rootMargin: '50px'
|
455
|
+
});
|
456
|
+
|
457
|
+
// Observe all gem cards
|
458
|
+
document.querySelectorAll('.gem-card-full').forEach(card => {
|
459
|
+
card.style.animationPlayState = 'paused';
|
460
|
+
observer.observe(card);
|
461
|
+
});
|
462
|
+
}
|
463
|
+
|
464
|
+
// Update last updated time
|
465
|
+
function updateLastUpdatedTime() {
|
466
|
+
const lastUpdatedElement = document.getElementById('last-updated');
|
467
|
+
if (lastUpdatedElement) {
|
468
|
+
const now = new Date();
|
469
|
+
const timeString = now.toLocaleTimeString([], {
|
470
|
+
hour: '2-digit',
|
471
|
+
minute: '2-digit'
|
472
|
+
});
|
473
|
+
lastUpdatedElement.textContent = `${timeString}`;
|
474
|
+
}
|
475
|
+
}
|
476
|
+
|
477
|
+
// Toast notifications
|
478
|
+
function showToast(message, type = 'info') {
|
479
|
+
const toast = document.createElement('div');
|
480
|
+
toast.className = `toast-notification ${type}`;
|
481
|
+
toast.textContent = message;
|
482
|
+
|
483
|
+
document.body.appendChild(toast);
|
484
|
+
|
485
|
+
// Trigger animation
|
486
|
+
setTimeout(() => toast.classList.add('visible'), 100);
|
487
|
+
|
488
|
+
// Remove after delay
|
489
|
+
setTimeout(() => {
|
490
|
+
toast.classList.remove('visible');
|
491
|
+
setTimeout(() => document.body.removeChild(toast), 300);
|
492
|
+
}, 3000);
|
493
|
+
}
|
494
|
+
|
495
|
+
// Handle form submissions with toast feedback
|
496
|
+
document.addEventListener('turbo:submit-start', function() {
|
497
|
+
showToast('Refreshing gem metadata...', 'info');
|
498
|
+
});
|
499
|
+
|
500
|
+
document.addEventListener('turbo:submit-end', function(event) {
|
501
|
+
if (event.detail.success) {
|
502
|
+
showToast('Gem metadata updated successfully!', 'success');
|
503
|
+
updateLastUpdatedTime();
|
504
|
+
} else {
|
505
|
+
showToast('Failed to update gem metadata. Please try again.', 'error');
|
506
|
+
}
|
507
|
+
});
|
508
|
+
|
509
|
+
// Add loading animation to stats cards
|
510
|
+
document.querySelectorAll('.stat-card').forEach((card, index) => {
|
511
|
+
card.style.animationDelay = `${index * 0.1}s`;
|
512
|
+
card.style.animation = 'fadeInUp 0.6s ease-out both';
|
513
|
+
});
|
514
|
+
|
515
|
+
// Enhanced keyboard navigation
|
516
|
+
let currentCardIndex = -1;
|
517
|
+
const cards = document.querySelectorAll('.gem-card-full');
|
518
|
+
|
519
|
+
searchInput.addEventListener('keydown', function(e) {
|
520
|
+
if (e.key === 'ArrowDown' && filteredGems.length > 0) {
|
521
|
+
e.preventDefault();
|
522
|
+
currentCardIndex = 0;
|
523
|
+
filteredGems[0].element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
524
|
+
filteredGems[0].element.focus();
|
525
|
+
}
|
526
|
+
});
|
527
|
+
|
528
|
+
// Add tabindex to cards for keyboard navigation
|
529
|
+
cards.forEach((card, index) => {
|
530
|
+
card.setAttribute('tabindex', '0');
|
531
|
+
card.addEventListener('keydown', function(e) {
|
532
|
+
if (e.key === 'ArrowDown' && currentCardIndex < filteredGems.length - 1) {
|
533
|
+
e.preventDefault();
|
534
|
+
currentCardIndex++;
|
535
|
+
filteredGems[currentCardIndex].element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
536
|
+
filteredGems[currentCardIndex].element.focus();
|
537
|
+
} else if (e.key === 'ArrowUp' && currentCardIndex > 0) {
|
538
|
+
e.preventDefault();
|
539
|
+
currentCardIndex--;
|
540
|
+
filteredGems[currentCardIndex].element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
541
|
+
filteredGems[currentCardIndex].element.focus();
|
542
|
+
} else if (e.key === 'Escape') {
|
543
|
+
searchInput.focus();
|
544
|
+
currentCardIndex = -1;
|
545
|
+
}
|
546
|
+
});
|
547
|
+
});
|
548
|
+
|
549
|
+
// Initialize on page load
|
550
|
+
initializeGems();
|
551
|
+
loadViewPreference(); // Load saved view preference
|
552
|
+
updateClearButton();
|
553
|
+
updateLastUpdatedTime();
|
554
|
+
});
|
@@ -10,6 +10,11 @@
|
|
10
10
|
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
11
|
* It is generally better to create a new file per style scope.
|
12
12
|
*
|
13
|
-
*= require_tree
|
13
|
+
*= require_tree ./components
|
14
14
|
*= require_self
|
15
15
|
*/
|
16
|
+
|
17
|
+
/* Base Solidstats styles */
|
18
|
+
.solidstats-dashboard {
|
19
|
+
font-family: system-ui, -apple-system, sans-serif;
|
20
|
+
}
|
@@ -0,0 +1,99 @@
|
|
1
|
+
/* ActionButtonComponent Styles */
|
2
|
+
.action-button {
|
3
|
+
display: inline-flex;
|
4
|
+
align-items: center;
|
5
|
+
gap: 0.5rem;
|
6
|
+
padding: 0.5rem 1rem;
|
7
|
+
border: 1px solid transparent;
|
8
|
+
border-radius: 0.375rem;
|
9
|
+
font-size: 0.875rem;
|
10
|
+
font-weight: 500;
|
11
|
+
line-height: 1.25rem;
|
12
|
+
text-decoration: none;
|
13
|
+
cursor: pointer;
|
14
|
+
transition: all 0.15s ease-in-out;
|
15
|
+
}
|
16
|
+
|
17
|
+
.action-button:hover {
|
18
|
+
text-decoration: none;
|
19
|
+
}
|
20
|
+
|
21
|
+
.action-button:focus {
|
22
|
+
outline: 2px solid transparent;
|
23
|
+
outline-offset: 2px;
|
24
|
+
box-shadow: 0 0 0 2px #3b82f6;
|
25
|
+
}
|
26
|
+
|
27
|
+
.action-button--primary {
|
28
|
+
background-color: #3b82f6;
|
29
|
+
color: #ffffff;
|
30
|
+
border-color: #3b82f6;
|
31
|
+
}
|
32
|
+
|
33
|
+
.action-button--primary:hover {
|
34
|
+
background-color: #2563eb;
|
35
|
+
border-color: #2563eb;
|
36
|
+
}
|
37
|
+
|
38
|
+
.action-button--secondary {
|
39
|
+
background-color: #6b7280;
|
40
|
+
color: #ffffff;
|
41
|
+
border-color: #6b7280;
|
42
|
+
}
|
43
|
+
|
44
|
+
.action-button--secondary:hover {
|
45
|
+
background-color: #4b5563;
|
46
|
+
border-color: #4b5563;
|
47
|
+
}
|
48
|
+
|
49
|
+
.action-button--outline {
|
50
|
+
background-color: transparent;
|
51
|
+
color: #374151;
|
52
|
+
border-color: #d1d5db;
|
53
|
+
}
|
54
|
+
|
55
|
+
.action-button--outline:hover {
|
56
|
+
background-color: #f9fafb;
|
57
|
+
border-color: #9ca3af;
|
58
|
+
}
|
59
|
+
|
60
|
+
.action-button--ghost {
|
61
|
+
background-color: transparent;
|
62
|
+
color: #374151;
|
63
|
+
border-color: transparent;
|
64
|
+
}
|
65
|
+
|
66
|
+
.action-button--ghost:hover {
|
67
|
+
background-color: #f3f4f6;
|
68
|
+
}
|
69
|
+
|
70
|
+
.action-button--danger {
|
71
|
+
background-color: #ef4444;
|
72
|
+
color: #ffffff;
|
73
|
+
border-color: #ef4444;
|
74
|
+
}
|
75
|
+
|
76
|
+
.action-button--danger:hover {
|
77
|
+
background-color: #dc2626;
|
78
|
+
border-color: #dc2626;
|
79
|
+
}
|
80
|
+
|
81
|
+
.action-button--small {
|
82
|
+
padding: 0.25rem 0.75rem;
|
83
|
+
font-size: 0.75rem;
|
84
|
+
}
|
85
|
+
|
86
|
+
.action-button--large {
|
87
|
+
padding: 0.75rem 1.5rem;
|
88
|
+
font-size: 1rem;
|
89
|
+
}
|
90
|
+
|
91
|
+
.action-button__icon {
|
92
|
+
display: inline-flex;
|
93
|
+
align-items: center;
|
94
|
+
font-size: 1em;
|
95
|
+
}
|
96
|
+
|
97
|
+
.action-button__text {
|
98
|
+
display: inline-block;
|
99
|
+
}
|