diff --git a/src/app/shell/RefreshButton.m.scss b/src/app/shell/RefreshButton.m.scss index 7ab74fa840..1c1ca33ff8 100644 --- a/src/app/shell/RefreshButton.m.scss +++ b/src/app/shell/RefreshButton.m.scss @@ -1,4 +1,5 @@ @use 'sass:color'; +@use 'sass:math'; @use '../variables' as *; @use '../dim-ui/tooltip-mixins' as *; @@ -53,3 +54,10 @@ margin-bottom: 1em; } } + +.fixedIcon { + display: inline-block; + width: math.div(20em, 16); // $fa-fw-width from AppIcon + text-align: center; + line-height: 1; +} diff --git a/src/app/shell/RefreshButton.m.scss.d.ts b/src/app/shell/RefreshButton.m.scss.d.ts index 4182555767..adb6944c67 100644 --- a/src/app/shell/RefreshButton.m.scss.d.ts +++ b/src/app/shell/RefreshButton.m.scss.d.ts @@ -3,6 +3,7 @@ interface CssExports { 'errorDetails': string; 'errorTooltip': string; + 'fixedIcon': string; 'outOfDate': string; 'refreshButton': string; 'userIsPlaying': string; diff --git a/src/app/shell/RefreshButton.tsx b/src/app/shell/RefreshButton.tsx index beae5f19c0..786b8d7eab 100644 --- a/src/app/shell/RefreshButton.tsx +++ b/src/app/shell/RefreshButton.tsx @@ -16,13 +16,13 @@ import { useSelector } from 'react-redux'; import { useSubscription } from 'use-subscription'; import ErrorPanel from './ErrorPanel'; import * as styles from './RefreshButton.m.scss'; -import { AppIcon, faClock, faExclamationTriangle, refreshIcon } from './icons'; +import { AppIcon, faClock, faExclamationTriangle, faHourglass, refreshIcon } from './icons'; import { loadingTracker } from './loading-tracker'; import { refresh } from './refresh-events'; /** We consider the profile stale if it's out of date with respect to the game data by this much */ const STALE_PROFILE_THRESHOLD = 90_000; -const MIN_SPIN = 1000; // 1 second +const WAIT_TIME = 1_000; export default function RefreshButton({ className }: { className?: string }) { const [disabled, setDisabled] = useState(false); @@ -34,24 +34,33 @@ export default function RefreshButton({ className }: { className?: string }) { ); const active = useSubscription(loadingTracker.active$); - // Always show the spinner for at least MIN_SPIN milliseconds - const [spin, setSpin] = useState(active ? Date.now() : 0); + // Track the minted date to detect if a refresh brought new data + const [lastMintedDate, setLastMintedDate] = useState(new Date(-1)); + const [showHourglass, setShowHourglass] = useState(false); + const [wasRecentlyActive, setWasRecentlyActive] = useState(false); + + const profileMintedDate = useSelector(profileMintedSelector); + useEffect(() => { - if (active && spin === 0) { - setSpin(Date.now()); - } else if (!active && spin !== 0) { - const elapsed = Date.now() - spin; - const remainingTime = Math.max(0, MIN_SPIN - elapsed); - if (remainingTime > 0) { + if (active && !wasRecentlyActive) { + // Refresh just started + setWasRecentlyActive(true); + } else if (!active && wasRecentlyActive) { + // Refresh just completed - check if we got new data + if (profileMintedDate.getTime() === lastMintedDate.getTime()) { + // No new data from this refresh - show hourglass + setShowHourglass(true); const timer = window.setTimeout(() => { - setSpin(0); - }, remainingTime); + setShowHourglass(false); + }, WAIT_TIME); return () => window.clearTimeout(timer); } else { - setSpin(0); + // New data received - update our tracking + setLastMintedDate(profileMintedDate); } + setWasRecentlyActive(false); } - }, [active, spin]); + }, [active, profileMintedDate, lastMintedDate, wasRecentlyActive]); useEventBusListener(isDragging$, handleChanges); @@ -80,7 +89,9 @@ export default function RefreshButton({ className }: { className?: string }) { title={t('Header.Refresh') + (autoRefresh ? `\n${t('Header.AutoRefresh')}` : '')} aria-keyshortcuts="R" > - + {(showHourglass && ( + + )) || } {autoRefresh &&
} {(profileError || showOutOfDateWarning) && (
diff --git a/src/app/shell/icons/AppIcon.tsx b/src/app/shell/icons/AppIcon.tsx index ca6d04748d..e7371b0f4e 100644 --- a/src/app/shell/icons/AppIcon.tsx +++ b/src/app/shell/icons/AppIcon.tsx @@ -10,17 +10,25 @@ function AppIcon({ title, spinning, ariaHidden, + fading, }: { icon: string | IconDefinition; className?: string; title?: string; spinning?: boolean; ariaHidden?: boolean; + fading?: boolean; }) { if (typeof icon === 'string') { return ( @@ -33,6 +41,7 @@ function AppIcon({ icon={icon} title={title} spin={spinning} + fade={fading} /> ); } diff --git a/src/app/shell/icons/Library.js b/src/app/shell/icons/Library.js index 39df537991..ef1b64d94a 100644 --- a/src/app/shell/icons/Library.js +++ b/src/app/shell/icons/Library.js @@ -96,3 +96,4 @@ export const lessThanIcon = 'fas fa-less-than-equal'; export const equalsIcon = 'fas fa-equals'; export const stackIcon = 'fas fa-layer-group'; export const slashIcon = 'fas fa-slash'; +export const faHourglass = 'fas fa-hourglass'; diff --git a/src/app/shell/icons/font-awesome.scss b/src/app/shell/icons/font-awesome.scss index 73ceaf0f1d..cc79a5dce3 100644 --- a/src/app/shell/icons/font-awesome.scss +++ b/src/app/shell/icons/font-awesome.scss @@ -117,6 +117,9 @@ .fa-th:before { content: fa-content($fa-var-th); } +.fa-hourglass:before { + content: fa-content($fa-var-hourglass); +} .fa-list:before { content: fa-content($fa-var-list); } diff --git a/src/data/webfonts/fa-solid-900.ttf b/src/data/webfonts/fa-solid-900.ttf index 9d62a092f1..d387881387 100644 Binary files a/src/data/webfonts/fa-solid-900.ttf and b/src/data/webfonts/fa-solid-900.ttf differ diff --git a/src/data/webfonts/fa-solid-900.woff2 b/src/data/webfonts/fa-solid-900.woff2 index f1cf9d66ac..0b1e208a9a 100644 Binary files a/src/data/webfonts/fa-solid-900.woff2 and b/src/data/webfonts/fa-solid-900.woff2 differ