Skip to content

Commit f8652d8

Browse files
Merge pull request #1417 from opencomponents/infinite-scroll-history
add infinite scrolling to history
2 parents d14850a + d0a784d commit f8652d8

File tree

2 files changed

+199
-46
lines changed

2 files changed

+199
-46
lines changed

src/registry/views/static/index.ts

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,75 @@ oc.cmd.push(function() {
5959
return false;
6060
};
6161
62+
var historyData = [];
63+
var historyRenderedCount = 0;
64+
var historyBatchSize = 50; // Number of items to render per batch
65+
var isLoadingMore = false;
66+
67+
var renderHistoryBatch = function() {
68+
if (historyRenderedCount >= historyData.length || isLoadingMore) {
69+
return;
70+
}
71+
72+
isLoadingMore = true;
73+
var historyContent = $('#history-content');
74+
var batchEnd = Math.min(historyRenderedCount + historyBatchSize, historyData.length);
75+
var batchHtml = '';
76+
77+
for (var i = historyRenderedCount; i < batchEnd; i++) {
78+
var item = historyData[i];
79+
var templateSizeText = item.templateSize ?
80+
' [' + Math.round(item.templateSize / 1024) + ' kb]' : '';
81+
82+
batchHtml += '<a href="' + item.name + '/' + item.version + '/~info">' +
83+
'<div class="componentRow row table">' +
84+
'<p class="release">' +
85+
item.publishDate + ' - Published ' + item.name + '@' + item.version +
86+
templateSizeText +
87+
'</p>' +
88+
'</div>' +
89+
'</a>';
90+
}
91+
92+
// Append new batch to existing content
93+
historyContent.append(batchHtml);
94+
historyRenderedCount = batchEnd;
95+
96+
// Add loading indicator if there are more items
97+
if (historyRenderedCount < historyData.length) {
98+
historyContent.append('<div id="history-loading-more" class="loading-more">Loading more...</div>');
99+
}
100+
101+
isLoadingMore = false;
102+
};
103+
104+
var setupHistoryScrollListener = function() {
105+
var historyContainer = $('#components-history');
106+
var checkScroll = function() {
107+
if (historyRenderedCount >= historyData.length) {
108+
return;
109+
}
110+
111+
var containerTop = historyContainer.offset().top;
112+
var containerHeight = historyContainer.outerHeight();
113+
var scrollTop = $(window).scrollTop();
114+
var windowHeight = $(window).height();
115+
116+
// Check if user scrolled close to the bottom of the history container
117+
var distanceFromBottom = (containerTop + containerHeight) - (scrollTop + windowHeight);
118+
119+
if (distanceFromBottom < 200) { // Load more when 200px from bottom
120+
$('#history-loading-more').remove();
121+
renderHistoryBatch();
122+
}
123+
};
124+
125+
$(window).on('scroll.history', checkScroll);
126+
127+
// Also check on resize
128+
$(window).on('resize.history', checkScroll);
129+
};
130+
62131
var loadComponentsHistory = function() {
63132
var historyLoader = $('#history-loader');
64133
var historyContent = $('#history-content');
@@ -69,6 +138,14 @@ oc.cmd.push(function() {
69138
historyContent.hide();
70139
historyError.hide();
71140
141+
// Reset state
142+
historyData = [];
143+
historyRenderedCount = 0;
144+
isLoadingMore = false;
145+
146+
// Remove any existing scroll listeners
147+
$(window).off('scroll.history resize.history');
148+
72149
// Fetch history data
73150
fetch('~registry/history')
74151
.then(function(response) {
@@ -78,27 +155,23 @@ oc.cmd.push(function() {
78155
return response.json();
79156
})
80157
.then(function(data) {
81-
var componentsHistory = data.componentsHistory || [];
82-
var historyHtml = '';
83-
84-
for (var i = 0; i < componentsHistory.length; i++) {
85-
var item = componentsHistory[i];
86-
var templateSizeText = item.templateSize ?
87-
' [' + Math.round(item.templateSize / 1024) + ' kb]' : '';
88-
89-
historyHtml += '<a href="' + item.name + '/' + item.version + '/~info">' +
90-
'<div class="componentRow row table">' +
91-
'<p class="release">' +
92-
item.publishDate + ' - Published ' + item.name + '@' + item.version +
93-
templateSizeText +
94-
'</p>' +
95-
'</div>' +
96-
'</a>';
97-
}
158+
historyData = data.componentsHistory || [];
98159
99-
historyContent.html(historyHtml);
160+
// Clear content and show container
161+
historyContent.empty();
100162
historyLoader.hide();
101163
historyContent.show();
164+
165+
if (historyData.length === 0) {
166+
historyContent.html('<p style="text-align: center; color: #64748b; padding: 2em;">No components history available.</p>');
167+
return;
168+
}
169+
170+
// Render first batch
171+
renderHistoryBatch();
172+
173+
// Setup scroll listener for infinite loading
174+
setupHistoryScrollListener();
102175
})
103176
.catch(function(error) {
104177
console.error('Error loading components history:', error);
@@ -117,11 +190,20 @@ oc.cmd.push(function() {
117190
$('#menuList a').removeClass('selected');
118191
$('#menuList a[href="' + target + '"]').addClass('selected');
119192
193+
// Clean up scroll listeners when leaving history tab
194+
if (target !== '#components-history') {
195+
$(window).off('scroll.history resize.history');
196+
}
197+
120198
// Load history data when history tab is selected for the first time
121199
if (target === '#components-history' && !isHistoryLoaded) {
122200
loadComponentsHistory();
123201
isHistoryLoaded = true;
124202
}
203+
// Re-enable scroll listeners when returning to history tab
204+
else if (target === '#components-history' && isHistoryLoaded && historyRenderedCount < historyData.length) {
205+
setupHistoryScrollListener();
206+
}
125207
};
126208
127209
var hash = location.href.split('#')[1] || '';

src/registry/views/static/style.ts

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,36 @@
11
export default `
22
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
33
4-
:root {
5-
/* New accent-based color system */
6-
--color-bg-default: #ffffff;
7-
--color-bg-muted: #f8fafc;
8-
--color-bg-alt: #f1f5f9;
9-
--color-bg-hover: #f3f0ff;
10-
11-
--color-text-default: #22223b;
12-
--color-text-muted: #64748b;
13-
--color-text-inverse: #ffffff;
14-
15-
--color-primary: #7c3aed;
16-
--color-primary-hover: #5f3dc4;
17-
/* Secondary accent for informational text (author names, etc.) */
18-
--color-info: #3b5bdb;
19-
20-
--color-border-default: #e0e0e0;
21-
--color-border-muted: #d1d5db;
22-
--color-border-focus: #5f3dc4;
23-
24-
--color-warning: #ffe066;
25-
--color-warning-text: #7c3aed;
26-
--color-danger: #ffd6d6;
27-
--color-danger-text: #c92a2a;
28-
--color-state-border: #e9ecef;
29-
}
4+
:root {
5+
/* New accent-based color system */
6+
--color-bg-default: #ffffff;
7+
--color-bg-muted: #f8fafc;
8+
--color-bg-alt: #f1f5f9;
9+
--color-bg-hover: #f3f0ff;
10+
11+
--color-text-default: #22223b;
12+
--color-text-muted: #64748b;
13+
--color-text-inverse: #ffffff;
14+
15+
--color-primary: #7c3aed;
16+
--color-primary-hover: #5f3dc4;
17+
/* Secondary accent for informational text (author names, etc.) */
18+
--color-info: #3b5bdb;
19+
20+
--color-border-default: #e0e0e0;
21+
--color-border-muted: #d1d5db;
22+
--color-border-focus: #5f3dc4;
23+
24+
--color-warning: #ffe066;
25+
--color-warning-text: #7c3aed;
26+
--color-danger: #ffd6d6;
27+
--color-danger-text: #c92a2a;
28+
--color-state-border: #e9ecef;
29+
30+
/* Shadow definitions */
31+
--shadow-main: 0 2px 8px rgba(60, 60, 100, 0.04);
32+
--shadow-hover: 0 4px 16px rgba(95, 61, 196, 0.08);
33+
}
3034
3135
body {
3236
width: 100%;
@@ -395,6 +399,73 @@ a.tab-link.selected {
395399
}
396400
.box {
397401
padding: 1em 0.5em;
402+
}
398403
}
399-
}
400-
`;
404+
405+
/* History loading states */
406+
.loader {
407+
text-align: center;
408+
padding: 2em;
409+
color: var(--color-text-muted);
410+
}
411+
412+
.loader p {
413+
margin: 0;
414+
font-style: italic;
415+
}
416+
417+
.loader::before {
418+
content: '';
419+
display: inline-block;
420+
width: 20px;
421+
height: 20px;
422+
border: 2px solid var(--color-border-muted);
423+
border-top: 2px solid var(--color-primary);
424+
border-radius: 50%;
425+
animation: spin 1s linear infinite;
426+
margin-right: 0.5em;
427+
vertical-align: middle;
428+
}
429+
430+
@keyframes spin {
431+
0% { transform: rotate(0deg); }
432+
100% { transform: rotate(360deg); }
433+
}
434+
435+
.loading-more {
436+
text-align: center;
437+
padding: 1em 2em;
438+
color: var(--color-text-muted);
439+
font-style: italic;
440+
font-size: 0.9em;
441+
border-top: 1px solid var(--color-border-default);
442+
margin-top: 1em;
443+
}
444+
445+
.loading-more::before {
446+
content: '';
447+
display: inline-block;
448+
width: 16px;
449+
height: 16px;
450+
border: 2px solid var(--color-border-muted);
451+
border-top: 2px solid var(--color-primary);
452+
border-radius: 50%;
453+
animation: spin 1s linear infinite;
454+
margin-right: 0.5em;
455+
vertical-align: middle;
456+
}
457+
458+
#history-error {
459+
text-align: center;
460+
padding: 2em;
461+
color: var(--color-danger-text);
462+
background-color: var(--color-danger);
463+
border: 1px solid var(--color-state-border);
464+
border-radius: 6px;
465+
margin: 1em 0;
466+
}
467+
468+
#history-error p {
469+
margin: 0;
470+
}
471+
`;

0 commit comments

Comments
 (0)