diff --git a/packages/apps/src/SideBar/Item.tsx b/packages/apps/src/SideBar/Item.tsx index 45ebe04838..2758538f25 100644 --- a/packages/apps/src/SideBar/Item.tsx +++ b/packages/apps/src/SideBar/Item.tsx @@ -2,12 +2,15 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { ApiProps } from '@polkadot/react-api/types'; import { I18nProps } from '@polkadot/react-components/types'; import { SubjectInfo } from '@polkadot/ui-keyring/observable/types'; import { Route } from '@polkadot/apps-routing/types'; +import { AccountId } from '@polkadot/types/interfaces'; -import React, { useContext } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { NavLink } from 'react-router-dom'; +import { ApiPromise } from '@polkadot/api'; import { Icon, Menu, Tooltip } from '@polkadot/react-components'; import accountObservable from '@polkadot/ui-keyring/observable/accounts'; import { ApiContext, withCalls, withMulti, withObservable } from '@polkadot/react-api'; @@ -20,10 +23,11 @@ interface Props extends I18nProps { onClick: () => void; allAccounts?: SubjectInfo; route: Route; - sudoKey: string; + sudoKey?: AccountId; } const disabledLog: Map = new Map(); +const TOOLTIP_OFFSET = { right: -4 }; function logDisabled (route: string, message: string): void { if (!disabledLog.get(route)) { @@ -33,53 +37,64 @@ function logDisabled (route: string, message: string): void { } } -function Item ({ allAccounts, route: { Modal, display: { isHidden, needsAccounts, needsApi, needsSudo }, i18n, icon, name }, t, isCollapsed, onClick, sudoKey }: Props): React.ReactElement | null { - const { api, isApiConnected, isApiReady } = useContext(ApiContext); +function hasEndpoint (api: ApiPromise, endpoint: string): boolean { + const [area, section, method] = endpoint.split('.'); - const _hasApi = (endpoint: string): boolean => { - const [area, section, method] = endpoint.split('.'); + try { + return isFunction((api as any)[area][section][method]); + } catch (error) { + return false; + } +} - try { - return isFunction((api as any)[area][section][method]); - } catch (error) { - return false; - } - }; - const _isVisible = (): boolean => { - const hasAccounts = !!allAccounts && Object.keys(allAccounts).length !== 0; - const hasSudo = !!allAccounts && Object.keys(allAccounts).some((address): boolean => address === sudoKey); +function checkVisible (name: string, { api, isApiReady, isApiConnected }: ApiProps, hasAccounts: boolean, hasSudo: boolean, { isHidden, needsAccounts, needsApi, needsSudo }: Route['display']): boolean { + if (isHidden) { + return false; + } else if (needsAccounts && !hasAccounts) { + return false; + } else if (!needsApi) { + return true; + } else if (!isApiReady || !isApiConnected) { + return false; + } else if (needsSudo && !hasSudo) { + logDisabled(name, 'Sudo key not available'); + return false; + } - if (isHidden) { - return false; - } else if (needsAccounts && !hasAccounts) { - return false; - } else if (!needsApi) { - return true; - } else if (!isApiReady || !isApiConnected) { - return false; - } else if (needsSudo) { - if (!hasSudo) { - logDisabled(name, 'Sudo key not available'); - return false; - } - } + const notFound = needsApi.filter((endpoint: string | string[]): boolean => { + const hasApi = Array.isArray(endpoint) + ? endpoint.reduce((hasApi, endpoint): boolean => hasApi || hasEndpoint(api, endpoint), false) + : hasEndpoint(api, endpoint); - const notFound = needsApi.filter((endpoint: string | string[]): boolean => { - const hasApi = Array.isArray(endpoint) - ? endpoint.reduce((hasApi, endpoint): boolean => hasApi || _hasApi(endpoint), false) - : _hasApi(endpoint); + return !hasApi; + }); - return !hasApi; - }); + if (notFound.length !== 0) { + logDisabled(name, `API not available: ${notFound}`); + } - if (notFound.length !== 0) { - logDisabled(name, `API not available: ${notFound}`); - } + return notFound.length === 0; +} - return notFound.length === 0; - }; +function Item ({ allAccounts, route: { Modal, display, i18n, icon, name }, t, isCollapsed, onClick, sudoKey }: Props): React.ReactElement | null { + const apiProps = useContext(ApiContext); + const [hasAccounts, setHasAccounts] = useState(false); + const [hasSudo, setHasSudo] = useState(false); + const [isVisible, setIsVisible] = useState(false); - if (!_isVisible()) { + useEffect((): void => { + setHasAccounts(Object.keys(allAccounts || {}).length !== 0); + }, [allAccounts]); + + useEffect((): void => { + setHasSudo(!!sudoKey && Object.keys(allAccounts || {}).some((address): boolean => sudoKey.eq(address))); + }, [allAccounts, sudoKey]); + + useEffect((): void => { + setIsVisible(checkVisible(name, apiProps, hasAccounts, hasSudo, display)); + }, [apiProps, hasAccounts, hasSudo]); + + if (!isVisible) { return null; } @@ -88,7 +103,7 @@ function Item ({ allAccounts, route: { Modal, display: { isHidden, needsAccounts {t(`sidebar.${name}`, i18n)} ( - ['query.sudo.key', { - propName: 'sudoKey', - transform: (key): string => - key.toString() - }] + ['query.sudo.key', { propName: 'sudoKey' }] ), withObservable(accountObservable.subject, { propName: 'allAccounts' }) );