Unified · Split
| @@ -28,62 +28,19 @@ const PROVIDER_UPDATE_PILL_PROGRESS_STYLES = { · 27 unmodified lines | ||
| 28 | 28 | error: "bg-destructive/14", |
| 29 | 29 | } as const; |
| 30 | 30 | |
| 31 | −function latestProviderCheckedAt( | |
| 32 | − providers: ReadonlyArray<Pick<ServerProvider, "checkedAt">>, | |
| 33 | −): string | undefined { | |
| 34 | − return providers.reduce<string | undefined>( | |
| 35 | − (latest, provider) => | |
| 36 | − latest === undefined || provider.checkedAt > latest ? provider.checkedAt : latest, | |
| 37 | − undefined, | |
| 38 | − ); | |
| 31 | +function useMountedIsoTimestamp(): string { | |
| 32 | + const [mountedIsoTimestamp] = useState(() => new Date().toISOString()); | |
| 33 | + return mountedIsoTimestamp; | |
| 39 | 34 | } |
| 40 | 35 | |
| 41 | −export function SidebarProviderUpdatePill() { | |
| 42 | − const navigate = useNavigate(); | |
| 43 | − const providers = useAtomValue(primaryServerProvidersAtom); | |
| 44 | − const [dismissedKeys, setDismissedKeys] = useState<ReadonlySet<string>>(() => new Set()); | |
| 45 | − const [renderedView, setRenderedView] = useState<ProviderUpdateSidebarPillView | null>(null); | |
| 46 | − const [pendingView, setPendingView] = useState<ProviderUpdateSidebarPillView | null>(null); | |
| 47 | − const [exitingKey, setExitingKey] = useState<string | null>(null); | |
| 48 | − const [dismissAfterExitKey, setDismissAfterExitKey] = useState<string | null>(null); | |
| 49 | − const [visibleAfterIso, setVisibleAfterIso] = useState<string | undefined>(); | |
| 50 | − const effectiveVisibleAfterIso = visibleAfterIso ?? latestProviderCheckedAt(providers); | |
| 51 | − const view = getProviderUpdateSidebarPillView(providers, { | |
| 52 | − ...(effectiveVisibleAfterIso !== undefined | |
| 53 | − ? { visibleAfterIso: effectiveVisibleAfterIso } | |
| 54 | − : {}), | |
| 55 | − dismissedKeys, | |
| 56 | − }); | |
| 57 | − | |
| 58 | − useEffect(() => { | |
| 59 | − if (visibleAfterIso === undefined && effectiveVisibleAfterIso !== undefined) { | |
| 60 | − setVisibleAfterIso(effectiveVisibleAfterIso); | |
| 61 | − } | |
| 62 | − }, [effectiveVisibleAfterIso, visibleAfterIso]); | |
| 63 | − | |
| 64 | − const openProviderSettings = useCallback(() => { | |
| 65 | − void navigate({ to: "/settings/providers" }); | |
| 66 | − }, [navigate]); | |
| 67 | − const displayedView = renderedView ?? view; | |
| 68 | − const dismissAfterVisibleMs = displayedView?.dismissAfterVisibleMs; | |
| 69 | − const viewKey = displayedView?.key ?? null; | |
| 70 | − const showDismissProgress = | |
| 71 | − dismissAfterVisibleMs !== undefined && | |
| 72 | − displayedView?.tone !== "loading" && | |
| 73 | − exitingKey !== viewKey; | |
| 74 | − | |
| 75 | − const startExit = useCallback( | |
| 76 | − (key: string, nextView: ProviderUpdateSidebarPillView | null, dismissKey?: string) => { | |
| 77 | − if (exitingKey === key) { | |
| 78 | − return; | |
| 79 | − } | |
| 80 | − setPendingView(nextView); | |
| 81 | − setExitingKey(key); | |
| 82 | − setDismissAfterExitKey(dismissKey ?? null); | |
| 83 | − }, | |
| 84 | − [exitingKey], | |
| 85 | − ); | |
| 86 | − | |
| 36 | +function useProviderUpdateRenderedViewSynchronization(input: { | |
| 37 | + readonly exitingKey: string | null; | |
| 38 | + readonly renderedView: ProviderUpdateSidebarPillView | null; | |
| 39 | + readonly setRenderedView: (view: ProviderUpdateSidebarPillView | null) => void; | |
| 40 | + readonly startExit: (key: string, nextView: ProviderUpdateSidebarPillView | null) => void; | |
| 41 | + readonly view: ProviderUpdateSidebarPillView | null; | |
| 42 | +}) { | |
| 43 | + const { exitingKey, renderedView, setRenderedView, startExit, view } = input; | |
| 87 | 44 | useEffect(() => { |
| 88 | 45 | if (exitingKey !== null) { |
| 89 | 46 | return; |
| @@ -102,8 +59,20 @@ export function SidebarProviderUpdatePill() { · 12 unmodified lines | ||
| 102 | 59 | startExit(renderedView.key, view); |
| 103 | 60 | return; |
| 104 | 61 | } |
| 105 | − }, [exitingKey, renderedView, startExit, view]); | |
| 62 | + }, [exitingKey, renderedView, setRenderedView, startExit, view]); | |
| 63 | +} | |
| 106 | 64 | |
| 65 | +function useProviderUpdateAutoDismiss(input: { | |
| 66 | + readonly dismissAfterVisibleMs: number | undefined; | |
| 67 | + readonly exitingKey: string | null; | |
| 68 | + readonly startExit: ( | |
| 69 | + key: string, | |
| 70 | + nextView: ProviderUpdateSidebarPillView | null, | |
| 71 | + dismissKey?: string, | |
| 72 | + ) => void; | |
| 73 | + readonly viewKey: string | null; | |
| 74 | +}) { | |
| 75 | + const { dismissAfterVisibleMs, exitingKey, startExit, viewKey } = input; | |
| 107 | 76 | useEffect(() => { |
| 108 | 77 | if (!dismissAfterVisibleMs || !viewKey) { |
| 109 | 78 | return; |
| @@ -117,6 +86,60 @@ export function SidebarProviderUpdatePill() { · 7 unmodified lines | ||
| 117 | 86 | |
| 118 | 87 | return () => window.clearTimeout(timeoutId); |
| 119 | 88 | }, [dismissAfterVisibleMs, exitingKey, startExit, viewKey]); |
| 89 | +} | |
| 90 | + | |
| 91 | +export function SidebarProviderUpdatePill() { | |
| 92 | + const navigate = useNavigate(); | |
| 93 | + const providers = useAtomValue(primaryServerProvidersAtom); | |
| 94 | + const [dismissedKeys, setDismissedKeys] = useState<ReadonlySet<string>>(() => new Set()); | |
| 95 | + const visibleAfterIso = useMountedIsoTimestamp(); | |
| 96 | + const view = getProviderUpdateSidebarPillView(providers, { | |
| 97 | + visibleAfterIso, | |
| 98 | + dismissedKeys, | |
| 99 | + }); | |
| 100 | + const [renderedView, setRenderedView] = useState<ProviderUpdateSidebarPillView | null>( | |
| 101 | + () => view, | |
| 102 | + ); | |
| 103 | + const [pendingView, setPendingView] = useState<ProviderUpdateSidebarPillView | null>(null); | |
| 104 | + const [exitingKey, setExitingKey] = useState<string | null>(null); | |
| 105 | + const [dismissAfterExitKey, setDismissAfterExitKey] = useState<string | null>(null); | |
| 106 | + | |
| 107 | + const openProviderSettings = useCallback(() => { | |
| 108 | + void navigate({ to: "/settings/providers" }); | |
| 109 | + }, [navigate]); | |
| 110 | + const displayedView = renderedView ?? view; | |
| 111 | + const dismissAfterVisibleMs = displayedView?.dismissAfterVisibleMs; | |
| 112 | + const viewKey = displayedView?.key ?? null; | |
| 113 | + const showDismissProgress = | |
| 114 | + dismissAfterVisibleMs !== undefined && | |
| 115 | + displayedView?.tone !== "loading" && | |
| 116 | + exitingKey !== viewKey; | |
| 117 | + | |
| 118 | + const startExit = useCallback( | |
| 119 | + (key: string, nextView: ProviderUpdateSidebarPillView | null, dismissKey?: string) => { | |
| 120 | + if (exitingKey === key) { | |
| 121 | + return; | |
| 122 | + } | |
| 123 | + setPendingView(nextView); | |
| 124 | + setExitingKey(key); | |
| 125 | + setDismissAfterExitKey(dismissKey ?? null); | |
| 126 | + }, | |
| 127 | + [exitingKey], | |
| 128 | + ); | |
| 129 | + | |
| 130 | + useProviderUpdateRenderedViewSynchronization({ | |
| 131 | + exitingKey, | |
| 132 | + renderedView, | |
| 133 | + setRenderedView, | |
| 134 | + startExit, | |
| 135 | + view, | |
| 136 | + }); | |
| 137 | + useProviderUpdateAutoDismiss({ | |
| 138 | + dismissAfterVisibleMs, | |
| 139 | + exitingKey, | |
| 140 | + startExit, | |
| 141 | + viewKey, | |
| 142 | + }); | |
| 120 | 143 | |
| 121 | 144 | if (!displayedView) { |
| 122 | 145 | return null; |