Item.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. // Copyright 2017-2020 @polkadot/apps authors & contributors
  2. // This software may be modified and distributed under the terms
  3. // of the Apache-2.0 license. See the LICENSE file for details.
  4. import { ApiProps } from '@polkadot/react-api/types';
  5. import { Route } from '@polkadot/apps-routing/types';
  6. import { AccountId } from '@polkadot/types/interfaces';
  7. import React, { useEffect, useState } from 'react';
  8. import { NavLink } from 'react-router-dom';
  9. import { Badge, Icon, Menu, Tooltip } from '@polkadot/react-components';
  10. import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
  11. import { findMissingApis } from '../endpoint';
  12. const DUMMY_COUNTER = (): null => null;
  13. interface Props {
  14. isCollapsed: boolean;
  15. onClick: () => void;
  16. route: Route;
  17. }
  18. const disabledLog = new Map<string, string>();
  19. const TOOLTIP_OFFSET = { right: -4 };
  20. function logDisabled (route: string, message: string): void {
  21. if (!disabledLog.get(route)) {
  22. disabledLog.set(route, message);
  23. console.warn(`Disabling ${route}: ${message}`);
  24. }
  25. }
  26. function checkVisible (name: string, { api, isApiConnected, isApiReady }: ApiProps, hasAccounts: boolean, hasSudo: boolean, { isHidden, needsAccounts, needsApi, needsSudo }: Route['display']): boolean {
  27. if (isHidden) {
  28. return false;
  29. } else if (needsAccounts && !hasAccounts) {
  30. return false;
  31. } else if (!needsApi) {
  32. return true;
  33. } else if (!isApiReady || !isApiConnected) {
  34. return false;
  35. } else if (needsSudo && !hasSudo) {
  36. logDisabled(name, 'Sudo key not available');
  37. return false;
  38. }
  39. const notFound = findMissingApis(api, needsApi);
  40. if (notFound.length !== 0) {
  41. logDisabled(name, `API not available: ${notFound.toString()}`);
  42. }
  43. return notFound.length === 0;
  44. }
  45. function Item ({ isCollapsed, onClick, route }: Props): React.ReactElement<Props> | null {
  46. const { allAccounts, hasAccounts } = useAccounts();
  47. const apiProps = useApi();
  48. const sudoKey = useCall<AccountId>(apiProps.isApiReady && apiProps.api.query.sudo?.key, []);
  49. const [hasSudo, setHasSudo] = useState(false);
  50. const [isVisible, setIsVisible] = useState(false);
  51. const count = (route.useCounter || DUMMY_COUNTER)();
  52. useEffect((): void => {
  53. setHasSudo(!!sudoKey && allAccounts.some((address): boolean => sudoKey.eq(address)));
  54. }, [allAccounts, sudoKey]);
  55. useEffect((): void => {
  56. const isVisible = checkVisible(route.name, apiProps, hasAccounts, hasSudo, route.display);
  57. route.isIgnored = !isVisible;
  58. setIsVisible(isVisible);
  59. }, [apiProps, hasAccounts, hasSudo, route]);
  60. if (!isVisible) {
  61. return null;
  62. }
  63. const { Modal, SubtitleComponent, icon, name, text } = route;
  64. const body = (
  65. <>
  66. <Icon icon={icon} />
  67. <span className='text'>
  68. {text}
  69. { SubtitleComponent && <SubtitleComponent/> }
  70. </span>
  71. {!!count && (
  72. <Badge
  73. color='counter'
  74. info={count}
  75. />
  76. )}
  77. <Tooltip
  78. offset={TOOLTIP_OFFSET}
  79. place='right'
  80. text={text}
  81. trigger={`nav-${name}`}
  82. />
  83. </>
  84. );
  85. return (
  86. <Menu.Item className='apps--SideBar-Item'>
  87. {Modal
  88. ? (
  89. <a
  90. className='apps--SideBar-Item-NavLink'
  91. data-for={`nav-${name}`}
  92. data-tip
  93. data-tip-disable={!isCollapsed}
  94. onClick={onClick}
  95. >
  96. {body}
  97. </a>
  98. )
  99. : (
  100. <NavLink
  101. activeClassName='apps--SideBar-Item-NavLink-active ui--highlight--border'
  102. className='apps--SideBar-Item-NavLink'
  103. data-for={`nav-${name}`}
  104. data-tip
  105. data-tip-disable={!isCollapsed}
  106. onClick={onClick}
  107. to={`/${name}`}
  108. >
  109. {body}
  110. </NavLink>
  111. )
  112. }
  113. </Menu.Item>
  114. );
  115. }
  116. export default React.memo(Item);