Add loading state with braille spinner to slide-show more button
This commit is contained in:
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.7.104",
|
||||
"VERSION_NUMBER": "0.7.104",
|
||||
"BUILD_DATE": "2026-08-17T10:13:32.771Z"
|
||||
"VERSION": "v0.7.105",
|
||||
"VERSION_NUMBER": "0.7.105",
|
||||
"BUILD_DATE": "2026-08-17T10:28:11.271Z"
|
||||
}
|
||||
|
||||
+74
-6
@@ -215,6 +215,33 @@
|
||||
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) {
|
||||
#divSlideGrid {
|
||||
@@ -382,6 +409,7 @@ const versionInfo = await getVersion();
|
||||
let notesSubscription = null;
|
||||
let initialLoadSettled = false;
|
||||
let initialLoadTimer = null;
|
||||
let renderDebounceTimer = null;
|
||||
|
||||
const noteMap = new Map();
|
||||
let imagePosts = [];
|
||||
@@ -601,7 +629,18 @@ const versionInfo = await getVersion();
|
||||
.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() {
|
||||
const visibleCount = Math.max(0, Math.min(visibleImageCount, imagePosts.length));
|
||||
@@ -756,6 +795,19 @@ const versionInfo = await getVersion();
|
||||
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() {
|
||||
if (imagePosts.length === 0) return;
|
||||
divSlideEmpty.style.display = 'none';
|
||||
@@ -998,14 +1050,20 @@ const versionInfo = await getVersion();
|
||||
const changed = upsertNoteIfImage(evt);
|
||||
if (changed) {
|
||||
rebuildImagePosts();
|
||||
renderGrid();
|
||||
setLoadingState(false);
|
||||
if (initialLoadSettled) showLatestPublishedImageIfEnabled();
|
||||
// During the initial load window we only accumulate events; the
|
||||
// single render happens in settleInitialLoad() using the estimate.
|
||||
// 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() {
|
||||
setLoadingState(true);
|
||||
notesSubscription = subscribe(
|
||||
{
|
||||
kinds: [1],
|
||||
@@ -1031,6 +1089,7 @@ const versionInfo = await getVersion();
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingState(true);
|
||||
publicPool = new window.NostrTools.SimplePool();
|
||||
publicSub = publicPool.subscribeMany(
|
||||
PUBLIC_READ_RELAYS,
|
||||
@@ -1040,8 +1099,12 @@ const versionInfo = await getVersion();
|
||||
const changed = upsertNoteIfImage(evt);
|
||||
if (changed) {
|
||||
rebuildImagePosts();
|
||||
renderGrid();
|
||||
if (initialLoadSettled) showLatestPublishedImageIfEnabled();
|
||||
// Same as handleNdkEvent: only render after the initial load
|
||||
// settles to avoid scroll jank and over-rendering.
|
||||
if (initialLoadSettled) {
|
||||
scheduleGridRender();
|
||||
showLatestPublishedImageIfEnabled();
|
||||
}
|
||||
}
|
||||
},
|
||||
oneose: () => {
|
||||
@@ -1086,6 +1149,11 @@ const versionInfo = await getVersion();
|
||||
clearTimeout(initialLoadTimer);
|
||||
initialLoadTimer = null;
|
||||
}
|
||||
|
||||
if (renderDebounceTimer) {
|
||||
clearTimeout(renderDebounceTimer);
|
||||
renderDebounceTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
|
||||
Reference in New Issue
Block a user