From 83d9a9b1c48a63d0095dd059ed0c5dee67d3fba9 Mon Sep 17 00:00:00 2001 From: KillariDev <13102010+KillariDev@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:43:37 +0000 Subject: [PATCH 01/31] Show loading shimmer while switching popup wallets --- app/ts/components/App.tsx | 25 ++++++++++++++-------- app/ts/components/pages/Home.tsx | 5 +++-- app/ts/types/user-interface-types.ts | 2 ++ test/tests/homeClearEmptyState.test.ts | 29 ++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/app/ts/components/App.tsx b/app/ts/components/App.tsx index 134b8ac42..3dd0b2b6f 100644 --- a/app/ts/components/App.tsx +++ b/app/ts/components/App.tsx @@ -71,20 +71,26 @@ export function App() { }, }) const boundaryResetKey = useSignal(0) + const isActiveAddressChanging = useSignal(false) async function setActiveAddressAndInformAboutIt(address: bigint | 'signer', persistedEntry?: AddressBookEntry) { - if (!isSettingsLoaded.value) return + if (!isSettingsLoaded.value || isActiveAddressChanging.value) return const selectableAddresses = includePersistedAddressBookEntry(activeAddresses.value, persistedEntry) if (!isActiveAddressSelectionAllowed(address, selectableAddresses, simulationMode.value, rpcNetwork.value?.chainId, tabState.value?.signerAccounts ?? [])) return - await requestActiveAddressChange(address, simulationMode.value) - const optimisticSelection = getOptimisticActiveAddressSelection(address, simulationMode.value, tabState.value?.signerAccounts ?? []) - if (optimisticSelection.mode === 'simulation') { - activeSimulationAddress.value = optimisticSelection.activeSimulationAddress - useSignersAddressAsActiveAddress.value = optimisticSelection.useSignersAddressAsActiveAddress - return + isActiveAddressChanging.value = true + try { + await requestActiveAddressChange(address, simulationMode.value) + const optimisticSelection = getOptimisticActiveAddressSelection(address, simulationMode.value, tabState.value?.signerAccounts ?? []) + if (optimisticSelection.mode === 'simulation') { + activeSimulationAddress.value = optimisticSelection.activeSimulationAddress + useSignersAddressAsActiveAddress.value = optimisticSelection.useSignersAddressAsActiveAddress + return + } + displayedSigningAddress.value = optimisticSelection.displayedSigningAddress + activeSigningSafeAddress.value = address === 'signer' ? undefined : optimisticSelection.displayedSigningAddress + } finally { + isActiveAddressChanging.value = false } - displayedSigningAddress.value = optimisticSelection.displayedSigningAddress - activeSigningSafeAddress.value = address === 'signer' ? undefined : optimisticSelection.displayedSigningAddress } function isSignerConnected() { @@ -250,6 +256,7 @@ export function App() { { if (!param.isInitialHomeDataLoaded.value) return @@ -615,7 +615,7 @@ function FirstCard(param: FirstCardParams) { } { isActiveAddressLoading - ? + ? : + { setSimulatingState.value.state === 'rejected' ? : <> } + { setSigningState.value.state === 'rejected' ? : <> } + { rpcChangeState.value.state === 'rejected' ? : <> } } @@ -334,6 +348,8 @@ function InterceptorDisabledButton({ disableInterceptorToggle, interceptorDisabl } type RichListParams = { + isSettingsChangePending: ReadonlySignal + setRichState: (enabled: boolean, address: bigint | 'CurrentAddress') => Promise makeCurrentAddressRich: Signal activeAddress: Signal richList: Signal @@ -341,14 +357,27 @@ type RichListParams = { isInitialHomeDataLoaded: Signal } -function RichList({ makeCurrentAddressRich, activeAddress, richList, renameAddressCallBack, isInitialHomeDataLoaded }: RichListParams) { - async function enableMakeCurrentAddressRich(enabled: boolean) { - if (!isInitialHomeDataLoaded.value) return - sendPopupMessageToBackgroundPage( { method: 'popup_modifyMakeMeRich', data: { add: enabled, address: 'CurrentAddress'} } ) +function RichList({ makeCurrentAddressRich, activeAddress, richList, renameAddressCallBack, isInitialHomeDataLoaded, isSettingsChangePending, setRichState }: RichListParams) { + const { value: richChangeState, waitFor: waitForRichChange } = useAsyncState() + const controlsDisabled = !isInitialHomeDataLoaded.value || isSettingsChangePending.value || richChangeState.value.state === 'pending' + const saveRichChange = (enabled: boolean, address: bigint | 'CurrentAddress') => { + void waitForRichChange(async () => { + try { + await setRichState(enabled, address) + } catch (error) { + // Read persisted settings: a failed visualization refresh does not undo a successful save. + await sendPopupMessageToBackgroundPage({ method: 'popup_requestNewHomeData', data: { refreshSignerAccounts: false, includeWebsiteAccessAddressMetadata: false } }) + throw error + } + }) + } + function enableMakeCurrentAddressRich(enabled: boolean) { + if (controlsDisabled) return makeCurrentAddressRich.value = enabled + saveRichChange(enabled, 'CurrentAddress') } - async function modifyRichList(addressBookEntry: AddressBookEntry, makeRich: boolean) { - if (!isInitialHomeDataLoaded.value) return + function modifyRichList(addressBookEntry: AddressBookEntry, makeRich: boolean) { + if (controlsDisabled) return richList.value = updateRichListAddress( richList.value, addressBookEntry.address, @@ -356,7 +385,7 @@ function RichList({ makeCurrentAddressRich, activeAddress, richList, renameAddre (element) => element.addressBookEntry.address, () => ({ addressBookEntry, makingRich: true, type: 'UserAdded' as const }), ) - sendPopupMessageToBackgroundPage( { method: 'popup_modifyMakeMeRich', data: { add: makeRich, address: addressBookEntry.address } } ) + saveRichChange(makeRich, addressBookEntry.address) } const showList = useSignal(false) @@ -377,7 +406,7 @@ function RichList({ makeCurrentAddressRich, activeAddress, richList, renameAddre
{ showList.value = !showList.value } }>

Make current account rich

@@ -386,11 +415,13 @@ function RichList({ makeCurrentAddressRich, activeAddress, richList, renameAddre
+ { richChangeState.value.state === 'pending' ?
Updating balances...
: <> } + { richChangeState.value.state === 'rejected' ? : <> } { !showList.value ? <> { !activeAddressSetAsRichViaFixedAddressList.value || activeAddress.value === undefined ? <> : <>
@@ -400,7 +431,7 @@ function RichList({ makeCurrentAddressRich, activeAddress, richList, renameAddre

Addresses being made rich

{ visibleRichList.value.map((richListElement) => ) } @@ -620,7 +651,7 @@ function FirstCard(param: FirstCardParams) { : !param.isFreshHomeDataLoaded.value ? : { setSimulatingState.value.state === 'rejected' ? : <> } diff --git a/app/ts/components/subcomponents/ChainSelector.tsx b/app/ts/components/subcomponents/ChainSelector.tsx index dd1255a98..cdb8137bb 100644 --- a/app/ts/components/subcomponents/ChainSelector.tsx +++ b/app/ts/components/subcomponents/ChainSelector.tsx @@ -10,6 +10,7 @@ interface RpcSelectorParams { rpcEntries: Signal changeRpc: (entry: RpcEntry) => void disabled?: boolean + pendingText?: string } export function findRpcEntryByIdentityKey(rpcEntries: RpcEntries, key: string) { @@ -47,7 +48,7 @@ export function RpcSelector(params: RpcSelectorParams) { if (newEntry === undefined) throw new Error('Tried to select an RPC entry that is no longer available.') params.changeRpc(newEntry) } - return + return } interface ChainSelectorParams { diff --git a/app/ts/components/subcomponents/DropDownMenu.tsx b/app/ts/components/subcomponents/DropDownMenu.tsx index 09b282a5b..2504aa6a9 100644 --- a/app/ts/components/subcomponents/DropDownMenu.tsx +++ b/app/ts/components/subcomponents/DropDownMenu.tsx @@ -1,6 +1,7 @@ import { type Signal, useSignal } from '@preact/signals' import { useId, useRef } from 'preact/hooks' import { clickOutsideAlerter } from '../ui-utils.js' +import { AsyncStatusIcon } from './AsyncAction.js' import { ChevronIcon } from './icons.js' import type { ComponentChildren } from 'preact' @@ -11,39 +12,42 @@ type DropDownMenuParams = { buttonClassses: string ariaLabel?: string disabled?: boolean + pendingText?: string getOptionLabel?: (option: OptionType) => string renderOption?: (option: OptionType) => ComponentChildren } -export function DropDownMenuButtonContent({ label }: { label: ComponentChildren }) { +export function DropDownMenuButtonContent({ label, pendingText }: { label: ComponentChildren, pendingText?: string }) { return <> { label } - + } -export const DropDownMenu = ({ selected, dropDownOptions, onChangedCallBack, buttonClassses, ariaLabel, disabled = false, getOptionLabel = (option) => option, renderOption = getOptionLabel }: DropDownMenuParams) => { +export const DropDownMenu = ({ selected, dropDownOptions, onChangedCallBack, buttonClassses, ariaLabel, disabled = false, pendingText, getOptionLabel = (option) => option, renderOption = getOptionLabel }: DropDownMenuParams) => { + const unavailable = disabled || pendingText !== undefined const isOpen = useSignal(false) const ref = useRef(null) const menuId = useId() clickOutsideAlerter(ref, () => { isOpen.value = false }) const toggle = () => { - if (disabled) return + if (unavailable) return isOpen.value = !isOpen.value } const onChanged = (newValue: OptionType) => { - if (disabled) return + if (unavailable) return isOpen.value = false onChangedCallBack(newValue) } return