1. import React, {Component} from 'react';
    
  2. import Draggable from 'react-draggable';
    
  3. import ReactNoop from 'react-noop-renderer';
    
  4. import Editor from './Editor';
    
  5. import Fibers from './Fibers';
    
  6. import describeFibers from './describeFibers';
    
  7. 
    
  8. // The only place where we use it.
    
  9. const ReactFiberInstrumentation =
    
  10.   ReactNoop.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
    
  11.     .ReactFiberInstrumentation;
    
  12. 
    
  13. function getFiberState(root, workInProgress) {
    
  14.   if (!root) {
    
  15.     return null;
    
  16.   }
    
  17.   return describeFibers(root.current, workInProgress);
    
  18. }
    
  19. 
    
  20. const defaultCode = `
    
  21. log('Render <div>Hello</div>');
    
  22. ReactNoop.render(<div>Hello</div>);
    
  23. ReactNoop.flush();
    
  24. 
    
  25. log('Render <h1>Goodbye</h1>');
    
  26. ReactNoop.render(<h1>Goodbye</h1>);
    
  27. ReactNoop.flush();
    
  28. `;
    
  29. 
    
  30. class App extends Component {
    
  31.   constructor(props) {
    
  32.     super(props);
    
  33.     this.state = {
    
  34.       code: localStorage.getItem('fiber-debugger-code') || defaultCode,
    
  35.       isEditing: false,
    
  36.       history: [],
    
  37.       currentStep: 0,
    
  38.       show: {
    
  39.         alt: false,
    
  40.         child: true,
    
  41.         sibling: true,
    
  42.         return: false,
    
  43.         fx: false,
    
  44.       },
    
  45.       graphSettings: {
    
  46.         rankdir: 'TB',
    
  47.         trackActive: true,
    
  48.       },
    
  49.     };
    
  50.   }
    
  51. 
    
  52.   componentDidMount() {
    
  53.     this.runCode(this.state.code);
    
  54.   }
    
  55. 
    
  56.   runCode(code) {
    
  57.     let currentStage;
    
  58.     let currentRoot;
    
  59. 
    
  60.     ReactFiberInstrumentation.debugTool = null;
    
  61.     ReactNoop.render(null);
    
  62.     ReactNoop.flush();
    
  63.     ReactFiberInstrumentation.debugTool = {
    
  64.       onMountContainer: root => {
    
  65.         currentRoot = root;
    
  66.       },
    
  67.       onUpdateContainer: root => {
    
  68.         currentRoot = root;
    
  69.       },
    
  70.       onBeginWork: fiber => {
    
  71.         const fibers = getFiberState(currentRoot, fiber);
    
  72.         const stage = currentStage;
    
  73.         this.setState(({history}) => ({
    
  74.           history: [
    
  75.             ...history,
    
  76.             {
    
  77.               action: 'BEGIN',
    
  78.               fibers,
    
  79.               stage,
    
  80.             },
    
  81.           ],
    
  82.         }));
    
  83.       },
    
  84.       onCompleteWork: fiber => {
    
  85.         const fibers = getFiberState(currentRoot, fiber);
    
  86.         const stage = currentStage;
    
  87.         this.setState(({history}) => ({
    
  88.           history: [
    
  89.             ...history,
    
  90.             {
    
  91.               action: 'COMPLETE',
    
  92.               fibers,
    
  93.               stage,
    
  94.             },
    
  95.           ],
    
  96.         }));
    
  97.       },
    
  98.       onCommitWork: fiber => {
    
  99.         const fibers = getFiberState(currentRoot, fiber);
    
  100.         const stage = currentStage;
    
  101.         this.setState(({history}) => ({
    
  102.           history: [
    
  103.             ...history,
    
  104.             {
    
  105.               action: 'COMMIT',
    
  106.               fibers,
    
  107.               stage,
    
  108.             },
    
  109.           ],
    
  110.         }));
    
  111.       },
    
  112.     };
    
  113.     window.React = React;
    
  114.     window.ReactNoop = ReactNoop;
    
  115.     window.expect = () => ({
    
  116.       toBe() {},
    
  117.       toContain() {},
    
  118.       toEqual() {},
    
  119.     });
    
  120.     window.log = s => (currentStage = s);
    
  121.     // eslint-disable-next-line
    
  122.     eval(
    
  123.       window.Babel.transform(code, {
    
  124.         presets: ['react', 'es2015'],
    
  125.       }).code
    
  126.     );
    
  127.   }
    
  128. 
    
  129.   handleEdit = e => {
    
  130.     e.preventDefault();
    
  131.     this.setState({
    
  132.       isEditing: true,
    
  133.     });
    
  134.   };
    
  135. 
    
  136.   handleCloseEdit = nextCode => {
    
  137.     localStorage.setItem('fiber-debugger-code', nextCode);
    
  138.     this.setState({
    
  139.       isEditing: false,
    
  140.       history: [],
    
  141.       currentStep: 0,
    
  142.       code: nextCode,
    
  143.     });
    
  144.     this.runCode(nextCode);
    
  145.   };
    
  146. 
    
  147.   render() {
    
  148.     const {history, currentStep, isEditing, code} = this.state;
    
  149.     if (isEditing) {
    
  150.       return <Editor code={code} onClose={this.handleCloseEdit} />;
    
  151.     }
    
  152. 
    
  153.     const {fibers, action, stage} = history[currentStep] || {};
    
  154.     let friendlyAction;
    
  155.     if (fibers) {
    
  156.       let wipFiber = fibers.descriptions[fibers.workInProgressID];
    
  157.       let friendlyFiber = wipFiber.type || wipFiber.tag + ' #' + wipFiber.id;
    
  158.       friendlyAction = `After ${action} on ${friendlyFiber}`;
    
  159.     }
    
  160. 
    
  161.     return (
    
  162.       <div style={{height: '100%'}}>
    
  163.         {fibers && (
    
  164.           <Draggable>
    
  165.             <Fibers
    
  166.               fibers={fibers}
    
  167.               show={this.state.show}
    
  168.               graphSettings={this.state.graphSettings}
    
  169.             />
    
  170.           </Draggable>
    
  171.         )}
    
  172.         <div
    
  173.           style={{
    
  174.             width: '100%',
    
  175.             textAlign: 'center',
    
  176.             position: 'fixed',
    
  177.             bottom: 0,
    
  178.             padding: 10,
    
  179.             zIndex: 1,
    
  180.             backgroundColor: '#fafafa',
    
  181.             border: '1px solid #ccc',
    
  182.           }}>
    
  183.           <div style={{width: '50%', float: 'left'}}>
    
  184.             <input
    
  185.               type="range"
    
  186.               style={{width: '25%'}}
    
  187.               min={0}
    
  188.               max={history.length - 1}
    
  189.               value={currentStep}
    
  190.               onChange={e =>
    
  191.                 this.setState({currentStep: Number(e.target.value)})
    
  192.               }
    
  193.             />
    
  194.             <p>
    
  195.               Step {currentStep}: {friendlyAction} (
    
  196.               <a style={{color: 'gray'}} onClick={this.handleEdit} href="#">
    
  197.                 Edit
    
  198.               </a>
    
  199.               )
    
  200.             </p>
    
  201.             {stage && <p>Stage: {stage}</p>}
    
  202.             {Object.keys(this.state.show).map(key => (
    
  203.               <label style={{marginRight: '10px'}} key={key}>
    
  204.                 <input
    
  205.                   type="checkbox"
    
  206.                   checked={this.state.show[key]}
    
  207.                   onChange={e => {
    
  208.                     this.setState(({show}) => ({
    
  209.                       show: {...show, [key]: !show[key]},
    
  210.                     }));
    
  211.                   }}
    
  212.                 />
    
  213.                 {key}
    
  214.               </label>
    
  215.             ))}
    
  216.           </div>
    
  217.           <div style={{width: '50%', float: 'right'}}>
    
  218.             <h5>Graph Settings</h5>
    
  219.             <p>
    
  220.               <label style={{display: ''}}>
    
  221.                 Direction:
    
  222.                 <select
    
  223.                   onChange={e => {
    
  224.                     const rankdir = e.target.value;
    
  225.                     this.setState(({graphSettings}) => ({
    
  226.                       graphSettings: {...graphSettings, rankdir},
    
  227.                     }));
    
  228.                   }}>
    
  229.                   <option value="TB">top-down</option>
    
  230.                   <option value="BT">down-top</option>
    
  231.                   <option value="LR">left-right</option>
    
  232.                   <option value="RL">right-left</option>
    
  233.                 </select>
    
  234.               </label>
    
  235.             </p>
    
  236.             <p>
    
  237.               <label style={{marginRight: '10px'}}>
    
  238.                 <input
    
  239.                   type="checkbox"
    
  240.                   checked={this.state.graphSettings.trackActive}
    
  241.                   onChange={e => {
    
  242.                     this.setState(({graphSettings}) => ({
    
  243.                       graphSettings: {
    
  244.                         ...graphSettings,
    
  245.                         trackActive: !graphSettings.trackActive,
    
  246.                       },
    
  247.                     }));
    
  248.                   }}
    
  249.                 />
    
  250.                 Track active fiber
    
  251.               </label>
    
  252.             </p>
    
  253.           </div>
    
  254.         </div>
    
  255.       </div>
    
  256.     );
    
  257.   }
    
  258. }
    
  259. 
    
  260. export default App;