mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
177 lines
5.1 KiB
JavaScript
177 lines
5.1 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ViewState} from './types';
|
|
|
|
import * as React from 'react';
|
|
import {
|
|
Suspense,
|
|
useContext,
|
|
useDeferredValue,
|
|
useLayoutEffect,
|
|
useRef,
|
|
useState,
|
|
} from 'react';
|
|
import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
|
|
import {ProfilerContext} from 'react-devtools-shared/src/devtools/views/Profiler/ProfilerContext';
|
|
import NoProfilingData from 'react-devtools-shared/src/devtools/views/Profiler/NoProfilingData';
|
|
import RecordingInProgress from 'react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress';
|
|
import {updateColorsToMatchTheme} from './content-views/constants';
|
|
import {TimelineContext} from './TimelineContext';
|
|
import CanvasPage from './CanvasPage';
|
|
import {importFile} from './timelineCache';
|
|
import TimelineSearchInput from './TimelineSearchInput';
|
|
import TimelineNotSupported from './TimelineNotSupported';
|
|
import {TimelineSearchContextController} from './TimelineSearchContext';
|
|
|
|
import styles from './Timeline.css';
|
|
|
|
export function Timeline(_: {}): React.Node {
|
|
const {
|
|
file,
|
|
inMemoryTimelineData,
|
|
isPerformanceTracksSupported,
|
|
isTimelineSupported,
|
|
setFile,
|
|
viewState,
|
|
} = useContext(TimelineContext);
|
|
const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
|
|
|
|
const ref = useRef(null);
|
|
|
|
// HACK: Canvas rendering uses an imperative API,
|
|
// but DevTools colors are stored in CSS variables (see root.css and SettingsContext).
|
|
// When the theme changes, we need to trigger update the imperative colors and re-draw the Canvas.
|
|
const {theme} = useContext(SettingsContext);
|
|
// HACK: SettingsContext also uses a useLayoutEffect to update styles;
|
|
// make sure the theme context in SettingsContext updates before this code.
|
|
const deferredTheme = useDeferredValue(theme);
|
|
// HACK: Schedule a re-render of the Canvas once colors have been updated.
|
|
// The easiest way to guarangee this happens is to recreate the inner Canvas component.
|
|
const [key, setKey] = useState<string>(theme);
|
|
useLayoutEffect(() => {
|
|
const pollForTheme = () => {
|
|
if (updateColorsToMatchTheme(((ref.current: any): HTMLDivElement))) {
|
|
clearInterval(intervalID);
|
|
setKey(deferredTheme);
|
|
}
|
|
};
|
|
|
|
const intervalID = setInterval(pollForTheme, 50);
|
|
|
|
return () => {
|
|
clearInterval(intervalID);
|
|
};
|
|
}, [deferredTheme]);
|
|
|
|
let content = null;
|
|
if (isProfiling) {
|
|
content = <RecordingInProgress />;
|
|
} else if (inMemoryTimelineData && inMemoryTimelineData.length > 0) {
|
|
// TODO (timeline) Support multiple renderers.
|
|
const timelineData = inMemoryTimelineData[0];
|
|
|
|
content = (
|
|
<TimelineSearchContextController
|
|
profilerData={timelineData}
|
|
viewState={viewState}>
|
|
<TimelineSearchInput />
|
|
<CanvasPage profilerData={timelineData} viewState={viewState} />
|
|
</TimelineSearchContextController>
|
|
);
|
|
} else if (file) {
|
|
content = (
|
|
<Suspense fallback={<ProcessingData />}>
|
|
<FileLoader
|
|
file={file}
|
|
key={key}
|
|
onFileSelect={setFile}
|
|
viewState={viewState}
|
|
/>
|
|
</Suspense>
|
|
);
|
|
} else if (didRecordCommits) {
|
|
content = <NoTimelineData />;
|
|
} else if (isTimelineSupported) {
|
|
content = <NoProfilingData />;
|
|
} else {
|
|
content = (
|
|
<TimelineNotSupported
|
|
isPerformanceTracksSupported={isPerformanceTracksSupported}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={styles.Content} ref={ref}>
|
|
{content}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ProcessingData = () => (
|
|
<div className={styles.EmptyStateContainer}>
|
|
<div className={styles.Header}>Processing data...</div>
|
|
<div className={styles.Row}>This should only take a minute.</div>
|
|
</div>
|
|
);
|
|
|
|
// $FlowFixMe[missing-local-annot]
|
|
const CouldNotLoadProfile = ({error, onFileSelect}) => (
|
|
<div className={styles.EmptyStateContainer}>
|
|
<div className={styles.Header}>Could not load profile</div>
|
|
{error.message && (
|
|
<div className={styles.Row}>
|
|
<div className={styles.ErrorMessage}>{error.message}</div>
|
|
</div>
|
|
)}
|
|
<div className={styles.Row}>
|
|
Try importing another Chrome performance profile.
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const NoTimelineData = () => (
|
|
<div className={styles.EmptyStateContainer}>
|
|
<div className={styles.Row}>
|
|
This current profile does not contain timeline data.
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const FileLoader = ({
|
|
file,
|
|
onFileSelect,
|
|
viewState,
|
|
}: {
|
|
file: File | null,
|
|
onFileSelect: (file: File) => void,
|
|
viewState: ViewState,
|
|
}) => {
|
|
if (file === null) {
|
|
return null;
|
|
}
|
|
|
|
const dataOrError = importFile(file);
|
|
if (dataOrError instanceof Error) {
|
|
return (
|
|
<CouldNotLoadProfile error={dataOrError} onFileSelect={onFileSelect} />
|
|
);
|
|
}
|
|
|
|
return (
|
|
<TimelineSearchContextController
|
|
profilerData={dataOrError}
|
|
viewState={viewState}>
|
|
<TimelineSearchInput />
|
|
<CanvasPage profilerData={dataOrError} viewState={viewState} />
|
|
</TimelineSearchContextController>
|
|
);
|
|
};
|