1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import * as React from 'react';
    
  11. import {
    
  12.   useContext,
    
  13.   unstable_useCacheRefresh as useCacheRefresh,
    
  14.   useTransition,
    
  15. } from 'react';
    
  16. import Button from '../Button';
    
  17. import ButtonIcon from '../ButtonIcon';
    
  18. import Store from '../../store';
    
  19. import sharedStyles from './InspectedElementSharedStyles.css';
    
  20. import styles from './InspectedElementErrorsAndWarningsTree.css';
    
  21. import {SettingsContext} from '../Settings/SettingsContext';
    
  22. import {
    
  23.   clearErrorsForElement as clearErrorsForElementAPI,
    
  24.   clearWarningsForElement as clearWarningsForElementAPI,
    
  25. } from 'react-devtools-shared/src/backendAPI';
    
  26. 
    
  27. import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
    
  28. import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
    
  29. 
    
  30. type Props = {
    
  31.   bridge: FrontendBridge,
    
  32.   inspectedElement: InspectedElement,
    
  33.   store: Store,
    
  34. };
    
  35. 
    
  36. export default function InspectedElementErrorsAndWarningsTree({
    
  37.   bridge,
    
  38.   inspectedElement,
    
  39.   store,
    
  40. }: Props): React.Node {
    
  41.   const refresh = useCacheRefresh();
    
  42. 
    
  43.   const [isErrorsTransitionPending, startClearErrorsTransition] =
    
  44.     useTransition();
    
  45.   const clearErrorsForInspectedElement = () => {
    
  46.     const {id} = inspectedElement;
    
  47.     const rendererID = store.getRendererIDForElement(id);
    
  48.     if (rendererID !== null) {
    
  49.       startClearErrorsTransition(() => {
    
  50.         clearErrorsForElementAPI({
    
  51.           bridge,
    
  52.           id,
    
  53.           rendererID,
    
  54.         });
    
  55.         refresh();
    
  56.       });
    
  57.     }
    
  58.   };
    
  59. 
    
  60.   const [isWarningsTransitionPending, startClearWarningsTransition] =
    
  61.     useTransition();
    
  62.   const clearWarningsForInspectedElement = () => {
    
  63.     const {id} = inspectedElement;
    
  64.     const rendererID = store.getRendererIDForElement(id);
    
  65.     if (rendererID !== null) {
    
  66.       startClearWarningsTransition(() => {
    
  67.         clearWarningsForElementAPI({
    
  68.           bridge,
    
  69.           id,
    
  70.           rendererID,
    
  71.         });
    
  72.         refresh();
    
  73.       });
    
  74.     }
    
  75.   };
    
  76. 
    
  77.   const {showInlineWarningsAndErrors} = useContext(SettingsContext);
    
  78.   if (!showInlineWarningsAndErrors) {
    
  79.     return null;
    
  80.   }
    
  81. 
    
  82.   const {errors, warnings} = inspectedElement;
    
  83. 
    
  84.   return (
    
  85.     <React.Fragment>
    
  86.       {errors.length > 0 && (
    
  87.         <Tree
    
  88.           badgeClassName={styles.ErrorBadge}
    
  89.           bridge={bridge}
    
  90.           className={styles.ErrorTree}
    
  91.           clearMessages={clearErrorsForInspectedElement}
    
  92.           entries={errors}
    
  93.           isTransitionPending={isErrorsTransitionPending}
    
  94.           label="errors"
    
  95.           messageClassName={styles.Error}
    
  96.         />
    
  97.       )}
    
  98.       {warnings.length > 0 && (
    
  99.         <Tree
    
  100.           badgeClassName={styles.WarningBadge}
    
  101.           bridge={bridge}
    
  102.           className={styles.WarningTree}
    
  103.           clearMessages={clearWarningsForInspectedElement}
    
  104.           entries={warnings}
    
  105.           isTransitionPending={isWarningsTransitionPending}
    
  106.           label="warnings"
    
  107.           messageClassName={styles.Warning}
    
  108.         />
    
  109.       )}
    
  110.     </React.Fragment>
    
  111.   );
    
  112. }
    
  113. 
    
  114. type TreeProps = {
    
  115.   badgeClassName: string,
    
  116.   actions: React$Node,
    
  117.   className: string,
    
  118.   clearMessages: () => void,
    
  119.   entries: Array<[string, number]>,
    
  120.   isTransitionPending: boolean,
    
  121.   label: string,
    
  122.   messageClassName: string,
    
  123. };
    
  124. 
    
  125. function Tree({
    
  126.   badgeClassName,
    
  127.   actions,
    
  128.   className,
    
  129.   clearMessages,
    
  130.   entries,
    
  131.   isTransitionPending,
    
  132.   label,
    
  133.   messageClassName,
    
  134. }: TreeProps) {
    
  135.   if (entries.length === 0) {
    
  136.     return null;
    
  137.   }
    
  138.   return (
    
  139.     <div className={`${sharedStyles.InspectedElementTree} ${className}`}>
    
  140.       <div className={`${sharedStyles.HeaderRow} ${styles.HeaderRow}`}>
    
  141.         <div className={sharedStyles.Header}>{label}</div>
    
  142.         <Button
    
  143.           disabled={isTransitionPending}
    
  144.           onClick={clearMessages}
    
  145.           title={`Clear all ${label} for this component`}>
    
  146.           <ButtonIcon type="clear" />
    
  147.         </Button>
    
  148.       </div>
    
  149.       {entries.map(([message, count], index) => (
    
  150.         <ErrorOrWarningView
    
  151.           key={`${label}-${index}`}
    
  152.           badgeClassName={badgeClassName}
    
  153.           className={messageClassName}
    
  154.           count={count}
    
  155.           message={message}
    
  156.         />
    
  157.       ))}
    
  158.     </div>
    
  159.   );
    
  160. }
    
  161. 
    
  162. type ErrorOrWarningViewProps = {
    
  163.   badgeClassName: string,
    
  164.   className: string,
    
  165.   count: number,
    
  166.   message: string,
    
  167. };
    
  168. 
    
  169. function ErrorOrWarningView({
    
  170.   className,
    
  171.   badgeClassName,
    
  172.   count,
    
  173.   message,
    
  174. }: ErrorOrWarningViewProps) {
    
  175.   return (
    
  176.     <div className={className}>
    
  177.       {count > 1 && <div className={badgeClassName}>{count}</div>}
    
  178.       <div className={styles.Message} title={message}>
    
  179.         {message}
    
  180.       </div>
    
  181.     </div>
    
  182.   );
    
  183. }