main
js 183 lines 4.53 KB
Raw
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 unstable_useCacheRefresh as useCacheRefresh,
13 useTransition,
14 } from 'react';
15 import Button from '../Button';
16 import ButtonIcon from '../ButtonIcon';
17 import Store from '../../store';
18 import sharedStyles from './InspectedElementSharedStyles.css';
19 import styles from './InspectedElementErrorsAndWarningsTree.css';
20 import {
21 clearErrorsForElement as clearErrorsForElementAPI,
22 clearWarningsForElement as clearWarningsForElementAPI,
23 } from 'react-devtools-shared/src/backendAPI';
24
25 import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
26 import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
27
28 type Props = {
29 bridge: FrontendBridge,
30 inspectedElement: InspectedElement,
31 store: Store,
32 };
33
34 export default function InspectedElementErrorsAndWarningsTree({
35 bridge,
36 inspectedElement,
37 store,
38 }: Props): React.Node {
39 const refresh = useCacheRefresh();
40
41 const [isErrorsTransitionPending, startClearErrorsTransition] =
42 useTransition();
43 const clearErrorsForInspectedElement = () => {
44 const {id} = inspectedElement;
45 const rendererID = store.getRendererIDForElement(id);
46 if (rendererID !== null) {
47 startClearErrorsTransition(() => {
48 clearErrorsForElementAPI({
49 bridge,
50 id,
51 rendererID,
52 });
53 refresh();
54 });
55 }
56 };
57
58 const [isWarningsTransitionPending, startClearWarningsTransition] =
59 useTransition();
60 const clearWarningsForInspectedElement = () => {
61 const {id} = inspectedElement;
62 const rendererID = store.getRendererIDForElement(id);
63 if (rendererID !== null) {
64 startClearWarningsTransition(() => {
65 clearWarningsForElementAPI({
66 bridge,
67 id,
68 rendererID,
69 });
70 refresh();
71 });
72 }
73 };
74
75 if (!store.displayingErrorsAndWarningsEnabled) {
76 return null;
77 }
78
79 const {errors, warnings} = inspectedElement;
80 if (errors.length === 0 && warnings.length === 0) {
81 return null;
82 }
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={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 }