main
js 147 lines 3.82 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 {useCallback, useContext, useEffect, useMemo, useRef} from 'react';
12 import {SettingsModalContext} from './SettingsModalContext';
13 import Button from '../Button';
14 import ButtonIcon from '../ButtonIcon';
15 import TabBar from '../TabBar';
16 import {StoreContext} from '../context';
17 import {
18 useLocalStorage,
19 useModalDismissSignal,
20 useSubscription,
21 } from '../hooks';
22 import ComponentsSettings from './ComponentsSettings';
23 import DebuggingSettings from './DebuggingSettings';
24 import GeneralSettings from './GeneralSettings';
25 import ProfilerSettings from './ProfilerSettings';
26
27 import styles from './SettingsModal.css';
28
29 import type Store from 'react-devtools-shared/src/devtools/store';
30
31 type TabID = 'general' | 'debugging' | 'components' | 'profiler';
32
33 export default function SettingsModal(): React.Node {
34 const {isModalShowing, setIsModalShowing} = useContext(SettingsModalContext);
35 const store = useContext(StoreContext);
36 const {profilerStore} = store;
37
38 // Updating preferences while profiling is in progress could break things (e.g. filtering)
39 // Explicitly disallow it for now.
40 const isProfilingSubscription = useMemo(
41 () => ({
42 getCurrentValue: () => profilerStore.isProfilingBasedOnUserInput,
43 subscribe: (callback: Function) => {
44 profilerStore.addListener('isProfiling', callback);
45 return () => profilerStore.removeListener('isProfiling', callback);
46 },
47 }),
48 [profilerStore],
49 );
50 const isProfiling = useSubscription<boolean>(isProfilingSubscription);
51 if (isProfiling && isModalShowing) {
52 setIsModalShowing(false);
53 }
54
55 if (!isModalShowing) {
56 return null;
57 }
58
59 return <SettingsModalImpl store={store} />;
60 }
61
62 type ImplProps = {store: Store};
63
64 function SettingsModalImpl({store}: ImplProps) {
65 const {setIsModalShowing, environmentNames, hookSettings} =
66 useContext(SettingsModalContext);
67 const dismissModal = useCallback(
68 () => setIsModalShowing(false),
69 [setIsModalShowing],
70 );
71
72 const [selectedTabID, selectTab] = useLocalStorage<TabID>(
73 'React::DevTools::selectedSettingsTabID',
74 'general',
75 );
76
77 const modalRef = useRef<HTMLDivElement | null>(null);
78 useModalDismissSignal(modalRef, dismissModal);
79
80 useEffect(() => {
81 if (modalRef.current !== null) {
82 modalRef.current.focus();
83 }
84 }, [modalRef]);
85
86 let view = null;
87 switch (selectedTabID) {
88 case 'components':
89 view = <ComponentsSettings environmentNames={environmentNames} />;
90 break;
91 case 'debugging':
92 view = <DebuggingSettings hookSettings={hookSettings} store={store} />;
93 break;
94 case 'general':
95 view = <GeneralSettings />;
96 break;
97 case 'profiler':
98 view = <ProfilerSettings />;
99 break;
100 default:
101 break;
102 }
103
104 return (
105 <div className={styles.Background}>
106 <div className={styles.Modal} ref={modalRef}>
107 <div className={styles.Tabs}>
108 <TabBar
109 currentTab={selectedTabID}
110 id="Settings"
111 selectTab={selectTab}
112 tabs={tabs}
113 type="settings"
114 />
115 <div className={styles.Spacer} />
116 <Button onClick={dismissModal} title="Close settings dialog">
117 <ButtonIcon type="close" />
118 </Button>
119 </div>
120 <div className={styles.Content}>{view}</div>
121 </div>
122 </div>
123 );
124 }
125
126 const tabs = [
127 {
128 id: 'general',
129 icon: 'settings',
130 label: 'General',
131 },
132 {
133 id: 'debugging',
134 icon: 'bug',
135 label: 'Debugging',
136 },
137 {
138 id: 'components',
139 icon: 'components',
140 label: 'Components',
141 },
142 {
143 id: 'profiler',
144 icon: 'profiler',
145 label: 'Profiler',
146 },
147 ];