Skip to content

Commit ea887ed

Browse files
committed
spinner and kbd tip
1 parent b7e9789 commit ea887ed

3 files changed

Lines changed: 101 additions & 0 deletions

File tree

index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ <h2>Getting Started</h2>
7979
</p>
8080
</div>
8181

82+
<div class="note">
83+
<p>
84+
<strong>Keyboard Navigation:</strong> When a cell type dropdown is open, you can quickly navigate to
85+
specific entries by typing on your keyboard. For example, instead of scrolling through thousands of
86+
cell types, simply type <kbd>V</kbd><kbd>S</kbd> to jump directly to cell types starting with "VS".
87+
This works with any letter combination to help you find what you're looking for faster.
88+
</p>
89+
</div>
90+
8291
<div class="note">
8392
<p>
8493
<strong>Display Recommendation:</strong> neuDiff works best on wide monitors where you can view

scripts/main.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,27 @@ document.addEventListener("DOMContentLoaded", () => {
360360

361361
const welcomeScreen = document.getElementById("welcome-screen");
362362

363+
// Get neuron select wrapper for loading spinner
364+
const neuronSelectWrapper = neuronSelect.closest(".selector");
365+
366+
// Helper functions for loading state
367+
const setLoadingState = (isLoading) => {
368+
// Disable all dropdowns
369+
const allSelects = document.querySelectorAll(".viewer-controls select");
370+
allSelects.forEach((select) => {
371+
select.disabled = isLoading;
372+
});
373+
374+
// Show/hide spinner on neuron select
375+
if (neuronSelectWrapper) {
376+
if (isLoading) {
377+
neuronSelectWrapper.classList.add("is-loading");
378+
} else {
379+
neuronSelectWrapper.classList.remove("is-loading");
380+
}
381+
}
382+
};
383+
363384
const hidePreviewPane = () => {
364385
frame.src = "about:blank";
365386
previewPane.classList.add("is-hidden");
@@ -484,6 +505,9 @@ document.addEventListener("DOMContentLoaded", () => {
484505
const selectedBase = matchingOption.value.trim();
485506
const loadToken = ++currentLoadToken;
486507

508+
// Enable loading state
509+
setLoadingState(true);
510+
487511
fetchDatasetData(selectedBase)
488512
.then((data) => {
489513
if (loadToken !== currentLoadToken) return;
@@ -540,11 +564,25 @@ document.addEventListener("DOMContentLoaded", () => {
540564

541565
isRestoringFromURL = false;
542566
updateURL();
567+
568+
// Disable loading state after DOM updates complete
569+
requestAnimationFrame(() => {
570+
requestAnimationFrame(() => {
571+
setLoadingState(false);
572+
});
573+
});
543574
})
544575
.catch((error) => {
545576
console.error(error);
546577
isRestoringFromURL = false;
547578
updateURL();
579+
580+
// Disable loading state after DOM updates complete
581+
requestAnimationFrame(() => {
582+
requestAnimationFrame(() => {
583+
setLoadingState(false);
584+
});
585+
});
548586
});
549587
} else {
550588
isRestoringFromURL = false;
@@ -569,6 +607,9 @@ document.addEventListener("DOMContentLoaded", () => {
569607

570608
const loadToken = currentLoadToken;
571609

610+
// Enable loading state
611+
setLoadingState(true);
612+
572613
fetchDatasetData(selectedBase)
573614
.then((data) => {
574615
if (loadToken !== currentLoadToken) return;
@@ -582,6 +623,13 @@ document.addEventListener("DOMContentLoaded", () => {
582623
state.neuronIndex = buildNeuronIndex(state.neuronData);
583624
populateNeuronOptions();
584625
updateURL();
626+
627+
// Disable loading state after DOM updates complete
628+
requestAnimationFrame(() => {
629+
requestAnimationFrame(() => {
630+
setLoadingState(false);
631+
});
632+
});
585633
})
586634
.catch((error) => {
587635
console.error(error);
@@ -594,6 +642,13 @@ document.addEventListener("DOMContentLoaded", () => {
594642
hidePreviewPane();
595643
checkIfBothViewersEmpty();
596644
updateURL();
645+
646+
// Disable loading state after DOM updates complete
647+
requestAnimationFrame(() => {
648+
requestAnimationFrame(() => {
649+
setLoadingState(false);
650+
});
651+
});
597652
});
598653
});
599654

styles/main.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ body {
297297
}
298298

299299
.selector {
300+
position: relative;
300301
display: flex;
301302
flex-direction: column;
302303
gap: 0.25rem;
@@ -320,6 +321,28 @@ body {
320321
background-color: rgba(20, 30, 50, 0.7);
321322
}
322323

324+
.selector.is-loading::after {
325+
content: "";
326+
position: absolute;
327+
top: 50%;
328+
right: 0.75rem;
329+
width: 1rem;
330+
height: 1rem;
331+
margin-top: 0.125rem;
332+
border: 2px solid rgba(120, 180, 255, 0.3);
333+
border-top-color: rgba(120, 180, 255, 0.9);
334+
border-radius: 50%;
335+
animation: spinner-rotate 0.6s linear infinite;
336+
pointer-events: none;
337+
z-index: 10;
338+
}
339+
340+
@keyframes spinner-rotate {
341+
to {
342+
transform: rotate(360deg);
343+
}
344+
}
345+
323346
main.viewer-split {
324347
flex: 1 1 0;
325348
display: grid;
@@ -455,6 +478,20 @@ main.viewer-split {
455478
color: rgba(247, 251, 255, 0.85);
456479
}
457480

481+
.welcome-content kbd {
482+
display: inline-block;
483+
padding: 0.15rem 0.4rem;
484+
font-size: 0.875em;
485+
font-family: monospace;
486+
line-height: 1;
487+
color: rgba(247, 251, 255, 0.95);
488+
background: rgba(255, 255, 255, 0.1);
489+
border: 1px solid rgba(255, 255, 255, 0.2);
490+
border-radius: 0.25rem;
491+
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
492+
margin: 0 0.1rem;
493+
}
494+
458495
@media (max-width: 960px) {
459496
.viewer-header {
460497
flex-wrap: wrap;

0 commit comments

Comments
 (0)