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 {Fragment, useContext, useCallback, useRef} from 'react';
    
  12. import {ProfilerContext} from './ProfilerContext';
    
  13. import {ModalDialogContext} from '../ModalDialog';
    
  14. import Button from '../Button';
    
  15. import ButtonIcon from '../ButtonIcon';
    
  16. import {StoreContext} from '../context';
    
  17. import {
    
  18.   prepareProfilingDataExport,
    
  19.   prepareProfilingDataFrontendFromExport,
    
  20. } from './utils';
    
  21. import {downloadFile} from '../utils';
    
  22. import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
    
  23. import isArray from 'shared/isArray';
    
  24. import hasOwnProperty from 'shared/hasOwnProperty';
    
  25. 
    
  26. import styles from './ProfilingImportExportButtons.css';
    
  27. 
    
  28. import type {ProfilingDataExport} from './types';
    
  29. 
    
  30. export default function ProfilingImportExportButtons(): React.Node {
    
  31.   const {isProfiling, profilingData, rootID} = useContext(ProfilerContext);
    
  32.   const {setFile} = useContext(TimelineContext);
    
  33.   const store = useContext(StoreContext);
    
  34.   const {profilerStore} = store;
    
  35. 
    
  36.   const inputRef = useRef<HTMLInputElement | null>(null);
    
  37.   const downloadRef = useRef<HTMLAnchorElement | null>(null);
    
  38. 
    
  39.   const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
    
  40. 
    
  41.   const doesHaveInMemoryData = profilerStore.didRecordCommits;
    
  42. 
    
  43.   const downloadData = useCallback(() => {
    
  44.     if (rootID === null) {
    
  45.       return;
    
  46.     }
    
  47. 
    
  48.     const anchorElement = downloadRef.current;
    
  49. 
    
  50.     if (profilingData !== null && anchorElement !== null) {
    
  51.       const profilingDataExport = prepareProfilingDataExport(profilingData);
    
  52.       const date = new Date();
    
  53.       const dateString = date
    
  54.         .toLocaleDateString(undefined, {
    
  55.           year: 'numeric',
    
  56.           month: '2-digit',
    
  57.           day: '2-digit',
    
  58.         })
    
  59.         .replace(/\//g, '-');
    
  60.       const timeString = date
    
  61.         .toLocaleTimeString(undefined, {
    
  62.           hour12: false,
    
  63.         })
    
  64.         .replace(/:/g, '-');
    
  65.       downloadFile(
    
  66.         anchorElement,
    
  67.         `profiling-data.${dateString}.${timeString}.json`,
    
  68.         JSON.stringify(profilingDataExport, null, 2),
    
  69.       );
    
  70.     }
    
  71.   }, [rootID, profilingData]);
    
  72. 
    
  73.   const clickInputElement = useCallback(() => {
    
  74.     if (inputRef.current !== null) {
    
  75.       inputRef.current.click();
    
  76.     }
    
  77.   }, []);
    
  78. 
    
  79.   // TODO (profiling) We should probably use a transition for this and suspend while loading the file.
    
  80.   // Local files load so fast it's probably not very noticeable though.
    
  81.   const handleChange = () => {
    
  82.     const input = inputRef.current;
    
  83.     if (input !== null && input.files.length > 0) {
    
  84.       const file = input.files[0];
    
  85. 
    
  86.       // TODO (profiling) Handle fileReader errors.
    
  87.       const fileReader = new FileReader();
    
  88.       fileReader.addEventListener('load', () => {
    
  89.         const raw = ((fileReader.result: any): string);
    
  90.         const json = JSON.parse(raw);
    
  91. 
    
  92.         if (!isArray(json) && hasOwnProperty.call(json, 'version')) {
    
  93.           // This looks like React profiling data.
    
  94.           // But first, clear any User Timing marks; we should only have one type open at a time.
    
  95.           setFile(null);
    
  96. 
    
  97.           try {
    
  98.             const profilingDataExport = ((json: any): ProfilingDataExport);
    
  99.             profilerStore.profilingData =
    
  100.               prepareProfilingDataFrontendFromExport(profilingDataExport);
    
  101.           } catch (error) {
    
  102.             modalDialogDispatch({
    
  103.               id: 'ProfilingImportExportButtons',
    
  104.               type: 'SHOW',
    
  105.               title: 'Import failed',
    
  106.               content: (
    
  107.                 <Fragment>
    
  108.                   <div>The profiling data you selected cannot be imported.</div>
    
  109.                   {error !== null && (
    
  110.                     <div className={styles.ErrorMessage}>{error.message}</div>
    
  111.                   )}
    
  112.                 </Fragment>
    
  113.               ),
    
  114.             });
    
  115.           }
    
  116.         } else {
    
  117.           // Otherwise let's assume this is Trace Event data and pass it to the Timeline preprocessor.
    
  118.           // But first, clear React profiling data; we should only have one type open at a time.
    
  119.           profilerStore.clear();
    
  120. 
    
  121.           // TODO (timeline) We shouldn't need to re-open the File but we'll need to refactor to avoid this.
    
  122.           setFile(file);
    
  123.         }
    
  124.       });
    
  125.       fileReader.readAsText(file);
    
  126.     }
    
  127.   };
    
  128. 
    
  129.   return (
    
  130.     <Fragment>
    
  131.       <div className={styles.VRule} />
    
  132.       <input
    
  133.         ref={inputRef}
    
  134.         className={styles.Input}
    
  135.         type="file"
    
  136.         accept=".json"
    
  137.         onChange={handleChange}
    
  138.         tabIndex={-1}
    
  139.       />
    
  140.       <a ref={downloadRef} className={styles.Input} />
    
  141.       <Button
    
  142.         disabled={isProfiling}
    
  143.         onClick={clickInputElement}
    
  144.         title="Load profile...">
    
  145.         <ButtonIcon type="import" />
    
  146.       </Button>
    
  147.       <Button
    
  148.         disabled={isProfiling || !doesHaveInMemoryData}
    
  149.         onClick={downloadData}
    
  150.         title="Save profile...">
    
  151.         <ButtonIcon type="export" />
    
  152.       </Button>
    
  153.     </Fragment>
    
  154.   );
    
  155. }