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": "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
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user