forked from wrenn/wrenn
Fix capsules table blink on background poll refresh
Poll fetches now silently update data without triggering loading states, spinner animations, or row fadeUp re-animations. Only manual refresh shows the spin indicator.
This commit is contained in:
@ -56,6 +56,9 @@
|
|||||||
// Briefly highlight a newly created capsule row
|
// Briefly highlight a newly created capsule row
|
||||||
let newCapsuleId = $state<string | null>(null);
|
let newCapsuleId = $state<string | null>(null);
|
||||||
|
|
||||||
|
// Track whether initial load animation has played (suppress on poll refreshes)
|
||||||
|
let initialAnimationDone = $state(false);
|
||||||
|
|
||||||
let filteredCapsules = $derived.by(() => {
|
let filteredCapsules = $derived.by(() => {
|
||||||
let list = searchQuery
|
let list = searchQuery
|
||||||
? capsules.filter((c) => c.id.toLowerCase().includes(searchQuery.toLowerCase()))
|
? capsules.filter((c) => c.id.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||||
@ -121,27 +124,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCapsules() {
|
async function fetchCapsules(manual = false) {
|
||||||
const wasEmpty = capsules.length === 0;
|
const wasEmpty = capsules.length === 0;
|
||||||
if (wasEmpty) loading = true;
|
if (wasEmpty) loading = true;
|
||||||
|
|
||||||
|
if (manual) {
|
||||||
spinning = true;
|
spinning = true;
|
||||||
const spinTimer = new Promise<void>((resolve) => setTimeout(resolve, SPIN_DURATION));
|
var spinTimer = new Promise<void>((resolve) => setTimeout(resolve, SPIN_DURATION));
|
||||||
|
}
|
||||||
|
|
||||||
error = null;
|
|
||||||
const result = await listCapsules();
|
const result = await listCapsules();
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
capsules = result.data;
|
capsules = result.data;
|
||||||
} else {
|
|
||||||
error = result.error;
|
|
||||||
}
|
}
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|
||||||
|
// Mark initial entrance animation as done after first successful fetch
|
||||||
|
if (!initialAnimationDone) {
|
||||||
|
setTimeout(() => { initialAnimationDone = true; }, 400 + (capsules.length * 40));
|
||||||
|
}
|
||||||
|
|
||||||
if (autoRefresh) countdown = REFRESH_INTERVAL;
|
if (autoRefresh) countdown = REFRESH_INTERVAL;
|
||||||
|
|
||||||
await spinTimer;
|
if (manual) {
|
||||||
|
await spinTimer!;
|
||||||
spinning = false;
|
spinning = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePause(id: string) {
|
async function handlePause(id: string) {
|
||||||
openMenuId = null;
|
openMenuId = null;
|
||||||
@ -297,7 +306,7 @@
|
|||||||
|
|
||||||
<!-- Refresh button -->
|
<!-- Refresh button -->
|
||||||
<button
|
<button
|
||||||
onclick={fetchCapsules}
|
onclick={() => fetchCapsules(true)}
|
||||||
disabled={spinning}
|
disabled={spinning}
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-[var(--radius-button)] border border-[var(--color-border)] text-[var(--color-text-tertiary)] transition-colors duration-150 hover:border-[var(--color-border-mid)] hover:text-[var(--color-text-secondary)] disabled:opacity-50"
|
class="flex h-8 w-8 items-center justify-center rounded-[var(--radius-button)] border border-[var(--color-border)] text-[var(--color-text-tertiary)] transition-colors duration-150 hover:border-[var(--color-border-mid)] hover:text-[var(--color-text-secondary)] disabled:opacity-50"
|
||||||
title="Refresh"
|
title="Refresh"
|
||||||
@ -415,7 +424,7 @@
|
|||||||
{@const stripeColor = capsule.status === 'running' ? 'bg-[var(--color-accent)]' : capsule.status === 'paused' ? 'bg-[var(--color-amber)]' : 'bg-[var(--color-text-muted)]'}
|
{@const stripeColor = capsule.status === 'running' ? 'bg-[var(--color-accent)]' : capsule.status === 'paused' ? 'bg-[var(--color-amber)]' : 'bg-[var(--color-text-muted)]'}
|
||||||
<div
|
<div
|
||||||
class="capsule-row relative grid grid-cols-[1.6fr_0.8fr_0.5fr_0.5fr_0.6fr_1fr_0.9fr] items-center overflow-hidden border-b border-[var(--color-border)] transition-colors duration-150 hover:bg-[var(--color-bg-3)] last:border-b-0 {newCapsuleId === capsule.id ? 'capsule-born' : ''}"
|
class="capsule-row relative grid grid-cols-[1.6fr_0.8fr_0.5fr_0.5fr_0.6fr_1fr_0.9fr] items-center overflow-hidden border-b border-[var(--color-border)] transition-colors duration-150 hover:bg-[var(--color-bg-3)] last:border-b-0 {newCapsuleId === capsule.id ? 'capsule-born' : ''}"
|
||||||
style="animation: fadeUp 0.35s ease both; animation-delay: {i * 40}ms"
|
style={initialAnimationDone ? '' : `animation: fadeUp 0.35s ease both; animation-delay: ${i * 40}ms`}
|
||||||
>
|
>
|
||||||
<!-- Left accent stripe -->
|
<!-- Left accent stripe -->
|
||||||
<div class="row-stripe pointer-events-none absolute left-0 top-0 h-full w-0.5 {stripeColor}"></div>
|
<div class="row-stripe pointer-events-none absolute left-0 top-0 h-full w-0.5 {stripeColor}"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user