Add loading state with braille spinner to slide-show more button

This commit is contained in:
Laan Tungir
2026-08-17 06:28:11 -04:00
parent ca38540cfa
commit 5e261023b2
2 changed files with 77 additions and 9 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
{ {
"VERSION": "v0.7.104", "VERSION": "v0.7.105",
"VERSION_NUMBER": "0.7.104", "VERSION_NUMBER": "0.7.105",
"BUILD_DATE": "2026-08-17T10:13:32.771Z" "BUILD_DATE": "2026-08-17T10:28:11.271Z"
} }
+74 -6
View File
@@ -215,6 +215,33 @@
filter: grayscale(var(--image-grayscale-hover)); filter: grayscale(var(--image-grayscale-hover));
} }
#btnSlideMore.is-loading {
cursor: progress;
opacity: 0.85;
}
.braille-spinner {
display: inline-block;
width: 1.1em;
text-align: left;
overflow: hidden;
vertical-align: middle;
white-space: nowrap;
font-family: monospace;
letter-spacing: 0;
}
.braille-spinner::after {
content: "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
display: inline-block;
animation: braille-spin 0.9s steps(10) infinite;
}
@keyframes braille-spin {
0% { transform: translateX(0); }
100% { transform: translateX(-10em); }
}
@media (max-width: 640px) { @media (max-width: 640px) {
#divSlideGrid { #divSlideGrid {
@@ -382,6 +409,7 @@ const versionInfo = await getVersion();
let notesSubscription = null; let notesSubscription = null;
let initialLoadSettled = false; let initialLoadSettled = false;
let initialLoadTimer = null; let initialLoadTimer = null;
let renderDebounceTimer = null;
const noteMap = new Map(); const noteMap = new Map();
let imagePosts = []; let imagePosts = [];
@@ -601,7 +629,18 @@ const versionInfo = await getVersion();
.sort((a, b) => (b.created_at || 0) - (a.created_at || 0)); .sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
} }
function setLoadingState(_isLoading) {} function setLoadingState(isLoading) {
if (!btnSlideMore) return;
if (isLoading) {
btnSlideMore.classList.add('is-loading');
btnSlideMore.disabled = true;
btnSlideMore.innerHTML = '<span class="braille-spinner"></span> loading';
} else {
btnSlideMore.classList.remove('is-loading');
btnSlideMore.disabled = false;
updateMoreButton();
}
}
function getVisibleImagePosts() { function getVisibleImagePosts() {
const visibleCount = Math.max(0, Math.min(visibleImageCount, imagePosts.length)); const visibleCount = Math.max(0, Math.min(visibleImageCount, imagePosts.length));
@@ -756,6 +795,19 @@ const versionInfo = await getVersion();
updateMoreButton(); updateMoreButton();
} }
// Debounce full re-renders triggered by live event updates so a burst of
// incoming events (e.g. the tail of a large subscription) doesn't clear and
// rebuild the grid on every single event, which causes scroll jank.
function scheduleGridRender() {
if (renderDebounceTimer) {
clearTimeout(renderDebounceTimer);
}
renderDebounceTimer = setTimeout(() => {
renderDebounceTimer = null;
renderGrid();
}, 250);
}
function appendMoreTiles() { function appendMoreTiles() {
if (imagePosts.length === 0) return; if (imagePosts.length === 0) return;
divSlideEmpty.style.display = 'none'; divSlideEmpty.style.display = 'none';
@@ -998,14 +1050,20 @@ const versionInfo = await getVersion();
const changed = upsertNoteIfImage(evt); const changed = upsertNoteIfImage(evt);
if (changed) { if (changed) {
rebuildImagePosts(); rebuildImagePosts();
renderGrid(); // During the initial load window we only accumulate events; the
setLoadingState(false); // single render happens in settleInitialLoad() using the estimate.
if (initialLoadSettled) showLatestPublishedImageIfEnabled(); // This avoids re-rendering (and re-laying-out) on every incoming
// event, which caused scroll jank and over-rendering.
if (initialLoadSettled) {
scheduleGridRender();
showLatestPublishedImageIfEnabled();
}
} }
} }
} }
function startKind1Subscription() { function startKind1Subscription() {
setLoadingState(true);
notesSubscription = subscribe( notesSubscription = subscribe(
{ {
kinds: [1], kinds: [1],
@@ -1031,6 +1089,7 @@ const versionInfo = await getVersion();
return; return;
} }
setLoadingState(true);
publicPool = new window.NostrTools.SimplePool(); publicPool = new window.NostrTools.SimplePool();
publicSub = publicPool.subscribeMany( publicSub = publicPool.subscribeMany(
PUBLIC_READ_RELAYS, PUBLIC_READ_RELAYS,
@@ -1040,8 +1099,12 @@ const versionInfo = await getVersion();
const changed = upsertNoteIfImage(evt); const changed = upsertNoteIfImage(evt);
if (changed) { if (changed) {
rebuildImagePosts(); rebuildImagePosts();
renderGrid(); // Same as handleNdkEvent: only render after the initial load
if (initialLoadSettled) showLatestPublishedImageIfEnabled(); // settles to avoid scroll jank and over-rendering.
if (initialLoadSettled) {
scheduleGridRender();
showLatestPublishedImageIfEnabled();
}
} }
}, },
oneose: () => { oneose: () => {
@@ -1086,6 +1149,11 @@ const versionInfo = await getVersion();
clearTimeout(initialLoadTimer); clearTimeout(initialLoadTimer);
initialLoadTimer = null; initialLoadTimer = null;
} }
if (renderDebounceTimer) {
clearTimeout(renderDebounceTimer);
renderDebounceTimer = null;
}
} }
(async function main() { (async function main() {