10
import type {ReactContext} from 'shared/ReactTypes';
11
12
import * as React from 'react';
13
-import {
14
- createContext,
15
- useCallback,
16
- useContext,
17
- useEffect,
18
- useMemo,
19
- useState,
20
-} from 'react';
21
-import {createResource} from 'react-devtools-shared/src/devtools/cache';
13
+import {createContext, useContext, useEffect, useState} from 'react';
14
import {
15
BridgeContext,
16
StoreContext,
17
} from 'react-devtools-shared/src/devtools/views/context';
26
-import {TreeStateContext} from '../TreeContext';
18
+import {TreeStateContext} from 'react-devtools-shared/src/devtools/views/Components/TreeContext';
19
28
-import type {StateContext} from '../TreeContext';
20
+import type {StateContext} from 'react-devtools-shared/src/devtools/views/Components/TreeContext';
21
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
22
import type Store from 'react-devtools-shared/src/devtools/store';
23
import type {StyleAndLayout as StyleAndLayoutBackend} from 'react-devtools-shared/src/backend/NativeStyleEditor/types';
24
import type {StyleAndLayout as StyleAndLayoutFrontend} from './types';
33
-import type {Element} from 'react-devtools-shared/src/frontend/types';
34
-import type {
35
- Resource,
36
- Thenable,
37
-} from 'react-devtools-shared/src/devtools/cache';
38
-
39
-export type GetStyleAndLayout = (id: number) => StyleAndLayoutFrontend | null;
25
41
-type Context = {
42
- getStyleAndLayout: GetStyleAndLayout,
43
-};
26
+type Context = StyleAndLayoutFrontend | null;
27
28
const NativeStyleContext: ReactContext<Context> = createContext<Context>(
29
((null: any): Context),
30
);
31
NativeStyleContext.displayName = 'NativeStyleContext';
32
50
-type ResolveFn = (styleAndLayout: StyleAndLayoutFrontend) => void;
51
-type InProgressRequest = {
52
- promise: Thenable<StyleAndLayoutFrontend>,
53
- resolveFn: ResolveFn,
54
-};
55
-
56
-const inProgressRequests: WeakMap<Element, InProgressRequest> = new WeakMap();
57
-const resource: Resource<Element, Element, StyleAndLayoutFrontend> =
58
- createResource(
59
- (element: Element) => {
60
- const request = inProgressRequests.get(element);
61
- if (request != null) {
62
- return request.promise;
63
- }
64
-
65
- let resolveFn:
66
- | ResolveFn
67
- | ((
68
- result: Promise<StyleAndLayoutFrontend> | StyleAndLayoutFrontend,
69
- ) => void) = ((null: any): ResolveFn);
70
- const promise = new Promise(resolve => {
71
- resolveFn = resolve;
72
- });
73
-
74
- inProgressRequests.set(element, ({promise, resolveFn}: $FlowFixMe));
75
-
76
- return (promise: $FlowFixMe);
77
- },
78
- (element: Element) => element,
79
- {useWeakMap: true},
80
- );
81
-
33
type Props = {
34
children: React$Node,
35
};
37
function NativeStyleContextController({children}: Props): React.Node {
38
const bridge = useContext<FrontendBridge>(BridgeContext);
39
const store = useContext<Store>(StoreContext);
89
-
90
- const getStyleAndLayout = useCallback<GetStyleAndLayout>(
91
- (id: number) => {
92
- const element = store.getElementByID(id);
93
- if (element !== null) {
94
- return resource.read(element);
95
- } else {
96
- return null;
97
- }
98
- },
99
- [store],
100
- );
101
-
102
- // It's very important that this context consumes inspectedElementID and not NativeStyleID.
103
- // Otherwise the effect that sends the "inspect" message across the bridge-
104
- // would itself be blocked by the same render that suspends (waiting for the data).
40
const {inspectedElementID} = useContext<StateContext>(TreeStateContext);
41
42
const [currentStyleAndLayout, setCurrentStyleAndLayout] =
43
useState<StyleAndLayoutFrontend | null>(null);
44
110
- // This effect handler invalidates the suspense cache and schedules rendering updates with React.
111
- useEffect(() => {
112
- const onStyleAndLayout = ({id, layout, style}: StyleAndLayoutBackend) => {
113
- const element = store.getElementByID(id);
114
- if (element !== null) {
115
- const styleAndLayout: StyleAndLayoutFrontend = {
116
- layout,
117
- style,
118
- };
119
- const request = inProgressRequests.get(element);
120
- if (request != null) {
121
- inProgressRequests.delete(element);
122
- request.resolveFn(styleAndLayout);
123
- setCurrentStyleAndLayout(styleAndLayout);
124
- } else {
125
- resource.write(element, styleAndLayout);
126
-
127
- // Schedule update with React if the currently-selected element has been invalidated.
128
- if (id === inspectedElementID) {
129
- setCurrentStyleAndLayout(styleAndLayout);
130
- }
131
- }
132
- }
133
- };
134
-
135
- bridge.addListener('NativeStyleEditor_styleAndLayout', onStyleAndLayout);
136
- return () =>
137
- bridge.removeListener(
138
- 'NativeStyleEditor_styleAndLayout',
139
- onStyleAndLayout,
140
- );
141
- }, [bridge, currentStyleAndLayout, inspectedElementID, store]);
142
-
45
// This effect handler polls for updates on the currently selected element.
46
useEffect(() => {
47
if (inspectedElementID === null) {
48
+ setCurrentStyleAndLayout(null);
49
return () => {};
50
}
51
149
- const rendererID = store.getRendererIDForElement(inspectedElementID);
150
-
151
- let timeoutID: TimeoutID | null = null;
152
-
52
+ let requestTimeoutId: TimeoutID | null = null;
53
const sendRequest = () => {
154
- timeoutID = null;
54
+ requestTimeoutId = null;
55
+ const rendererID = store.getRendererIDForElement(inspectedElementID);
56
57
if (rendererID !== null) {
58
bridge.send('NativeStyleEditor_measure', {
66
// We'll poll for an update in the response handler below.
67
sendRequest();
68
168
- const onStyleAndLayout = ({id}: StyleAndLayoutBackend) => {
69
+ const onStyleAndLayout = ({id, layout, style}: StyleAndLayoutBackend) => {
70
// If this is the element we requested, wait a little bit and then ask for another update.
71
if (id === inspectedElementID) {
171
- if (timeoutID !== null) {
172
- clearTimeout(timeoutID);
72
+ if (requestTimeoutId !== null) {
73
+ clearTimeout(requestTimeoutId);
74
}
174
- timeoutID = setTimeout(sendRequest, 1000);
75
+ requestTimeoutId = setTimeout(sendRequest, 1000);
76
}
77
+
78
+ const styleAndLayout: StyleAndLayoutFrontend = {
79
+ layout,
80
+ style,
81
+ };
82
+ setCurrentStyleAndLayout(styleAndLayout);
83
};
84
85
bridge.addListener('NativeStyleEditor_styleAndLayout', onStyleAndLayout);
179
-
86
return () => {
87
bridge.removeListener(
88
'NativeStyleEditor_styleAndLayout',
89
onStyleAndLayout,
90
);
91
186
- if (timeoutID !== null) {
187
- clearTimeout(timeoutID);
92
+ if (requestTimeoutId !== null) {
93
+ clearTimeout(requestTimeoutId);
94
}
95
};
96
}, [bridge, inspectedElementID, store]);
97
192
- const value = useMemo(
193
- () => ({getStyleAndLayout}),
194
- // NativeStyle is used to invalidate the cache and schedule an update with React.
195
- [currentStyleAndLayout, getStyleAndLayout],
196
- );
197
-
98
return (
199
- <NativeStyleContext.Provider value={value}>
99
+ <NativeStyleContext.Provider value={currentStyleAndLayout}>
100
{children}
101
</NativeStyleContext.Provider>
102
);