diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 77ab280032..0000000000 --- a/.babelrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "plugins": [ - ["@babel/plugin-transform-flow-strip-types"], - ["@babel/plugin-proposal-class-properties", { "loose": false }] - ], - "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-flow"] -} diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 6acfe4c656..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -# node_modules ignored by default - -**/build/** -**/vendor/** -**/node_modules/** -flow/* diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 6b6d63ccdb..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,47 +0,0 @@ ---- -parser: babel-eslint - -extends: - - ./node_modules/fbjs-scripts/eslint/.eslintrc.js - -plugins: - - react - -globals: - chrome: false - React$Element: false - -rules: - brace-style: [2, 1tbs] - comma-dangle: [2, always-multiline] - consistent-return: 2 - dot-location: [2, property] - dot-notation: 2 - eol-last: 2 - indent: [2, 2, {SwitchCase: 1}] - jsx-quotes: 2 - keyword-spacing: 2 - max-len: 0 - no-multi-spaces: 2 - no-redeclare: 2 - no-shadow: 2 - no-unused-expressions: 2 - no-unused-vars: [2, {args: none}] - prefer-const: 2 - quotes: [2, single, avoid-escape] - space-before-blocks: 2 - space-before-function-paren: 2 - - # JSX - # Our transforms set this automatically - react/display-name: 0 - react/jsx-boolean-value: [2, always] - react/jsx-no-undef: 2 - react/jsx-sort-prop-types: 0 - react/jsx-sort-props: 0 - react/jsx-uses-react: 2 - react/jsx-uses-vars: 2 - react/no-unknown-property: 2 - react/react-in-jsx-scope: 2 - react/self-closing-comp: 2 - react/wrap-multilines: [2, {declaration: false, assignment: false}] diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index ada0b64de9..0000000000 --- a/.flowconfig +++ /dev/null @@ -1,27 +0,0 @@ -[ignore] -.*/react/node_modules/.* -.*/electron/node_modules/.* -.*node_modules/archiver-utils -.*node_modules/babel.* -.*node_modules/browserify-zlib/.* -.*node_modules/classnames.* -.*node_modules/gh-pages/.* -.*node_modules/invariant/.* -.*node_modules/json-loader.* -.*node_modules/json5.* -.*node_modules/node-libs-browser.* -.*node_modules/webpack.* -.*node_modules/fbjs/flow.* -.*node_modules/web-ext.* - -[libs] -flow - -[options] -esproposal.class_instance_fields=enable -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue -suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore - -[version] -0.66.0 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 88d9b069b5..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/.github/ISSUE_TEMPLATE/issues-have-been-disabled.md b/.github/ISSUE_TEMPLATE/issues-have-been-disabled.md new file mode 100644 index 0000000000..196cf396ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issues-have-been-disabled.md @@ -0,0 +1,15 @@ +--- +name: Issues have been disabled +about: Please report new issues at github.com/facebook/react +title: NEW ISSUES ARE NOT MONITORED +labels: '' +assignees: '' + +--- + +This repository has been merged into the main React repo (github.com/facebook/react) + +Please open issues there: +https://github.com/facebook/react/issues/new + +New issues opened in this repository will be ignored. diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b53e962391..0000000000 --- a/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -/shells/chrome.crx -/shells/chrome.pem -/shells/firefox/*.xpi -build -node_modules -npm-debug.log -yarn-error.log -.DS_Store -yarn-error.log - diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b7c8a7492e..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -sudo: false -install: - - yarn -language: node_js -node_js: - - '8' -script: - - yarn run lint - - yarn run typecheck - - yarn run test -cache: - yarn: true -env: - - CXX=g++-4.8 -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index d5fe812031..0000000000 --- a/.yarnrc +++ /dev/null @@ -1,4 +0,0 @@ -ignore-scripts false - -# Allow dependencies to be added to the root package.json. ---ignore-workspace-root-check true diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 55203be746..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,3 +0,0 @@ -# Code of Conduct - -Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct) so that you can understand what actions will and will not be tolerated. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index c916b236a5..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,61 +0,0 @@ -# Contributing to React Devtools - -## Code of Conduct -Facebook has adopted a Code of Conduct that we expect project -participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) -so that you can understand what actions will and will not be tolerated. - -### Pull Requests - -The core team will be monitoring for pull requests. - -*Before* submitting a pull request, please make sure the following is done… - -1. Fork the repo and create your branch from `master`. -2. Within the repo, run `yarn install` -3. If you've added code that should be tested, add tests! -4. If you've changed APIs, update the documentation. -5. Make sure your code lints (`yarn run lint`) - we've done our best to make sure these rules match our internal linting guidelines. -6. Also make sure your code passes flow check(`yarn run typecheck`). -7. If you haven't already, complete the CLA. - -### Contributor License Agreement ("CLA") - -In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username. - -Complete your CLA here: - -## Bugs - -### Where to Find Known Issues - -We will be using GitHub Issues for our public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn't already exist. - -### Reporting New Issues - -The best way to get your bug fixed is to provide a reduced test case. jsFiddle, jsBin, and other sites provide a way to give live examples. Those are especially helpful though may not work for `JSX`-based code. - -### Security Bugs - -Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues and go through the process outlined on that page. - -## How to Get in Touch - -* IRC - [#reactjs on freenode](http://webchat.freenode.net/?channels=reactjs) -* Mailing list - [reactjs on Google Groups](http://groups.google.com/group/reactjs) - -## Coding Style - -* Use semicolons; -* Commas last, -* 2 spaces for indentation (no tabs) -* Prefer `'` over `"` -* `"use strict";` -* 80 character line length -* "Attractive" - -Please `yarn run lint`. - -## License - -By contributing to React, you agree that your contributions will be licensed under the [attached License](LICENSE). diff --git a/PATENTS b/PATENTS deleted file mode 100644 index e6b4bb420f..0000000000 --- a/PATENTS +++ /dev/null @@ -1,33 +0,0 @@ -Additional Grant of Patent Rights Version 2 - -“Software” means the react-devtools software distributed by Facebook, Inc. - -Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software -("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable -(subject to the termination provision below) license under any Necessary -Claims, to make, have made, use, sell, offer to sell, import, and otherwise -transfer the Software. For avoidance of doubt, no license is granted under -Facebook's rights in any patent claims that are infringed by (i) modifications -to the Software made by you or any third party or (ii) the Software in -combination with any software or other technology. - -The license granted hereunder will terminate, automatically and without notice, -if you (or any of your subsidiaries, corporate affiliates or agents) initiate -directly or indirectly, or take a direct financial interest in, any Patent -Assertion: (i) against Facebook or any of its subsidiaries or corporate -affiliates, (ii) against any party if such Patent Assertion arises in whole or -in part from any software, technology, product or service of Facebook or any of -its subsidiaries or corporate affiliates, or (iii) against any party relating -to the Software. Notwithstanding the foregoing, if Facebook or any of its -subsidiaries or corporate affiliates files a lawsuit alleging patent -infringement against you in the first instance, and you respond by filing a -patent infringement counterclaim in that lawsuit against that party that is -unrelated to the Software, the license granted hereunder will not terminate -under section (i) of this paragraph due to such counterclaim. - -A "Necessary Claim" is a claim of a patent owned by Facebook that is -necessarily infringed by the Software standing alone. - -A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, -or contributory infringement or inducement to infringe any patent, including a -cross-claim or counterclaim. diff --git a/README.md b/README.md index 14099a4b14..a9168c3ed1 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,14 @@ -# React Developer Tools [![Build Status](https://travis-ci.org/facebook/react-devtools.svg?branch=master)](https://travis-ci.org/facebook/react-devtools) +#### This project has migrated to [github.com/facebook/react](https://github.com/facebook/react) -React Developer Tools lets you inspect the React component hierarchy, including component props and state. +The source code for the v3 of the extension can be found in the [`v3` branch](https://github.com/facebook/react-devtools/tree/v3). -It exists both as a browser extension (for [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) and [Firefox](https://addons.mozilla.org/firefox/addon/react-devtools/)), and as a [standalone app](https://github.com/facebook/react-devtools/tree/master/packages/react-devtools) (works with other environments including Safari, IE, and React Native). +To build the v3 browser extension from source: +```sh +git checkout v3 -![](/images/devtools-full.gif) - -## Installation - -### Pre-packaged - -The official extensions represent the current stable release. - -- [Chrome extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) -- [Firefox extension](https://addons.mozilla.org/firefox/addon/react-devtools/) -- [Standalone app (Safari, React Native, etc)](https://github.com/facebook/react-devtools/blob/master/packages/react-devtools/README.md) - -Opera users can [enable Chrome extensions](https://addons.opera.com/extensions/details/download-chrome-extension-9/) and then install the [Chrome extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi). - -## Usage - -The extension icon will light up on the websites using React: - -Extension icon becomes active - -On such websites, you will see a tab called React in Chrome Developer Tools: - -React tab in DevTools - -A quick way to bring up the DevTools is to right-click on the page and press Inspect. - -### Tree View - -- Arrow keys or hjkl for navigation -- Right click a component to show in elements pane, scroll into view, show - source, etc. -- Differently-colored collapser means the component has state/context - -![](/images/devtools-tree-view.png) - -### Side Pane - -- Right-click to store as global variable -- Updates are highlighted - -![](/images/devtools-side-pane.gif) - -### Search Bar - -- Use the search bar to find components by name - -![](/images/devtools-search-new.gif) - -### Handy Tips - -#### Finding Component by a DOM Node - -If you inspect a React element on the page using the regular **Elements** tab, then switch over to the **React** tab, that element will be automatically selected in the React tree. - -#### Finding DOM Node by a Component - -You can right-click any React element in the **React** tab, and choose "Find the DOM node". This will bring you to the corresponding DOM node in the **Elements** tab. - -#### Displaying Element Source - -You may include the [transform-react-jsx-source](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx-source) Babel plugin to see the source file and line number of React elements. This information appears in the bottom of the right panel when available. Don't forget to disable it in production! (Tip: if you use [Create React App](https://github.com/facebookincubator/create-react-app) it is already enabled in development.) - -#### Usage with React Native and Safari - -There is a [standalone version](https://github.com/facebook/react-devtools/blob/master/packages/react-devtools/README.md) that works with other environments such as React Native and Safari. - -## FAQ - -### The React Tab Doesn't Show Up - -**If you are running your app from a local `file://` URL**, don't forget to check "Allow access to file URLs" on the Chrome Extensions settings page. You can find it by opening Settings > Extensions: - -![Allow access to file URLs](http://i.imgur.com/Yt1rmUp.png) - -Or you could develop with a local HTTP server [like `serve`](https://www.npmjs.com/package/serve). - -**The React tab won't show up if the site doesn't use React**, or if React can't communicate with the devtools. When the page loads, the devtools sets a global named `__REACT_DEVTOOLS_GLOBAL_HOOK__`, then React communicates with that hook during initialization. You can test this on the [React website](http://facebook.github.io/react/) or by inspecting [Facebook](https://www.facebook.com/). - -**If your app is inside of CodePen**, make sure you are registered. Then press Fork (if it's not your pen), and then choose Change View > Debug. The Debug view is inspectable with DevTools because it doesn't use an iframe. - -**If your app is inside an iframe, a Chrome extension, React Native, or in another unusual environment**, try [the standalone version instead](https://github.com/facebook/react-devtools/tree/master/packages/react-devtools). Chrome apps are currently not inspectable. - -**If you still have issues** please [report them](https://github.com/facebook/react-devtools/issues/new). Don't forget to specify your OS, browser version, extension version, and the exact instructions to reproduce the issue with a screenshot. - -### Does "Highlight Updates" trace renders? - -With React 15 and earlier, "Highlight Updates" had false positives and highlighted more components than were actually re-rendering. - -Since React 16, it correctly highlights only components that were re-rendered. - -## Contributing - -For changes that don't directly involve Chrome/Firefox/etc. APIs, there is a -"plain" shell that just renders the devtools into an html page along with a -TodoMVC test app. This is by far the quickest way to develop. Check out -[the Readme.md](/shells/plain) in `/shells/plain` for info. - -For other shells (Chrome, Firefox, etc.), see the respective directories in `/shells`. - -For a list of good contribution opportunities, check the [good first bug](https://github.com/facebook/react-devtools/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+bug%22) label. We're happy to answer any questions on those issues! - -To read more about the community and guidelines for submitting pull requests, -please read the [Contributing document](CONTRIBUTING.md). - -## Debugging (in Chrome) - -What to do if the extension breaks. - -- check the error console of devtools. Part of React Devtools runs scripts in - the context of your page, and is vulnerable to misbehaving polyfills. -- open devtools out into a new window, and then hit the shortcut to open - devtools again (cmd+option+j or ctrl+shift+j). This is the "debug - devtools" debugger. Check the console there for errors. -- open `chrome://extensions`, find react devtools, and click "background page" - under "Inspected views". You might find the errors there. +# Install dependencies and build the unpacked extension +yarn install +yarn build:extension +# Follow the on-screen instructions to complete installation +``` \ No newline at end of file diff --git a/agent/Agent.js b/agent/Agent.js deleted file mode 100644 index 66bab517d5..0000000000 --- a/agent/Agent.js +++ /dev/null @@ -1,492 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var {EventEmitter} = require('events'); - -var assign = require('object-assign'); -var nullthrows = require('nullthrows').default; -var guid = require('../utils/guid'); -var getIn = require('./getIn'); - -import type {RendererID, DataType, OpaqueNodeHandle, NativeType, Helpers} from '../backend/types'; - -type ElementID = string; - -import type Bridge from './Bridge'; - -/** - * The agent lives on the page in the same context as React, observes events - * from the `backend`, and communicates (via a `Bridge`) with the frontend. - * - * It is responsible for generating string IDs (ElementID) for each react - * element, maintaining a mapping of those IDs to elements, handling messages - * from the frontend, and translating between react elements and native - * handles. - * - * - * React - * | - * v - * backend - * | - * v - * ----------- - * | **Agent** | - * ----------- - * ^ - * | - * v - * (Bridge) - * ^ - * | - * serialization - * | - * v - * (Bridge) - * ^ - * | - * v - * ---------------- - * | Frontend Store | - * ---------------- - * - * - * Events from the `backend`: - * - root (got a root) - * - mount (a component mounted) - * - update (a component updated) - * - unmount (a component mounted) - * - * Events from the `frontend` Store: - * - see `addBridge` for subscriptions - * - * Events that Agent fires: - * - selected - * - hideHighlight - * - startInspecting - * - stopInspecting - * - shutdown - * - highlight /highlightMany - * - setSelection - * - root - * - mount - * - update - * - unmount - */ -class Agent extends EventEmitter { - // the window or global -> used to "make a value available in the console" - global: Object; - internalInstancesById: Map; - idsByInternalInstances: WeakMap; - renderers: Map; - elementData: Map; - roots: Set; - reactInternals: {[key: RendererID]: Helpers}; - _prevSelected: ?NativeType; - _scrollUpdate: boolean; - capabilities: {[key: string]: boolean}; - _updateScroll: () => void; - _inspectEnabled: boolean; - - constructor(global: Object, capabilities?: Object) { - super(); - this.global = global; - this.internalInstancesById = new Map(); - this.idsByInternalInstances = new WeakMap(); - this.renderers = new Map(); - this.elementData = new Map(); - this.roots = new Set(); - this.reactInternals = {}; - var lastSelected; - this.on('selected', id => { - var data = this.elementData.get(id); - if (data && data.publicInstance && this.global.$r === lastSelected) { - this.global.$r = data.publicInstance; - lastSelected = data.publicInstance; - } - }); - this._prevSelected = null; - this._scrollUpdate = false; - var isReactDOM = window.document && typeof window.document.createElement === 'function'; - this.capabilities = assign({ - scroll: isReactDOM && typeof window.document.body.scrollIntoView === 'function', - dom: isReactDOM, - editTextContent: false, - }, capabilities); - - if (isReactDOM) { - this._updateScroll = this._updateScroll.bind(this); - window.addEventListener('scroll', this._onScroll.bind(this), true); - window.addEventListener('click', this._onClick.bind(this), true); - window.addEventListener('mouseover', this._onMouseOver.bind(this), true); - window.addEventListener('resize', this._onResize.bind(this), true); - } - } - - // returns an "unsubscribe" function - sub(ev: string, fn: (data: any) => void): () => void { - this.on(ev, fn); - return () => { - this.removeListener(ev, fn); - }; - } - - setReactInternals(renderer: RendererID, reactInternals: Helpers) { - this.reactInternals[renderer] = reactInternals; - } - - addBridge(bridge: Bridge) { - /** Events received from the frontend **/ - // the initial handshake - bridge.on('requestCapabilities', () => { - bridge.send('capabilities', this.capabilities); - this.emit('connected'); - }); - bridge.on('setState', this._setState.bind(this)); - bridge.on('setProps', this._setProps.bind(this)); - bridge.on('setContext', this._setContext.bind(this)); - bridge.on('makeGlobal', this._makeGlobal.bind(this)); - bridge.on('highlight', id => this.highlight(id)); - bridge.on('highlightMany', id => this.highlightMany(id)); - bridge.on('hideHighlight', () => this.emit('hideHighlight')); - bridge.on('startInspecting', () => this.emit('startInspecting')); - bridge.on('stopInspecting', () => this.emit('stopInspecting')); - bridge.on('selected', id => this.emit('selected', id)); - bridge.on('isRecording', isRecording => this.emit('isRecording', isRecording)); - bridge.on('setInspectEnabled', enabled => { - this._inspectEnabled = enabled; - this.emit('stopInspecting'); - }); - bridge.on('shutdown', () => this.emit('shutdown')); - bridge.on('changeTextContent', ({id, text}) => { - var node = this.getNodeForID(id); - if (!node) { - return; - } - node.textContent = text; - }); - // used to "inspect node in Elements pane" - bridge.on('putSelectedNode', id => { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$node = this.getNodeForID(id); - }); - // used to "view source in Sources pane" - bridge.on('putSelectedInstance', id => { - var node = this.elementData.get(id); - if (node) { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$type = node.type; - } else { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$type = null; - } - if (node && node.publicInstance) { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$inst = node.publicInstance; - } else { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$inst = null; - } - }); - // used to select the inspected node ($0) - bridge.on('checkSelection', () => { - var newSelected = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0; - if (newSelected !== this._prevSelected) { - this._prevSelected = newSelected; - var sentSelected = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$node; - if (newSelected !== sentSelected) { - this.selectFromDOMNode(newSelected, true); - } - } - }); - bridge.on('scrollToNode', id => this.scrollToNode(id)); - bridge.on('traceupdatesstatechange', value => this.emit('traceupdatesstatechange', value)); - bridge.on('colorizerchange', value => this.emit('colorizerchange', value)); - - /** Events sent to the frontend **/ - this.on('root', id => bridge.send('root', id)); - this.on('mount', data => bridge.send('mount', data)); - this.on('update', data => bridge.send('update', data)); - this.on('updateProfileTimes', data => bridge.send('updateProfileTimes', data)); - this.on('unmount', id => { - bridge.send('unmount', id); - // once an element has been unmounted, the bridge doesn't need to be - // able to inspect it anymore. - bridge.forget(id); - }); - this.on('setSelection', data => bridge.send('select', data)); - this.on('setInspectEnabled', data => bridge.send('setInspectEnabled', data)); - this.on('isRecording', isRecording => bridge.send('isRecording', isRecording)); - this.on('storeSnapshot', (data) => bridge.send('storeSnapshot', data)); - this.on('clearSnapshots', () => bridge.send('clearSnapshots')); - } - - scrollToNode(id: ElementID): void { - var node = this.getNodeForID(id); - if (!node) { - console.warn('unable to get the node for scrolling'); - return; - } - var domElement = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement; - if (!domElement) { - console.warn('unable to get the domElement for scrolling'); - return; - } - - if (typeof domElement.scrollIntoViewIfNeeded === 'function') { - domElement.scrollIntoViewIfNeeded(); - } else if (typeof domElement.scrollIntoView === 'function') { - domElement.scrollIntoView(); - } - this.highlight(id); - } - - highlight(id: ElementID) { - var data = this.elementData.get(id); - var node = this.getNodeForID(id); - if (data && node) { - this.emit('highlight', {node, name: data.name, props: data.props}); - } - } - - highlightMany(ids: Array) { - var nodes = []; - ids.forEach(id => { - var node = this.getNodeForID(id); - if (node) { - nodes.push(node); - } - }); - if (nodes.length) { - this.emit('highlightMany', nodes); - } - } - - getNodeForID(id: ElementID): ?Object { - var component = this.internalInstancesById.get(id); - if (!component) { - return null; - } - var renderer = this.renderers.get(id); - if (renderer && this.reactInternals[renderer].getNativeFromReactElement) { - return this.reactInternals[renderer].getNativeFromReactElement(component); - } - return null; - } - - selectFromDOMNode(node: Object, quiet?: boolean, offsetFromLeaf: ?number = 0) { - var id = this.getIDForNode(node); - if (!id) { - return; - } - this.emit('setSelection', {id, quiet, offsetFromLeaf}); - } - - // TODO: remove this method because it's breaking encapsulation. - // It was used by RN inspector but this required leaking Fibers to it. - // RN inspector will use selectFromDOMNode() instead now. - // Remove this method in a few months after this comment was added. - selectFromReactInstance(instance: OpaqueNodeHandle, quiet?: boolean) { - var id = this.getId(instance); - if (!id) { - console.log('no instance id', instance); - return; - } - this.emit('setSelection', {id, quiet}); - } - - getIDForNode(node: Object): ?ElementID { - if (!this.reactInternals) { - return null; - } - var component; - for (var renderer in this.reactInternals) { - // If a renderer doesn't know about a reactId, it will throw an error. - try { - // $FlowFixMe possibly null - it's not null - component = this.reactInternals[renderer].getReactElementFromNative(node); - } catch (e) {} - if (component) { - return this.getId(component); - } - } - return null; - } - - _setProps({id, path, value}: {id: ElementID, path: Array, value: any}) { - var data = this.elementData.get(id); - if (data && data.updater && typeof data.updater.setInProps === 'function') { - data.updater.setInProps(path, value); - } else { - console.warn("trying to set props on a component that doesn't support it"); - } - } - - _setState({id, path, value}: {id: ElementID, path: Array, value: any}) { - var data = this.elementData.get(id); - if (data && data.updater && typeof data.updater.setInState === 'function') { - data.updater.setInState(path, value); - } else { - console.warn("trying to set state on a component that doesn't support it"); - } - } - - _setContext({id, path, value}: {id: ElementID, path: Array, value: any}) { - var data = this.elementData.get(id); - if (data && data.updater && typeof data.updater.setInContext === 'function') { - // $FlowFixMe - data.updater.setInContext(path, value); - } else { - console.warn("trying to set context on a component that doesn't support it"); - } - } - - _makeGlobal({id, path}: {id: ElementID, path: Array}) { - var data = this.elementData.get(id); - if (!data) { - return; - } - var value; - if (path === 'instance') { - value = data.publicInstance; - } else { - value = getIn(data, path); - } - this.global.$tmp = value; - console.log('$tmp =', value); - } - - getId(internalInstance: OpaqueNodeHandle): ElementID { - if (typeof internalInstance !== 'object' || !internalInstance) { - return internalInstance; - } - if (!this.idsByInternalInstances.has(internalInstance)) { - this.idsByInternalInstances.set(internalInstance, guid()); - this.internalInstancesById.set( - nullthrows(this.idsByInternalInstances.get(internalInstance)), - internalInstance - ); - } - return nullthrows(this.idsByInternalInstances.get(internalInstance)); - } - - addRoot(renderer: RendererID, internalInstance: OpaqueNodeHandle) { - var id = this.getId(internalInstance); - this.roots.add(id); - this.emit('root', id); - } - - rootCommitted(renderer: RendererID, internalInstance: OpaqueNodeHandle, data: DataType) { - var id = this.getId(internalInstance); - this.emit('rootCommitted', id, internalInstance, data); - } - - onMounted(renderer: RendererID, component: OpaqueNodeHandle, data: DataType) { - var id = this.getId(component); - this.renderers.set(id, renderer); - this.elementData.set(id, data); - - var send = assign({}, data); - if (send.children && send.children.map) { - send.children = send.children.map(c => this.getId(c)); - } - send.id = id; - send.canUpdate = send.updater && !!send.updater.forceUpdate; - delete send.type; - delete send.updater; - this.emit('mount', send); - } - - onUpdated(component: OpaqueNodeHandle, data: DataType) { - var id = this.getId(component); - this.elementData.set(id, data); - - var send = assign({}, data); - if (send.children && send.children.map) { - send.children = send.children.map(c => this.getId(c)); - } - send.id = id; - send.canUpdate = send.updater && !!send.updater.forceUpdate; - delete send.type; - delete send.updater; - this.emit('update', send); - } - - onUpdatedProfileTimes(component: OpaqueNodeHandle, data: DataType) { - var id = this.getId(component); - this.elementData.set(id, data); - - var send = assign({}, data); - if (send.children && send.children.map) { - send.children = send.children.map(c => this.getId(c)); - } - send.id = id; - send.canUpdate = send.updater && !!send.updater.forceUpdate; - delete send.type; - delete send.updater; - this.emit('updateProfileTimes', send); - } - - onUnmounted(component: OpaqueNodeHandle) { - var id = this.getId(component); - this.elementData.delete(id); - if (this.roots.has(id)) { - this.roots.delete(id); - this.emit('rootUnmounted', id); - } - this.renderers.delete(id); - this.emit('unmount', id); - this.idsByInternalInstances.delete(component); - } - - _onScroll() { - if (!this._scrollUpdate) { - this._scrollUpdate = true; - window.requestAnimationFrame(this._updateScroll); - } - } - - _updateScroll() { - this.emit('refreshMultiOverlay'); - this.emit('stopInspecting'); - this._scrollUpdate = false; - } - - _onClick(event: Event) { - if (!this._inspectEnabled) { - return; - } - - var id = this.getIDForNode(event.target); - if (!id) { - return; - } - - event.stopPropagation(); - event.preventDefault(); - - this.emit('setSelection', {id}); - this.emit('setInspectEnabled', false); - } - - _onMouseOver(event: Event) { - if (this._inspectEnabled) { - const id = this.getIDForNode(event.target); - if (!id) { - return; - } - - this.highlight(id); - } - } - - _onResize(event: Event) { - this.emit('stopInspecting'); - } -} - -module.exports = Agent; diff --git a/agent/Bridge.js b/agent/Bridge.js deleted file mode 100644 index c48eb990e2..0000000000 --- a/agent/Bridge.js +++ /dev/null @@ -1,466 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var consts = require('./consts'); -var hydrate = require('./hydrate'); -var dehydrate = require('./dehydrate'); -var getIn = require('./getIn'); -var performanceNow = require('fbjs/lib/performanceNow'); - -// Use the polyfill if the function is not native implementation -function getWindowFunction(name, polyfill): Function { - if (String(window[name]).indexOf('[native code]') === -1) { - return polyfill; - } - return window[name]; -} - -// Custom polyfill that runs the queue with a backoff. -// If you change it, make sure it behaves reasonably well in Firefox. -var lastRunTimeMS = 5; -var cancelIdleCallback = getWindowFunction('cancelIdleCallback', clearTimeout); -var requestIdleCallback = getWindowFunction('requestIdleCallback', function(cb, options) { - // Magic numbers determined by tweaking in Firefox. - // There is no special meaning to them. - var delayMS = 3000 * lastRunTimeMS; - if (delayMS > 500) { - delayMS = 500; - } - - return setTimeout(() => { - var startTime = performanceNow(); - cb({ - didTimeout: false, - timeRemaining() { - return Infinity; - }, - }); - var endTime = performanceNow(); - lastRunTimeMS = (endTime - startTime) / 1000; - }, delayMS); -}); - -type AnyFn = (...x: any) => any; -export type Wall = { - listen: (fn: (data: PayloadType) => void) => void, - send: (data: PayloadType) => void, -}; - -type IdleDeadline = { - didTimeout: bool, - timeRemaining: () => number, -}; - -type EventPayload = { - type: 'event', - cleaned: ?Array>, - evt: string, - data: any, -}; - -type PayloadType = { - type: 'inspect', - id: string, - path: Array, - callback: number, -} | { - type: 'many-events', - events: Array, -} | { - type: 'call', - name: string, - args: Array, - callback: number, -} | { - type: 'callback', - id: number, - args: Array, -} | { - type: 'pause', -} | { - type: 'resume', -} | EventPayload; - -/** - * The bridge is responsible for serializing requests between the Agent and - * the Frontend Store. It needs to be connected to a Wall object that can send - * JSONable data to the bridge on the other side. - * - * complex data - * | - * v - * [Bridge] - * | - * jsonable data - * | - * v - * [wall] - * | - * v - * ~ some barrier ~ - * | - * v - * [wall] - * | - * v - * [Bridge] - * | - * v - * "hydrated" data - * - * When an item is passed in that can't be serialized (anything other than a - * plain array, object, or literal value), the object is "cleaned", and - * rehydrated on the other side with `Symbol` attributes indicating that the - * object needs to be inspected for more detail. - * - * Example: - * - * bridge.send('evname', {id: 'someid', foo: MyCoolObjectInstance}) - * -> - * shows up, hydrated as - * { - * id: 'someid', - * foo: { - * [consts.name]: 'MyCoolObjectInstance', - * [consts.type]: 'object', - * [consts.meta]: {}, - * [consts.inspected]: false, - * } - * } - * - * The `consts` variables are Symbols, and as such are non-ennumerable. - * The front-end therefore needs to check for `consts.inspected` on received - * objects, and can thereby display object proxies and inspect them. - * - * Complex objects that are passed are expected to have a top-level `id` - * attribute, which is used for later lookup + inspection. Once it has been - * determined that an object is no longer needed, call `.forget(id)` to clean - * up. - */ -class Bridge { - _buffer: Array<{evt: string, data: any}>; - _cbs: Map; - _cid: number; - _inspectables: Map; - _listeners: {[key: string]: Array<(data: any) => void>}; - _flushHandle: ?number; - _wall: Wall; - _callers: {[key: string]: AnyFn}; - _paused: boolean; - - constructor(wall: Wall) { - this._cbs = new Map(); - this._inspectables = new Map(); - this._cid = 0; - this._listeners = {}; - this._buffer = []; - this._flushHandle = null; - this._callers = {}; - this._paused = false; - this._wall = wall; - - wall.listen(this._handleMessage.bind(this)); - } - - inspect(id: string, path: Array, cb: (val: any) => any) { - var _cid = this._cid++; - this._cbs.set(_cid, (data, cleaned, proto, protoclean) => { - if (cleaned.length) { - hydrate(data, cleaned); - } - if (proto && protoclean.length) { - hydrate(proto, protoclean); - } - if (proto) { - data[consts.proto] = proto; - } - cb(data); - }); - - this._wall.send({ - type: 'inspect', - callback: _cid, - path, - id, - }); - } - - call(name: string, args: Array, cb: (val: any) => any) { - var _cid = this._cid++; - this._cbs.set(_cid, cb); - - this._wall.send({ - type: 'call', - callback: _cid, - args, - name, - }); - } - - onCall(name: string, handler: (data: any) => any) { - if (this._callers[name]) { - throw new Error('only one call handler per call name allowed'); - } - this._callers[name] = handler; - } - - pause() { - this._wall.send({ - type: 'pause', - }); - } - - resume() { - this._wall.send({ - type: 'resume', - }); - } - - setInspectable(id: string, data: Object) { - var prev = this._inspectables.get(id); - if (!prev) { - this._inspectables.set(id, data); - return; - } - this._inspectables.set(id, {...prev, ...data}); - } - - send(evt: string, data: any) { - this._buffer.push({evt, data}); - this.scheduleFlush(); - } - - scheduleFlush() { - if (!this._flushHandle && this._buffer.length) { - var timeout = this._paused ? 5000 : 500; - this._flushHandle = requestIdleCallback( - this.flushBufferWhileIdle.bind(this), - {timeout} - ); - } - } - - cancelFlush() { - if (this._flushHandle) { - cancelIdleCallback(this._flushHandle); - this._flushHandle = null; - } - } - - flushBufferWhileIdle(deadline: IdleDeadline) { - this._flushHandle = null; - - // Magic numbers were determined by tweaking in a heavy UI and seeing - // what performs reasonably well both when DevTools are hidden and visible. - // The goal is that we try to catch up but avoid blocking the UI. - // When paused, it's okay to lag more, but not forever because otherwise - // when user activates React tab, it will freeze syncing. - var chunkCount = this._paused ? 20 : 10; - var chunkSize = Math.round(this._buffer.length / chunkCount); - var minChunkSize = this._paused ? 50 : 100; - - while (this._buffer.length && ( - deadline.timeRemaining() > 0 || - deadline.didTimeout - )) { - var take = Math.min(this._buffer.length, Math.max(minChunkSize, chunkSize)); - var currentBuffer = this._buffer.splice(0, take); - this.flushBufferSlice(currentBuffer); - } - - if (this._buffer.length) { - this.scheduleFlush(); - } - } - - flushBufferSlice(bufferSlice: Array<{evt: string, data: any}>) { - var events = bufferSlice.map(({evt, data}) => { - var cleaned = []; - var san = dehydrate(data, cleaned); - if (cleaned.length) { - this.setInspectable(data.id, data); - } - return {type: 'event', evt, data: san, cleaned}; - }); - this._wall.send({type: 'many-events', events}); - } - - forget(id: string) { - this._inspectables.delete(id); - } - - on(evt: string, fn: AnyFn) { - if (!this._listeners[evt]) { - this._listeners[evt] = [fn]; - } else { - this._listeners[evt].push(fn); - } - } - - off(evt: string, fn: AnyFn) { - if (!this._listeners[evt]) { - return; - } - var ix = this._listeners[evt].indexOf(fn); - if (ix !== -1) { - this._listeners[evt].splice(ix, 1); - } - } - - once(evt: string, fn: AnyFn) { - var self = this; - var listener = function() { - fn.apply(this, arguments); - self.off(evt, listener); - }; - this.on(evt, listener); - } - - _handleMessage(payload: PayloadType) { - if (payload.type === 'resume') { - this._paused = false; - this.scheduleFlush(); - return; - } - - if (payload.type === 'pause') { - this._paused = true; - this.cancelFlush(); - return; - } - - if (payload.type === 'callback') { - var callback = this._cbs.get(payload.id); - if (callback) { - callback(...payload.args); - this._cbs.delete(payload.id); - } - return; - } - - if (payload.type === 'call') { - this._handleCall(payload.name, payload.args, payload.callback); - return; - } - - if (payload.type === 'inspect') { - this._inspectResponse(payload.id, payload.path, payload.callback); - return; - } - - if (payload.type === 'event') { - // console.log('[bridge<-]', payload.evt); - if (payload.cleaned) { - hydrate(payload.data, payload.cleaned); - } - var fns = this._listeners[payload.evt]; - var data = payload.data; - if (fns) { - fns.forEach(fn => fn(data)); - } - } - - if (payload.type === 'many-events') { - payload.events.forEach(event => { - // console.log('[bridge<-]', payload.evt); - if (event.cleaned) { - hydrate(event.data, event.cleaned); - } - var handlers = this._listeners[event.evt]; - if (handlers) { - handlers.forEach(fn => fn(event.data)); - } - }); - } - } - - _handleCall(name: string, args: Array, callback: number) { - if (!this._callers[name]) { - console.warn('unknown call: "' + name + '"'); - return; - } - args = !Array.isArray(args) ? [args] : args; - var result; - try { - result = this._callers[name].apply(null, args); - } catch (e) { - console.error('Failed to call', e); - return; - } - this._wall.send({ - type: 'callback', - id: callback, - args: [result], - }); - } - - _inspectResponse(id: string, path: Array, callback: number) { - var inspectable = this._inspectables.get(id); - var result = {}; - var cleaned = []; - var proto = null; - var protoclean = []; - - if (inspectable) { - var val = getIn(inspectable, path); - var protod = false; - var isFn = typeof val === 'function'; - - if (val && typeof val[Symbol.iterator] === 'function') { - var iterVal = Object.create({}); // flow throws "object literal incompatible with object type" - var count = 0; - for (const entry of val) { - if (count > 100) { - // TODO: replace this if block with better logic to handle large iterables - break; - } - iterVal[count] = entry; - count++; - } - val = iterVal; - } - - Object.getOwnPropertyNames(val).forEach(name => { - if (name === '__proto__') { - protod = true; - } - if (isFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { - return; - } - // $FlowIgnore This is intentional - result[name] = dehydrate(val[name], cleaned, [name]); - }); - - /* eslint-disable no-proto */ - if (!protod && val.__proto__ && val.constructor.name !== 'Object') { - var newProto = {}; - var pIsFn = typeof val.__proto__ === 'function'; - Object.getOwnPropertyNames(val.__proto__).forEach(name => { - if (pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { - return; - } - newProto[name] = dehydrate(val.__proto__[name], protoclean, [name]); - }); - proto = newProto; - } - /* eslint-enable no-proto */ - } - - this._wall.send({ - type: 'callback', - id: callback, - args: [result, cleaned, proto, protoclean], - }); - } -} - -module.exports = Bridge; diff --git a/agent/__tests__/Agent-test.js b/agent/__tests__/Agent-test.js deleted file mode 100644 index 433de76b03..0000000000 --- a/agent/__tests__/Agent-test.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -var Agent = require('../Agent'); - -describe('Agent', () => { - - const publicInstance1 = {}; - const publicInstance2 = {}; - let agent; - beforeEach(() => { - agent = new Agent({}); - agent.elementData.set('test1', { publicInstance: publicInstance1 }); - agent.elementData.set('test2', { publicInstance: publicInstance2 }); - }); - - it('sets global $r if it is not set', () => { - delete agent.global.$r; - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe(publicInstance1); - }); - - it('overwrites global $r if it was last set by itself', () => { - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe(publicInstance1); - agent.emit('selected', 'test2'); - expect(agent.global.$r).toBe(publicInstance2); - }); - - it('does not overwrite global $r if was not last set by itself', () => { - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe(publicInstance1); - agent.global.$r = 'set externally'; - expect(agent.global.$r).toBe('set externally'); - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe('set externally'); - }); - -}); diff --git a/agent/__tests__/dehydrate-test.js b/agent/__tests__/dehydrate-test.js deleted file mode 100644 index b21c7d3231..0000000000 --- a/agent/__tests__/dehydrate-test.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -var dehydrate = require('../dehydrate'); - -describe('dehydrate', () => { - it('leaves an empty object alone', () => { - var cleaned = []; - var result = dehydrate({}, cleaned); - expect(result).toEqual({}); - }); - - it('preserves a shallowly nested object', () => { - var object = { - a: {b: 1, c: 2, d: 3}, - b: ['h', 'i', 'j'], - }; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([]); - expect(result).toEqual(object); - }); - - it('cleans a deeply nested object', () => { - var object = {a: {b: {c: {d: 4}}}}; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([['a', 'b', 'c']]); - expect(result.a.b.c).toEqual({type: 'object', name: '', meta: {}}); - }); - - it('cleans a deeply nested array', () => { - var object = {a: {b: {c: [1, 3]}}}; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([['a', 'b', 'c']]); - expect(result.a.b.c).toEqual({type: 'array', name: 'Array', meta: {length: 2}}); - }); - - it('cleans multiple things', () => { - var Something = function() {}; - var object = {a: {b: {c: [1, 3], d: new Something()}}}; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([['a', 'b', 'c'], ['a', 'b', 'd']]); - expect(result.a.b.c).toEqual({type: 'array', name: 'Array', meta: {length: 2}}); - expect(result.a.b.d).toEqual({type: 'object', name: 'Something', meta: {}}); - }); - - it('returns readable name for dates', () => { - var d = new Date(); - var object = {a: d }; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(result.a).toEqual({type: 'date', name: d.toString(), meta: {uninspectable: true}}); - }); -}); diff --git a/agent/consts.js b/agent/consts.js deleted file mode 100644 index 38e1b5ef05..0000000000 --- a/agent/consts.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var Symbol = require('es6-symbol'); - -module.exports = { - name: Symbol('name'), - type: Symbol('type'), - inspected: Symbol('inspected'), - meta: Symbol('meta'), - proto: Symbol('proto'), -}; diff --git a/agent/dehydrate.js b/agent/dehydrate.js deleted file mode 100644 index 764d63fba0..0000000000 --- a/agent/dehydrate.js +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/** - * Get a enhanced/artificial type string based on the object instance - */ -function getPropType(data: Object): string | null { - if (!data) { - return null; - } - var type = typeof data; - - if (type === 'object') { - if (data._reactFragment) { - return 'react_fragment'; - } - if (Array.isArray(data)) { - return 'array'; - } - if (ArrayBuffer.isView(data)) { - if (data instanceof DataView) { - return 'data_view'; - } - return 'typed_array'; - } - if (data instanceof ArrayBuffer) { - return 'array_buffer'; - } - if (typeof data[Symbol.iterator] === 'function') { - return 'iterator'; - } - if (Object.prototype.toString.call(data) === '[object Date]') { - return 'date'; - } - } - - return type; -} - -/** - * Generate the dehydrated metadata for complex object instances - */ -function createDehydrated(type: string, data: Object, cleaned: Array>, path: Array): Object { - var meta = {}; - - if (type === 'array' || type === 'typed_array') { - meta.length = data.length; - } - if (type === 'iterator' || type === 'typed_array') { - meta.readOnly = true; - } - - cleaned.push(path); - - return { - type, - meta, - name: !data.constructor || data.constructor.name === 'Object' ? '' : data.constructor.name, - }; -} - -/** - * Strip out complex data (instances, functions, and data nested > 2 levels - * deep). The paths of the stripped out objects are appended to the `cleaned` - * list. On the other side of the barrier, the cleaned list is used to - * "re-hydrate" the cleaned representation into an object with symbols as - * attributes, so that a sanitized object can be distinguished from a normal - * object. - * - * Input: {"some": {"attr": fn()}, "other": AnInstance} - * Output: { - * "some": { - * "attr": {"name": the fn.name, type: "function"} - * }, - * "other": { - * "name": "AnInstance", - * "type": "object", - * }, - * } - * and cleaned = [["some", "attr"], ["other"]] - */ -function dehydrate(data: Object, cleaned: Array>, path?: Array = [], level?: number = 0): string | Object { - - var type = getPropType(data); - - switch (type) { - - case 'function': - cleaned.push(path); - return { - name: data.name, - type: 'function', - }; - - case 'string': - return data.length <= 500 ? data : data.slice(0, 500) + '...'; - - // We have to do this assignment b/c Flow doesn't think "symbol" is - // something typeof would return. Error 'unexpected predicate "symbol"' - case 'symbol': - cleaned.push(path); - return { - type: 'symbol', - name: data.toString(), - }; - - // React Fragments error if you try to inspect them. - case 'react_fragment': - return 'A React Fragment'; - - // ArrayBuffers error if you try to inspect them. - case 'array_buffer': - case 'data_view': - cleaned.push(path); - return { - type, - name: type === 'data_view' ? 'DataView' : 'ArrayBuffer', - meta: { - length: data.byteLength, - uninspectable: true, - }, - }; - - case 'array': - if (level > 2) { - return createDehydrated(type, data, cleaned, path); - } - return data.map((item, i) => dehydrate(item, cleaned, path.concat([i]), level + 1)); - - case 'typed_array': - case 'iterator': - return createDehydrated(type, data, cleaned, path); - case 'date': - cleaned.push(path); - return { - name: data.toString(), - type: 'date', - meta: { - uninspectable: true, - }, - }; - case 'object': - if (level > 2 || (data.constructor && typeof data.constructor === 'function' && data.constructor.name !== 'Object')) { - return createDehydrated(type, data, cleaned, path); - } else { - - var res = {}; - for (var name in data) { - res[name] = dehydrate(data[name], cleaned, path.concat([name]), level + 1); - } - return res; - } - - default: - return data; - } -} - -module.exports = dehydrate; diff --git a/agent/getIn.js b/agent/getIn.js deleted file mode 100644 index 954e4a9700..0000000000 --- a/agent/getIn.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -var hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * Retrieves the value from the path of nested objects - * @param {Object} base Base or root object for path - * @param {Array} path nested path - * @return {any} Value at end of path or `mull` - */ -function getIn(base, path) { - return path.reduce((obj, attr) => { - if (obj) { - if (hasOwnProperty.call(obj, attr)) { - return obj[attr]; - } - if (typeof obj[Symbol.iterator] === 'function') { - // Convert iterable to array and return array[index] - return [...obj][attr]; - } - } - - return null; - }, base); -} - -module.exports = getIn; diff --git a/agent/hydrate.js b/agent/hydrate.js deleted file mode 100644 index 7a3d2d6a3c..0000000000 --- a/agent/hydrate.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var consts = require('./consts'); - -function hydrate(data: Object, cleaned: Array>): void { - cleaned.forEach(path => { - var last = path.pop(); - var obj = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, data); - if (!obj || !obj[last]) { - return; - } - var replace: {[key: Symbol]: boolean | string} = {}; - replace[consts.name] = obj[last].name; - replace[consts.type] = obj[last].type; - replace[consts.meta] = obj[last].meta; - replace[consts.inspected] = false; - obj[last] = replace; - }); -} - -module.exports = hydrate; diff --git a/agent/inject.js b/agent/inject.js deleted file mode 100644 index b84a56b4f2..0000000000 --- a/agent/inject.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Hook} from '../backend/types'; -import type Agent from './Agent'; - -var setupBackend = require('../backend/backend'); - -module.exports = function(hook: Hook, agent: Agent) { - var subs = [ - // Basic functionality - hook.sub('renderer-attached', ({id, renderer, helpers}) => { - agent.setReactInternals(id, helpers); - helpers.walkTree(agent.onMounted.bind(agent, id), agent.addRoot.bind(agent, id)); - }), - hook.sub('mount', ({renderer, internalInstance, data}) => agent.onMounted(renderer, internalInstance, data)), - hook.sub('unmount', ({renderer, internalInstance}) => agent.onUnmounted(internalInstance)), - hook.sub('update', ({renderer, internalInstance, data}) => agent.onUpdated(internalInstance, data)), - - // Required by Profiler plugin - hook.sub('root', ({renderer, internalInstance}) => agent.addRoot(renderer, internalInstance)), - hook.sub('rootCommitted', ({renderer, internalInstance, data}) => agent.rootCommitted(renderer, internalInstance, data)), - hook.sub('updateProfileTimes', ({renderer, internalInstance, data}) => agent.onUpdatedProfileTimes(internalInstance, data)), - ]; - - var success = setupBackend(hook); - if (!success) { - return; - } - - hook.emit('react-devtools', agent); - hook.reactDevtoolsAgent = agent; - agent.on('shutdown', () => { - subs.forEach(fn => fn()); - hook.reactDevtoolsAgent = null; - }); -}; diff --git a/backend/__tests__/copyWithSet-test.js b/backend/__tests__/copyWithSet-test.js deleted file mode 100644 index a9687e4ca8..0000000000 --- a/backend/__tests__/copyWithSet-test.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -var copyWithSet = require('../copyWithSet'); - -describe('copyWithSet', function() { - it('adds a property', function() { - var res = copyWithSet({c: 2, d: 4}, ['a'], 'b'); - expect(res).toEqual({a: 'b', c: 2, d: 4}); - }); - - it('modifies a property', function() { - var res = copyWithSet({c: 2, d: 4}, ['c'], '10'); - expect(res).toEqual({c: '10', d: 4}); - }); - - it('preserves deep objects', function() { - var res = copyWithSet({a: {b: {c: 3}, d: 2}, e: 1}, ['a', 'b', 'c'], 10); - expect(res).toEqual({a: {b: {c: 10}, d: 2}, e: 1}); - }); - - it('modifies an array', function() { - var res = copyWithSet(['a', 'b', 'x'], [2], 'c'); - expect(res).toEqual(['a', 'b', 'c']); - }); - - it('works with complexity', function() { - var res = copyWithSet([0, 1, {2: {3: [4, 5, {6: {7: 8}}, 9], 10: 11}}, 12], [2, '2', '3', 2, '6', '7'], 'moose'); - expect(res).toEqual([0, 1, {2: {3: [4, 5, {6: {7: 'moose'}}, 9], 10: 11}}, 12]); - }); -}); diff --git a/backend/attachRenderer.js b/backend/attachRenderer.js deleted file mode 100644 index c0370f264e..0000000000 --- a/backend/attachRenderer.js +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {DataType, OpaqueNodeHandle, Hook, ReactRenderer, Helpers} from './types'; -var getData = require('./getData'); -var getData012 = require('./getData012'); -var attachRendererFiber = require('./attachRendererFiber'); - -type NodeLike = {}; - -/** - * This takes care of patching the renderer to emit events on the global - * `Hook`. The returned object has a `.cleanup` method to un-patch everything. - */ -function attachRenderer(hook: Hook, rid: string, renderer: ReactRenderer): Helpers { - var rootNodeIDMap = new Map(); - var extras = {}; - // Before 0.13 there was no Reconciler, so we patch Component.Mixin - var isPre013 = !renderer.Reconciler; - - // React Fiber - if (typeof renderer.findFiberByHostInstance === 'function') { - return attachRendererFiber(hook, rid, renderer); - } - - // React Native - if (renderer.Mount.findNodeHandle && renderer.Mount.nativeTagToRootNodeID) { - extras.getNativeFromReactElement = function(component) { - return renderer.Mount.findNodeHandle(component); - }; - - extras.getReactElementFromNative = function(nativeTag) { - var id = renderer.Mount.nativeTagToRootNodeID(nativeTag); - return rootNodeIDMap.get(id); - }; - // React DOM 15+ - } else if (renderer.ComponentTree) { - extras.getNativeFromReactElement = function(component) { - return renderer.ComponentTree.getNodeFromInstance(component); - }; - - extras.getReactElementFromNative = function(node) { - return renderer.ComponentTree.getClosestInstanceFromNode(node); - }; - // React DOM - } else if (renderer.Mount.getID && renderer.Mount.getNode) { - extras.getNativeFromReactElement = function(component) { - try { - return renderer.Mount.getNode(component._rootNodeID); - } catch (e) { - return undefined; - } - }; - - extras.getReactElementFromNative = function(node) { - // $FlowFixMe - var id = renderer.Mount.getID(node); - while (node && node.parentNode && !id) { - node = node.parentNode; - // $FlowFixMe - id = renderer.Mount.getID(node); - } - return rootNodeIDMap.get(id); - }; - } else { - console.warn('Unknown react version (does not have getID), probably an unshimmed React Native'); - } - - var oldMethods; - var oldRenderComponent; - var oldRenderRoot; - - // React DOM - if (renderer.Mount._renderNewRootComponent) { - oldRenderRoot = decorateResult(renderer.Mount, '_renderNewRootComponent', (internalInstance) => { - hook.emit('root', {renderer: rid, internalInstance}); - }); - // React Native - } else if (renderer.Mount.renderComponent) { - oldRenderComponent = decorateResult(renderer.Mount, 'renderComponent', internalInstance => { - hook.emit('root', {renderer: rid, internalInstance: internalInstance._reactInternalInstance}); - }); - } - - if (renderer.Component) { - console.error('You are using a version of React with limited support in this version of the devtools.\nPlease upgrade to use at least 0.13, or you can downgrade to use the old version of the devtools:\ninstructions here https://github.com/facebook/react-devtools/tree/devtools-next#how-do-i-use-this-for-react--013'); - // 0.11 - 0.12 - // $FlowFixMe renderer.Component is not "possibly undefined" - oldMethods = decorateMany(renderer.Component.Mixin, { - mountComponent() { - rootNodeIDMap.set(this._rootNodeID, this); - // FIXME DOMComponent calls Component.Mixin, and sets up the - // `children` *after* that call, meaning we don't have access to the - // children at this point. Maybe we should find something else to shim - // (do we have access to DOMComponent here?) so that we don't have to - // setTimeout. - setTimeout(() => { - hook.emit('mount', {internalInstance: this, data: getData012(this), renderer: rid}); - }, 0); - }, - updateComponent() { - setTimeout(() => { - hook.emit('update', {internalInstance: this, data: getData012(this), renderer: rid}); - }, 0); - }, - unmountComponent() { - hook.emit('unmount', {internalInstance: this, renderer: rid}); - rootNodeIDMap.delete(this._rootNodeID); - }, - }); - } else if (renderer.Reconciler) { - oldMethods = decorateMany(renderer.Reconciler, { - mountComponent(internalInstance, rootID, transaction, context) { - var data = getData(internalInstance); - rootNodeIDMap.set(internalInstance._rootNodeID, internalInstance); - hook.emit('mount', {internalInstance, data, renderer: rid}); - }, - performUpdateIfNecessary(internalInstance, nextChild, transaction, context) { - hook.emit('update', {internalInstance, data: getData(internalInstance), renderer: rid}); - }, - receiveComponent(internalInstance, nextChild, transaction, context) { - hook.emit('update', {internalInstance, data: getData(internalInstance), renderer: rid}); - }, - unmountComponent(internalInstance) { - hook.emit('unmount', {internalInstance, renderer: rid}); - rootNodeIDMap.delete(internalInstance._rootNodeID); - }, - }); - } - - extras.walkTree = function(visit: (component: OpaqueNodeHandle, data: DataType) => void, visitRoot: (internalInstance: OpaqueNodeHandle) => void) { - var onMount = (component, data) => { - rootNodeIDMap.set(component._rootNodeID, component); - visit(component, data); - }; - walkRoots(renderer.Mount._instancesByReactRootID || renderer.Mount._instancesByContainerID, onMount, visitRoot, isPre013); - }; - - extras.cleanup = function() { - if (oldMethods) { - if (renderer.Component) { - restoreMany(renderer.Component.Mixin, oldMethods); - } else { - restoreMany(renderer.Reconciler, oldMethods); - } - } - if (oldRenderRoot) { - renderer.Mount._renderNewRootComponent = oldRenderRoot; - } - if (oldRenderComponent) { - renderer.Mount.renderComponent = oldRenderComponent; - } - oldMethods = null; - oldRenderRoot = null; - oldRenderComponent = null; - }; - - return extras; -} - -function walkRoots(roots, onMount, onRoot, isPre013) { - for (var name in roots) { - walkNode(roots[name], onMount, isPre013); - onRoot(roots[name]); - } -} - -function walkNode(internalInstance, onMount, isPre013) { - var data = isPre013 ? getData012(internalInstance) : getData(internalInstance); - if (data.children && Array.isArray(data.children)) { - data.children.forEach(child => walkNode(child, onMount, isPre013)); - } - onMount(internalInstance, data); -} - -function decorateResult(obj, attr, fn) { - var old = obj[attr]; - obj[attr] = function(instance: NodeLike) { - var res = old.apply(this, arguments); - fn(res); - return res; - }; - return old; -} - -function decorate(obj, attr, fn) { - var old = obj[attr]; - obj[attr] = function(instance: NodeLike) { - var res = old.apply(this, arguments); - fn.apply(this, arguments); - return res; - }; - return old; -} - -function decorateMany(source, fns) { - var olds = {}; - for (var name in fns) { - olds[name] = decorate(source, name, fns[name]); - } - return olds; -} - -function restoreMany(source, olds) { - for (var name in olds) { - source[name] = olds[name]; - } -} - -module.exports = attachRenderer; diff --git a/backend/attachRendererFiber.js b/backend/attachRendererFiber.js deleted file mode 100644 index 0d22472136..0000000000 --- a/backend/attachRendererFiber.js +++ /dev/null @@ -1,805 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Hook, ReactRenderer, DataType, Helpers} from './types'; - -var semver = require('semver'); - -var copyWithSet = require('./copyWithSet'); -var getDisplayName = require('./getDisplayName'); - -// Taken from ReactElement. -function resolveDefaultProps(Component: any, baseProps: Object): Object { - if (Component && Component.defaultProps) { - // Resolve default props. Taken from ReactElement - const props = Object.assign({}, baseProps); - const defaultProps = Component.defaultProps; - for (const propName in defaultProps) { - if (props[propName] === undefined) { - props[propName] = defaultProps[propName]; - } - } - return props; - } - return baseProps; -} - -function getInternalReactConstants(version) { - var ReactTypeOfWork; - var ReactSymbols; - var ReactTypeOfSideEffect; - - // ********************************************************** - // The section below is copied from files in React repo. - // Keep it in sync, and add version guards if it changes. - // ********************************************************** - if (semver.gte(version, '16.6.0-beta.0')) { - ReactTypeOfWork = { - ClassComponent: 1, - ContextConsumer: 9, - ContextProvider: 10, - CoroutineComponent: -1, // Removed - CoroutineHandlerPhase: -1, // Removed - ForwardRef: 11, - Fragment: 7, - FunctionalComponent: 0, - HostComponent: 5, - HostPortal: 4, - HostRoot: 3, - HostText: 6, - IncompleteClassComponent: 17, - IndeterminateComponent: 2, - LazyComponent: 16, - MemoComponent: 14, - Mode: 8, - Profiler: 12, - SimpleMemoComponent: 15, - SuspenseComponent: 13, - YieldComponent: -1, // Removed - }; - } else if (semver.gte(version, '16.4.3-alpha')) { - ReactTypeOfWork = { - ClassComponent: 2, - ContextConsumer: 11, - ContextProvider: 12, - CoroutineComponent: -1, // Removed - CoroutineHandlerPhase: -1, // Removed - ForwardRef: 13, - Fragment: 9, - FunctionalComponent: 0, - HostComponent: 7, - HostPortal: 6, - HostRoot: 5, - HostText: 8, - IncompleteClassComponent: -1, // Doesn't exist yet - IndeterminateComponent: 4, - LazyComponent: -1, // Doesn't exist yet - MemoComponent: -1, // Doesn't exist yet - Mode: 10, - Profiler: 15, - SimpleMemoComponent: -1, // Doesn't exist yet - SuspenseComponent: 16, - YieldComponent: -1, // Removed - }; - } else { - ReactTypeOfWork = { - ClassComponent: 2, - ContextConsumer: 12, - ContextProvider: 13, - CoroutineComponent: 7, - CoroutineHandlerPhase: 8, - ForwardRef: 14, - Fragment: 10, - FunctionalComponent: 1, - HostComponent: 5, - HostPortal: 4, - HostRoot: 3, - HostText: 6, - IncompleteClassComponent: -1, // Doesn't exist yet - IndeterminateComponent: 0, - LazyComponent: -1, // Doesn't exist yet - MemoComponent: -1, // Doesn't exist yet - Mode: 11, - Profiler: 15, - SimpleMemoComponent: -1, // Doesn't exist yet - SuspenseComponent: 16, - YieldComponent: 9, - }; - } - ReactSymbols = { - CONCURRENT_MODE_NUMBER: 0xeacf, - CONCURRENT_MODE_SYMBOL_STRING: 'Symbol(react.concurrent_mode)', - DEPRECATED_ASYNC_MODE_SYMBOL_STRING: 'Symbol(react.async_mode)', - CONTEXT_CONSUMER_NUMBER: 0xeace, - CONTEXT_CONSUMER_SYMBOL_STRING: 'Symbol(react.context)', - CONTEXT_PROVIDER_NUMBER: 0xeacd, - CONTEXT_PROVIDER_SYMBOL_STRING: 'Symbol(react.provider)', - FORWARD_REF_NUMBER: 0xead0, - FORWARD_REF_SYMBOL_STRING: 'Symbol(react.forward_ref)', - MEMO_NUMBER: 0xead3, - MEMO_SYMBOL_STRING: 'Symbol(react.memo)', - PROFILER_NUMBER: 0xead2, - PROFILER_SYMBOL_STRING: 'Symbol(react.profiler)', - STRICT_MODE_NUMBER: 0xeacc, - STRICT_MODE_SYMBOL_STRING: 'Symbol(react.strict_mode)', - SUSPENSE_NUMBER: 0xead1, - SUSPENSE_SYMBOL_STRING: 'Symbol(react.suspense)', - DEPRECATED_PLACEHOLDER_SYMBOL_STRING: 'Symbol(react.placeholder)', - }; - ReactTypeOfSideEffect = { - PerformedWork: 1, - }; - // ********************************************************** - // End of copied code. - // ********************************************************** - - return { - ReactTypeOfWork, - ReactSymbols, - ReactTypeOfSideEffect, - }; -} - -function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer): Helpers { - var {ReactTypeOfWork, ReactSymbols, ReactTypeOfSideEffect} = getInternalReactConstants(renderer.version); - var {PerformedWork} = ReactTypeOfSideEffect; - var { - FunctionalComponent, - ClassComponent, - ContextConsumer, - Fragment, - ForwardRef, - HostRoot, - HostPortal, - HostComponent, - HostText, - IncompleteClassComponent, - IndeterminateComponent, - MemoComponent, - SimpleMemoComponent, - } = ReactTypeOfWork; - var { - CONCURRENT_MODE_NUMBER, - CONCURRENT_MODE_SYMBOL_STRING, - DEPRECATED_ASYNC_MODE_SYMBOL_STRING, - CONTEXT_CONSUMER_NUMBER, - CONTEXT_CONSUMER_SYMBOL_STRING, - CONTEXT_PROVIDER_NUMBER, - CONTEXT_PROVIDER_SYMBOL_STRING, - PROFILER_NUMBER, - PROFILER_SYMBOL_STRING, - STRICT_MODE_NUMBER, - STRICT_MODE_SYMBOL_STRING, - SUSPENSE_NUMBER, - SUSPENSE_SYMBOL_STRING, - DEPRECATED_PLACEHOLDER_SYMBOL_STRING, - } = ReactSymbols; - - // TODO: we might want to change the data structure - // once we no longer suppport Stack versions of `getData`. - function getDataFiber(fiber: Object): DataType { - var elementType = fiber.elementType; - var type = fiber.type; - var key = fiber.key; - var ref = fiber.ref; - var source = fiber._debugSource; - var publicInstance = null; - var props = null; - var state = null; - var children = null; - var context = null; - var updater = null; - var nodeType = null; - var name = null; - var text = null; - - // Tracing - var memoizedInteractions = null; - - // Profiler - var actualDuration = null; - var actualStartTime = null; - var treeBaseDuration = null; - - // Suspense - var isTimedOutSuspense = false; - - var resolvedType = type; - if (typeof type === 'object' && type !== null) { - if (typeof type.then === 'function') { - resolvedType = type._reactResult; - } - } - - switch (fiber.tag) { - case ClassComponent: - case FunctionalComponent: - case IncompleteClassComponent: - case IndeterminateComponent: - nodeType = 'Composite'; - name = getDisplayName(resolvedType); - publicInstance = fiber.stateNode; - props = fiber.memoizedProps; - state = fiber.memoizedState; - if (publicInstance != null) { - context = publicInstance.context; - if (context && Object.keys(context).length === 0) { - context = null; - } - } - const inst = publicInstance; - if (inst) { - updater = { - setState: inst.setState && inst.setState.bind(inst), - forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst), - setInProps: inst.forceUpdate && setInProps.bind(null, fiber), - setInState: inst.forceUpdate && setInState.bind(null, inst), - setInContext: inst.forceUpdate && setInContext.bind(null, inst), - }; - } - children = []; - break; - case ForwardRef: - const functionName = getDisplayName(resolvedType.render, ''); - nodeType = 'Special'; - name = resolvedType.displayName || ( - functionName !== '' - ? `ForwardRef(${functionName})` - : 'ForwardRef' - ); - children = []; - break; - case HostRoot: - nodeType = 'Wrapper'; - children = []; - memoizedInteractions = fiber.stateNode.memoizedInteractions; - break; - case HostPortal: - nodeType = 'Portal'; - name = 'ReactPortal'; - props = { - target: fiber.stateNode.containerInfo, - }; - children = []; - break; - case HostComponent: - nodeType = 'Native'; - name = fiber.type; - - // TODO (bvaughn) we plan to remove this prefix anyway. - // We can cut this special case out when it's gone. - name = name.replace('topsecret-', ''); - - publicInstance = fiber.stateNode; - props = fiber.memoizedProps; - if ( - typeof props.children === 'string' || - typeof props.children === 'number' - ) { - children = props.children.toString(); - } else { - children = []; - } - if (typeof fiber.stateNode.setNativeProps === 'function') { - // For editing styles in RN - updater = { - setNativeProps(nativeProps) { - fiber.stateNode.setNativeProps(nativeProps); - }, - }; - } - break; - case HostText: - nodeType = 'Text'; - text = fiber.memoizedProps; - break; - case Fragment: - nodeType = 'Wrapper'; - children = []; - break; - case MemoComponent: - case SimpleMemoComponent: - nodeType = 'Composite'; - if (elementType.displayName) { - name = elementType.displayName; - } else { - const displayName = type.displayName || type.name; - name = displayName ? `Memo(${displayName})` : 'Memo'; - } - children = []; - break; - default: - const symbolOrNumber = typeof type === 'object' && type !== null - ? type.$$typeof - : type; - // $FlowFixMe facebook/flow/issues/2362 - const switchValue = typeof symbolOrNumber === 'symbol' - ? symbolOrNumber.toString() - : symbolOrNumber; - - switch (switchValue) { - case CONCURRENT_MODE_NUMBER: - case CONCURRENT_MODE_SYMBOL_STRING: - case DEPRECATED_ASYNC_MODE_SYMBOL_STRING: - nodeType = 'Special'; - name = 'ConcurrentMode'; - children = []; - break; - case CONTEXT_PROVIDER_NUMBER: - case CONTEXT_PROVIDER_SYMBOL_STRING: - nodeType = 'Special'; - props = fiber.memoizedProps; - name = `${fiber.type._context.displayName || 'Context'}.Provider`; - children = []; - break; - case CONTEXT_CONSUMER_NUMBER: - case CONTEXT_CONSUMER_SYMBOL_STRING: - nodeType = 'Special'; - props = fiber.memoizedProps; - // NOTE: TraceUpdatesBackendManager depends on the name ending in '.Consumer' - // If you change the name, figure out a more resilient way to detect it. - name = `${fiber.type.displayName || 'Context'}.Consumer`; - children = []; - break; - case STRICT_MODE_NUMBER: - case STRICT_MODE_SYMBOL_STRING: - nodeType = 'Special'; - name = 'StrictMode'; - children = []; - break; - case SUSPENSE_NUMBER: - case SUSPENSE_SYMBOL_STRING: - case DEPRECATED_PLACEHOLDER_SYMBOL_STRING: - nodeType = 'Special'; - name = 'Suspense'; - props = fiber.memoizedProps; - children = []; - - // Suspense components only have a non-null memoizedState if they're timed-out. - isTimedOutSuspense = fiber.memoizedState !== null; - break; - case PROFILER_NUMBER: - case PROFILER_SYMBOL_STRING: - nodeType = 'Special'; - props = fiber.memoizedProps; - name = `Profiler(${fiber.memoizedProps.id})`; - children = []; - break; - default: - nodeType = 'Native'; - props = fiber.memoizedProps; - name = 'TODO_NOT_IMPLEMENTED_YET'; - children = []; - break; - } - break; - } - - if ( - props !== null && - typeof fiber.elementType !== undefined && - fiber.type !== fiber.elementType - ) { - props = resolveDefaultProps(fiber.type, props); - } - - if (Array.isArray(children)) { - if (isTimedOutSuspense) { - // The behavior of timed-out Suspense trees is unique. - // Rather than unmount the timed out content (and possibly lose important state), - // React re-parents this content within a hidden Fragment while the fallback is showing. - // This behavior doesn't need to be observable in the DevTools though. - // It might even result in a bad user experience for e.g. node selection in the Elements panel. - // The easiest fix is to strip out the intermediate Fragment fibers, - // so the Elements panel and Profiler don't need to special case them. - const primaryChildFragment = fiber.child; - const primaryChild = primaryChildFragment.child; - const fallbackChildFragment = primaryChildFragment.sibling; - const fallbackChild = fallbackChildFragment.child; - children.push(primaryChild); - children.push(fallbackChild); - } else { - let child = fiber.child; - while (child) { - children.push(getOpaqueNode(child)); - child = child.sibling; - } - } - } - - if (fiber.actualDuration !== undefined) { - actualDuration = fiber.actualDuration; - actualStartTime = fiber.actualStartTime; - treeBaseDuration = fiber.treeBaseDuration; - } - - // $FlowFixMe - return { - nodeType, - type, - key, - ref, - source, - name, - props, - state, - context, - children, - text, - updater, - publicInstance, - - // Tracing - memoizedInteractions, - - // Profiler data - actualDuration, - actualStartTime, - treeBaseDuration, - }; - } - - function setInProps(fiber, path: Array, value: any) { - const inst = fiber.stateNode; - fiber.pendingProps = copyWithSet(inst.props, path, value); - if (fiber.alternate) { - // We don't know which fiber is the current one because DevTools may bail out of getDataFiber() call, - // and so the data object may refer to another version of the fiber. Therefore we update pendingProps - // on both. I hope that this is safe. - fiber.alternate.pendingProps = fiber.pendingProps; - } - fiber.stateNode.forceUpdate(); - } - - function setInState(inst, path: Array, value: any) { - setIn(inst.state, path, value); - inst.forceUpdate(); - } - - function setInContext(inst, path: Array, value: any) { - setIn(inst.context, path, value); - inst.forceUpdate(); - } - - function setIn(obj: Object, path: Array, value: any) { - var last = path.pop(); - var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); - if (parent) { - parent[last] = value; - } - } - - // This is a slightly annoying indirection. - // It is currently necessary because DevTools wants - // to use unique objects as keys for instances. - // However fibers have two versions. - // We use this set to remember first encountered fiber for - // each conceptual instance. - const opaqueNodes = new Set(); - function getOpaqueNode(fiber) { - if (opaqueNodes.has(fiber)) { - return fiber; - } - const {alternate} = fiber; - if (alternate != null && opaqueNodes.has(alternate)) { - return alternate; - } - opaqueNodes.add(fiber); - return fiber; - } - - function hasDataChanged(prevFiber, nextFiber) { - switch (nextFiber.tag) { - case ClassComponent: - case FunctionalComponent: - case ContextConsumer: - case MemoComponent: - case SimpleMemoComponent: - // For types that execute user code, we check PerformedWork effect. - // We don't reflect bailouts (either referential or sCU) in DevTools. - // eslint-disable-next-line no-bitwise - return (nextFiber.effectTag & PerformedWork) === PerformedWork; - // Note: ContextConsumer only gets PerformedWork effect in 16.3.3+ - // so it won't get highlighted with React 16.3.0 to 16.3.2. - default: - // For host components and other types, we compare inputs - // to determine whether something is an update. - return ( - prevFiber.memoizedProps !== nextFiber.memoizedProps || - prevFiber.memoizedState !== nextFiber.memoizedState || - prevFiber.ref !== nextFiber.ref - ); - } - } - - function haveProfilerTimesChanged(prevFiber, nextFiber) { - return ( - prevFiber.actualDuration !== undefined && // Short-circuit check for non-profiling builds - ( - prevFiber.actualDuration !== nextFiber.actualDuration || - prevFiber.actualStartTime !== nextFiber.actualStartTime || - prevFiber.treeBaseDuration !== nextFiber.treeBaseDuration - ) - ); - } - - let pendingEvents = []; - - function flushPendingEvents() { - const events = pendingEvents; - pendingEvents = []; - for (let i = 0; i < events.length; i++) { - const event = events[i]; - hook.emit(event.type, event); - } - } - - function enqueueMount(fiber) { - pendingEvents.push({ - internalInstance: getOpaqueNode(fiber), - data: getDataFiber(fiber), - renderer: rid, - type: 'mount', - }); - - const isRoot = fiber.tag === HostRoot; - if (isRoot) { - pendingEvents.push({ - internalInstance: getOpaqueNode(fiber), - renderer: rid, - type: 'root', - }); - } - } - - function enqueueUpdateIfNecessary(fiber, hasChildOrderChanged) { - if ( - !hasChildOrderChanged && - !hasDataChanged(fiber.alternate, fiber) - ) { - // If only timing information has changed, we still need to update the nodes. - // But we can do it in a faster way since we know it's safe to skip the children. - // It's also important to avoid emitting an "update" signal for the node in this case, - // Since that would indicate to the Profiler that it was part of the "commit" when it wasn't. - if (haveProfilerTimesChanged(fiber.alternate, fiber)) { - pendingEvents.push({ - internalInstance: getOpaqueNode(fiber), - data: getDataFiber(fiber), - renderer: rid, - type: 'updateProfileTimes', - }); - } - return; - } - pendingEvents.push({ - internalInstance: getOpaqueNode(fiber), - data: getDataFiber(fiber), - renderer: rid, - type: 'update', - }); - } - - function enqueueUnmount(fiber) { - const isRoot = fiber.tag === HostRoot; - const opaqueNode = getOpaqueNode(fiber); - const event = { - internalInstance: opaqueNode, - renderer: rid, - type: 'unmount', - }; - if (isRoot) { - pendingEvents.push(event); - } else { - // Non-root fibers are deleted during the commit phase. - // They are deleted in the child-first order. However - // DevTools currently expects deletions to be parent-first. - // This is why we unshift deletions rather than push them. - pendingEvents.unshift(event); - } - opaqueNodes.delete(opaqueNode); - } - - function markRootCommitted(fiber) { - pendingEvents.push({ - internalInstance: getOpaqueNode(fiber), - data: getDataFiber(fiber), - renderer: rid, - type: 'rootCommitted', - }); - } - - function mountFiber(fiber) { - // Depth-first. - // Logs mounting of children first, parents later. - let node = fiber; - outer: while (true) { - if (node.child) { - node.child.return = node; - node = node.child; - continue; - } - enqueueMount(node); - if (node == fiber) { - return; - } - if (node.sibling) { - node.sibling.return = node.return; - node = node.sibling; - continue; - } - while (node.return) { - node = node.return; - enqueueMount(node); - if (node == fiber) { - return; - } - if (node.sibling) { - node.sibling.return = node.return; - node = node.sibling; - continue outer; - } - } - return; - } - } - - function updateFiber(nextFiber, prevFiber) { - // Suspense components only have a non-null memoizedState if they're timed-out. - const isTimedOutSuspense = ( - nextFiber.tag === ReactTypeOfWork.SuspenseComponent && - nextFiber.memoizedState !== null - ); - - if (isTimedOutSuspense) { - // The behavior of timed-out Suspense trees is unique. - // Rather than unmount the timed out content (and possibly lose important state), - // React re-parents this content within a hidden Fragment while the fallback is showing. - // This behavior doesn't need to be observable in the DevTools though. - // It might even result in a bad user experience for e.g. node selection in the Elements panel. - // The easiest fix is to strip out the intermediate Fragment fibers, - // so the Elements panel and Profiler don't need to special case them. - const primaryChildFragment = nextFiber.child; - const fallbackChildFragment = primaryChildFragment.sibling; - const fallbackChild = fallbackChildFragment.child; - // The primary, hidden child is never actually updated in this case, - // so we can skip any updates to its tree. - // We only need to track updates to the Fallback UI for now. - if (fallbackChild.alternate) { - updateFiber(fallbackChild, fallbackChild.alternate); - } else { - mountFiber(fallbackChild); - } - enqueueUpdateIfNecessary(nextFiber, false); - } else { - let hasChildOrderChanged = false; - if (nextFiber.child !== prevFiber.child) { - // If the first child is different, we need to traverse them. - // Each next child will be either a new child (mount) or an alternate (update). - let nextChild = nextFiber.child; - let prevChildAtSameIndex = prevFiber.child; - while (nextChild) { - // We already know children will be referentially different because - // they are either new mounts or alternates of previous children. - // Schedule updates and mounts depending on whether alternates exist. - // We don't track deletions here because they are reported separately. - if (nextChild.alternate) { - const prevChild = nextChild.alternate; - updateFiber(nextChild, prevChild); - // However we also keep track if the order of the children matches - // the previous order. They are always different referentially, but - // if the instances line up conceptually we'll want to know that. - if (!hasChildOrderChanged && prevChild !== prevChildAtSameIndex) { - hasChildOrderChanged = true; - } - } else { - mountFiber(nextChild); - if (!hasChildOrderChanged) { - hasChildOrderChanged = true; - } - } - // Try the next child. - nextChild = nextChild.sibling; - // Advance the pointer in the previous list so that we can - // keep comparing if they line up. - if (!hasChildOrderChanged && prevChildAtSameIndex != null) { - prevChildAtSameIndex = prevChildAtSameIndex.sibling; - } - } - // If we have no more children, but used to, they don't line up. - if (!hasChildOrderChanged && prevChildAtSameIndex != null) { - hasChildOrderChanged = true; - } - } - enqueueUpdateIfNecessary(nextFiber, hasChildOrderChanged); - } - } - - function walkTree() { - hook.getFiberRoots(rid).forEach(root => { - // Hydrate all the roots for the first time. - mountFiber(root.current); - markRootCommitted(root.current); - }); - flushPendingEvents(); - } - - function cleanup() { - // We don't patch any methods so there is no cleanup. - } - - function handleCommitFiberUnmount(fiber) { - // This is not recursive. - // We can't traverse fibers after unmounting so instead - // we rely on React telling us about each unmount. - // It will be flushed after the root is committed. - enqueueUnmount(fiber); - } - - function handleCommitFiberRoot(root) { - const current = root.current; - const alternate = current.alternate; - - if (alternate) { - // TODO: relying on this seems a bit fishy. - const wasMounted = alternate.memoizedState != null && alternate.memoizedState.element != null; - const isMounted = current.memoizedState != null && current.memoizedState.element != null; - if (!wasMounted && isMounted) { - // Mount a new root. - mountFiber(current); - } else if (wasMounted && isMounted) { - // Update an existing root. - updateFiber(current, alternate); - } else if (wasMounted && !isMounted) { - // Unmount an existing root. - enqueueUnmount(current); - } - } else { - // Mount a new root. - mountFiber(current); - } - markRootCommitted(current); - // We're done here. - flushPendingEvents(); - } - - // The naming is confusing. - // They deal with opaque nodes (fibers), not elements. - function getNativeFromReactElement(fiber) { - try { - const opaqueNode = fiber; - const hostInstance = renderer.findHostInstanceByFiber(opaqueNode); - return hostInstance; - } catch (err) { - // The fiber might have unmounted by now. - return null; - } - } - function getReactElementFromNative(hostInstance) { - const fiber = renderer.findFiberByHostInstance(hostInstance); - if (fiber != null) { - // TODO: type fibers. - const opaqueNode = getOpaqueNode((fiber: any)); - return opaqueNode; - } - return null; - } - return { - getNativeFromReactElement, - getReactElementFromNative, - handleCommitFiberRoot, - handleCommitFiberUnmount, - cleanup, - walkTree, - }; -} - -module.exports = attachRendererFiber; diff --git a/backend/backend.js b/backend/backend.js deleted file mode 100644 index ff64a5ccd9..0000000000 --- a/backend/backend.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - * - * This is the chrome devtools - * - * 1. Devtools sets the __REACT_DEVTOOLS_GLOBAL_HOOK__ global. - * 2. React (if present) calls .inject() with the internal renderer - * 3. Devtools sees the renderer, and then adds this backend, along with the Agent - * and whatever else is needed. - * 4. The agent then calls `.emit('react-devtools', agent)` - * - * Now things are hooked up. - * - * When devtools closes, it calls `cleanup()` to remove the listeners - * and any overhead caused by the backend. - */ -'use strict'; - -import type {Hook} from './types'; - -var attachRenderer = require('./attachRenderer'); - -module.exports = function setupBackend(hook: Hook): boolean { - var oldReact = window.React && window.React.__internals; - if (oldReact && Object.keys(hook._renderers).length === 0) { - hook.inject(oldReact); - } - - for (var rid in hook._renderers) { - hook.helpers[rid] = attachRenderer(hook, rid, hook._renderers[rid]); - hook.emit('renderer-attached', {id: rid, renderer: hook._renderers[rid], helpers: hook.helpers[rid]}); - } - - hook.on('renderer', ({id, renderer}) => { - hook.helpers[id] = attachRenderer(hook, id, renderer); - hook.emit('renderer-attached', {id, renderer, helpers: hook.helpers[id]}); - }); - - var shutdown = () => { - for (var id in hook.helpers) { - hook.helpers[id].cleanup(); - } - hook.off('shutdown', shutdown); - }; - hook.on('shutdown', shutdown); - - return true; -}; diff --git a/backend/copyWithSet.js b/backend/copyWithSet.js deleted file mode 100644 index 8c04739614..0000000000 --- a/backend/copyWithSet.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -function copyWithSetImpl(obj, path, idx, value) { - if (idx >= path.length) { - return value; - } - var key = path[idx]; - var updated = Array.isArray(obj) ? obj.slice() : {...obj}; - // $FlowFixMe number or string is fine here - updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value); - return updated; -} - -function copyWithSet(obj: Object | Array, path: Array, value: any): Object | Array { - return copyWithSetImpl(obj, path, 0, value); -} - -module.exports = copyWithSet; diff --git a/backend/getData.js b/backend/getData.js deleted file mode 100644 index cccb4fa1e4..0000000000 --- a/backend/getData.js +++ /dev/null @@ -1,221 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -// ---------------------------------------------------- -// This is Stack-only version. -// The Fiber version is inlined in attachRendererFiber. -// ---------------------------------------------------- - -import type {DataType} from './types'; -var copyWithSet = require('./copyWithSet'); -var getDisplayName = require('./getDisplayName'); -var traverseAllChildrenImpl = require('./traverseAllChildrenImpl'); - -/** - * Convert a react internal instance to a sanitized data object. - */ -function getData(internalInstance: Object): DataType { - var children = null; - var props = null; - var state = null; - var context = null; - var updater = null; - var name = null; - var type = null; - var key = null; - var ref = null; - var source = null; - var text = null; - var publicInstance = null; - var nodeType = 'Native'; - // If the parent is a native node without rendered children, but with - // multiple string children, then the `element` that gets passed in here is - // a plain value -- a string or number. - if (typeof internalInstance !== 'object') { - nodeType = 'Text'; - text = internalInstance + ''; - } else if (internalInstance._currentElement === null || internalInstance._currentElement === false) { - nodeType = 'Empty'; - } else if (internalInstance._renderedComponent) { - nodeType = 'NativeWrapper'; - children = [internalInstance._renderedComponent]; - props = internalInstance._instance.props; - state = internalInstance._instance.state; - context = internalInstance._instance.context; - if (context && Object.keys(context).length === 0) { - context = null; - } - } else if (internalInstance._renderedChildren) { - children = childrenList(internalInstance._renderedChildren); - } else if (internalInstance._currentElement && internalInstance._currentElement.props) { - // This is a native node without rendered children -- meaning the children - // prop is the unfiltered list of children. - // This may include 'null' or even other invalid values, so we need to - // filter it the same way that ReactDOM does. - // Instead of pulling in the whole React library, we just copied over the - // 'traverseAllChildrenImpl' method. - // https://github.com/facebook/react/blob/240b84ed8e1db715d759afaae85033718a0b24e1/src/isomorphic/children/ReactChildren.js#L112-L158 - const unfilteredChildren = internalInstance._currentElement.props.children; - var filteredChildren = []; - traverseAllChildrenImpl( - unfilteredChildren, - '', // nameSoFar - (_traverseContext, child) => { - const childType = typeof child; - if (childType === 'string' || childType === 'number') { - filteredChildren.push(child); - } - }, - // traverseContext - ); - if (filteredChildren.length <= 1) { - // children must be an array of nodes or a string or undefined - // can't be an empty array - children = filteredChildren.length - ? String(filteredChildren[0]) - : undefined; - } else { - children = filteredChildren; - } - } - - if (!props && internalInstance._currentElement && internalInstance._currentElement.props) { - props = internalInstance._currentElement.props; - } - - // != used deliberately here to catch undefined and null - if (internalInstance._currentElement != null) { - type = internalInstance._currentElement.type; - if (internalInstance._currentElement.key) { - key = String(internalInstance._currentElement.key); - } - source = internalInstance._currentElement._source; - ref = internalInstance._currentElement.ref; - if (typeof type === 'string') { - name = type; - if (internalInstance._nativeNode != null) { - publicInstance = internalInstance._nativeNode; - } - if (internalInstance._hostNode != null) { - publicInstance = internalInstance._hostNode; - } - } else if (typeof type === 'function') { - nodeType = 'Composite'; - name = getDisplayName(type); - // 0.14 top-level wrapper - // TODO(jared): The backend should just act as if these don't exist. - if (internalInstance._renderedComponent && ( - internalInstance._currentElement.props === internalInstance._renderedComponent._currentElement || - internalInstance._currentElement.type.isReactTopLevelWrapper - )) { - nodeType = 'Wrapper'; - } - if (name === null) { - name = 'No display name'; - } - } else if (typeof internalInstance._stringText === 'string') { - nodeType = 'Text'; - text = internalInstance._stringText; - } else { - name = getDisplayName(type); - } - } - - if (internalInstance._instance) { - var inst = internalInstance._instance; - - // A forceUpdate for stateless (functional) components. - var forceUpdate = inst.forceUpdate || (inst.updater && inst.updater.enqueueForceUpdate && function(cb) { - inst.updater.enqueueForceUpdate(this, cb, 'forceUpdate'); - }); - updater = { - setState: inst.setState && inst.setState.bind(inst), - forceUpdate: forceUpdate && forceUpdate.bind(inst), - setInProps: forceUpdate && setInProps.bind(null, internalInstance, forceUpdate), - setInState: inst.forceUpdate && setInState.bind(null, inst), - setInContext: forceUpdate && setInContext.bind(null, inst, forceUpdate), - }; - if (typeof type === 'function') { - publicInstance = inst; - } - - // TODO: React ART currently falls in this bucket, but this doesn't - // actually make sense and we should clean this up after stabilizing our - // API for backends - if (inst._renderedChildren) { - children = childrenList(inst._renderedChildren); - } - } - - if (typeof internalInstance.setNativeProps === 'function') { - // For editing styles in RN - updater = { - setNativeProps(nativeProps) { - internalInstance.setNativeProps(nativeProps); - }, - }; - } - - // $FlowFixMe - return { - nodeType, - type, - key, - ref, - source, - name, - props, - state, - context, - children, - text, - updater, - publicInstance, - }; -} - -function setInProps(internalInst, forceUpdate, path: Array, value: any) { - var element = internalInst._currentElement; - internalInst._currentElement = { - ...element, - props: copyWithSet(element.props, path, value), - }; - forceUpdate.call(internalInst._instance); -} - -function setInState(inst, path: Array, value: any) { - setIn(inst.state, path, value); - inst.forceUpdate(); -} - -function setInContext(inst, forceUpdate, path: Array, value: any) { - setIn(inst.context, path, value); - forceUpdate.call(inst); -} - -function setIn(obj: Object, path: Array, value: any) { - var last = path.pop(); - var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); - if (parent) { - parent[last] = value; - } -} - -function childrenList(children) { - var res = []; - for (var name in children) { - res.push(children[name]); - } - return res; -} - -module.exports = getData; diff --git a/backend/getData012.js b/backend/getData012.js deleted file mode 100644 index d5a329f748..0000000000 --- a/backend/getData012.js +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -// ---------------------------------------------------- -// This is Stack-only version. -// The Fiber version is inlined in attachRendererFiber. -// ---------------------------------------------------- - -import type {DataType} from './types'; -var copyWithSet = require('./copyWithSet'); - -function getData012(internalInstance: Object): DataType { - var children = null; - var props = internalInstance.props; - var state = internalInstance.state; - var context = internalInstance.context; - var updater = null; - var name = null; - var type = null; - var key = null; - var ref = null; - var text = null; - var publicInstance = null; - var nodeType = 'Native'; - if (internalInstance._renderedComponent) { - nodeType = 'Wrapper'; - children = [internalInstance._renderedComponent]; - if (context && Object.keys(context).length === 0) { - context = null; - } - } else if (internalInstance._renderedChildren) { - name = internalInstance.constructor.displayName; - children = childrenList(internalInstance._renderedChildren); - } else if (typeof props.children === 'string') { - // string children - name = internalInstance.constructor.displayName; - children = props.children; - nodeType = 'Native'; - } - - if (!props && internalInstance._currentElement && internalInstance._currentElement.props) { - props = internalInstance._currentElement.props; - } - - if (internalInstance._currentElement) { - type = internalInstance._currentElement.type; - if (internalInstance._currentElement.key) { - key = String(internalInstance._currentElement.key); - } - ref = internalInstance._currentElement.ref; - if (typeof type === 'string') { - name = type; - } else { - nodeType = 'Composite'; - name = type.displayName; - if (!name) { - name = 'No display name'; - } - } - } - - if (!name) { - name = internalInstance.constructor.displayName || 'No display name'; - nodeType = 'Composite'; - } - - if (typeof props === 'string') { - nodeType = 'Text'; - text = props; - props = null; - name = null; - } - - if (internalInstance.forceUpdate) { - updater = { - setState: internalInstance.setState.bind(internalInstance), - forceUpdate: internalInstance.forceUpdate.bind(internalInstance), - setInProps: internalInstance.forceUpdate && setInProps.bind(null, internalInstance), - setInState: internalInstance.forceUpdate && setInState.bind(null, internalInstance), - setInContext: internalInstance.forceUpdate && setInContext.bind(null, internalInstance), - }; - publicInstance = internalInstance; - } - - // $FlowFixMe - return { - nodeType, - type, - key, - ref, - source: null, - name, - props, - state, - context, - children, - text, - updater, - publicInstance, - }; -} - -function setInProps(inst, path: Array, value: any) { - inst.props = copyWithSet(inst.props, path, value); - inst.forceUpdate(); -} - -function setInState(inst, path: Array, value: any) { - setIn(inst.state, path, value); - inst.forceUpdate(); -} - -function setInContext(inst, path: Array, value: any) { - setIn(inst.context, path, value); - inst.forceUpdate(); -} - -function setIn(obj: Object, path: Array, value: any) { - var last = path.pop(); - var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); - if (parent) { - parent[last] = value; - } -} - -function childrenList(children) { - var res = []; - for (var name in children) { - res.push(children[name]); - } - return res; -} - -module.exports = getData012; diff --git a/backend/getDisplayName.js b/backend/getDisplayName.js deleted file mode 100644 index 7eb2c695d7..0000000000 --- a/backend/getDisplayName.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const FB_MODULE_RE = /^(.*) \[from (.*)\]$/; -const cachedDisplayNames: WeakMap = new WeakMap(); - -function getDisplayName(type: Function, fallbackName: string = 'Unknown'): string { - const nameFromCache = cachedDisplayNames.get(type); - if (nameFromCache != null) { - return nameFromCache; - } - - let displayName; - - // The displayName property is not guaranteed to be a string. - // It's only safe to use for our purposes if it's a string. - // github.com/facebook/react-devtools/issues/803 - if (typeof type.displayName === 'string') { - displayName = type.displayName; - } - - if (!displayName) { - displayName = type.name || fallbackName; - } - - // Facebook-specific hack to turn "Image [from Image.react]" into just "Image". - // We need displayName with module name for error reports but it clutters the DevTools. - const match = displayName.match(FB_MODULE_RE); - if (match) { - const componentName = match[1]; - const moduleName = match[2]; - if (componentName && moduleName) { - if ( - moduleName === componentName || - moduleName.startsWith(componentName + '.') - ) { - displayName = componentName; - } - } - } - - cachedDisplayNames.set(type, displayName); - return displayName; -} - -module.exports = getDisplayName; diff --git a/backend/installGlobalHook.js b/backend/installGlobalHook.js deleted file mode 100644 index b7b1599d23..0000000000 --- a/backend/installGlobalHook.js +++ /dev/null @@ -1,229 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Hook} from './types'; - -/** - * NOTE: This file cannot `require` any other modules. We `.toString()` the - * function in some places and inject the source into the page. - */ -function installGlobalHook(window: Object) { - if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { - return; - } - function detectReactBuildType(renderer) { - try { - if (typeof renderer.version === 'string') { - // React DOM Fiber (16+) - if (renderer.bundleType > 0) { - // This is not a production build. - // We are currently only using 0 (PROD) and 1 (DEV) - // but might add 2 (PROFILE) in the future. - return 'development'; - } - - // React 16 uses flat bundles. If we report the bundle as production - // version, it means we also minified and envified it ourselves. - return 'production'; - // Note: There is still a risk that the CommonJS entry point has not - // been envified or uglified. In this case the user would have *both* - // development and production bundle, but only the prod one would run. - // This would be really bad. We have a separate check for this because - // it happens *outside* of the renderer injection. See `checkDCE` below. - } - var toString = Function.prototype.toString; - if (renderer.Mount && renderer.Mount._renderNewRootComponent) { - // React DOM Stack - var renderRootCode = toString.call(renderer.Mount._renderNewRootComponent); - // Filter out bad results (if that is even possible): - if (renderRootCode.indexOf('function') !== 0) { - // Hope for the best if we're not sure. - return 'production'; - } - // Check for React DOM Stack < 15.1.0 in development. - // If it contains "storedMeasure" call, it's wrapped in ReactPerf (DEV only). - // This would be true even if it's minified, as method name still matches. - if (renderRootCode.indexOf('storedMeasure') !== -1) { - return 'development'; - } - // For other versions (and configurations) it's not so easy. - // Let's quickly exclude proper production builds. - // If it contains a warning message, it's either a DEV build, - // or an PROD build without proper dead code elimination. - if (renderRootCode.indexOf('should be a pure function') !== -1) { - // Now how do we tell a DEV build from a bad PROD build? - // If we see NODE_ENV, we're going to assume this is a dev build - // because most likely it is referring to an empty shim. - if (renderRootCode.indexOf('NODE_ENV') !== -1) { - return 'development'; - } - // If we see "development", we're dealing with an envified DEV build - // (such as the official React DEV UMD). - if (renderRootCode.indexOf('development') !== -1) { - return 'development'; - } - // I've seen process.env.NODE_ENV !== 'production' being smartly - // replaced by `true` in DEV by Webpack. I don't know how that - // works but we can safely guard against it because `true` was - // never used in the function source since it was written. - if (renderRootCode.indexOf('true') !== -1) { - return 'development'; - } - // By now either it is a production build that has not been minified, - // or (worse) this is a minified development build using non-standard - // environment (e.g. "staging"). We're going to look at whether - // the function argument name is mangled: - if ( - // 0.13 to 15 - renderRootCode.indexOf('nextElement') !== -1 || - // 0.12 - renderRootCode.indexOf('nextComponent') !== -1 - ) { - // We can't be certain whether this is a development build or not, - // but it is definitely unminified. - return 'unminified'; - } else { - // This is likely a minified development build. - return 'development'; - } - } - // By now we know that it's envified and dead code elimination worked, - // but what if it's still not minified? (Is this even possible?) - // Let's check matches for the first argument name. - if ( - // 0.13 to 15 - renderRootCode.indexOf('nextElement') !== -1 || - // 0.12 - renderRootCode.indexOf('nextComponent') !== -1 - ) { - return 'unminified'; - } - // Seems like we're using the production version. - // However, the branch above is Stack-only so this is 15 or earlier. - return 'outdated'; - } - } catch (err) { - // Weird environments may exist. - // This code needs a higher fault tolerance - // because it runs even with closed DevTools. - // TODO: should we catch errors in all injected code, and not just this part? - } - return 'production'; - } - - let hasDetectedBadDCE = false; - - const hook = ({ - // Shared between Stack and Fiber: - _renderers: {}, - helpers: {}, - checkDCE: function(fn) { - // This runs for production versions of React. - // Needs to be super safe. - try { - var toString = Function.prototype.toString; - var code = toString.call(fn); - // This is a string embedded in the passed function under DEV-only - // condition. However the function executes only in PROD. Therefore, - // if we see it, dead code elimination did not work. - if (code.indexOf('^_^') > -1) { - // Remember to report during next injection. - hasDetectedBadDCE = true; - // Bonus: throw an exception hoping that it gets picked up by - // a reporting system. Not synchronously so that it doesn't break the - // calling code. - setTimeout(function() { - throw new Error( - 'React is running in production mode, but dead code ' + - 'elimination has not been applied. Read how to correctly ' + - 'configure React for production: ' + - 'https://fb.me/react-perf-use-the-production-build' - ); - }); - } - } catch (err) { } - }, - inject: function(renderer) { - var id = Math.random().toString(16).slice(2); - hook._renderers[id] = renderer; - var reactBuildType = hasDetectedBadDCE ? - 'deadcode' : - detectReactBuildType(renderer); - hook.emit('renderer', {id, renderer, reactBuildType}); - return id; - }, - _listeners: {}, - sub: function(evt, fn) { - hook.on(evt, fn); - return () => hook.off(evt, fn); - }, - on: function(evt, fn) { - if (!hook._listeners[evt]) { - hook._listeners[evt] = []; - } - hook._listeners[evt].push(fn); - }, - off: function(evt, fn) { - if (!hook._listeners[evt]) { - return; - } - var ix = hook._listeners[evt].indexOf(fn); - if (ix !== -1) { - hook._listeners[evt].splice(ix, 1); - } - if (!hook._listeners[evt].length) { - hook._listeners[evt] = null; - } - }, - emit: function(evt, data) { - if (hook._listeners[evt]) { - hook._listeners[evt].map(fn => fn(data)); - } - }, - // Fiber-only: - supportsFiber: true, - _fiberRoots: {}, - getFiberRoots(rendererID) { - const roots = hook._fiberRoots; - if (!roots[rendererID]) { - roots[rendererID] = new Set(); - } - return roots[rendererID]; - }, - onCommitFiberUnmount: function(rendererID, fiber) { - // TODO: can we use hook for roots too? - if (hook.helpers[rendererID]) { - hook.helpers[rendererID].handleCommitFiberUnmount(fiber); - } - }, - onCommitFiberRoot: function(rendererID, root) { - const mountedRoots = hook.getFiberRoots(rendererID); - const current = root.current; - const isKnownRoot = mountedRoots.has(root); - const isUnmounting = current.memoizedState == null || current.memoizedState.element == null; - // Keep track of mounted roots so we can hydrate when DevTools connect. - if (!isKnownRoot && !isUnmounting) { - mountedRoots.add(root); - } else if (isKnownRoot && isUnmounting) { - mountedRoots.delete(root); - } - if (hook.helpers[rendererID]) { - hook.helpers[rendererID].handleCommitFiberRoot(root); - } - }, - }); - Object.defineProperty(window, '__REACT_DEVTOOLS_GLOBAL_HOOK__', { - value: (hook : Hook), - }); -} - -module.exports = installGlobalHook; diff --git a/backend/integration/Makefile b/backend/integration/Makefile deleted file mode 100644 index 0f2b4ebff7..0000000000 --- a/backend/integration/Makefile +++ /dev/null @@ -1,14 +0,0 @@ - -install: - for version in v0*; do\ - (cd $$version;npm install);\ - done; - -test: - for source in attach-*; do\ - browserify -t babelify is-travis.js $$source > tmp.js;\ - echo "Built $$source";\ - cat tmp.js | tape-run;\ - echo "Ran $$source";\ - done; - rm tmp.js diff --git a/backend/integration/Readme.md b/backend/integration/Readme.md deleted file mode 100644 index 9fbcd3f284..0000000000 --- a/backend/integration/Readme.md +++ /dev/null @@ -1,14 +0,0 @@ -# Testing React Integration - -- `npm install` in the `v0.xx` directories -- `npm install` here -- `webpack-dev-server --hot` - -Open `http://localhost:8080`, click a version and you should be running! - -Any changes to the test files will make the browser page reload with the new -code, re-running the tests. - -## Todo - -- [ ] make a page w/ iframes to all of the versions diff --git a/backend/integration/all.html b/backend/integration/all.html deleted file mode 100644 index f42d4105ce..0000000000 --- a/backend/integration/all.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - Home -
- - - - - diff --git a/backend/integration/attach-0.11.js b/backend/integration/attach-0.11.js deleted file mode 100644 index f26fd1b76a..0000000000 --- a/backend/integration/attach-0.11.js +++ /dev/null @@ -1,305 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -require('es6-map/implement'); -require('es6-set/implement'); - -var test = require('tape-catch'); -var makeReporter = require('./reporter'); -var spy = require('./spy'); - -var attachRenderer = require('../attachRenderer'); -var globalHook = require('../GlobalHook.js'); -globalHook(window); - -if (!window.IS_TRAVIS) { - makeReporter(test.createStream({objectMode: true})); -} - -var React = require('./v0.11/node_modules/react'); -var {EventEmitter} = require('events'); - -var renderer = React.__internals; - -function tracker(hook) { - var els = new Map(); - var roots = new Set(); - hook.on('root', ({internalInstance}) => roots.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => { - roots.delete(internalInstance); - els.delete(internalInstance); - }); - hook.on('mount', ({internalInstance, data}) => { - els.set(internalInstance, [data]); - }); - hook.on('update', ({internalInstance, data}) => { - els.get(internalInstance).push(data); - }); - return {els, roots}; -} - -function setup(hook) { - var handlers = { - root: spy(), - mount: spy(), - update: spy(), - unmount: spy(), - }; - for (var name in handlers) { - hook.on(name, handlers[name]); - } - return handlers; -} - -function wrapElement(hook, internalInstance) { - var extras = attachRenderer(hook, 'abc', renderer); - var node = document.createElement('div'); - React.renderComponent(internalInstance, node); - setTimeout(() => { - extras.cleanup(); - React.unmountComponentAtNode(node); - }, 0); -} - -function wrapRender(hook, fn) { - var extras = attachRenderer(hook, 'abc', renderer); - fn(); - setTimeout(() => { - extras.cleanup(); - }, 0); -} - -var SimpleApp = React.createClass({ - render() { - return React.DOM.div(null, 'Hello'); - }, -}); - -// Mounting and Unmounting - -test('should work with plain DOM node', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook, React.DOM.div(null, 'Plain')); - - setTimeout(() => { - t.ok(handlers.root.calledOnce, 'One root'); - // the div - t.equal(handlers.mount.callCount, 1, 'One mount'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); - }, 0); -}); - -test('should work with simple composite component', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook, ); - - setTimeout(() => { - t.ok(handlers.root.calledOnce, 'One root'); - // the composite component, and the div - t.equal(handlers.mount.callCount, 2, 'Two mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); - }, 0); -}); - -test('attaching late should work', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - var node = document.createElement('div'); - React.renderComponent(, node); - - var extras = attachRenderer(hook, 'abc', renderer); - extras.walkTree((component, data) => handlers.mount({component, data}), component => handlers.root({component})); - - t.equal(handlers.root.callCount, 1, 'One root'); - // the composite component, and the div - t.equal(handlers.mount.callCount, 2, 'Two mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - - // cleanup after - extras.cleanup(); - React.unmountComponentAtNode(node); - - t.end(); -}); - -test('should unmount everything', t => { - var hook = new EventEmitter(); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - var node = document.createElement('div'); - wrapRender(hook, () => { - React.renderComponent(, node); - setTimeout(() => { - t.ok(els.size > 0, 'Some elements'); - React.unmountComponentAtNode(node); - }, 0); - }); - - setTimeout(() => { - t.equal(els.size, 0, 'Everything unmounted'); - t.end(); - }, 0); -}); - -test('should register two roots', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapRender(hook, () => { - var node = document.createElement('div'); - var node2 = document.createElement('div'); - React.renderComponent(, node); - React.renderComponent(, node2); - React.unmountComponentAtNode(node); - React.unmountComponentAtNode(node2); - }); - - t.equal(handlers.root.callCount, 2, 'Two roots'); - t.end(); -}); - -test('Double render', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - wrapNode(node => { - wrapRender(hook, () => { - React.renderComponent(, node); - t.equal(handlers.update.callCount, 0, 'No updates'); - React.renderComponent(, node); - }); - }); - - setTimeout(() => { - t.equal(handlers.root.callCount, 1, 'One root'); - t.ok(handlers.update.callCount > 0, 'Updates'); - t.equal(els.size, 2, 'Only two mounted'); - t.end(); - }, 0); -}); - -test('Plain text nodes', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var PlainApp = React.createClass({ - render() { - return React.DOM.div(null, 'one', ['two'], 'three'); - }, - }); - wrapElement(hook, ); - - setTimeout(() => { - var composite = roots.values().next().value; - var div = els.get(composite)[0].children[0]; - var texts = els.get(div)[0].children; - - var contents = ['one', 'two', 'three']; - - t.equals(texts.length, 3, '3 text children'); - - texts.forEach((comp, i) => { - t.equals(els.get(comp)[0].text, contents[i], i + ') Text content correct'); - t.equals(els.get(comp)[0].nodeType, 'Text', i + ') NodeType = text'); - }); - - t.end(); - }, 0); -}); - -// State updating - -var StateApp = React.createClass({ - getInitialState() { - return {updated: false}; - }, - render() { - return React.DOM.div(null, this.state.updated ? 'Updated' : 'Not updated'); - }, -}); - -function wrapNode(fn) { - var node = document.createElement('div'); - fn(node); - setTimeout(() => { - React.unmountComponentAtNode(node); - }, 0); -} - -test('State update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.renderComponent(, node); - App.setState({updated: true}); - }); - }); - - setTimeout(() => { - var composite = roots.values().next().value; - var div = els.get(composite)[0].children[0]; - - var divUpdates = els.get(div); - t.equal(divUpdates[0].nodeType, 'Native', '[Div] Native type'); - t.equal(divUpdates[0].name, 'div', 'Named "div"'); - t.equal(divUpdates[1].children, 'Updated', 'Then, updated'); - var updates = els.get(composite); - t.equal(updates[0].nodeType, 'Composite', '[App] Composite type'); - t.equal(updates[0].name, 'StateApp', 'Named "StateApp"'); - t.equal(updates[1].state.updated, true, 'State[1] updated=true'); - t.end(); - }, 0); -}); - -test('Props update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var StateProps = React.createClass({ - getInitialState() { - return {pass: false}; - }, - render() { - return ; - }, - }); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.renderComponent(, node); - App.setState({pass: 100}); - }); - }); - - setTimeout(() => { - var composite = roots.values().next().value; - var simple = els.get(composite)[0].children[0]; - - var updates = els.get(simple); - // because of timing issues, the first mount will show the setState :( - t.equal(updates[1].props.pass, 100, 't=1, prop=100'); - t.end(); - }, 0); -}); diff --git a/backend/integration/attach-0.12.js b/backend/integration/attach-0.12.js deleted file mode 100644 index f0f5a1be26..0000000000 --- a/backend/integration/attach-0.12.js +++ /dev/null @@ -1,306 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -require('es6-map/implement'); -require('es6-set/implement'); - -var test = require('tape-catch'); -var makeReporter = require('./reporter'); -var spy = require('./spy'); - -var attachRenderer = require('../attachRenderer'); -var globalHook = require('../GlobalHook.js'); -globalHook(window); - -if (!window.IS_TRAVIS) { - makeReporter(test.createStream({objectMode: true})); -} - -var React = require('./v0.12/node_modules/react'); -var {EventEmitter} = require('events'); - -var renderers = window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers; -var renderer = renderers[Object.keys(renderers)[0]]; - -function tracker(hook) { - var els = new Map(); - var roots = new Set(); - hook.on('root', ({internalInstance}) => roots.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => { - roots.delete(internalInstance); - els.delete(internalInstance); - }); - hook.on('mount', ({internalInstance, data}) => { - els.set(internalInstance, [data]); - }); - hook.on('update', ({internalInstance, data}) => { - els.get(internalInstance).push(data); - }); - return {els, roots}; -} - -function setup(hook) { - var handlers = { - root: spy(), - mount: spy(), - update: spy(), - unmount: spy(), - }; - for (var name in handlers) { - hook.on(name, handlers[name]); - } - return handlers; -} - -function wrapElement(hook, internalInstance) { - var extras = attachRenderer(hook, 'abc', renderer); - var node = document.createElement('div'); - React.render(internalInstance, node); - setTimeout(() => { - extras.cleanup(); - React.unmountComponentAtNode(node); - }, 0); -} - -function wrapRender(hook, fn) { - var extras = attachRenderer(hook, 'abc', renderer); - fn(); - setTimeout(() => { - extras.cleanup(); - }, 0); -} - -var SimpleApp = React.createClass({ - render() { - return
Hello
; - }, -}); - -// Mounting and Unmounting - -test('should work with plain DOM node', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook,
Plain
); - - setTimeout(() => { - t.ok(handlers.root.calledOnce, 'One root'); - // the div - t.equal(handlers.mount.callCount, 1, 'One mount'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); - }, 0); -}); - -test('should work with simple composite component', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook, ); - - setTimeout(() => { - t.ok(handlers.root.calledOnce, 'One root'); - // the composite component, and the div - t.equal(handlers.mount.callCount, 2, 'Two mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); - }, 0); -}); - -test('attaching late should work', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - var node = document.createElement('div'); - React.render(, node); - - var extras = attachRenderer(hook, 'abc', renderer); - extras.walkTree((component, data) => handlers.mount({component, data}), component => handlers.root({component})); - - t.equal(handlers.root.callCount, 1, 'One root'); - // the composite component, and the div - t.equal(handlers.mount.callCount, 2, 'Two mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - - // cleanup after - extras.cleanup(); - React.unmountComponentAtNode(node); - - t.end(); -}); - -test('should unmount everything', t => { - var hook = new EventEmitter(); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - var node = document.createElement('div'); - wrapRender(hook, () => { - React.render(, node); - setTimeout(() => { - t.ok(els.size > 0, 'Some elements'); - React.unmountComponentAtNode(node); - }, 0); - }); - - setTimeout(() => { - t.equal(els.size, 0, 'Everything unmounted'); - t.end(); - }, 0); -}); - -test('should register two roots', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapRender(hook, () => { - var node = document.createElement('div'); - var node2 = document.createElement('div'); - React.render(, node); - React.render(, node2); - React.unmountComponentAtNode(node); - React.unmountComponentAtNode(node2); - }); - - t.equal(handlers.root.callCount, 2, 'Two roots'); - t.end(); -}); - -test('Double render', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - wrapNode(node => { - wrapRender(hook, () => { - React.render(, node); - t.equal(handlers.update.callCount, 0, 'No updates'); - React.render(, node); - }); - }); - - setTimeout(() => { - t.equal(handlers.root.callCount, 1, 'One root'); - t.ok(handlers.update.callCount > 0, 'Updates'); - t.equal(els.size, 2, 'Only two mounted'); - t.end(); - }, 0); -}); - -test('Plain text nodes', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var PlainApp = React.createClass({ - render() { - return
one{['two']}three
; - }, - }); - wrapElement(hook, ); - - setTimeout(() => { - var composite = roots.values().next().value; - var div = els.get(composite)[0].children[0]; - var texts = els.get(div)[0].children; - - var contents = ['one', 'two', 'three']; - - t.equals(texts.length, 3, '3 text children'); - - texts.forEach((comp, i) => { - t.equals(els.get(comp)[0].text, contents[i], i + ') Text content correct'); - t.equals(els.get(comp)[0].nodeType, 'Text', i + ') NodeType = text'); - }); - - t.end(); - }, 0); -}); - -// State updating - -var StateApp = React.createClass({ - getInitialState() { - return {updated: false}; - }, - render() { - return
{this.state.updated ? 'Updated' : 'Not updated'}
; - }, -}); - -function wrapNode(fn) { - var node = document.createElement('div'); - fn(node); - setTimeout(() => { - React.unmountComponentAtNode(node); - }, 0); -} - -test('State update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.render(, node); - App.setState({updated: true}); - }); - }); - - setTimeout(() => { - var composite = roots.values().next().value; - var div = els.get(composite)[0].children[0]; - - var divUpdates = els.get(div); - t.equal(divUpdates[0].nodeType, 'Native', '[Div] Native type'); - t.equal(divUpdates[0].name, 'div', 'Named "div"'); - t.equal(divUpdates[1].children, 'Updated', 'Then, updated'); - var updates = els.get(composite); - t.equal(updates[0].nodeType, 'Composite', '[App] Composite type'); - t.equal(updates[0].name, 'StateApp', 'Named "StateApp"'); - t.equal(updates[1].state.updated, true, 'State[1] updated=true'); - t.end(); - }, 0); -}); - -test('Props update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var StateProps = React.createClass({ - getInitialState() { - return {pass: false}; - }, - render() { - return ; - }, - }); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.render(, node); - App.setState({pass: 100}); - }); - }); - - setTimeout(() => { - var composite = roots.values().next().value; - var simple = els.get(composite)[0].children[0]; - - var updates = els.get(simple); - // because of timing issues, the first mount will show the setState :( - t.equal(updates[1].props.pass, 100, 't=1, prop=100'); - t.end(); - }, 0); -}); diff --git a/backend/integration/attach-0.13.js b/backend/integration/attach-0.13.js deleted file mode 100644 index d4bb65fee5..0000000000 --- a/backend/integration/attach-0.13.js +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -require('es6-map/implement'); -require('es6-set/implement'); - -var test = require('tape'); -var makeReporter = require('./reporter'); -var spy = require('./spy'); - -var attachRenderer = require('../attachRenderer'); -var globalHook = require('../GlobalHook.js'); -globalHook(window); - -if (!window.IS_TRAVIS) { - makeReporter(test.createStream({objectMode: true})); -} - -var React = require('./v0.13/node_modules/react'); -var {EventEmitter} = require('events'); - -var renderers = window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers; -var renderer = renderers[Object.keys(renderers)[0]]; - -function tracker(hook) { - var els = new Map(); - var roots = new Set(); - hook.on('root', ({internalInstance}) => roots.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => { - roots.delete(internalInstance); - els.delete(internalInstance); - }); - hook.on('mount', ({internalInstance, data}) => { - els.set(internalInstance, [data]); - }); - hook.on('update', ({internalInstance, data}) => { - els.get(internalInstance).push(data); - }); - return {els, roots}; -} - -function setup(hook) { - var handlers = { - root: spy(), - mount: spy(), - update: spy(), - unmount: spy(), - }; - for (var name in handlers) { - hook.on(name, handlers[name]); - } - return handlers; -} - -function wrapElement(hook, element) { - var extras = attachRenderer(hook, 'abc', renderer); - var node = document.createElement('div'); - React.render(element, node); - extras.cleanup(); - React.unmountComponentAtNode(node); -} - -function wrapRender(hook, fn) { - var extras = attachRenderer(hook, 'abc', renderer); - fn(); - extras.cleanup(); -} - -var SimpleApp = React.createClass({ - render() { - return
Hello
; - }, -}); - -// Mounting and Unmounting - -test('should work with plain DOM node', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook,
Plain
); - - t.ok(handlers.root.calledOnce, 'One root'); - // the root-level wrapper, and the div - t.equal(handlers.mount.callCount, 2, 'Two mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); -}); - -test('should work with simple composite component', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook, ); - - t.ok(handlers.root.calledOnce, 'One root'); - // the root-level wrapper, the composite component, and the div - t.equal(handlers.mount.callCount, 3, 'Three mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); -}); - -test('attaching late should work', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - var node = document.createElement('div'); - React.render(, node); - - var extras = attachRenderer(hook, 'abc', renderer); - extras.walkTree((component, data) => handlers.mount({component, data}), component => handlers.root({component})); - - t.equal(handlers.root.callCount, 1, 'One root'); - // the root-level wrapper, the composite component, and the div - t.equal(handlers.mount.callCount, 3, 'Three mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - - // cleanup after - extras.cleanup(); - React.unmountComponentAtNode(node); - - t.end(); -}); - -test('should unmount everything', t => { - var hook = new EventEmitter(); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - wrapRender(hook, () => { - var node = document.createElement('div'); - React.render(, node); - t.ok(els.size > 0, 'Some elements'); - React.unmountComponentAtNode(node); - }); - - t.equal(els.size, 0, 'Everything unmounted'); - t.end(); -}); - -test('should register two roots', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapRender(hook, () => { - var node = document.createElement('div'); - var node2 = document.createElement('div'); - React.render(, node); - React.render(, node2); - React.unmountComponentAtNode(node); - React.unmountComponentAtNode(node2); - }); - - t.equal(handlers.root.callCount, 2, 'Two roots'); - t.end(); -}); - -test('Double render', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - wrapNode(node => { - wrapRender(hook, () => { - React.render(, node); - t.equal(handlers.update.callCount, 0, 'No updates'); - React.render(, node); - }); - }); - - t.equal(handlers.root.callCount, 1, 'One root'); - t.ok(handlers.update.callCount > 0, 'Updates'); - t.equal(els.size, 3, 'Only three mounted'); - t.end(); -}); - -test('Plain text nodes', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var PlainApp = React.createClass({ - render() { - return
one{['two']}three
; - }, - }); - wrapElement(hook, ); - - var root = roots.values().next().value; - var composite = els.get(root)[0].children[0]; - var div = els.get(composite)[0].children[0]; - var texts = els.get(div)[0].children; - - var contents = ['one', 'two', 'three']; - - t.equals(texts.length, 3, '3 text children'); - - texts.forEach((comp, i) => { - t.equals(els.get(comp)[0].text, contents[i], i + ') Text content correct'); - t.equals(els.get(comp)[0].nodeType, 'Text', i + ') NodeType = text'); - }); - - t.end(); -}); - -// State updating - -var StateApp = React.createClass({ - getInitialState() { - return {updated: false}; - }, - render() { - return
{this.state.updated ? 'Updated' : 'Not updated'}
; - }, -}); - -function wrapNode(fn) { - var node = document.createElement('div'); - fn(node); - React.unmountComponentAtNode(node); -} - -test('State update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.render(, node); - App.setState({updated: true}); - }); - }); - - var composite = roots.values().next().value; - var wrapper = els.get(composite)[0].children[0]; - var div = els.get(wrapper)[0].children[0]; - - var divUpdates = els.get(div); - t.equal(divUpdates[0].nodeType, 'Native', '[Div] Native type'); - t.equal(divUpdates[0].name, 'div', 'Named "div"'); - t.equal(divUpdates[0].children, 'Not updated', 'At first, not updated'); - t.equal(divUpdates[1].children, 'Updated', 'Then, updated'); - var updates = els.get(composite); - t.equal(updates[0].nodeType, 'Composite', '[App] Composite type'); - t.equal(updates[0].name, 'StateApp', 'Named "StateApp"'); - t.equal(updates[0].state.updated, false, 'State[0] updated=false'); - t.equal(updates[1].state.updated, true, 'State[1] updated=true'); - t.end(); -}); - -test('Props update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var StateProps = React.createClass({ - getInitialState() { - return {pass: false}; - }, - render() { - return ; - }, - }); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.render(, node); - App.setState({pass: true}); - App.setState({pass: 100}); - }); - }); - - var composite = roots.values().next().value; - var simple = els.get(composite)[0].children[0]; - - var updates = els.get(simple); - t.equal(updates[0].props.pass, false, 't=0, prop=false'); - t.equal(updates[1].props.pass, true, 't=1, prop=true'); - t.equal(updates[2].props.pass, 100, 't=2, prop=100'); - t.end(); -}); diff --git a/backend/integration/attach-0.14.js b/backend/integration/attach-0.14.js deleted file mode 100644 index 81da56d3ea..0000000000 --- a/backend/integration/attach-0.14.js +++ /dev/null @@ -1,291 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -require('es6-map/implement'); -require('es6-set/implement'); - -var test = require('tape'); -var makeReporter = require('./reporter'); -var spy = require('./spy'); - -var attachRenderer = require('../attachRenderer'); -var globalHook = require('../GlobalHook.js'); -globalHook(window); - -if (!window.IS_TRAVIS) { - makeReporter(test.createStream({objectMode: true})); -} - -var React = require('./v0.14/node_modules/react'); -var {EventEmitter} = require('events'); - -var renderers = window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers; -var renderer = renderers[Object.keys(renderers)[0]]; - -function tracker(hook) { - var els = new Map(); - var roots = new Set(); - hook.on('root', ({internalInstance}) => roots.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => { - roots.delete(internalInstance); - els.delete(internalInstance); - }); - hook.on('mount', ({internalInstance, data}) => { - els.set(internalInstance, [data]); - }); - hook.on('update', ({internalInstance, data}) => { - els.get(internalInstance).push(data); - }); - return {els, roots}; -} - -function setup(hook) { - var handlers = { - root: spy(), - mount: spy(), - update: spy(), - unmount: spy(), - }; - for (var name in handlers) { - hook.on(name, handlers[name]); - } - return handlers; -} - -function wrapElement(hook, element) { - var extras = attachRenderer(hook, 'abc', renderer); - var node = document.createElement('div'); - React.render(element, node); - extras.cleanup(); - React.unmountComponentAtNode(node); -} - -function wrapRender(hook, fn) { - var extras = attachRenderer(hook, 'abc', renderer); - fn(); - extras.cleanup(); -} - -var SimpleApp = React.createClass({ - render() { - return
Hello
; - }, -}); - -// Mounting and Unmounting - -test('should work with plain DOM node', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook,
Plain
); - - t.ok(handlers.root.calledOnce, 'One root'); - // the root-level wrapper, and the div - t.equal(handlers.mount.callCount, 2, 'Two mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); -}); - -test('should work with simple composite component', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapElement(hook, ); - - t.ok(handlers.root.calledOnce, 'One root'); - // the root-level wrapper, the composite component, and the div - t.equal(handlers.mount.callCount, 3, 'Three mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - t.end(); -}); - -test('attaching late should work', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - var node = document.createElement('div'); - React.render(, node); - - var extras = attachRenderer(hook, 'abc', renderer); - extras.walkTree((component, data) => handlers.mount({component, data}), component => handlers.root({component})); - - t.equal(handlers.root.callCount, 1, 'One root'); - // the root-level wrapper, the composite component, and the div - t.equal(handlers.mount.callCount, 3, 'Three mounts'); - t.notOk(handlers.unmount.called, 'No unmounts'); - - // cleanup after - extras.cleanup(); - React.unmountComponentAtNode(node); - - t.end(); -}); - -test('should unmount everything', t => { - var hook = new EventEmitter(); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - wrapRender(hook, () => { - var node = document.createElement('div'); - React.render(, node); - t.ok(els.size > 0, 'Some elements'); - React.unmountComponentAtNode(node); - }); - - t.equal(els.size, 0, 'Everything unmounted'); - t.end(); -}); - -test('should register two roots', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - - wrapRender(hook, () => { - var node = document.createElement('div'); - var node2 = document.createElement('div'); - React.render(, node); - React.render(, node2); - React.unmountComponentAtNode(node); - React.unmountComponentAtNode(node2); - }); - - t.equal(handlers.root.callCount, 2, 'Two roots'); - t.end(); -}); - -test('Double render', t => { - var hook = new EventEmitter(); - var handlers = setup(hook); - var els = new Set(); - hook.on('mount', ({internalInstance}) => els.add(internalInstance)); - hook.on('unmount', ({internalInstance}) => els.delete(internalInstance)); - - wrapNode(node => { - wrapRender(hook, () => { - React.render(, node); - t.equal(handlers.update.callCount, 0, 'No updates'); - React.render(, node); - }); - }); - - t.equal(handlers.root.callCount, 1, 'One root'); - t.ok(handlers.update.callCount > 0, 'Updates'); - t.equal(els.size, 3, 'Only three mounted'); - t.end(); -}); - -test('Plain text nodes', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var PlainApp = React.createClass({ - render() { - return
one{['two']}three
; - }, - }); - wrapElement(hook, ); - - var root = roots.values().next().value; - var composite = els.get(root)[0].children[0]; - var div = els.get(composite)[0].children[0]; - var texts = els.get(div)[0].children; - - var contents = ['one', 'two', 'three']; - - t.equals(texts.length, 3, '3 text children'); - - texts.forEach((comp, i) => { - t.equals(els.get(comp)[0].text, contents[i], i + ') Text content correct'); - t.equals(els.get(comp)[0].nodeType, 'Text', i + ') NodeType = text'); - }); - - t.end(); -}); - -// State updating - -var StateApp = React.createClass({ - getInitialState() { - return {updated: false}; - }, - render() { - return
{this.state.updated ? 'Updated' : 'Not updated'}
; - }, -}); - -function wrapNode(fn) { - var node = document.createElement('div'); - fn(node); - React.unmountComponentAtNode(node); -} - -test('State update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.render(, node); - App.setState({updated: true}); - }); - }); - - var root = roots.values().next().value; - var composite = els.get(root)[0].children[0]; - var div = els.get(composite)[0].children[0]; - - var divUpdates = els.get(div); - t.equal(divUpdates[0].nodeType, 'Native', '[Div] Native type'); - t.equal(divUpdates[0].name, 'div', 'Named "div"'); - t.equal(divUpdates[0].children, 'Not updated', 'At first, not updated'); - t.equal(divUpdates[1].children, 'Updated', 'Then, updated'); - var updates = els.get(composite); - t.equal(updates[0].nodeType, 'Composite', '[App] Composite type'); - t.equal(updates[0].name, 'StateApp', 'Named "StateApp"'); - t.equal(updates[0].state.updated, false, 'State[0] updated=false'); - t.equal(updates[1].state.updated, true, 'State[1] updated=true'); - t.end(); -}); - -test('Props update', t => { - var hook = new EventEmitter(); - var {roots, els} = tracker(hook); - - var StateProps = React.createClass({ - getInitialState() { - return {pass: false}; - }, - render() { - return ; - }, - }); - - wrapNode(node => { - wrapRender(hook, () => { - var App = React.render(, node); - App.setState({pass: true}); - App.setState({pass: 100}); - }); - }); - - var root = roots.values().next().value; - var composite = els.get(root)[0].children[0]; - var simple = els.get(composite)[0].children[0]; - - var updates = els.get(simple); - t.equal(updates[0].props.pass, false, 't=0, prop=false'); - t.equal(updates[1].props.pass, true, 't=1, prop=true'); - t.equal(updates[2].props.pass, 100, 't=2, prop=100'); - t.end(); -}); diff --git a/backend/integration/index.html b/backend/integration/index.html deleted file mode 100644 index c1d8281b90..0000000000 --- a/backend/integration/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - React Tests - - - -

Home

- - - - diff --git a/backend/integration/is-travis.js b/backend/integration/is-travis.js deleted file mode 100644 index 92be80582a..0000000000 --- a/backend/integration/is-travis.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -window.IS_TRAVIS = true; diff --git a/backend/integration/package.json b/backend/integration/package.json deleted file mode 100644 index 1658b56745..0000000000 --- a/backend/integration/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "devDependencies": { - "babelify": "^6.1.3", - "tap-browser-color": "^0.1.2", - "tape": "^4.0.1", - "tape-catch": "^1.0.4", - "webpack-dev-server": "^3.1.10" - }, - "dependencies": { - "es6-map": "^0.1.1", - "es6-set": "^0.1.1" - } -} diff --git a/backend/integration/reporter.js b/backend/integration/reporter.js deleted file mode 100644 index 2cf766f72c..0000000000 --- a/backend/integration/reporter.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -module.exports = stream => { - var status = document.createElement('div'); - status.className = 'status'; - var current = document.createElement('pre'); - current.className = 'current'; - var failures = document.createElement('pre'); - failures.className = 'failures'; - document.body.appendChild(status); - status.innerHTML = 'Status:'; - document.body.appendChild(current); - // document.body.appendChild(status); - var pass = 0; - var fail = 0; - var curTest = null; - stream.on('data', data => { - if (data.type === 'test') { - current.innerHTML += ' ' + data.id + ': ' + data.name + '\n'; - curTest = data.name; - } - if (data.type === 'assert') { - if (data.ok) { - pass += 1; - current.innerHTML += ' OK ' + data.name + '\n'; - } else { - fail += 1; - current.innerHTML += ' FAIL ' + data.name + '\n'; - current.innerHTML += ' Expected: ' + data.expected + ', Actual: ' + data.actual + '\n'; - console.log(curTest, data); - } - var color = fail ? 'red' : 'green'; - status.innerHTML = 'Status: ' + pass + '/' + (pass + fail) + ''; - } - }); -}; diff --git a/backend/integration/spy.js b/backend/integration/spy.js deleted file mode 100644 index c8459a78b5..0000000000 --- a/backend/integration/spy.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -module.exports = function spy() { - var fn = function() { - fn.callCount += 1; - fn.calledOnce = fn.callCount === 1; - fn.called = true; - fn.calls.push([].slice.call(arguments)); - }; - fn.calls = []; - fn.callCount = 0; - return fn; -}; diff --git a/backend/integration/v0.11/package.json b/backend/integration/v0.11/package.json deleted file mode 100644 index 8a5d86b5b0..0000000000 --- a/backend/integration/v0.11/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "react": "^0.11.2" - } -} diff --git a/backend/integration/v0.12/package.json b/backend/integration/v0.12/package.json deleted file mode 100644 index 8fc2f2b69c..0000000000 --- a/backend/integration/v0.12/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "react": "^0.12.2" - } -} diff --git a/backend/integration/v0.13/package.json b/backend/integration/v0.13/package.json deleted file mode 100644 index c883573591..0000000000 --- a/backend/integration/v0.13/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "react": "^0.13.3" - } -} diff --git a/backend/integration/v0.14/package.json b/backend/integration/v0.14/package.json deleted file mode 100644 index f989178567..0000000000 --- a/backend/integration/v0.14/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "react": "^0.14.0-beta1" - } -} diff --git a/backend/integration/webpack.config.js b/backend/integration/webpack.config.js deleted file mode 100644 index 4895c90a93..0000000000 --- a/backend/integration/webpack.config.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * A TodoMVC++ app for trying out the inspector - * - */ -'use strict'; - -const {readFileSync} = require('fs'); -const {resolve} = require('path'); - -module.exports = { - devtool: 'cheap-module-eval-source-map', - entry: { - v014: ['webpack/hot/dev-server', './attach-0.14.js'], - v013: ['webpack/hot/dev-server', './attach-0.13.js'], - v012: ['webpack/hot/dev-server', './attach-0.12.js'], - v011: ['webpack/hot/dev-server', './attach-0.11.js'], - }, - output: { - path: __dirname + '/build', - filename: '[name].js', - }, - - node: { - fs: 'empty', - }, - - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), - }, - ], - }, -}; diff --git a/backend/traverseAllChildrenImpl.js b/backend/traverseAllChildrenImpl.js deleted file mode 100644 index a80a1d8588..0000000000 --- a/backend/traverseAllChildrenImpl.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var invariant = require('fbjs/lib/invariant'); - -var SEPARATOR = '.'; -var SUBSEPARATOR = ':'; - -var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. -// The Symbol used to tag the ReactElement type. If there is no native Symbol -// nor polyfill, then a plain number is used for performance. -var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; -const REACT_ELEMENT_TYPE = - ( - typeof Symbol === 'function' - && Symbol.for - && Symbol.for('react.element') - ) - || 0xeac7; - - -/** - * Escape and wrap key so it is safe to use as a reactid - * - * @param {string} key to be escaped. - * @return {string} the escaped key. - */ -function escape(key: string): string { - var escapeRegex = /[=:]/g; - var escaperLookup = { - '=': '=0', - ':': '=2', - }; - var escapedString = ('' + key).replace(escapeRegex, function(match) { - return escaperLookup[match]; - }); - - return '$' + escapedString; -} - -/** - * Generate a key string that identifies a component within a set. - * - * @param {*} component A component that could contain a manual key. - * @param {number} index Index that is used if a manual key is not provided. - * @return {string} - */ -function getComponentKey(component, index) { - // Do some typechecking here since we call this blindly. We want to ensure - // that we don't block potential future ES APIs. - if ( - typeof component === 'object' && - component !== null && - component.key != null - ) { - // Explicit key - return escape(component.key); - } - // Implicit key determined by the index in the set - return index.toString(36); -} - -/** - * We do a copied the 'traverseAllChildrenImpl' method from - * `React.Children` so that we don't pull in the whole React library. - * @param {?*} children Children tree container. - * @param {!string} nameSoFar Name of the key path so far. - * @param {!function} callback Callback to invoke with each child found. - * @param {?*} traverseContext Used to pass information throughout the traversal - * process. - * @return {!number} The number of children in this subtree. - */ -function traverseAllChildrenImpl( - children: any, - nameSoFar: string, - callback: Function, - traverseContext: any -): number { - var type = typeof children; - - if (type === 'undefined' || type === 'boolean') { - // All of the above are perceived as null. - children = null; - } - - if ( - children === null || - type === 'string' || - type === 'number' || - // The following is inlined from ReactElement. This means we can optimize - // some checks. React Fiber also inlines this logic for similar purposes. - (type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) - ) { - callback( - traverseContext, - children, - // If it's the only child, treat the name as if it was wrapped in an array - // so that it's consistent if the number of children grows. - nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar, - ); - return 1; - } - - var child; - var nextName; - var subtreeCount = 0; // Count of children found in the current subtree. - var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; - - if (Array.isArray(children)) { - for (var i = 0; i < children.length; i++) { - child = children[i]; - nextName = nextNamePrefix + getComponentKey(child, i); - subtreeCount += traverseAllChildrenImpl( - child, - nextName, - callback, - traverseContext, - ); - } - } else { - var iteratorFn = - (ITERATOR_SYMBOL && children[ITERATOR_SYMBOL]) || - children[FAUX_ITERATOR_SYMBOL]; - if (typeof iteratorFn === 'function') { - var iterator = iteratorFn.call(children); - var step; - var ii = 0; - while (!(step = iterator.next()).done) { - child = step.value; - nextName = nextNamePrefix + getComponentKey(child, ii++); - subtreeCount += traverseAllChildrenImpl( - child, - nextName, - callback, - traverseContext, - ); - } - } else if (type === 'object') { - var addendum = - ' If you meant to render a collection of children, use an array ' + - 'instead.'; - var childrenString = '' + children; - invariant( - false, - 'The React Devtools cannot render an object as a child. (found: %s).%s', - childrenString === '[object Object]' - ? 'object with keys {' + Object.keys(children).join(', ') + '}' - : childrenString, - addendum, - ); - } - } - - return subtreeCount; -} - -module.exports = traverseAllChildrenImpl; diff --git a/backend/types.js b/backend/types.js deleted file mode 100644 index 20c29b7149..0000000000 --- a/backend/types.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -type CompositeUpdater = { - setInProps: ?(path: Array, value: any) => void, - setInState: ?(path: Array, value: any) => void, - setInContext: ?(path: Array, value: any) => void, - forceUpdate: ?() => void, -}; - -type NativeUpdater = { - setNativeProps: ?(nativeProps: {[key: string]: any}) => void, -}; - -export type Interaction = {| - name: string, - timestamp: number, -|}; - -export type DataType = { - nodeType: 'Native' | 'Wrapper' | 'NativeWrapper' | 'Composite' | 'Special' | 'Text' | 'Portal' | 'Empty', - type: ?(string | AnyFn), - key: ?string, - ref: ?(string | AnyFn), - source: ?Object, - name: ?string, - props: ?Object, - state: ?Object, - context: ?Object, - children: ?(string | Array), - text: ?string, - updater: ?(CompositeUpdater | NativeUpdater), - publicInstance: ?Object, - - // Tracing - memoizedInteractions: ?Set, - - // Profiler - actualDuration: ?number, - actualStartTime: ?number, - treeBaseDuration: ?number, -}; - -// This type is entirely opaque to the backend. -export type OpaqueNodeHandle = { - _rootNodeID: string, -}; -export type NativeType = {}; -export type RendererID = string; - -type DOMNode = {}; - -export type AnyFn = (...args: Array) => any; - -type BundleType = - // PROD - | 0 - // DEV - | 1; - -export type ReactRenderer = { - // Fiber - findHostInstanceByFiber: (fiber: Object) => ?NativeType, - findFiberByHostInstance: (hostInstance: NativeType) => ?OpaqueNodeHandle, - version: string, - bundleType: BundleType, - - // Stack - Reconciler: { - mountComponent: AnyFn, - performUpdateIfNecessary: AnyFn, - receiveComponent: AnyFn, - unmountComponent: AnyFn, - }, - Component?: { - Mixin: Object, - }, - Mount: { - // React Native - nativeTagToRootNodeID: (tag: ?NativeType) => string, - findNodeHandle: (component: Object) => ?NativeType, - renderComponent: AnyFn, - _instancesByContainerID: Object, - - // React DOM - getID: (node: DOMNode) => string, - getNode: (id: string) => ?DOMNode, - _instancesByReactRootID: Object, - _renderNewRootComponent: AnyFn, - }, - ComponentTree: { - getNodeFromInstance: (component: OpaqueNodeHandle) => ?NativeType, - getClosestInstanceFromNode: (component: NativeType) => ?OpaqueNodeHandle, - }, -}; - -export type Helpers = { - getNativeFromReactElement?: ?(component: OpaqueNodeHandle) => ?NativeType, - getReactElementFromNative?: ?(component: NativeType) => ?OpaqueNodeHandle, - walkTree: (visit: (component: OpaqueNodeHandle, data: DataType) => void, visitRoot: (element: OpaqueNodeHandle) => void) => void, - cleanup: () => void, -}; - -export type Handler = (data: any) => void; - -export type Hook = { - _renderers: {[key: string]: ReactRenderer}, - _listeners: {[key: string]: Array}, - helpers: {[key: string]: Helpers}, - inject: (renderer: ReactRenderer) => string | null, - emit: (evt: string, data: any) => void, - sub: (evt: string, handler: Handler) => () => void, - on: (evt: string, handler: Handler) => void, - off: (evt: string, handler: Handler) => void, - reactDevtoolsAgent?: ?Object, - getFiberRoots: (rendererID : string) => Set, -}; diff --git a/flow/chrome.js b/flow/chrome.js deleted file mode 100644 index fd339da6db..0000000000 --- a/flow/chrome.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -declare var chrome: { - devtools: { - network: { - onNavigated: { - addListener: (cb: (url: string) => void) => void, - removeListener: (cb: () => void) => void, - }, - }, - inspectedWindow: { - eval: (code: string, cb?: (res: any, err: ?Object) => any) => void, - tabId: number, - }, - panels: { - create: (title: string, icon: string, filename: string, cb: (panel: { - onHidden: { - addListener: (cb: (window: Object) => void) => void, - }, - onShown: { - addListener: (cb: (window: Object) => void) => void, - } - }) => void) => void, - themeName: ?string, - }, - }, - tabs: { - create: (options: Object) => void, - executeScript: (tabId: number, options: Object, fn: () => void) => void, - onUpdated: { - addListener: (fn: (tabId: number, changeInfo: Object, tab: Object) => void) => void, - }, - query: (options: Object, fn: (tabArray: Array) => void) => void, - }, - browserAction: { - setIcon: (options: { - tabId: number, - path: {[key: string]: string} - }) => void, - setPopup: (options: { - tabId: number, - popup: string, - }) => void, - }, - runtime: { - getURL: (path: string) => string, - sendMessage: (config: Object) => void, - connect: (config: Object) => { - disconnect: () => void, - onMessage: { - addListener: (fn: (message: Object) => void) => void, - }, - onDisconnect: { - addListener: (fn: (message: Object) => void) => void, - }, - postMessage: (data: Object) => void, - }, - onConnect: { - addListener: (fn: (port: { - name: string, - sender: { - tab: { - id: number, - url: string, - }, - }, - }) => void) => void, - }, - onMessage: { - addListener: (fn: (req: Object, sender: { - url: string, - tab: { - id: number, - }, - }) => void) => void, - }, - }, -}; diff --git a/flow/performance.js b/flow/performance.js deleted file mode 100644 index b59b3accb8..0000000000 --- a/flow/performance.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -declare var performance: { - now: () => number, -}; diff --git a/frontend/BlurInput.js b/frontend/BlurInput.js deleted file mode 100644 index e6be38b4bc..0000000000 --- a/frontend/BlurInput.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var React = require('react'); -var Input = require('./Input'); - -import type {DOMEvent, DOMNode} from './types'; - -type Props = { - value: ?string, - onChange: (text: string) => mixed, -}; - -type DefaultProps = {}; -type State = { - text: string, -}; - - -class BlurInput extends React.Component { - defaultProps: DefaultProps; - node: ?DOMNode; - - constructor(props: Object) { - super(props); - this.state = {text: this.props.value || ''}; - } - - componentWillReceiveProps(nextProps: Object) { - if (nextProps.value !== this.props.value) { - this.setState({text: '' + nextProps.value}); - } - } - - done() { - if (this.state.text !== (this.props.value || '')) { - this.props.onChange(this.state.text); - } - } - - onKeyDown(e: DOMEvent) { - if (e.key === 'Enter') { - this.done(); - } - } - - render() { - return ( - this.node = i} - onChange={e => this.setState({text: e.target.value})} - onBlur={this.done.bind(this)} - onKeyDown={e => this.onKeyDown(e)} - /> - ); - } -} - -module.exports = BlurInput; diff --git a/frontend/Breadcrumb.js b/frontend/Breadcrumb.js deleted file mode 100644 index 5d92698f0f..0000000000 --- a/frontend/Breadcrumb.js +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type Store from './Store'; -import type {ElementID} from './types'; -import type {Theme} from './types'; - -const {sansSerif} = require('./Themes/Fonts'); -const PropTypes = require('prop-types'); -const React = require('react'); -const decorate = require('./decorate'); - -type BreadcrumbPath = Array<{id: ElementID, node: Object}>; - -type Props = { - hover: (string, boolean) => void; - selected: string, - path: BreadcrumbPath, - select: string => ElementID, -} - -type State ={ - hovered: ?string -} - -class Breadcrumb extends React.Component { - context: {theme: Theme}; - // $FlowFixMe createRef() - selectedListItem = React.createRef(); - - constructor(props) { - super(props); - this.state = { hovered: null }; - } - - componentDidMount() { - if (this.props.selected) { - this.ensureInView(); - } - } - - componentDidUpdate(prevProps, prevState) { - if (this.props.selected !== prevProps.selected) { - this.ensureInView(); - } - } - - handleCrumbMouseOver(id) { - this.setState({ hovered: id }); - this.props.hover(id, true); - } - - handleCrumbMouseOut(id) { - this.setState({ hovered: null }); - this.props.hover(id, false); - } - - render() { - const {theme} = this.context; - const {path, selected} = this.props; - - return ( -
    - {path.map(({ id, node }) => { - const isSelected = id === selected; - const style = itemStyle( - isSelected, - node.get('nodeType'), - theme, - ); - - return ( -
  • this.handleCrumbMouseOver(id)} - onMouseOut={() => this.handleCrumbMouseOut(id)} - onClick={isSelected ? null : () => this.props.select(id)} - ref={isSelected ? this.selectedListItem : undefined} - > - {node.get('name') || '"' + node.get('text') + '"'} -
  • - ); - })} -
- ); - } - - ensureInView() { - const selectedListItem = this.selectedListItem.current; - if (selectedListItem != null) { - if (typeof selectedListItem.scrollIntoViewIfNeeded === 'function') { - selectedListItem.scrollIntoViewIfNeeded({ - inline: 'nearest', - }); - } else if (typeof selectedListItem.scrollIntoView === 'function') { - selectedListItem.scrollIntoView({ - inline: 'nearest', - }); - } - } - } -} - -Breadcrumb.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const containerStyle = (theme: Theme) => ({ - fontFamily: sansSerif.family, - listStyle: 'none', - padding: '0 0.5rem', - margin: 0, - maxHeight: '80px', - backgroundColor: theme.base01, - borderTop: `1px solid ${theme.base03}`, - whiteSpace: 'nowrap', - overflow: 'auto', -}); - -const itemStyle = (isSelected: boolean, nodeType: string, theme: Theme) => { - let color; - if (isSelected) { - color = theme.state02; - } else if (nodeType === 'Special') { - color = theme.special01; - } else if (nodeType === 'Composite') { - color = theme.special05; - } - - return { - backgroundColor: isSelected ? theme.state00 : 'transparent', - color, - cursor: isSelected ? 'default' : 'pointer', - padding: '0.25rem 0.5rem', - WebkitUserSelect: 'none', - MozUserSelect: 'none', - userSelect: 'none', - display: 'inline-block', - }; -}; - -function getBreadcrumbPath(store: Store): BreadcrumbPath { - var path = []; - var current = store.breadcrumbHead; - while (current) { - path.unshift({ - id: current, - node: store.get(current), - }); - current = store.skipWrapper(store.getParent(current), true); - } - return path; -} - -module.exports = decorate({ - listeners: () => ['breadcrumbHead', 'selected'], - props(store, props) { - return { - select: id => store.selectBreadcrumb(id), - hover: (id, isHovered) => store.setHover(id, isHovered, false), - selected: store.selected, - path: getBreadcrumbPath(store), - }; - }, -}, Breadcrumb); diff --git a/frontend/Container.js b/frontend/Container.js deleted file mode 100644 index 7e4043acfc..0000000000 --- a/frontend/Container.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var ContextMenu = require('./ContextMenu'); -var PropState = require('./PropState'); -var React = require('react'); -var LeftPane = require('./LeftPane'); -var PreferencesPanel = require('./PreferencesPanel'); -var SplitPane = require('./SplitPane'); -var TabbedPane = require('./TabbedPane'); - -import type MenuItem from './ContextMenu'; -import type {Theme} from './types'; - -type Props = { - reload?: () => void, - extraPanes: Array<(node: Object) => React.Node>, - extraTabs: ?{[key: string]: () => React.Node}, - menuItems: { - tree?: (id: string, node: Object, store: Object) => ?Array, - attr?: ( - id: string, - node: Object, - val: any, - path: Array, - name: string, - store: Object - ) => ?Array, - }, - extraTabs: {[key: string]: () => React.Node}, - preferencesPanelShown: boolean, - theme: Theme, - onViewElementSource: null | (id: string, node: Object) => void, -}; - -type State = { - isVertical: boolean, -}; - -var IS_VERTICAL_BREAKPOINT = 500; - -function shouldUseVerticalLayout(window) { - return window.innerWidth < IS_VERTICAL_BREAKPOINT; -} - -class Container extends React.Component { - // eslint shouldn't error on type positions. TODO: update eslint - // eslint-disable-next-line no-undef - resizeTimeout: ?TimeoutID; - - constructor(props: Props) { - super(props); - - this.state = { - isVertical: shouldUseVerticalLayout(window), - }; - } - - componentDidMount() { - window.addEventListener('resize', this.handleResize, false); - this.setState({ - isVertical: shouldUseVerticalLayout(window), - }); - } - - componentWillUnmount() { - window.removeEventListener('resize', this.handleResize); - if (this.resizeTimeout != null) { - clearTimeout(this.resizeTimeout); - } - } - - // $FlowFixMe future versions of Flow can infer this - handleResize = (e: Event): void => { - if (!this.resizeTimeout) { - this.resizeTimeout = setTimeout(this.handleResizeTimeout, 50); - } - }; - - // $FlowFixMe future versions of Flow can infer this - handleResizeTimeout = (): void => { - this.resizeTimeout = null; - - this.setState({ - isVertical: shouldUseVerticalLayout(window), - }); - }; - - render() { - const {preferencesPanelShown, theme} = this.props; - - var tabs = { - Elements: () => ( - } - right={() => ( - - )} - isVertical={this.state.isVertical} - /> - ), - ...this.props.extraTabs, - }; - - return ( -
- - - -
- ); - } -} - -var DEFAULT_MENU_ITEMS = { - tree: (id, node, store) => { - var items = []; - if (node.get('name')) { - items.push({ - key: 'showNodesOfType', - title: 'Show all ' + node.get('name'), - action: () => store.changeSearch(node.get('name')), - }); - } - if (store.capabilities.scroll) { - items.push({ - key: 'scrollToNode', - title: 'Scroll to node', - action: () => store.scrollToNode(id), - }); - } - if (node.get('nodeType') === 'Composite' && node.get('name')) { - items.push({ - key: 'copyNodeName', - title: 'Copy element name', - action: () => store.copyNodeName(node.get('name')), - }); - } - const props = node.get('props'); - if (props) { - const numKeys = Object.keys(props) - .filter(key => key !== 'children') - .length; - - if (numKeys > 0) { - items.push({ - key: 'copyNodeProps', - title: 'Copy element props', - action: () => store.copyNodeProps(props), - }); - } - } - return items; - }, - attr: (id, node, val, path, name, store) => { - return [{ - key: 'storeAsGlobal', - title: 'Store as global variable', - action: () => store.makeGlobal(id, path), - }]; - }, -}; - -const containerStyle = (preferencesPanelShown: boolean, theme: Theme) => ({ - backgroundColor: theme.base00, - color: theme.base05, - flex: 1, - display: 'flex', - minWidth: 0, - position: preferencesPanelShown ? 'relative' : null, -}); - -module.exports = Container; diff --git a/frontend/ContextMenu.js b/frontend/ContextMenu.js deleted file mode 100644 index 2c869b95f8..0000000000 --- a/frontend/ContextMenu.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const nullthrows = require('nullthrows').default; -const {sansSerif} = require('./Themes/Fonts'); -const HighlightHover = require('./HighlightHover'); - -const decorate = require('./decorate'); - -import type {Theme} from './types'; - -export type MenuItem = { - key: string, - title: string, - action: () => void -}; - -type Props = { - open: boolean, - hideContextMenu: () => void, - items: Array, - pos: { - x: number, - y: number, - }, -}; - -type State = { - elementHeight: number, - windowHeight: number, -}; - -class ContextMenu extends React.Component { - _clickout: (evt: Object) => void; - - context: { - theme: Theme, - }; - - state = { - elementHeight: 0, - windowHeight: 0, - }; - - handleBackdropClick: () => void; - - constructor(props) { - super(props); - - this.handleBackdropClick = this.handleBackdropClick.bind(this); - } - - onClick(i, evt) { - this.props.items[i].action(); - } - - handleBackdropClick(evt) { - evt.preventDefault(); - this.props.hideContextMenu(); - } - - _setRef = element => { - if (!element) { - return; - } - - const elementHeight = nullthrows(element.querySelector('ul')).clientHeight; - const windowHeight = window.innerHeight; - - if (this.state.elementHeight === elementHeight && this.state.windowHeight === windowHeight) { - return; - } - - this.setState({ - elementHeight: elementHeight, - windowHeight: windowHeight, - }); - }; - - render() { - const {theme} = this.context; - const {items, open, pos} = this.props; - const {elementHeight, windowHeight} = this.state; - - if (pos && (pos.y + elementHeight) > windowHeight) { - pos.y -= elementHeight; - } - - if (!open) { - return
; - } - - return ( -
-
    - {!items.length && ( -
  • No actions
  • - )} - {items.map((item, i) => item && ( -
  • this.onClick(i, evt)}> - - {item.title} - -
  • - ))} -
-
- ); - } -} - -ContextMenu.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -var Wrapped = decorate({ - listeners() { - return ['contextMenu']; - }, - props(store, props) { - if (!store.contextMenu) { - return {open: false}; - } - var {x, y, type, args} = store.contextMenu; - - var items = []; - args.push(store); - - props.itemSources.forEach(source => { - if (!source || !source[type]) { - return; - } - var newItems = source[type](...args); - if (newItems) { - items = items.concat(newItems.filter(v => !!v)); - } - }); - - return { - open: true, - pos: { x, y }, - hideContextMenu: () => store.hideContextMenu(), - items, - }; - }, -}, ContextMenu); - - -const containerStyle = (xPos: number, yPos: number, theme: Theme) => ({ - top: `${yPos}px`, - left: `${xPos}px`, - position: 'fixed', - listStyle: 'none', - margin: 0, - padding: '0.25rem 0', - fontSize: sansSerif.sizes.large, - fontFamily: sansSerif.family, - borderRadius: '0.25rem', - overflow: 'hidden', - zIndex: 1, - backgroundColor: theme.base01, -}); - -const emptyStyle = (theme: Theme) => ({ - padding: '0.25rem 0.5rem', - color: theme.base03, -}); - -const listItemStyle = (theme: Theme) => ({ - color: theme.base05, -}); - -var styles = { - hidden: { - display: 'none', - }, - - backdrop: { - position: 'fixed', - left: 0, - right: 0, - top: 0, - bottom: 0, - zIndex: 1, - }, - - highlightHoverItem: { - padding: '0.25rem 0.5rem', - cursor: 'default', - WebkitUserSelect: 'none', - MozUserSelect: 'none', - userSelect: 'none', - }, -}; - -module.exports = Wrapped; diff --git a/frontend/DataView/DataView.js b/frontend/DataView/DataView.js deleted file mode 100644 index 0cc5daa94a..0000000000 --- a/frontend/DataView/DataView.js +++ /dev/null @@ -1,398 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme, DOMEvent} from '../types'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const Simple = require('./Simple'); -const nullthrows = require('nullthrows').default; - -const consts = require('../../agent/consts'); -const previewComplex = require('./previewComplex'); - -type Inspect = (path: Array, cb: () => void) => void; -type ShowMenu = boolean | (e: DOMEvent, val: any, path: Array, name: string) => void; - -type DataViewProps = { - data: ?Object, - path: Array, - inspect: Inspect, - showMenu: ShowMenu, - startOpen?: boolean, - noSort?: boolean, - readOnly?: boolean, -}; - -class DataView extends React.Component { - context: { - theme: Theme, - }; - - renderSparseArrayHole(count: number, key: string) { - const {theme} = this.context; - - return ( -
  • -
    -
    - undefined × {count} -
    -
    -
  • - ); - } - - renderItem(name: string, key: string) { - const data = nullthrows(this.props.data); - return ( - - ); - } - - render() { - const {theme} = this.context; - var data = this.props.data; - if (!data) { - return
    null
    ; - } - - var isArray = Array.isArray(data); - var elements = []; - if (isArray) { - // Iterate over array, filling holes with special items - var lastIndex = -1; - data.forEach((item, i) => { - if (lastIndex < i - 1) { - // Have we skipped over a hole? - var holeCount = (i - 1) - lastIndex; - elements.push( - this.renderSparseArrayHole(holeCount, i + '-hole') - ); - } - elements.push(this.renderItem(i, i)); - lastIndex = i; - }); - if (lastIndex < data.length - 1) { - // Is there a hole at the end? - var holeCount = (data.length - 1) - lastIndex; - elements.push( - this.renderSparseArrayHole(holeCount, lastIndex + '-hole') - ); - } - } else { - // Iterate over a regular object - var names = Object.keys(data); - if (!this.props.noSort) { - names.sort(alphanumericSort); - } - names.forEach((name, i) => { - elements.push(this.renderItem(name, name)); - }); - } - - if (!elements.length) { - return ( -
    - {isArray ? 'Empty array' : 'Empty object'} -
    - ); - } - - return ( -
      - {data[consts.proto] && - } - - {elements} -
    - ); - } -} - -DataView.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -type Props = { - path: Array, - inspect: Inspect, - showMenu: ShowMenu, - startOpen?: boolean, - noSort?: boolean, - readOnly?: boolean, - name: string, - value: any, -}; - -type State = { - open: boolean, - loading: boolean, -} - -class DataItem extends React.Component { - context: { - onChange: (path: Array, checked: boolean) => void, - theme: Theme, - }; - defaultProps: {}; - - constructor(props) { - super(props); - this.state = {open: !!this.props.startOpen, loading: false}; - } - - componentDidMount() { - if (this.state.open && this.props.value && this.props.value[consts.inspected] === false) { - this.inspect(); - } - } - - componentWillReceiveProps(nextProps) { - if (this.state.open && nextProps.value && nextProps.value[consts.inspected] === false) { - this.inspect(); - } - } - - inspect() { - this.setState({loading: true, open: true}); - this.props.inspect(this.props.path, () => { - this.setState({loading: false}); - }); - } - - toggleOpen() { - if (this.state.loading) { - return; - } - if (this.props.value && this.props.value[consts.inspected] === false) { - this.inspect(); - return; - } - - this.setState({ - open: !this.state.open, - }); - } - - toggleBooleanValue(e) { - this.context.onChange(this.props.path, e.target.checked); - } - - render() { - const {theme} = this.context; - var data = this.props.value; - var otype = typeof data; - var complex = true; - var preview; - if (otype === 'number' || otype === 'string' || data == null /* null or undefined */ || otype === 'boolean') { - preview = ( - - ); - complex = false; - } else { - preview = previewComplex(data, theme); - } - - var inspectable = !data || !data[consts.meta] || !data[consts.meta].uninspectable; - var open = inspectable && this.state.open && (!data || data[consts.inspected] !== false); - var opener = null; - - if (complex && inspectable) { - opener = ( -
    - {open ? - : - } -
    - ); - } else if (otype === 'boolean' && !this.props.readOnly) { - opener = ( - - ); - } - - var children = null; - if (complex && open) { - var readOnly = this.props.readOnly || (data[consts.meta] && data[consts.meta].readOnly); - // TODO path - children = ( -
    - -
    - ); - } - - var name = this.props.name; - if (name.length > 50) { - name = name.slice(0, 50) + '…'; - } - - return ( -
  • -
    - {opener} -
    - {name}: -
    -
    { - if (typeof this.props.showMenu === 'function') { - this.props.showMenu(e, this.props.value, this.props.path, name); - } - }} - style={previewStyle(theme)} - > - {preview} -
    -
    - {children} -
  • - ); - } -} - -DataItem.contextTypes = { - onChange: PropTypes.func, - theme: PropTypes.object.isRequired, -}; - -function alphanumericSort(a: string, b: string): number { - if ('' + (+a) === a) { - if ('' + (+b) !== b) { - return -1; - } - return (+a < +b) ? -1 : 1; - } - return (a < b) ? -1 : 1; -} - -const nameStyle = (isComplex: boolean, theme: Theme) => ({ - cursor: isComplex ? 'pointer' : 'default', - color: theme.special03, - margin: '0 0.25rem', -}); - -const previewStyle = (theme: Theme) => ({ - display: 'flex', - whiteSpace: 'pre', - wordBreak: 'break-word', - flex: 1, - color: theme.special01, -}); - -const emptyStyle = (theme: Theme) => ({ - lineHeight: '1.25rem', - color: theme.base04, - paddingLeft: '1rem', -}); - -const missingStyle = (theme: Theme) => ({ - fontWeight: 'bold', - lineHeight: '1.25rem', - color: theme.base03, - paddingLeft: '1rem', -}); - -const collapsedArrowStyle = (theme: Theme) => ({ - borderColor: `transparent transparent transparent ${theme.base03}`, - borderStyle: 'solid', - borderWidth: '4px 0 4px 6px', - display: 'inline-block', - verticalAlign: 'middle', -}); - -const expandedArrowStyle = (theme: Theme) => ({ - borderColor: `${theme.base03} transparent transparent transparent`, - borderStyle: 'solid', - borderWidth: '6px 4px 0 4px', - display: 'inline-block', - verticalAlign: 'middle', -}); - -const sparseArrayHoleStyle = (theme: Theme) => ({ - fontStyle: 'italic', - color: theme.base03, - margin: '2px 3px', -}); - -var styles = { - container: { - listStyle: 'none', - margin: 0, - padding: 0, - marginLeft: '0.75rem', - }, - - children: { - }, - - opener: { - cursor: 'pointer', - marginLeft: -10, - position: 'absolute', - }, - - toggler: { - left: -15, - position: 'absolute', - top: -1, - }, - - head: { - display: 'flex', - position: 'relative', - lineHeight: '1.25rem', - }, - - value: { - }, -}; - -module.exports = DataView; diff --git a/frontend/DataView/Simple.js b/frontend/DataView/Simple.js deleted file mode 100644 index b815ee1e58..0000000000 --- a/frontend/DataView/Simple.js +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme, DOMEvent, DOMNode} from '../types'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const ReactDOM = require('react-dom'); - -const Input = require('../Input'); -const flash = require('../flash'); -const {monospace} = require('../Themes/Fonts'); - -type Props = { - data: any, - path: Array, - readOnly: ?boolean, -} - -type State = { - editing: boolean, - text: string, -}; - -class Simple extends React.Component { - context: { - onChange: (path: Array, value: any) => void, - theme: Theme, - }; - input: DOMNode; - - constructor(props: Object) { - super(props); - this.state = { - text: '', - editing: false, - }; - } - - onChange(e: DOMEvent) { - this.setState({ - text: e.target.value, - }); - } - - onKeyDown(e: DOMEvent) { - if (e.key === 'Enter') { - this.onSubmit(true); - this.setState({ - editing: false, - }); - } - if (e.key === 'Escape') { - this.setState({ - editing: false, - }); - } - } - - onSubmit(editing: boolean) { - if (this.state.text === valueToText(this.props.data)) { - this.setState({ - editing: editing, - }); - return; - } - var value = textToValue(this.state.text); - if (value === BAD_INPUT) { - this.setState({ - text: valueToText(this.props.data), - editing: editing, - }); - return; - } - this.context.onChange(this.props.path, value); - this.setState({ - editing: editing, - }); - } - - startEditing() { - if (this.props.readOnly) { - return; - } - this.setState({ - editing: true, - text: valueToText(this.props.data), - }); - } - - selectAll() { - const input = this.input; - input.selectionStart = 0; - input.selectionEnd = input.value.length; - } - - componentDidUpdate(prevProps: Object, prevState: Object) { - if (this.state.editing && !prevState.editing) { - this.selectAll(); - } - if (!this.state.editing && this.props.data !== prevProps.data) { - // $FlowFixMe replace with root ref - flash(ReactDOM.findDOMNode(this), this.context.theme.state04, 'transparent', 1); - } - } - - render() { - const {theme} = this.context; - const {readOnly} = this.props; - const {editing, text} = this.state; - - if (editing) { - return ( - this.input = i} - style={inputStyle(theme)} - onChange={e => this.onChange(e)} - onBlur={() => this.onSubmit(false)} - onKeyDown={this.onKeyDown.bind(this)} - value={text} - /> - ); - } - - let {data} = this.props; - if (typeof data === 'string' && data.length > 200) { - data = data.slice(0, 200) + '…'; - } - - return ( -
    - {valueToText(data)} -
    - ); - } -} - -Simple.propTypes = { - data: PropTypes.any, - path: PropTypes.array, - readOnly: PropTypes.bool, -}; - -Simple.contextTypes = { - onChange: PropTypes.func, - theme: PropTypes.object.isRequired, -}; - -const inputStyle = (theme: Theme) => ({ - flex: 1, - minWidth: 50, - boxSizing: 'border-box', - border: 'none', - padding: 0, - outline: 'none', - boxShadow: `0 0 3px ${theme.base02}`, - fontFamily: monospace.family, - fontSize: 'inherit', -}); - -const simpleStyle = (readOnly: ?boolean, theme: Theme) => ({ - display: 'flex', - flex: 1, - whiteSpace: 'pre-wrap', - cursor: readOnly ? 'default' : 'pointer', -}); - -const BAD_INPUT = Symbol('bad input'); - -function textToValue(txt) { - if (!txt.length) { - return BAD_INPUT; - } - if (txt === 'undefined') { - return undefined; - } - try { - return JSON.parse(txt); - } catch (e) { - return BAD_INPUT; - } -} - -function valueToText(value) { - if (value === undefined) { - return 'undefined'; - } else if (typeof value === 'number') { - return value.toString(); - } - return JSON.stringify(value); -} - -module.exports = Simple; diff --git a/frontend/DataView/previewComplex.js b/frontend/DataView/previewComplex.js deleted file mode 100644 index e0d6446c30..0000000000 --- a/frontend/DataView/previewComplex.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../types'; - -var React = require('react'); - -var consts = require('../../agent/consts'); - -function previewComplex(data: Object, theme: Theme) { - const style={ color: theme.special04 }; - - if (Array.isArray(data)) { - return ( - - Array[{data.length}] - - ); - } - - switch (data[consts.type]) { - case 'function': - return ( - - {data[consts.name] || 'fn'}() - - ); - case 'object': - return ( - - {data[consts.name] + '{…}'} - - ); - case 'date': - return ( - - {data[consts.name]} - - ); - case 'symbol': - return ( - - {data[consts.name]} - - ); - case 'iterator': - return ( - - {data[consts.name] + '(…)'} - - ); - - case 'array_buffer': - case 'data_view': - case 'array': - case 'typed_array': - return ( - - {`${data[consts.name]}[${data[consts.meta].length}]`} - - ); - - case undefined: - case null: - return '{…}'; - } - return null; -} - -module.exports = previewComplex; diff --git a/frontend/Draggable.js b/frontend/Draggable.js deleted file mode 100644 index f73be767d9..0000000000 --- a/frontend/Draggable.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var React = require('react'); -var ReactDOM = require('react-dom'); -import type {DOMEvent} from './types'; - -type Props = { - children?: React.Node, - onMove: (x: number, y: number) => void, - onStart: () => void, - onStop: () => void, - style: Object, -}; - -class Draggable extends React.Component { - _onMove: (evt: DOMEvent) => void; - _onUp: (evt: DOMEvent) => void; - - componentDidMount() { - this._onMove = this.onMove.bind(this); - this._onUp = this.onUp.bind(this); - } - - _startDragging(evt: DOMEvent) { - evt.preventDefault(); - // $FlowFixMe use root ref - var doc = ReactDOM.findDOMNode(this).ownerDocument; - // $FlowFixMe - doc.addEventListener('mousemove', this._onMove); - // $FlowFixMe - doc.addEventListener('mouseup', this._onUp); - this.props.onStart(); - } - - onMove(evt: DOMEvent) { - evt.preventDefault(); - this.props.onMove(evt.pageX, evt.pageY); - } - - onUp(evt: DOMEvent) { - evt.preventDefault(); - // $FlowFixMe use root ref - var doc = ReactDOM.findDOMNode(this).ownerDocument; - // $FlowFixMe - doc.removeEventListener('mousemove', this._onMove); - // $FlowFixMe - doc.removeEventListener('mouseup', this._onUp); - this.props.onStop(); - } - - render() { - return ( -
    - {this.props.children} -
    - ); - } -} - -module.exports = Draggable; diff --git a/frontend/HighlightHover.js b/frontend/HighlightHover.js deleted file mode 100644 index 16106954c1..0000000000 --- a/frontend/HighlightHover.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const assign = require('object-assign'); - -import type {Theme} from './types'; - -type Context = { - theme: Theme, -}; - -type Props = { - style: ?Object, - children?: any, -}; - -type State = { - hover: boolean, -}; - -class HighlightHover extends React.Component { - context: Context; - props: Props; - defaultProps: {}; - state: State; - - constructor(props: Object) { - super(props); - this.state = {hover: false}; - } - - render() { - const {theme} = this.context; - - return ( -
    !this.state.hover && this.setState({hover: true})} - onMouseOut={() => this.state.hover && this.setState({hover: false})} - style={assign({}, this.props.style, { - backgroundColor: this.state.hover ? theme.base02 : 'transparent', - })}> - {this.props.children} -
    - ); - } -} - -HighlightHover.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -module.exports = HighlightHover; diff --git a/frontend/Highlighter/Highlighter.js b/frontend/Highlighter/Highlighter.js deleted file mode 100644 index 8e64ae6246..0000000000 --- a/frontend/Highlighter/Highlighter.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {DOMNode, DOMEvent} from '../types'; - -var Overlay = require('./Overlay'); -var MultiOverlay = require('./MultiOverlay'); - -/** - * Manages the highlighting of items on an html page, as well as - * hover-to-inspect. - */ -class Highlighter { - _overlay: ?Overlay; - _multiOverlay: ?MultiOverlay; - _win: Object; - _onSelect: (node: DOMNode) => void; - _inspecting: boolean; - _subs: Array<() => void>; - _button: DOMNode; - - constructor(win: Object, onSelect: (node: DOMNode) => void) { - this._win = win; - this._onSelect = onSelect; - this._overlay = null; - this._multiOverlay = null; - this._subs = []; - } - - startInspecting() { - this._inspecting = true; - this._subs = [ - captureSubscription(this._win, 'mouseover', this.onHover.bind(this)), - captureSubscription(this._win, 'mousedown', this.onMouseDown.bind(this)), - captureSubscription(this._win, 'click', this.onClick.bind(this)), - ]; - } - - stopInspecting() { - this._subs.forEach(unsub => unsub()); - this.hideHighlight(); - } - - remove() { - this.stopInspecting(); - if (this._button && this._button.parentNode) { - this._button.parentNode.removeChild(this._button); - } - } - - highlight(node: DOMNode, name?: string) { - this.removeMultiOverlay(); - if (node.nodeType !== Node.COMMENT_NODE) { - if (!this._overlay) { - this._overlay = new Overlay(this._win); - } - this._overlay.inspect(node, name); - } - } - - highlightMany(nodes: Array) { - this.removeOverlay(); - if (!this._multiOverlay) { - this._multiOverlay = new MultiOverlay(this._win); - } - this._multiOverlay.highlightMany(nodes); - } - - hideHighlight() { - this._inspecting = false; - this.removeOverlay(); - this.removeMultiOverlay(); - } - - refreshMultiOverlay() { - if (!this._multiOverlay) { - return; - } - this._multiOverlay.refresh(); - } - - removeOverlay() { - if (!this._overlay) { - return; - } - this._overlay.remove(); - this._overlay = null; - } - - removeMultiOverlay() { - if (!this._multiOverlay) { - return; - } - this._multiOverlay.remove(); - this._multiOverlay = null; - } - - onMouseDown(evt: DOMEvent) { - if (!this._inspecting) { - return; - } - evt.preventDefault(); - evt.stopPropagation(); - evt.cancelBubble = true; - this._onSelect(evt.target); - } - - onClick(evt: DOMEvent) { - if (!this._inspecting) { - return; - } - this._subs.forEach(unsub => unsub()); - evt.preventDefault(); - evt.stopPropagation(); - evt.cancelBubble = true; - this.hideHighlight(); - } - - onHover(evt: DOMEvent) { - if (!this._inspecting) { - return; - } - evt.preventDefault(); - evt.stopPropagation(); - evt.cancelBubble = true; - this.highlight(evt.target); - } - - injectButton() { - this._button = makeMagnifier(); - this._button.onclick = this.startInspecting.bind(this); - this._win.document.body.appendChild(this._button); - } -} - -function captureSubscription(obj, evt, cb) { - obj.addEventListener(evt, cb, true); - return () => obj.removeEventListener(evt, cb, true); -} - -function makeMagnifier() { - var button = window.document.createElement('button'); - button.innerHTML = '🔍'; - button.style.backgroundColor = 'transparent'; - button.style.border = 'none'; - button.style.outline = 'none'; - button.style.cursor = 'pointer'; - button.style.position = 'fixed'; - button.style.bottom = '10px'; - button.style.right = '10px'; - button.style.fontSize = '30px'; - button.style.zIndex = 10000000; - return button; -} - -module.exports = Highlighter; diff --git a/frontend/Highlighter/MultiOverlay.js b/frontend/Highlighter/MultiOverlay.js deleted file mode 100644 index 64a5dd1180..0000000000 --- a/frontend/Highlighter/MultiOverlay.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var assign = require('object-assign'); - -import type {DOMNode} from '../types'; - -class MultiOverlay { - win: Object; - container: DOMNode; - _currentNodes: ?Array; - - constructor(window: Object) { - this.win = window; - var doc = window.document; - this.container = doc.createElement('div'); - doc.body.appendChild(this.container); - this._currentNodes = null; - } - - highlightMany(nodes: Array) { - this._currentNodes = nodes; - this.container.innerHTML = ''; - - nodes.forEach(node => { - var div = this.win.document.createElement('div'); - if (typeof node.getBoundingClientRect !== 'function') { - return; - } - var box = node.getBoundingClientRect(); - if (box.bottom < 0 || box.top > window.innerHeight) { - return; - } - assign(div.style, { - top: box.top + 'px', - left: box.left + 'px', - width: box.width + 'px', - height: box.height + 'px', - border: '2px dotted rgba(200, 100, 100, .8)', - boxSizing: 'border-box', - backgroundColor: 'rgba(200, 100, 100, .2)', - position: 'fixed', - zIndex: 10000000, - pointerEvents: 'none', - }); - this.container.appendChild(div); - }); - } - - refresh() { - if (this._currentNodes) { - this.highlightMany(this._currentNodes); - } - } - - remove() { - if (this.container.parentNode) { - this.container.parentNode.removeChild(this.container); - this._currentNodes = null; - } - } -} - -module.exports = MultiOverlay; diff --git a/frontend/Highlighter/Overlay.js b/frontend/Highlighter/Overlay.js deleted file mode 100644 index 783c017fd2..0000000000 --- a/frontend/Highlighter/Overlay.js +++ /dev/null @@ -1,291 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var assign = require('object-assign'); -var {monospace} = require('../Themes/Fonts'); - -import type {DOMNode, DOMRect, Window} from '../types'; - -/** - * Note that this component is not affected by the active Theme, - * Because it highlights elements in the main Chrome window (outside of devtools). - * The colors below were chosen to roughly match those used by Chrome devtools. - */ -class Overlay { - win: Object; - container: DOMNode; - node: DOMNode; - border: DOMNode; - padding: DOMNode; - content: DOMNode; - tip: DOMNode; - nameSpan: DOMNode; - dimSpan: DOMNode; - - constructor(window: Object) { - var doc = window.document; - this.win = window; - this.container = doc.createElement('div'); - this.node = doc.createElement('div'); - this.border = doc.createElement('div'); - this.padding = doc.createElement('div'); - this.content = doc.createElement('div'); - - this.border.style.borderColor = overlayStyles.border; - this.padding.style.borderColor = overlayStyles.padding; - this.content.style.backgroundColor = overlayStyles.background; - - assign(this.node.style, { - borderColor: overlayStyles.margin, - pointerEvents: 'none', - position: 'fixed', - }); - - this.tip = doc.createElement('div'); - assign(this.tip.style, { - backgroundColor: '#333740', - borderRadius: '2px', - fontFamily: monospace.family, - fontWeight: 'bold', - padding: '3px 5px', - position: 'fixed', - fontSize: monospace.sizes.normal + 'px', - }); - - this.nameSpan = doc.createElement('span'); - this.tip.appendChild(this.nameSpan); - assign(this.nameSpan.style, { - color: '#ee78e6', - borderRight: '1px solid #aaaaaa', - paddingRight: '0.5rem', - marginRight: '0.5rem', - }); - this.dimSpan = doc.createElement('span'); - this.tip.appendChild(this.dimSpan); - assign(this.dimSpan.style, { - color: '#d7d7d7', - }); - - this.container.style.zIndex = 10000000; - this.node.style.zIndex = 10000000; - this.tip.style.zIndex = 10000000; - this.container.appendChild(this.node); - this.container.appendChild(this.tip); - this.node.appendChild(this.border); - this.border.appendChild(this.padding); - this.padding.appendChild(this.content); - doc.body.appendChild(this.container); - } - - remove() { - if (this.container.parentNode) { - this.container.parentNode.removeChild(this.container); - } - } - - inspect(node: DOMNode, name?: ?string) { - // We can't get the size of text nodes or comment nodes. React as of v15 - // heavily uses comment nodes to delimit text. - if (node.nodeType !== Node.ELEMENT_NODE) { - return; - } - var box = getNestedBoundingClientRect(node, this.win); - var dims = getElementDimensions(node); - - boxWrap(dims, 'margin', this.node); - boxWrap(dims, 'border', this.border); - boxWrap(dims, 'padding', this.padding); - - assign(this.content.style, { - height: box.height - dims.borderTop - dims.borderBottom - dims.paddingTop - dims.paddingBottom + 'px', - width: box.width - dims.borderLeft - dims.borderRight - dims.paddingLeft - dims.paddingRight + 'px', - }); - - assign(this.node.style, { - top: box.top - dims.marginTop + 'px', - left: box.left - dims.marginLeft + 'px', - }); - - this.nameSpan.textContent = (name || node.nodeName.toLowerCase()); - this.dimSpan.textContent = box.width + 'px × ' + box.height + 'px'; - - var tipPos = findTipPos({ - top: box.top - dims.marginTop, - left: box.left - dims.marginLeft, - height: box.height + dims.marginTop + dims.marginBottom, - width: box.width + dims.marginLeft + dims.marginRight, - }, this.win); - assign(this.tip.style, tipPos); - } -} - -function findTipPos(dims, win) { - var tipHeight = 20; - var margin = 5; - var top; - if (dims.top + dims.height + tipHeight <= win.innerHeight) { - if (dims.top + dims.height < 0) { - top = margin; - } else { - top = dims.top + dims.height + margin; - } - } else if (dims.top - tipHeight <= win.innerHeight) { - if (dims.top - tipHeight - margin < margin) { - top = margin; - } else { - top = dims.top - tipHeight - margin; - } - } else { - top = win.innerHeight - tipHeight - margin; - } - - top += 'px'; - - if (dims.left < 0) { - return {top, left: margin}; - } - if (dims.left + 200 > win.innerWidth) { - return {top, right: margin}; - } - return {top, left: dims.left + margin + 'px'}; -} - -function getElementDimensions(domElement) { - var calculatedStyle = window.getComputedStyle(domElement); - - return { - borderLeft: +calculatedStyle.borderLeftWidth.match(/[0-9]*/)[0], - borderRight: +calculatedStyle.borderRightWidth.match(/[0-9]*/)[0], - borderTop: +calculatedStyle.borderTopWidth.match(/[0-9]*/)[0], - borderBottom: +calculatedStyle.borderBottomWidth.match(/[0-9]*/)[0], - marginLeft: +calculatedStyle.marginLeft.match(/[0-9]*/)[0], - marginRight: +calculatedStyle.marginRight.match(/[0-9]*/)[0], - marginTop: +calculatedStyle.marginTop.match(/[0-9]*/)[0], - marginBottom: +calculatedStyle.marginBottom.match(/[0-9]*/)[0], - paddingLeft: +calculatedStyle.paddingLeft.match(/[0-9]*/)[0], - paddingRight: +calculatedStyle.paddingRight.match(/[0-9]*/)[0], - paddingTop: +calculatedStyle.paddingTop.match(/[0-9]*/)[0], - paddingBottom: +calculatedStyle.paddingBottom.match(/[0-9]*/)[0], - }; -} - -// Get the window object for the document that a node belongs to, -// or return null if it cannot be found (node not attached to DOM, -// etc). -function getOwnerWindow(node: DOMNode): Window | null { - if (!node.ownerDocument) { - return null; - } - return node.ownerDocument.defaultView; -} - -// Get the iframe containing a node, or return null if it cannot -// be found (node not within iframe, etc). -function getOwnerIframe(node: DOMNode): DOMNode | null { - var nodeWindow = getOwnerWindow(node); - if (nodeWindow) { - return nodeWindow.frameElement; - } - return null; -} - -// Get a bounding client rect for a node, with an -// offset added to compensate for its border. -function getBoundingClientRectWithBorderOffset(node: DOMNode) { - var dimensions = getElementDimensions(node); - - return mergeRectOffsets([ - node.getBoundingClientRect(), - { - top: dimensions.borderTop, - left: dimensions.borderLeft, - bottom: dimensions.borderBottom, - right: dimensions.borderRight, - // This width and height won't get used by mergeRectOffsets (since this - // is not the first rect in the array), but we set them so that this - // object typechecks as a DOMRect. - width: 0, - height: 0, - }, - ]); -} - -// Add together the top, left, bottom, and right properties of -// each DOMRect, but keep the width and height of the first one. -function mergeRectOffsets(rects: Array): DOMRect { - return rects.reduce((previousRect, rect) => { - if (previousRect == null) { - return rect; - } - - return { - top: previousRect.top + rect.top, - left: previousRect.left + rect.left, - width: previousRect.width, - height: previousRect.height, - bottom: previousRect.bottom + rect.bottom, - right: previousRect.right + rect.right, - }; - }); -} - -// Calculate a boundingClientRect for a node relative to boundaryWindow, -// taking into account any offsets caused by intermediate iframes. -function getNestedBoundingClientRect(node: DOMNode, boundaryWindow: Window): DOMRect { - var ownerIframe = getOwnerIframe(node); - if ( - ownerIframe && - ownerIframe !== boundaryWindow - ) { - var rects = [node.getBoundingClientRect()]; - var currentIframe = ownerIframe; - var onlyOneMore = false; - while (currentIframe) { - var rect = getBoundingClientRectWithBorderOffset(currentIframe); - rects.push(rect); - currentIframe = getOwnerIframe(currentIframe); - - if (onlyOneMore) { - break; - } - // We don't want to calculate iframe offsets upwards beyond - // the iframe containing the boundaryWindow, but we - // need to calculate the offset relative to the boundaryWindow. - if (currentIframe && getOwnerWindow(currentIframe) === boundaryWindow) { - onlyOneMore = true; - } - } - - return mergeRectOffsets(rects); - } else { - return node.getBoundingClientRect(); - } -} - -function boxWrap(dims, what, node) { - assign(node.style, { - borderTopWidth: dims[what + 'Top'] + 'px', - borderLeftWidth: dims[what + 'Left'] + 'px', - borderRightWidth: dims[what + 'Right'] + 'px', - borderBottomWidth: dims[what + 'Bottom'] + 'px', - borderStyle: 'solid', - }); -} - -var overlayStyles = { - background: 'rgba(120, 170, 210, 0.7)', - padding: 'rgba(77, 200, 0, 0.3)', - margin: 'rgba(255, 155, 0, 0.3)', - border: 'rgba(255, 200, 50, 0.3)', -}; - -module.exports = Overlay; diff --git a/frontend/Highlighter/setup.js b/frontend/Highlighter/setup.js deleted file mode 100644 index 55f5ca9b6f..0000000000 --- a/frontend/Highlighter/setup.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var Highlighter = require('./Highlighter'); - -import type Agent from '../../agent/Agent'; - -module.exports = function setup(agent: Agent) { - var hl = new Highlighter(window, node => { - agent.selectFromDOMNode(node); - }); - agent.on('highlight', data => hl.highlight(data.node, data.name)); - agent.on('highlightMany', nodes => hl.highlightMany(nodes)); - agent.on('hideHighlight', () => hl.hideHighlight()); - agent.on('refreshMultiOverlay', () => hl.refreshMultiOverlay()); - agent.on('startInspecting', () => hl.startInspecting()); - agent.on('stopInspecting', () => hl.stopInspecting()); - agent.on('shutdown', () => { - hl.remove(); - }); -}; diff --git a/frontend/Hoverable.js b/frontend/Hoverable.js deleted file mode 100644 index 0b768ba782..0000000000 --- a/frontend/Hoverable.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); - -type Props = { -}; - -type State = { - isHovered: boolean, - isPressed: boolean, -}; - -const Hoverable = (Component: any) => { - class HoverableImplementation extends React.Component { - props: Props; - state: State = { - isHovered: false, - isPressed: false, - }; - - render() { - const {isHovered, isPressed} = this.state; - - return ( - - ); - } - - _onMouseDown: Function = (event: SyntheticMouseEvent<>): void => { - this.setState({ isPressed: true }); - }; - - _onMouseEnter: Function = (event: SyntheticMouseEvent<>): void => { - this.setState({ isHovered: true }); - }; - - _onMouseLeave: Function = (event: SyntheticMouseEvent<>): void => { - this.setState({ isHovered: false, isPressed: false }); - }; - - _onMouseUp: Function = (event: SyntheticMouseEvent<>): void => { - this.setState({ isPressed: false }); - }; - } - - return HoverableImplementation; -}; - -module.exports = Hoverable; diff --git a/frontend/Icons.js b/frontend/Icons.js deleted file mode 100644 index e4f332c5ec..0000000000 --- a/frontend/Icons.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const Icons = { - BACK: ` - M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z - `, - BARS: ` - M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z - `, - CHECK: ` - M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z - `, - CLOSE: ` - M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 - 17.59 19 19 17.59 13.41 12z - `, - COPY: ` - M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0, - 0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z - `, - DOM_ELEMENT: ` - M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z - `, - EDIT: ` - M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12, - 5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z - `, - FLAME_CHART: ` - M10.0650893,21.5040462 C7.14020814,20.6850349 5,18.0558698 5,14.9390244 C5,14.017627 - 5,9.81707317 7.83333333,7.37804878 C7.83333333,7.37804878 7.58333333,11.199187 10, - 10.6300813 C11.125,10.326087 13.0062497,7.63043487 8.91666667,2.5 C14.1666667,3.06910569 - 19,9.32926829 19,14.9390244 C19,18.0558698 16.8597919,20.6850349 13.9349107,21.5040462 - C14.454014,21.0118505 14.7765152,20.3233394 14.7765152,19.5613412 C14.7765152,17.2826087 - 12,15.0875871 12,15.0875871 C12,15.0875871 9.22348485,17.2826087 9.22348485,19.5613412 - C9.22348485,20.3233394 9.54598603,21.0118505 10.0650893,21.5040462 Z M12.0833333,20.6514763 - C11.3814715,20.6514763 10.8125,20.1226027 10.8125,19.4702042 C10.8125,18.6069669 - 12.0833333,16.9347829 12.0833333,16.9347829 C12.0833333,16.9347829 13.3541667,18.6069669 - 13.3541667,19.4702042 C13.3541667,20.1226027 12.7851952,20.6514763 12.0833333,20.6514763 Z - `, - FORWARD: ` - M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z - `, - INSPECT: ` - M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M3.05, - 13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95, - 11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05, - 13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z - `, - INTERACTION: ` - M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 - 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 - 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 - 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 - 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z - `, - RANKED_CHART: ` - M3 5h18v3H3zM3 10.5h13v3H3zM3 16h8v3H3z - `, - RECORD: ` - M4,12a8,8 0 1,0 16,0a8,8 0 1,0 -16,0 - `, - REFRESH: ` - M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 - 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 - 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z - `, - SEARCH: ` - M31.008 27.231l-7.58-6.447c-0.784-0.705-1.622-1.029-2.299-0.998 1.789-2.096 2.87-4.815 - 2.87-7.787 0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12c2.972 0 5.691-1.081 - 7.787-2.87-0.031 0.677 0.293 1.515 0.998 2.299l6.447 7.58c1.104 1.226 2.907 1.33 4.007 - 0.23s0.997-2.903-0.23-4.007zM12 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 - 8-8 8z - `, - SETTINGS: ` - M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0, - 1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43, - 11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05, - 5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14, - 2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73, - 4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5, - 11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34, - 15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13, - 18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87, - 18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66, - 18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z - `, - SHARE: ` - M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9, - 11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8A3,3 0 0,0 21,5A3, - 3 0 0,0 18,2A3,3 0 0,0 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79, - 9 6,9A3,3 0 0,0 3,12A3,3 0 0,0 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11, - 18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92, - 20.61 20.92,19A2.92,2.92 0 0,0 18,16.08Z - `, - VIEW_DETAILS: ` - M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 - 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 - 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z - `, -}; - -module.exports = Icons; diff --git a/frontend/Input.js b/frontend/Input.js deleted file mode 100644 index 2e66e16ecc..0000000000 --- a/frontend/Input.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from './types'; - -const PropTypes = require('prop-types'); -const React = require('react'); - -type Context = { - theme: Theme, -}; - -type Props = { - theme?: Theme, - style?: Object, - innerRef? : Function, -}; - -/** - * Same as base component but with pre-applied theme styles. - * Props theme overrides context theme if provided. - */ -const Input = (props: Props, context: Context) => { - const { - style = {}, - theme, - innerRef, - // A dangling comma here is now invalid js. TODO: update eslint - // eslint-disable-next-line comma-dangle - ...rest - } = props; - - const chosenTheme = theme ? theme : context.theme; - - return ( - - ); -}; - -Input.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const inputStyle = (theme: Theme) => ({ - backgroundColor: theme.base00, - color: theme.base05, -}); - -module.exports = Input; diff --git a/frontend/LeftPane.js b/frontend/LeftPane.js deleted file mode 100644 index 6081a60c70..0000000000 --- a/frontend/LeftPane.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - * $FLowFixMe - * - thinks all react component classes must inherit from React.Component - */ -'use strict'; - -var PropTypes = require('prop-types'); -var React = require('react'); -var SettingsPane = require('./SettingsPane'); -var TreeView = require('./TreeView'); - -type Props = { - reload?: () => void, -} - -type State = { - focused: boolean, -}; - -class LeftPane extends React.Component { - input: ?HTMLElement; - state: State; - - render() { - return ( -
    - - -
    - ); - } -} - -LeftPane.propTypes = { - reload: PropTypes.func, -}; - -var styles = { - container: { - flex: 1, - display: 'flex', - flexDirection: 'column', - minWidth: 0, - }, -}; - -module.exports = LeftPane; diff --git a/frontend/Node.js b/frontend/Node.js deleted file mode 100644 index f82fff87d2..0000000000 --- a/frontend/Node.js +++ /dev/null @@ -1,617 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const nullthrows = require('nullthrows').default; - -const decorate = require('./decorate'); -const Props = require('./Props'); -const {getInvertedWeak, hexToRgba} = require('./Themes/utils'); - -import type {Map} from 'immutable'; -import type {Theme} from './types'; - -const {Fragment} = React; - -type PropsType = { - hovered: boolean, - selected: boolean, - node: Map, - depth: number, - isBottomTagHovered: boolean, - isBottomTagSelected: boolean, - searchRegExp: ?RegExp, - wrappedChildren: ?Array, - onHover: (isHovered: boolean) => void, - onHoverBottom: (isHovered: boolean) => void, - onContextMenu: () => void, - onToggleCollapse: () => void, - onSelectBottom: () => void, - onSelect: () => void, -}; - -type StateType = { - isWindowFocused: boolean, -}; - -class Node extends React.Component { - _head: ?HTMLElement; - _tail: ?HTMLElement; - _ownerWindow: any; - - context: { - scrollTo: (node: HTMLElement) => void, - theme: Theme, - }; - props: PropsType; - state: StateType = { - isWindowFocused: true, - }; - - shouldComponentUpdate(nextProps: PropsType, nextState: StateType) { - return ( - nextProps !== this.props || - nextState.isWindowFocused !== this.state.isWindowFocused - ); - } - - componentDidMount() { - if (this.props.selected) { - this.ensureInView(); - // This is done lazily so we only have one subscription at a time at most. - // We'll unsubscribe and resubscribe depending on props.selected in componentDidUpdate(). - this.subscribeToWindowFocus(); - } - } - - componentDidUpdate(prevProps) { - if (this.props.selected && !prevProps.selected) { - // Gaining selection. - this.ensureInView(); - this.subscribeToWindowFocus(); - } else if (!this.props.selected && prevProps.selected) { - // Losing selection. - this.unsubscribeFromWindowFocus(); - } - } - - componentWillUnmount() { - if (this.props.selected) { - this.unsubscribeFromWindowFocus(); - } - this._ownerWindow = null; - } - - findOwnerWindow() { - if (!this._head) { - return null; - } - var doc = this._head.ownerDocument; - if (!doc) { - return null; - } - var win = doc.defaultView; - if (!win) { - return null; - } - return win; - } - - subscribeToWindowFocus() { - if (!this._ownerWindow) { - // Lazily find the window first time we subscribed. - this._ownerWindow = this.findOwnerWindow(); - if (!this._ownerWindow) { - return; - } - } - var win = this._ownerWindow; - win.addEventListener('focus', this._handleWindowFocus); - win.addEventListener('blur', this._handleWindowBlur); - // Make sure our initial state is right. - if (this.props.selected) { - this.setState({ - isWindowFocused: win.document.hasFocus(), - }); - } - } - - unsubscribeFromWindowFocus() { - if (!this._ownerWindow) { - return; - } - var win = this._ownerWindow; - win.removeEventListener('focus', this._handleWindowFocus); - win.removeEventListener('blur', this._handleWindowBlur); - } - - _handleWindowFocus = () => { - // We're coming from a global window event handler so React - // hasn't processed the events yet. We likely have a click - // selecting another node, which would cause flicker if we update - // right now. So instead we wait just enough for UI to process - // events and update the selected note. (I know it's not pretty.) - setTimeout(() => { - if (!this._ownerWindow) { - return; - } - var doc = this._ownerWindow.document; - this.setState({isWindowFocused: doc.hasFocus()}); - }, 50); - }; - - _handleWindowBlur = () => { - this.setState({isWindowFocused: false}); - }; - - ensureInView() { - var node = this.props.isBottomTagSelected ? this._tail : this._head; - if (node != null) { - if (typeof node.scrollIntoViewIfNeeded === 'function') { - node.scrollIntoViewIfNeeded(); - } else if (typeof node.scrollIntoView === 'function') { - node.scrollIntoView({ - // $FlowFixMe Flow does not realize block:"nearest" is a valid option - block: 'nearest', - inline: 'start', - }); - } - } - } - - _setTailRef = tail => { - this._tail = tail; - }; - _setHeadRef = head => { - this._head = head; - }; - - render() { - const {theme} = this.context; - const { - depth, - hovered, - isBottomTagHovered, - isBottomTagSelected, - node, - onContextMenu, - onHover, - onHoverBottom, - onSelect, - onSelectBottom, - onToggleCollapse, - searchRegExp, - selected, - wrappedChildren, - } = this.props; - const {isWindowFocused} = this.state; - - if (!node) { - return 'Node was deleted'; - } - - let children = node.get('children'); - - if (node.get('nodeType') === 'Wrapper') { - return children.map(child => - - ); - } - - if (node.get('nodeType') === 'NativeWrapper') { - children = wrappedChildren; - } - - const collapsed = node.get('collapsed'); - const inverted = selected && isWindowFocused; - - const headWrapperStyle = wrapperStyle(depth, inverted && !isBottomTagSelected, theme); - - const sharedHeadStyle = headStyle({ - depth, - isBottomTagHovered, - isBottomTagSelected, - isCollapsed: collapsed, - isHovered: hovered, - isSelected: selected, - isWindowFocused, - theme, - }); - - const headEvents = { - onContextMenu: onContextMenu, - onDoubleClick: onToggleCollapse, - onMouseOver: () => onHover(true), - onMouseOut: () => onHover(false), - onMouseDown: onSelect, - }; - const tailEvents = { - onContextMenu: onContextMenu, - onDoubleClick: onToggleCollapse, - onMouseOver: () => onHoverBottom(true), - onMouseOut: () => onHoverBottom(false), - onMouseDown: onSelectBottom, - }; - - const nodeType = node.get('nodeType'); - if (nodeType === 'Text' || nodeType === 'Empty') { - let tag; - if (nodeType === 'Text') { - const text = node.get('text'); - tag = - - "{text}" - ; - } else if (nodeType === 'Empty') { - tag = - - null - ; - } - return ( -
    - {tag} -
    - ); - } - - let name = node.get('name') + ''; - - // If the user's filtering then highlight search terms in the tag name. - // This will serve as a visual reminder that the visible tree is filtered. - if (searchRegExp) { - const unmatched = name.split(searchRegExp); - const matched = name.match(searchRegExp); - const pieces = [ - {unmatched.shift()}, - ]; - while (unmatched.length > 0) { - pieces.push( - {nullthrows(matched).shift()} - ); - pieces.push( - {unmatched.shift()} - ); - } - - name = pieces; - } - - const dollarRStyle = { - color: isWindowFocused ? getInvertedWeak(theme.state02) : 'inherit', - }; - - // Single-line tag (collapsed / simple content / no content) - if (!children || typeof children === 'string' || !children.length) { - const jsxSingleLineTagStyle = jsxTagStyle(inverted, nodeType, theme); - const content = children; - const isCollapsed = content === null || content === undefined; - return ( -
    -
    - < - {name} - {node.get('key') && - - } - {node.get('props') && - - } - {isCollapsed ? ' />' : '>'} - {!isCollapsed && [ - - {content} - , - - </ - {name} - > - , - ]} - {selected &&  == $r} -
    -
    - ); - } - - const jsxCloseTagStyle = jsxTagStyle(inverted && (isBottomTagSelected || collapsed), nodeType, theme); - const closeTag = ( - - </ - {name} - > - {selected && ((collapsed && !this.props.isBottomTagSelected) || this.props.isBottomTagSelected) && -  == $r - } - - ); - - const headInverted = inverted && (!isBottomTagSelected || collapsed); - - const jsxOpenTagStyle = jsxTagStyle(inverted && (!isBottomTagSelected || collapsed), nodeType, theme); - const head = ( -
    - - {collapsed ? '▶' : '▼'} - - < - {name} - {node.get('key') && - - } - {node.get('props') && - - } - > - {selected && !collapsed && !this.props.isBottomTagSelected && -  == $r - } - {collapsed && '…'} - {collapsed && closeTag} -
    - ); - - if (collapsed) { - return head; - } - - const tailStyleActual = tailStyle({ - depth, - isBottomTagHovered, - isBottomTagSelected, - isHovered: hovered, - isSelected: selected, - isWindowFocused, - theme, - }); - - return ( -
    - {head} -
    -
    - {children.map(id => )} -
    -
    - {closeTag} -
    -
    - ); - } -} - -Node.contextTypes = { - scrollTo: PropTypes.func, - theme: PropTypes.object.isRequired, -}; - -var WrappedNode = decorate({ - listeners(props) { - return [props.id]; - }, - props(store, props) { - var node = store.get(props.id); - var wrappedChildren = null; - if (node && node.get('nodeType') === 'NativeWrapper') { - var child = store.get(node.get('children')[0]); - wrappedChildren = child && child.get('children'); - } - return { - node, - wrappedChildren, - selected: store.selected === props.id, - isBottomTagSelected: store.isBottomTagSelected, - isBottomTagHovered: store.isBottomTagHovered, - hovered: store.hovered === props.id, - searchRegExp: props.searchRegExp, - onToggleCollapse: e => { - e.preventDefault(); - store.toggleCollapse(props.id); - }, - onHover: isHovered => store.setHover(props.id, isHovered, false), - onHoverBottom: isHovered => store.setHover(props.id, isHovered, true), - onSelect: e => { - store.selectTop(props.id); - }, - onSelectBottom: e => { - store.selectBottom(props.id); - }, - onContextMenu: e => { - store.showContextMenu('tree', e, props.id, node); - }, - }; - }, - shouldUpdate(nextProps, prevProps) { - return ( - nextProps.id !== prevProps.id || - nextProps.searchRegExp !== prevProps.searchRegExp - ); - }, -}, Node); - -const paddingRight = 5; - -type headStyleParams = { - depth: number, - isBottomTagHovered: boolean, - isBottomTagSelected: boolean, - isCollapsed: boolean, - isHovered: boolean, - isSelected: boolean, - isWindowFocused: boolean, - theme: Theme -}; - -const headStyle = ({ - depth, - isBottomTagHovered, - isBottomTagSelected, - isCollapsed, - isHovered, - isSelected, - isWindowFocused, - theme, -}: headStyleParams) => { - let backgroundColor; - if (isSelected && (isCollapsed || !isBottomTagSelected)) { - backgroundColor = isWindowFocused - ? theme.state00 - : theme.state01; - } else if (isHovered && (isCollapsed || !isBottomTagHovered)) { - backgroundColor = theme.state03; - } - - const isInverted = isSelected && isWindowFocused && (isCollapsed || !isBottomTagSelected); - const color = isInverted ? theme.state02 : undefined; - - return { - cursor: 'default', - borderTop: '1px solid transparent', - position: 'relative', - display: 'flex', - flexShrink: 0, - flexWrap: 'wrap', - borderRadius: '0.125rem', - paddingLeft: '1rem', - paddingRight, - backgroundColor, - color, - }; -}; - -const jsxTagStyle = (inverted: boolean, nodeType: string, theme: Theme) => { - let color; - if (inverted) { - color = theme.state02; - } else if (nodeType === 'Special') { - color = theme.special01; - } else if (nodeType === 'Composite') { - color = theme.special00; - } else { - color = theme.special07; - } - - return { - color, - }; -}; - -const tagTextStyle = (inverted: boolean, theme: Theme) => ({ - flex: 1, - color: inverted ? getInvertedWeak(theme.state02) : theme.special06, -}); - -const wrapperStyle = (depth: number, inverted: boolean, theme: Theme) => ({ - position: 'relative', - color: inverted ? getInvertedWeak(theme.state02) : theme.special07, -}); - -const highlightStyle = (theme: Theme) => ({ - backgroundColor: theme.state04, - color: theme.state05, -}); - -type tailStyleParams = { - depth: number, - isBottomTagHovered: boolean, - isBottomTagSelected: boolean, - isHovered: boolean, - isSelected: boolean, - isWindowFocused: boolean, - theme: Theme -}; - -const tailStyle = ({ - depth, - isBottomTagHovered, - isBottomTagSelected, - isHovered, - isSelected, - isWindowFocused, - theme, -}: tailStyleParams) => { - let backgroundColor; - if (isSelected && isBottomTagSelected) { - backgroundColor = isWindowFocused - ? theme.state00 - : theme.state01; - } else if (isHovered && isBottomTagHovered) { - backgroundColor = theme.state03; - } - - const isInverted = isSelected && isWindowFocused && isBottomTagSelected; - const color = isInverted ? theme.state02 : theme.base04; - - return { - borderTop: '1px solid transparent', - cursor: 'default', - paddingLeft: '1rem', - paddingRight, - backgroundColor, - color, - }; -}; - -const guidelineStyle = (depth: number, isSelected: boolean, isHovered: boolean, isBottomTagHovered: boolean, theme: Theme) => { - let borderLeftColor = 'transparent'; - if (isSelected) { - borderLeftColor = hexToRgba(theme.state00, 0.45); - } else if (isHovered && !isBottomTagHovered) { - // Only show hover for the top tag, or it gets too noisy. - borderLeftColor = hexToRgba(theme.base04, 0.2); - } - - return { - position: 'absolute', - width: '1px', - borderLeft: `1px solid ${borderLeftColor}`, - top: '1rem', - bottom: 0, - left: '0.5rem', - // Bring it in front of the hovered children, but make sure - // hovering over parents doesn't draw on top of selected - // guideline even when we've selected the closing tag. - // When unsure, refer to how Chrome does it (it's subtle!) - zIndex: isSelected ? 1 : 0, - }; -}; - -// Static styles -const styles = { - falseyLiteral: { - fontStyle: 'italic', - }, -}; - -module.exports = WrappedNode; diff --git a/frontend/Panel.js b/frontend/Panel.js deleted file mode 100644 index f07c029241..0000000000 --- a/frontend/Panel.js +++ /dev/null @@ -1,448 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* globals chrome */ - -const PropTypes = require('prop-types'); -const React = require('react'); -const Container = require('./Container'); -const Store = require('./Store'); -const keyboardNav = require('./keyboardNav'); -const invariant = require('./invariant'); -const assign = require('object-assign'); - -const Bridge = require('../agent/Bridge'); -const {sansSerif} = require('./Themes/Fonts'); -const NativeStyler = require('../plugins/ReactNativeStyle/ReactNativeStyle.js'); -const ProfilerPlugin = require('../plugins/Profiler/ProfilerPlugin'); -const Themes = require('./Themes/Themes'); -const ThemeStore = require('./Themes/Store'); - -const consts = require('../agent/consts'); - -import type {Theme} from './types'; -import type {DOMEvent} from './types'; -import type {Wall} from '../agent/Bridge'; - -export type Props = { - alreadyFoundReact: boolean, - browserName?: string, - showInspectButton?: boolean, - showHiddenThemes?: boolean, - themeName?: string, - inject: (done: (wall: Wall, onDisconnect?: () => void) => void) => void, - preferencesPanelShown?: boolean, - - // if alreadyFoundReact, then these don’t need to be passed - checkForReact?: (cb: (isReact: boolean) => void) => void, - reload?: () => void, - - // optionals - showComponentSource?: ( - globalPathToInst: string, - globalPathToType: string, - ) => void, - showElementSource?: ( - source: Object - ) => void, - - reloadSubscribe?: (reloadFn: () => void) => () => void, - showAttrSource?: (path: Array) => void, - executeFn?: (path: Array) => void, - selectElement?: (id: string, bridge: Bridge) => void, - getNewSelection?: (bridge: Bridge) => void, -}; - -type DefaultProps = {}; -type State = { - loading: boolean, - isReact: boolean, - preferencesPanelShown: boolean, - themeKey: number, - themeName: ?string, - showTroubleshooting: boolean, -}; - -class Panel extends React.Component { - _teardownWall: ?() => void; - _keyListener: ?(e: DOMEvent) => void; - // eslint shouldn't error on type positions. TODO: update eslint - // eslint-disable-next-line no-undef - _checkTimeout: ?TimeoutID; - _troubleshootingTimeout: ?TimeoutID; // eslint-disable-line no-undef - _unMounted: boolean; - _bridge: Bridge; - _store: Store; - _themeStore: ThemeStore; - _unsub: ?() => void; - // TODO: typecheck plugin interface - plugins: Array; - - defaultProps: DefaultProps; - - constructor(props: Props) { - super(props); - this.state = { - loading: true, - showTroubleshooting: false, - preferencesPanelShown: false, - isReact: props.alreadyFoundReact, - themeKey: 0, - themeName: props.themeName, - }; - this._unMounted = false; - window.panel = this; - this.plugins = []; - } - - getChildContext(): Object { - return { - browserName: this.props.browserName || '', - defaultThemeName: this._themeStore && this._themeStore.defaultThemeName || '', - showHiddenThemes: !!this.props.showHiddenThemes, - showInspectButton: this.props.showInspectButton !== false, - store: this._store, - theme: this._themeStore && this._themeStore.theme || Themes.ChromeDefault, - themeName: this._themeStore && this._themeStore.themeName || '', - themes: this._themeStore && this._themeStore.themes || {}, - }; - } - - componentDidMount() { - if (this.props.alreadyFoundReact) { - this.inject(); - } else { - this.lookForReact(); - } - - if (this.props.reloadSubscribe) { - this._unsub = this.props.reloadSubscribe(() => this.reload()); - } - - if (this.state.loading) { - this._troubleshootingTimeout = setTimeout( - () => this.setState({showTroubleshooting: true}), - 3000 - ); - } - } - - componentWillUnmount() { - this._unMounted = true; - if (this._unsub) { - this._unsub(); - } - this.teardown(); - - if (this._troubleshootingTimeout !== null) { - clearTimeout(this._troubleshootingTimeout); - } - } - - pauseTransfer() { - if (this._bridge) { - this._bridge.pause(); - } - } - - resumeTransfer() { - if (this._bridge) { - this._bridge.resume(); - } - } - - reload() { - if (this._unsub) { - this._unsub(); - } - this.teardown(); - if (!this._unMounted) { - this.setState({loading: true}, this.props.reload); - } - } - - getNewSelection() { - if (!this._bridge || !this.props.getNewSelection) { - return; - } - this.props.getNewSelection(this._bridge); - } - - hideHighlight() { - this._store.hideHighlight(); - } - - sendSelection(id: string) { - if (!this._bridge || (!id && !this._store.selected)) { - return; - } - invariant(this.props.selectElement, 'cannot send selection if props.selectElement is not defined'); - // $FlowFixMe either id or this._store.selected is a string - this.props.selectElement(id || this._store.selected, this._bridge); - } - - viewComponentSource(id: string) { - if (!this._bridge) { - return; - } - this._bridge.send('putSelectedInstance', id); - setTimeout(() => { - invariant(this.props.showComponentSource, 'cannot view source if props.showComponentSource is not supplied'); - this.props.showComponentSource( - '__REACT_DEVTOOLS_GLOBAL_HOOK__.$inst', - '__REACT_DEVTOOLS_GLOBAL_HOOK__.$type' - ); - }, 100); - } - - viewElementSource(id: string, source: Object) { - if (!this._bridge) { - return; - } - this._bridge.send('putSelectedInstance', id); - setTimeout(() => { - invariant(this.props.showElementSource, 'cannot view source if props.showElementSource is not supplied'); - this.props.showElementSource(source); - }, 100); - } - - teardown() { - this.plugins.forEach(p => p.teardown()); - this.plugins = []; - if (this._keyListener) { - window.removeEventListener('keydown', this._keyListener); - this._keyListener = null; - } - if (this._bridge) { - this._bridge.send('shutdown'); - } - if (this._teardownWall) { - this._teardownWall(); - this._teardownWall = null; - } - } - - inject() { - this.props.inject((wall, teardown) => { - this._teardownWall = teardown; - - this._bridge = new Bridge(wall); - - this._themeStore = new ThemeStore(this.state.themeName); - this._store = new Store(this._bridge, this._themeStore); - - var refresh = () => this.forceUpdate(); - this.plugins = [ - new ProfilerPlugin(this._store, this._bridge, refresh), - ]; - - this._keyListener = keyboardNav(this._store, window); - - window.addEventListener('keydown', this._keyListener); - - this._store.on('connected', () => { - this.setState({ - loading: false, - themeName: this._themeStore.themeName, - }); - this.getNewSelection(); - }); - this._store.on('preferencesPanelShown', () => { - this.setState({ - preferencesPanelShown: this._store.preferencesPanelShown, - }); - }); - this._store.on('theme', () => { - // Force a deep re-render when theme changes. - // Use an incrementor so changes to Custom theme also update. - this.setState(state => ({ - themeKey: state.themeKey + 1, - themeName: this._themeStore.theme.displayName, - })); - }); - }); - } - - componentDidUpdate(prevProps: Props, prevState: State) { - if (!this.state.isReact) { - if (!this._checkTimeout) { - this._checkTimeout = setTimeout(() => { - this._checkTimeout = null; - this.lookForReact(); - }, 200); - } - } - - if (prevState.loading && !this.state.loading) { - if (this._troubleshootingTimeout !== null) { - clearTimeout(this._troubleshootingTimeout); - } - } else if (!prevState.loading && this.state.loading) { - this._troubleshootingTimeout = setTimeout( - () => this.setState({showTroubleshooting: true}), - 3000 - ); - } - } - - lookForReact() { - if (typeof this.props.checkForReact !== 'function') { - return; - } - this.props.checkForReact(isReact => { - if (isReact) { - this.setState({isReact: true, loading: true}); - this.inject(); - } else { - console.log('still looking...'); - this.setState({isReact: false, loading: false}); - } - }); - } - - render() { - var theme = this._store ? this._themeStore.theme : Themes.ChromeDefault; - if (this.state.loading) { - // TODO: This currently shows in the Firefox shell when navigating from a - // React page to a non-React page. We should show a better message but - // properly doing so probably requires refactoring how we load the panel - // and communicate with the bridge. - return ( - - ); - } - if (!this.state.isReact) { - return ( -
    -

    Looking for React…

    -
    - ); - } - var extraTabs = assign.apply(null, [{}].concat(this.plugins.map(p => p.tabs()))); - var extraPanes = [].concat(...this.plugins.map(p => p.panes())); - if (this._store.capabilities.rnStyle) { - extraPanes.push(panelRNStyle(this._bridge, this._store.capabilities.rnStyleMeasure, theme)); - } - return ( - { - if (!val || node.get('nodeType') !== 'Composite' || val[consts.type] !== 'function') { - return undefined; - } - return [this.props.showAttrSource && { - key: 'showSource', - title: 'Show function source', - // $FlowFixMe showAttrSource is provided - action: () => this.props.showAttrSource(path), - }, this.props.executeFn && { - key: 'executeFunction', - title: 'Execute function', - // $FlowFixMe executeFn is provided - action: () => this.props.executeFn(path), - }]; - }, - tree: (id, node) => { - return [this.props.selectElement && this._store.capabilities.dom && { - key: 'findDOMNode', - title: 'Find the DOM node', - action: () => this.sendSelection(id), - }, this.props.showComponentSource && node.get('nodeType') === 'Composite' && { - key: 'showComponentSource', - title: 'Show ' + node.get('name') + ' source', - action: () => this.viewComponentSource(id), - }, this.props.showElementSource && node.get('source') && { - key: 'showElementSource', - title: 'Show <' + node.get('name') + ' /> in source', - action: () => this.viewElementSource(id, node.get('source')), - }]; - }, - }} - extraPanes={extraPanes} - extraTabs={extraTabs} - preferencesPanelShown={this.state.preferencesPanelShown} - theme={theme} - onViewElementSource={ - this.props.showElementSource ? this.viewElementSource.bind(this) : null - } - /> - ); - } -} - -Panel.childContextTypes = { - browserName: PropTypes.string.isRequired, - defaultThemeName: PropTypes.string.isRequired, - showHiddenThemes: PropTypes.bool.isRequired, - showInspectButton: PropTypes.bool.isRequired, - store: PropTypes.object, - theme: PropTypes.object.isRequired, - themeName: PropTypes.string.isRequired, - themes: PropTypes.object.isRequired, -}; - -var panelRNStyle = (bridge, supportsMeasure, theme) => (node, id) => { - var props = node.get('props'); - if (!props || !props.style) { - return ( -
    - No style -
    - ); - } - return ( -
    - React Native Style Editor - -
    - ); -}; - -const containerStyle = (theme: Theme) => ({ - borderTop: `1px solid ${theme.base01}`, - padding: '0.25rem', - marginBottom: '0.25rem', - flexShrink: 0, -}); -const loadingStyle = (theme: Theme) => ({ - fontFamily: sansSerif.family, - fontSize: sansSerif.sizes.large, - textAlign: 'center', - padding: 30, - flex: 1, - - // This color is hard-coded to match app.html and standalone.js - // Without it, the loading headers change colors and look weird - color: '#aaa', -}); - -module.exports = Panel; diff --git a/frontend/PreferencesPanel.js b/frontend/PreferencesPanel.js deleted file mode 100644 index 58d810bc2e..0000000000 --- a/frontend/PreferencesPanel.js +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); - -const decorate = require('./decorate'); -const {sansSerif} = require('./Themes/Fonts'); -const {CUSTOM_THEME_NAME} = require('./Themes/constants'); -const Icons = require('./Icons'); -const SvgIcon = require('./SvgIcon'); -const ThemeEditor = require('./Themes/Editor/Editor'); -const Hoverable = require('./Hoverable'); -const TraceUpdatesFrontendControl = require('../plugins/TraceUpdates/TraceUpdatesFrontendControl'); -const ColorizerFrontendControl = require('../plugins/Colorizer/ColorizerFrontendControl'); - -import type {Theme} from './types'; - -type Props = { - changeTheme: (themeName: string) => void, - hasCustomTheme: boolean, - hide: () => void, - open: bool, -}; - -type State = { - editMode: bool, -}; - -class PreferencesPanel extends React.Component { - _selectRef: any; - - context: { - browserName: string, - showHiddenThemes: boolean, - theme: Theme, - themeName: string, - themes: { [key: string]: Theme }, - }; - - constructor(props, context) { - super(props, context); - - this.state = { - editMode: false, - }; - } - - componentDidMount(prevProps, prevState) { - if (this.props.open) { - this._selectRef.focus(); - } - } - - componentDidUpdate(prevProps, prevState) { - if (this.props.open && !prevProps.open) { - this._selectRef.focus(); - } - } - - render() { - const {browserName, showHiddenThemes, theme, themeName, themes} = this.context; - const {hasCustomTheme, hide, open} = this.props; - const {editMode} = this.state; - - if (!open) { - return null; - } - - let content; - if (editMode) { - content = ( - - ); - } else { - let themeNames = Object.keys(themes); - if (!showHiddenThemes) { - themeNames = themeNames.filter(key => !themes[key].hidden); - } - - content = ( -
    -

    Preferences

    -
    - -
    -
    - -
    -

    Theme

    -
    - - - - -
    -
    - -
    -
    - ); - } - - return ( -
    - {content} -
    - ); - } - - _changeTheme = (event) => { - const {changeTheme} = this.props; - - changeTheme(event.target.value); - }; - - _hide = () => { - const {hide} = this.props; - const {editMode} = this.state; - - if (editMode) { - this.setState({ - editMode: false, - }); - } else { - hide(); - } - }; - - _onEditCustomThemeClick = () => { - this.setState({ - editMode: true, - }); - }; - - _onKeyUp = ({ key }) => { - if (key === 'Escape') { - this.props.hide(); - } - }; - - _setSelectRef = (ref) => { - this._selectRef = ref; - }; -} - -PreferencesPanel.contextTypes = { - browserName: PropTypes.string.isRequired, - showHiddenThemes: PropTypes.bool.isRequired, - theme: PropTypes.object.isRequired, - themeName: PropTypes.string.isRequired, - themes: PropTypes.object.isRequired, -}; -PreferencesPanel.propTypes = { - changeTheme: PropTypes.func, - hide: PropTypes.func, - open: PropTypes.bool, -}; - - -const EditButton = Hoverable( - ({ isHovered, onClick, onMouseEnter, onMouseLeave, theme }) => ( - - ) -); - -const EditIcon = () => ( - -); - -const blockClick = event => event.stopPropagation(); - -const WrappedPreferencesPanel = decorate({ - listeners() { - return ['preferencesPanelShown']; - }, - props(store, props) { - return { - changeTheme: themeName => store.changeTheme(themeName), - hasCustomTheme: !!store.themeStore.customTheme, - hide: () => store.hidePreferencesPanel(), - open: store.preferencesPanelShown, - }; - }, -}, PreferencesPanel); - -const panelStyle = (theme: Theme) => ({ - maxWidth: '100%', - margin: '0.5rem', - padding: '0.5rem', - borderRadius: '0.25rem', - display: 'flex', - flexDirection: 'column', - alignItems: 'flex-start', - zIndex: 1, - fontFamily: sansSerif.family, - backgroundColor: theme.base01, - border: `1px solid ${theme.base03}`, - color: theme.base05, -}); - -const buttonStyle = (isHovered: boolean, theme: Theme) => ({ - padding: '0.25rem', - marginLeft: '0.25rem', - height: '1.5rem', - background: 'none', - border: 'none', - color: isHovered ? theme.state06 : 'inherit', -}); - -const styles = { - backdrop: { - position: 'fixed', - zIndex: 1, - width: '100%', - height: '100%', - top: 0, - left: 0, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'rgba(0,0,0,0)', - }, - header: { - margin: '0 0 0.5rem', - }, - buttonBar: { - flexDirection: 'row', - }, - button: { - marginTop: '0.5rem', - marginRight: '0.25rem', - padding: '0.25rem', - }, - preference: { - margin: '0 0 0.5rem', - fontSize: sansSerif.sizes.normal, - }, - selectAndPreviewRow: { - display: 'flex', - direction: 'row', - alignItems: 'center', - }, -}; - -module.exports = WrappedPreferencesPanel; diff --git a/frontend/PropState.js b/frontend/PropState.js deleted file mode 100644 index 019f7a647b..0000000000 --- a/frontend/PropState.js +++ /dev/null @@ -1,254 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const BlurInput = require('./BlurInput'); -const DataView = require('./DataView/DataView'); -const DetailPane = require('./detail_pane/DetailPane'); -const DetailPaneSection = require('./detail_pane/DetailPaneSection'); -const {sansSerif} = require('./Themes/Fonts'); -const PropVal = require('./PropVal'); -const PropTypes = require('prop-types'); -const React = require('react'); - -const decorate = require('./decorate'); -const invariant = require('./invariant'); - -import type {Theme} from './types'; - -type Props = { - id: string, - extraPanes: Array, - inspect: Function, - showMenu: Function, - node: Map, - onChange: (path: Array, val: any) => mixed, - onChangeText: (string) => mixed, - onViewElementSource?: (id: string, node: ?Object) => mixed, -}; - -class PropState extends React.Component { - context: { - onChange: () => void, - theme: Theme, - }; - - getChildContext() { - return { - onChange: (path, val) => { - this.props.onChange(path, val); - }, - }; - } - - renderSource(): React.Node { - const {theme} = this.context; - const {id, node, onViewElementSource} = this.props; - const source = node.get('source'); - if (!source) { - return null; - } - - let onClick; - if (onViewElementSource) { - onClick = () => onViewElementSource(id, source); - } - - return ( -
    - {source.fileName} - - :{source.lineNumber} - -
    - ); - } - - render() { - var theme = this.context.theme; - - if (!this.props.node) { - return No selection; - } - - var nodeType = this.props.node.get('nodeType'); - - if (nodeType === 'Text') { - if (this.props.canEditTextContent) { - return ( - - - - ); - } - return No props/state.; - } else if (nodeType === 'Empty') { - return No props/state.; - } - - var editTextContent = null; - if (this.props.canEditTextContent) { - if (typeof this.props.node.get('children') === 'string') { - editTextContent = ( - - ); - } - } - - var key = this.props.node.get('key'); - var ref = this.props.node.get('ref'); - var state = this.props.node.get('state'); - var context = this.props.node.get('context'); - var propsReadOnly = !this.props.node.get('canUpdate'); - - return ( - - {key && - - - - } - {ref && - - - - } - {editTextContent} - - - - - {state && - - - } - {context && - - - } - {this.props.extraPanes && - this.props.extraPanes.map(fn => fn && fn(this.props.node, this.props.id))} -
    - {this.renderSource()} - - ); - } -} - -PropState.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -PropState.childContextTypes = { - onChange: PropTypes.func, -}; - -var WrappedPropState = decorate({ - listeners(props, store) { - return ['selected', store.selected]; - }, - - props(store) { - var node = store.selected ? store.get(store.selected) : null; - return { - id: store.selected, - node, - canEditTextContent: store.capabilities.editTextContent, - onChangeText(text) { - store.changeTextContent(store.selected, text); - }, - onChange(path, val) { - if (path[0] === 'props') { - store.setProps(store.selected, path.slice(1), val); - } else if (path[0] === 'state') { - store.setState(store.selected, path.slice(1), val); - } else if (path[0] === 'context') { - store.setContext(store.selected, path.slice(1), val); - } else { - invariant(false, 'the path to change() must start wth props, state, or context'); - } - }, - showMenu(e, val, path, name) { - store.showContextMenu('attr', e, store.selected, node, val, path, name); - }, - inspect: store.inspect.bind(store, store.selected), - }; - }, -}, PropState); - -const emptyStyle = (theme: Theme) => ({ - fontFamily: sansSerif.family, - fontSize: sansSerif.sizes.large, - fontStyle: 'italic', - margin: 'auto', - color: theme.base04, -}); - -const sourceStyle = (hasViewElementSource: boolean, theme: Theme) => ({ - padding: '0.25rem 0.5rem', - color: theme.base05, - overflowWrap: 'break-word', - cursor: hasViewElementSource ? 'pointer' : 'default', -}); - -const sourcePosStyle = (theme: Theme) => ({ - color: theme.base03, -}); - -const noPropsStateStyle = (theme: Theme) => ({ - fontFamily: sansSerif.family, - fontSize: sansSerif.sizes.normal, - color: theme.base03, - textAlign: 'center', - fontStyle: 'italic', - padding: '0.5rem', -}); - -module.exports = WrappedPropState; diff --git a/frontend/PropVal.js b/frontend/PropVal.js deleted file mode 100644 index 67de19e1f6..0000000000 --- a/frontend/PropVal.js +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var React = require('react'); -var ReactDOM = require('react-dom'); -var PropTypes = require('prop-types'); - -var consts = require('../agent/consts'); -var {getInvertedWeak} = require('./Themes/utils'); -var flash = require('./flash'); - -import type {Theme} from './types'; - -type Props = { - val: any, - nested?: boolean, - inverted?: boolean, -}; - -class PropVal extends React.Component { - context: { - theme: Theme, - }; - componentDidUpdate(prevProps: Object) { - if (this.props.val === prevProps.val) { - return; - } - if (this.props.val && prevProps.val && typeof this.props.val === 'object' && typeof prevProps.val === 'object') { - return; - } - var node = ReactDOM.findDOMNode(this); - // $FlowFixMe - flash(node, this.context.theme.state04, 'transparent', 1); - } - - render() { - return previewProp(this.props.val, !!this.props.nested, !!this.props.inverted, this.context.theme); - } -} - -PropVal.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -function previewProp(val: any, nested: boolean, inverted: boolean, theme: Theme) { - let style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special01, - }; - - if (typeof val === 'number') { - return {val}; - } - if (typeof val === 'string') { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special02, - }; - if (val.length > 50) { - val = val.slice(0, 50) + '…'; - } - - return ( - "{val}" - ); - } - if (typeof val === 'boolean') { - return {'' + val}; - } - if (Array.isArray(val)) { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special02, - }; - if (nested) { - return [({val.length})]; - } - return previewArray(val, inverted, theme); - } - if (!val) { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.base03, - }; - return {'' + val}; - } - if (typeof val !== 'object') { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special04, - }; - return ; - } - - switch (val[consts.type]) { - case 'date': { - return {val[consts.name]}; - } - case 'function': { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special04, - }; - return {val[consts.name] || 'fn'}(); - } - case 'object': { - return {val[consts.name] + '{…}'}; - } - case 'array': { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special02, - }; - return Array[{val[consts.meta].length}]; - } - case 'typed_array': - case 'array_buffer': - case 'data_view': { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special02, - }; - return {`${val[consts.name]}[${val[consts.meta].length}]`}; - } - case 'iterator': { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.base05, - }; - return {val[consts.name] + '(…)'}; - } - case 'symbol': { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.base05, - }; - // the name is "Symbol(something)" - return {val[consts.name]}; - } - } - - if (nested) { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.base05, - }; - return {'{…}'}; - } - - return previewObject(val, inverted, theme); -} - -function previewArray(val, inverted, theme) { - var items = []; - val.slice(0, 3).forEach((item, i) => { - if (i > 0) { - items.push(', '); - } - items.push( - - ); - }); - var style = { - color: inverted ? theme.base03 : theme.special01, - }; - return ( - - {'['}{items}{val.length > 3 ? ', …' : ''}{']'} - - ); -} - -function previewObject(val, inverted, theme) { - var names = Object.keys(val); - var items = []; - var attrStyle = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special06, - }; - names.slice(0, 3).forEach((name, i) => { - items.push( // Fragment - - {i > 0 ? ', ' : ''} - {name} - {': '} - - - ); - }); - var style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.special01, - }; - return ( - - {'{'}{items}{names.length > 3 ? ', …' : ''}{'}'} - - ); -} - -module.exports = PropVal; diff --git a/frontend/Props.js b/frontend/Props.js deleted file mode 100644 index 5202ef55f5..0000000000 --- a/frontend/Props.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const {getInvertedMid, getInvertedWeak} = require('./Themes/utils'); - -const {Fragment} = React; - -import type {Theme} from './types'; - -type PropsProps = { - props: any, - inverted: boolean, -}; - -class Props extends React.Component { - context: { - theme: Theme, - }; - - shouldComponentUpdate(nextProps: PropsProps): boolean { - return nextProps.props !== this.props.props || nextProps.inverted !== this.props.inverted; - } - - render() { - var theme = this.context.theme; - var {inverted, props} = this.props; - if (!props || typeof props !== 'object') { - return null; - } - - const propKeys = Object.keys(props); - var names = propKeys.filter(name => { - const value = props[name]; - return name[0] !== '_' && name !== 'children' && ( - typeof value === 'boolean' || - typeof value === 'string' || - typeof value === 'number' || - value == null - ); - }); - - var items = []; - - names.forEach(name => { - const valueIsString = typeof props[name] === 'string'; - - let displayValue = props[name]; - if (displayValue === undefined) { - displayValue = 'undefined'; - } else if (displayValue === null) { - displayValue = 'null'; - } else if (typeof displayValue === 'boolean') { - displayValue = displayValue ? 'true' : 'false'; - } else if (valueIsString && displayValue.length > 50) { - displayValue = displayValue.slice(0, 50) + '…'; - } - - items.push( - -   - - {name} - - {valueIsString ? '="' : '={'} - - {displayValue} - - {valueIsString ? '"' : '}'} - - ); - }); - - return items; - } -} - -Props.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -module.exports = Props; diff --git a/frontend/SearchUtils.js b/frontend/SearchUtils.js deleted file mode 100644 index cf48524af8..0000000000 --- a/frontend/SearchUtils.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -function isValidRegex(needle: ?string): boolean { - let isValid = true; - - if (needle) { - try { - searchTextToRegExp(needle); - } catch (error) { - isValid = false; - } - } - - return isValid; -} - -/** - * Convert the specified search text to a RegExp. - */ -function searchTextToRegExp(needle: string): RegExp { - return new RegExp(trimSearchText(needle), 'gi'); -} - -/** - * Should the current search text be converted to a RegExp? - */ -function shouldSearchUseRegex(needle: ?string): boolean { - return !!needle && needle.charAt(0) === '/' && trimSearchText(needle).length > 0; -} - -/** - * '/foo/' => 'foo' - * '/bar' => 'bar' - * 'baz' => 'baz' - */ -function trimSearchText(needle: string): string { - if (needle.charAt(0) === '/') { - needle = needle.substr(1); - } - if (needle.charAt(needle.length - 1) === '/') { - needle = needle.substr(0, needle.length - 1); - } - return needle; -} - -module.exports = { - isValidRegex, - searchTextToRegExp, - shouldSearchUseRegex, - trimSearchText, -}; diff --git a/frontend/SettingsCheckbox.js b/frontend/SettingsCheckbox.js deleted file mode 100644 index 773199b43b..0000000000 --- a/frontend/SettingsCheckbox.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -const React = require('react'); -const immutable = require('immutable'); - -const {sansSerif} = require('./Themes/Fonts'); - -import type {ControlState} from './types.js'; - -type Props = { - state: any, - text: string, - onChange: (v: ControlState) => void, -}; - -type State = StateRecord; - -type DefaultProps = {}; - -const StateRecord = immutable.Record({ - enabled: false, -}); - -class SettingsCheckbox extends React.Component { - defaultProps: DefaultProps; - - _defaultState: ControlState; - _toggle: (b: boolean) => void; - - constructor(props: Props) { - super(props); - this._toggle = this._toggle.bind(this); - this._defaultState = new StateRecord(); - } - - componentDidMount(): void { - if (!this.props.state !== this._defaultState) { - this.props.onChange(this._defaultState); - } - } - - render() { - var state = this.props.state || this._defaultState; - return ( -
    - - {this.props.text} -
    - ); - } - - _toggle() { - var state = this.props.state || this._defaultState; - var nextState = state.merge({ - enabled: !state.enabled, - }); - - this.props.onChange(nextState); - } -} - -var styles = { - checkbox: { - pointerEvents: 'none', - marginRight: '5px', - }, - container: { - WebkitUserSelect: 'none', - cursor: 'default', - display: 'inline-block', - outline: 'none', - fontFamily: sansSerif.family, - userSelect: 'none', - marginRight: '10px', - }, -}; - -module.exports = SettingsCheckbox; diff --git a/frontend/SettingsPane.js b/frontend/SettingsPane.js deleted file mode 100644 index d8d1f2c98b..0000000000 --- a/frontend/SettingsPane.js +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const ReactDOM = require('react-dom'); -const {sansSerif} = require('./Themes/Fonts'); -const SearchUtils = require('./SearchUtils'); -const SvgIcon = require('./SvgIcon'); -const Icons = require('./Icons'); -const Input = require('./Input'); -const Hoverable = require('./Hoverable'); - -const decorate = require('./decorate'); - -import type {Theme} from './types'; - -type EventLike = { - keyCode: number, - target: Node, - preventDefault: () => void, - stopPropagation: () => void, -}; - -class SettingsPane extends React.Component { - context: { - theme: Theme, - }; - - _key: (evt: EventLike) => void; - - constructor(props) { - super(props); - this.state = {focused: false}; - } - - componentDidMount() { - this._key = this.onDocumentKeyDown.bind(this); - const doc = ReactDOM.findDOMNode(this).ownerDocument; - // capture=true is needed to prevent chrome devtools console popping up - doc.addEventListener('keydown', this._key, true); - } - - componentWillUnmount() { - const doc = ReactDOM.findDOMNode(this).ownerDocument; - doc.removeEventListener('keydown', this._key, true); - } - - onDocumentKeyDown(e) { - if ( - e.keyCode === 191 && // forward slash - e.target.nodeName !== 'INPUT' && - !e.target.isContentEditable && - this.input - ) { - this.input.focus(); - e.preventDefault(); - } - if (e.keyCode === 27) { // escape - if (!this.props.searchText && !this.state.focused) { - return; - } - e.stopPropagation(); - e.preventDefault(); - this.cancel(); - } - } - - cancel() { - this.props.onChangeSearch(''); - if (this.input) { - this.input.blur(); - } - } - - onKeyDown(key) { - if (key === 'Enter' && this.input) { - // switch focus to tree view - this.input.blur(); - this.props.selectFirstSearchResult(); - } - } - - render() { - var theme = this.context.theme; - var searchText = this.props.searchText; - - var inputStyle; - if ( - searchText && - SearchUtils.shouldSearchUseRegex(searchText) && - !SearchUtils.isValidRegex(searchText) - ) { - inputStyle = errorInputStyle(theme); - } else if (searchText || this.state.focused) { - inputStyle = highlightedInputStyle(theme); - } else { - inputStyle = baseInputStyle(theme); - } - - return ( -
    - {this.context.showInspectButton && ( - - )} - -
    - this.input = i} - value={searchText} - onFocus={() => this.setState({focused: true})} - onBlur={() => this.setState({focused: false})} - onKeyDown={e => this.onKeyDown(e.key)} - placeholder="Search (text or /regex/)" - onChange={e => this.props.onChangeSearch(e.target.value)} - title="Search by React component name or text" - /> - - {!!searchText && ( - - )} -
    - - -
    - ); - } -} - -SettingsPane.contextTypes = { - showInspectButton: PropTypes.bool.isRequired, - theme: PropTypes.object.isRequired, -}; -SettingsPane.propTypes = { - isInspectEnabled: PropTypes.bool, - isRecording: PropTypes.bool, - searchText: PropTypes.string, - selectFirstSearchResult: PropTypes.func, - toggleRecord: PropTypes.func, - onChangeSearch: PropTypes.func, - toggleInspectEnabled: PropTypes.func, -}; - -var Wrapped = decorate({ - listeners(props) { - return ['isInspectEnabled', 'isRecording', 'searchText']; - }, - props(store) { - return { - isInspectEnabled: store.isInspectEnabled, - isRecording: store.isRecording, - onChangeSearch: text => store.changeSearch(text), - searchText: store.searchText, - selectFirstSearchResult: store.selectFirstSearchResult.bind(store), - showPreferencesPanel() { - store.showPreferencesPanel(); - }, - toggleInspectEnabled: () => store.setInspectEnabled(!store.isInspectEnabled), - toggleRecord: () => store.setIsRecording(!store.isRecording), - }; - }, -}, SettingsPane); - -const ClearSearchButton = Hoverable( - ({ isHovered, onClick, onMouseEnter, onMouseLeave, theme }) => ( -
    - × -
    - ) -); - -const InspectMenuButton = Hoverable( - ({ isHovered, isInspectEnabled, onClick, onMouseEnter, onMouseLeave, theme }) => ( - - ) -); - -const SettingsMenuButton = Hoverable( - ({ isHovered, onClick, onMouseEnter, onMouseLeave, theme }) => ( - - ) -); - -function SearchIcon({ theme }) { - return ( - - ); -} - -const settingsPaneStyle = (theme: Theme) => ({ - display: 'flex', - flexWrap: 'wrap', - flexShrink: 0, - alignItems: 'center', - position: 'relative', - backgroundColor: theme.base01, - borderBottom: `1px solid ${theme.base03}`, -}); - -const clearSearchButtonStyle = (isHovered: boolean, theme: Theme) => ({ - fontSize: sansSerif.sizes.large, - padding: '0 0.5rem', - position: 'absolute', - cursor: 'default', - right: 0, - lineHeight: '28px', - color: isHovered ? theme.base04 : theme.base02, -}); - -const inspectMenuButtonStyle = (isInspectEnabled: boolean, isHovered: boolean, theme: Theme) => { - let color; - if (isInspectEnabled) { - color = theme.state00; - } else if (isHovered) { - color = theme.state06; - } else { - color = 'inherit'; - } - - return { - display: 'flex', - background: 'none', - border: 'none', - outline: 'none', // Use custom active highlight instead - marginLeft: '0.25rem', - color, - }; -}; - -const searchIconStyle = (theme: Theme) => ({ - position: 'absolute', - display: 'inline-block', - pointerEvents: 'none', - left: '0.5rem', - top: 0, - width: '1em', - height: '100%', - strokeWidth: 0, - stroke: theme.base03, - fill: theme.base03, - lineHeight: '28px', - fontSize: sansSerif.sizes.normal, -}); - -const settingsMenuButtonStyle = (isHovered: boolean, theme: Theme) => ({ - display: 'flex', - background: 'none', - border: 'none', - outline: 'none', - marginRight: '0.5rem', - color: isHovered ? theme.state06 : 'inherit', -}); - -const baseInputStyle = (theme: Theme) => ({ - fontSize: sansSerif.sizes.normal, - padding: '0.25rem', - margin: '0.25rem', - marginLeft: '1.75rem', - backgroundColor: 'transparent', - border: 'none', - outline: 'none', - width: '100%', -}); - -const highlightedInputStyle = (theme: Theme) => ({ - ...baseInputStyle(theme), - backgroundColor: theme.base00, -}); - -const errorInputStyle = (theme: Theme) => ({ - ...baseInputStyle(theme), -}); - -var styles = { - growToFill: { - flexGrow: 1, - }, - searchInputWrapper: { - display: 'flex', - alignItems: 'center', - flexGrow: 1, - flexShrink: 0, - position: 'relative', - }, -}; - -module.exports = Wrapped; diff --git a/frontend/SplitPane.js b/frontend/SplitPane.js deleted file mode 100644 index c437aeb100..0000000000 --- a/frontend/SplitPane.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const ReactDOM = require('react-dom'); -const Draggable = require('./Draggable'); -const nullthrows = require('nullthrows').default; - -import type {Theme} from './types'; - -type Context = { - theme: Theme, -}; - -type Props = { - style?: {[key: string]: any}, - left: () => React.Node, - right: () => React.Node, - initialWidth: number, - initialHeight: number, - isVertical: bool, -}; - -type State = { - moving: boolean, - width: number, - height: number, -}; - -class SplitPane extends React.Component { - context: Context; - - constructor(props: Props) { - super(props); - this.state = { - moving: false, - width: props.initialWidth, - height: props.initialHeight, - }; - } - - componentDidMount() { - // $FlowFixMe use a ref on the root - var node: HTMLDivElement = nullthrows(ReactDOM.findDOMNode(this)); - - const width = Math.floor(node.offsetWidth * (this.props.isVertical ? 0.6 : 0.3)); - - this.setState({ - width: Math.min(250, width), - height: Math.floor(node.offsetHeight * 0.3), - }); - } - - onMove(x: number, y: number) { - // $FlowFixMe use a ref on the root - var rect = ReactDOM.findDOMNode(this).getBoundingClientRect(); - - this.setState(prevState => ({ - width: this.props.isVertical ? - prevState.width : - Math.floor(rect.left + rect.width - x), - height: !this.props.isVertical ? - prevState.height : - Math.floor(rect.top + rect.height - y), - })); - } - - render() { - const {theme} = this.context; - const {isVertical} = this.props; - const {height, width} = this.state; - - return ( -
    -
    - {this.props.left()} -
    -
    - this.setState({moving: true})} - onMove={(x, y) => this.onMove(x, y)} - onStop={() => this.setState({moving: false})}> -
    - -
    - {this.props.right()} -
    -
    -
    - ); - } -} - -SplitPane.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const containerStyle = (isVertical: boolean) => ({ - display: 'flex', - minWidth: 0, - flex: 1, - flexDirection: isVertical ? 'column' : 'row', - maxWidth: '100vw', -}); - -const draggerInnerStyle = (isVertical: boolean, theme: Theme) => ({ - height: isVertical ? '1px' : '100%', - width: isVertical ? '100%' : '1px', - backgroundColor: theme.base04, -}); - -const draggerStyle = (isVertical: boolean) => ({ - position: 'relative', - zIndex: 1, - padding: isVertical ? '0.25rem 0' : '0 0.25rem', - margin: isVertical ? '-0.25rem 0' : '0 -0.25rem', - cursor: isVertical ? 'ns-resize' : 'ew-resize', -}); - -const rightStyle = (isVertical: boolean, width: number, height: number) => ({ - ...containerStyle(isVertical), - width: isVertical ? null : width, - height: isVertical ? height : null, - flex: 'initial', - minHeight: 120, - minWidth: 150, -}); - -const styles = { - rightPane: { - display: 'flex', - width: '100%', - overflow: 'auto', - }, - leftPane: { - display: 'flex', - minWidth: '50%', - minHeight: '50%', - flex: 1, - overflow: 'hidden', - }, -}; - -module.exports = SplitPane; diff --git a/frontend/Store.js b/frontend/Store.js deleted file mode 100644 index d84144f72e..0000000000 --- a/frontend/Store.js +++ /dev/null @@ -1,751 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var {EventEmitter} = require('events'); -var {Map, Set, List, Record} = require('immutable'); -var assign = require('object-assign'); -var { copy } = require('clipboard-js'); -var nodeMatchesText = require('./nodeMatchesText'); -var consts = require('../agent/consts'); -var serializePropsForCopy = require('../utils/serializePropsForCopy'); -var invariant = require('./invariant'); -var SearchUtils = require('./SearchUtils'); -var ThemeStore = require('./Themes/Store'); -const {get, set} = require('../utils/storage'); - -const LOCAL_STORAGE_TRACE_UPDATES_KEY = 'traceUpdates'; - -import type Bridge from '../agent/Bridge'; -import type {ControlState, DOMEvent, ElementID, Theme} from './types'; -import type {Snapshot} from '../plugins/Profiler/ProfilerTypes'; - -type ListenerFunction = () => void; -type DataType = Map; -type ContextMenu = { - type: string, - x: number, - y: number, - args: Array, -}; - -/** - * This is the main frontend [fluxy?] Store, responsible for taking care of - * state. It emits events when things change that you can subscribe to. The - * best way to interact with the Store (if you are a React Component) is to - * use the decorator in `decorate.js`. The top-level component (defined - * by a shell) is generally responsible for creating the Store connecting it - * up to a bridge, and putting it on `context` so the decorator can access it. - * - * Public events: - * - * - connected / connection failed - * - roots - * - searchText - * - searchRoots - * - contextMenu - * - hover - * - selected - * - [node id] - * - * Public state: - * see attrs / constructor - * - * Public actions: - * - scrollToNode(id) - * - changeTextContent(id, text) - * - changeSearch - * - hoverClass - * - selectFirstOfClass - * - showContextMenu - * - hideContextMenu - * - selectFirstSearchResult - * - toggleCollapse - * - toggleAllChildrenNodes - * - setProps/State/Context - * - makeGlobal(id, path) - * - setHover(id, isHovered, isBottomTag) - * - selectTop(id) - * - selectBottom(id) - * - select(id) - * - * Public methods: - * - get(id) => Map (the node) - * - getParent(id) => pid - * - skipWrapper(id, up?) => id - * - hasBottom(id) => bool - * - on / off - * - inspect(id, path, cb) - */ -class Store extends EventEmitter { - _bridge: Bridge; - _nodes: Map; - _parents: Map; - _nodesByName: Map; - _eventQueue: Array; - // eslint shouldn't error on type positions. TODO: update eslint - // eslint-disable-next-line no-undef - _eventTimer: ?TimeoutID; - - // Public state - isInspectEnabled: boolean; - traceupdatesState: ?ControlState; - colorizerState: ?ControlState; - contextMenu: ?ContextMenu; - hovered: ?ElementID; - isBottomTagHovered: boolean; - isBottomTagSelected: boolean; - preferencesPanelShown: boolean; - refreshSearch: boolean; - roots: List; - searchRoots: ?List; - searchText: string; - selectedTab: string; - selected: ?ElementID; - themeStore: ThemeStore; - breadcrumbHead: ?ElementID; - snapshotQueue: Array = []; - // an object describing the capabilities of the inspected runtime. - capabilities: { - scroll?: boolean, - rnStyle?: boolean, - rnStyleMeasure?: boolean, - }; - - constructor(bridge: Bridge, themeStore: ThemeStore) { - super(); - - this._nodes = new Map(); - this._parents = new Map(); - this._nodesByName = new Map(); - this._bridge = bridge; - - // Public state - this.isInspectEnabled = false; - this.roots = new List(); - this.contextMenu = null; - this.searchRoots = null; - this.hovered = null; - this.selected = null; - this.selectedTab = 'Elements'; - this.breadcrumbHead = null; - this.isBottomTagHovered = false; - this.isBottomTagSelected = false; - this.searchText = ''; - this.capabilities = {}; - this.traceupdatesState = null; - this.colorizerState = null; - this.refreshSearch = false; - this.themeStore = themeStore; - - // for debugging - window.store = this; - - // events from the backend - this._bridge.on('root', id => { - if (this.roots.contains(id)) { - return; - } - this.roots = this.roots.push(id); - if (!this.selected) { - this.selected = this.skipWrapper(id); - this.breadcrumbHead = this.selected; - this.emit('selected'); - this.emit('breadcrumbHead'); - this._bridge.send('selected', this.selected); - } - this.emit('roots'); - }); - this._bridge.on('mount', (data) => this._mountComponent(data)); - this._bridge.on('update', (data) => this._updateComponent(data)); - this._bridge.on('updateProfileTimes', (data) => this._updateComponentProfileTimes(data)); - this._bridge.on('unmount', id => this._unmountComponent(id)); - this._bridge.on('setInspectEnabled', (data) => this.setInspectEnabled(data)); - this._bridge.on('select', ({id, quiet, offsetFromLeaf = 0}) => { - // Backtrack if we want to skip leaf nodes - while (offsetFromLeaf > 0) { - offsetFromLeaf--; - var pid = this._parents.get(id); - if (pid) { - id = pid; - } else { - break; - } - } - this._revealDeep(id); - this.selectTop(this.skipWrapper(id), quiet); - this.setSelectedTab('Elements'); - }); - this._bridge.on('storeSnapshot', storeSnapshot => { - // Store snapshot data for ProfilerStore to process later. - // It's important to store it as a queue, because events may be batched. - this.snapshotQueue.push({ - ...storeSnapshot, - nodes: this._nodes, - }); - this.emit('storeSnapshot'); - }); - this._bridge.on('clearSnapshots', () => { - this.snapshotQueue.length = 0; - this.emit('clearSnapshots'); - }); - - this._establishConnection(); - this._eventQueue = []; - this._eventTimer = null; - } - - emit(event: string): boolean { - if (this._eventQueue.indexOf(event) !== -1) { - // to appease flow - return true; - } - this._eventQueue.push(event); - if (!this._eventTimer) { - this._eventTimer = setTimeout(() => this.flush(), 50); - } - return true; - } - - flush() { - if (this._eventTimer) { - clearTimeout(this._eventTimer); - this._eventTimer = null; - } - this._eventQueue.forEach(evt => { - EventEmitter.prototype.emit.call(this, evt); - }); - this._eventQueue = []; - } - - // Public actions - scrollToNode(id: ElementID): void { - this._bridge.send('scrollToNode', id); - } - - copyNodeName(name: string): void { - copy(name); - } - - copyNodeProps(props: Object): void { - copy(serializePropsForCopy(props)); - } - - setSelectedTab(name: string): void { - if (this.selectedTab === name) { - return; - } - this.selectedTab = name; - this.emit('selectedTab'); - } - - // TODO(jared): get this working for react native - changeTextContent(id: ElementID, text: string): void { - this._bridge.send('changeTextContent', {id, text}); - var node = this._nodes.get(id); - if (node.get('nodeType') === 'Text') { - this._nodes = this._nodes.set(id, node.set('text', text)); - } else { - this._nodes = this._nodes.set(id, node.set('children', text)); - var props = node.get('props'); - props.children = text; - } - this.emit(id); - } - - changeSearch(text: string): void { - var needle = text.toLowerCase(); - if (needle === this.searchText.toLowerCase() && !this.refreshSearch) { - return; - } - if (!text || SearchUtils.trimSearchText(text).length === 0) { - this.searchRoots = null; - } else { - if ( - this.searchRoots && - needle.indexOf(this.searchText.toLowerCase()) === 0 && - !SearchUtils.shouldSearchUseRegex(text) - ) { - this.searchRoots = this.searchRoots - .filter(item => { - var node = this.get(item); - return (node.get('name') && node.get('name').toLowerCase().indexOf(needle) !== -1) || - (node.get('text') && node.get('text').toLowerCase().indexOf(needle) !== -1) || - (typeof node.get('children') === 'string' && node.get('children').toLowerCase().indexOf(needle) !== -1); - }); - } else { - this.searchRoots = this._nodes.entrySeq() - .filter(([key, val]) => nodeMatchesText(val, needle, key, this)) - .map(([key, val]) => key) - .toList(); - } - this.searchRoots.forEach(id => { - if (this.hasBottom(id)) { - this._nodes = this._nodes.setIn([id, 'collapsed'], true); - } - }); - } - this.searchText = text; - this.emit('searchText'); - this.emit('searchRoots'); - if (this.searchRoots && !this.searchRoots.contains(this.selected)) { - this.select(null, true); - } else if (!this.searchRoots) { - if (this.selected) { - this._revealDeep(this.selected); - } else { - this.select(this.roots.get(0)); - } - } - - this.highlightSearch(); - this.refreshSearch = false; - - // Search input depends on this change being flushed synchronously. - this.flush(); - } - - highlight(id: string): void { - // Individual highlighting is disabled while colorizer is active - if (!this.colorizerState || !this.colorizerState.enabled) { - this._bridge.send('highlight', id); - } - } - - highlightMany(ids: Array): void { - this._bridge.send('highlightMany', ids); - } - - highlightSearch(): void { - if (this.colorizerState && this.colorizerState.enabled) { - this._bridge.send('hideHighlight'); - if (this.searchRoots) { - this.highlightMany(this.searchRoots.toArray()); - } - } - } - - hoverClass(name: string): void { - if (name === null) { - this.hideHighlight(); - return; - } - var ids = this._nodesByName.get(name); - if (!ids) { - return; - } - this.highlightMany(ids.toArray()); - } - - selectFirstOfClass(name: string): void { - var ids = this._nodesByName.get(name); - if (!ids || !ids.size) { - return; - } - var id = ids.toSeq().first(); - this._revealDeep(id); - this.selectTop(id); - } - - showContextMenu(type: string, evt: DOMEvent, ...args: Array) { - evt.preventDefault(); - this.contextMenu = {type, x: evt.pageX, y: evt.pageY, args}; - this.emit('contextMenu'); - } - - hideContextMenu() { - this.contextMenu = null; - this.emit('contextMenu'); - } - - changeTheme(themeName: ?string) { - this.themeStore.update(themeName); - this.emit('theme'); - } - - changeDefaultTheme(defaultThemeName: ?string) { - this.themeStore.setDefaultTheme(defaultThemeName); - this.emit('theme'); - } - - saveCustomTheme(theme: Theme) { - this.themeStore.saveCustomTheme(theme); - this.emit('theme'); - } - - showPreferencesPanel() { - this.preferencesPanelShown = true; - this.emit('preferencesPanelShown'); - } - - hidePreferencesPanel() { - this.preferencesPanelShown = false; - this.emit('preferencesPanelShown'); - } - - selectFirstSearchResult() { - if (this.searchRoots) { - this.select(this.searchRoots.get(0), true); - } - } - - hasBottom(id: ElementID): boolean { - var node = this.get(id); - var children = node.get('children'); - if (node.get('nodeType') === 'NativeWrapper') { - children = this.get(children[0]).get('children'); - } - if (typeof children === 'string' || !children || !children.length || node.get('collapsed')) { - return false; - } - return true; - } - - toggleCollapse(id: ElementID) { - this._nodes = this._nodes.updateIn([id, 'collapsed'], c => !c); - this.emit(id); - } - - toggleAllChildrenNodes(value: boolean) { - var id = this.selected; - if (!id) { - return; - } - this._toggleDeepChildren(id, value); - } - - setProps(id: ElementID, path: Array, value: any) { - this._bridge.send('setProps', {id, path, value}); - } - - setState(id: ElementID, path: Array, value: any) { - this._bridge.send('setState', {id, path, value}); - } - - setContext(id: ElementID, path: Array, value: any) { - this._bridge.send('setContext', {id, path, value}); - } - - makeGlobal(id: ElementID, path: Array) { - this._bridge.send('makeGlobal', {id, path}); - } - - setHover(id: ElementID, isHovered: boolean, isBottomTag: boolean) { - if (isHovered) { - var old = this.hovered; - this.hovered = id; - this.isBottomTagHovered = isBottomTag; - if (old) { - this.emit(old); - } - this.emit(id); - this.emit('hover'); - this.highlight(id); - } else if (this.hovered === id) { - this.hideHighlight(); - this.isBottomTagHovered = false; - } - } - - hideHighlight() { - if (this.colorizerState && this.colorizerState.enabled) { - return; - } - this._bridge.send('hideHighlight'); - if (!this.hovered) { - return; - } - var id = this.hovered; - this.hovered = null; - this.emit(id); - this.emit('hover'); - } - - selectBreadcrumb(id: ElementID) { - this._revealDeep(id); - this.changeSearch(''); - this.isBottomTagSelected = false; - this.select(id, false, true); - } - - selectTop(id: ?ElementID, noHighlight?: boolean) { - this.isBottomTagSelected = false; - this.select(id, noHighlight); - } - - selectBottom(id: ElementID) { - this.isBottomTagSelected = true; - this.select(id); - } - - select(id: ?ElementID, noHighlight?: boolean, keepBreadcrumb?: boolean) { - var oldSel = this.selected; - this.selected = id; - if (oldSel) { - this.emit(oldSel); - } - if (id) { - this.emit(id); - } - if (!keepBreadcrumb) { - this.breadcrumbHead = id; - this.emit('breadcrumbHead'); - } - this.emit('selected'); - this._bridge.send('selected', id); - if (!noHighlight && id) { - this.highlight(id); - } - } - - // Public methods - get(id: ElementID): DataType { - return this._nodes.get(id); - } - - getParent(id: ElementID): ElementID { - return this._parents.get(id); - } - - skipWrapper(id: ElementID, up?: boolean, end?: boolean): ?ElementID { - if (!id) { - return undefined; - } - while (true) { - var node = this.get(id); - var nodeType = node.get('nodeType'); - - if (nodeType !== 'Wrapper' && nodeType !== 'Native') { - return id; - } - if (nodeType === 'Native' && (!up || this.get(this._parents.get(id)).get('nodeType') !== 'NativeWrapper')) { - return id; - } - if (up) { - var parentId = this._parents.get(id); - if (!parentId) { - // Don't show the Stack root wrapper in breadcrumbs - return undefined; - } - id = parentId; - } else { - var children = node.get('children'); - if (children.length === 0) { - return undefined; - } - var index = end ? children.length - 1 : 0; - var childId = children[index]; - id = childId; - } - } - } - - off(evt: string, fn: ListenerFunction): void { - this.removeListener(evt, fn); - } - - inspect(id: ElementID, path: Array, cb: () => void) { - invariant(path[0] === 'props' || path[0] === 'state' || path[0] === 'context', - 'Inspected path must be one of props, state, or context'); - this._bridge.inspect(id, path, value => { - var base = this.get(id).get(path[0]); - // $FlowFixMe - var inspected: ?{[string]: boolean} = path.slice(1).reduce((obj, attr) => obj ? obj[attr] : null, base); - if (inspected) { - assign(inspected, value); - inspected[consts.inspected] = true; - } - cb(); - }); - } - - changeTraceUpdates(state: ControlState) { - this.traceupdatesState = state; - this.emit('traceupdatesstatechange'); - invariant(state.toJS, 'state.toJS should exist'); - this._bridge.send('traceupdatesstatechange', state.toJS()); - set(LOCAL_STORAGE_TRACE_UPDATES_KEY, state.enabled); - } - - changeColorizer(state: ControlState) { - this.colorizerState = state; - this.emit('colorizerchange'); - this._bridge.send('colorizerchange', state.toJS()); - if (this.colorizerState && this.colorizerState.enabled) { - this.highlightSearch(); - } else { - this.hideHighlight(); - } - } - - setInspectEnabled(isInspectEnabled: boolean) { - this.isInspectEnabled = isInspectEnabled; - this.emit('isInspectEnabled'); - this._bridge.send('setInspectEnabled', isInspectEnabled); - } - - setIsRecording(isRecording: boolean) { - this._bridge.send('isRecording', isRecording); - } - - // Private stuff - _establishConnection() { - var tries = 0; - var requestInt; - this._bridge.once('capabilities', capabilities => { - clearInterval(requestInt); - this.capabilities = assign(this.capabilities, capabilities); - this.emit('connected'); - - const traceUpdates = get(LOCAL_STORAGE_TRACE_UPDATES_KEY); - - if (traceUpdates) { - this.changeTraceUpdates(new Record({enabled: true})()); - } - - }); - this._bridge.send('requestCapabilities'); - requestInt = setInterval(() => { - tries += 1; - if (tries > 100) { - console.error('failed to connect'); - clearInterval(requestInt); - this.emit('connection failed'); - return; - } - this._bridge.send('requestCapabilities'); - }, 500); - } - - _revealDeep(id: ElementID) { - if (this.searchRoots && this.searchRoots.contains(id)) { - return; - } - var pid = this._parents.get(id); - while (pid) { - if (this._nodes.getIn([pid, 'collapsed'])) { - this._nodes = this._nodes.setIn([pid, 'collapsed'], false); - this.emit(pid); - } - if (this.searchRoots && this.searchRoots.contains(pid)) { - return; - } - pid = this._parents.get(pid); - } - } - - _toggleDeepChildren(id: ElementID, value: boolean) { - var node = this._nodes.get(id); - if (!node) { - return; - } - if (node.get('collapsed') !== value) { - this._nodes = this._nodes.setIn([id, 'collapsed'], value); - this.emit(id); - } - var children = node.get('children'); - if (children && children.forEach) { - children.forEach(cid => this._toggleDeepChildren(cid, value)); - } - } - - _mountComponent(data: DataType) { - var map = Map(data).set('renders', 1); - if (data.nodeType === 'Composite') { - map = map.set('collapsed', true); - } - this._nodes = this._nodes.set(data.id, map); - if (data.children && data.children.forEach) { - data.children.forEach(cid => { - this._parents = this._parents.set(cid, data.id); - }); - } - var curNodes = this._nodesByName.get(data.name) || new Set(); - this._nodesByName = this._nodesByName.set(data.name, curNodes.add(data.id)); - this.emit(data.id); - } - - _updateComponent(data: DataType) { - var id = data.id; - var node = this.get(id); - if (!node) { - return; - } - data.renders = node.get('renders') + 1; - this._nodes = this._nodes.mergeIn([id], Map(data)); - if (data.children && data.children.forEach) { - data.children.forEach(cid => { - if (!this._parents.has(cid)) { - this._parents = this._parents.set(cid, id); - var childNode = this._nodes.get(cid); - var childID = childNode.get('id'); - if ( - this.searchRoots && - nodeMatchesText( - childNode, - this.searchText.toLowerCase(), - childID, - this, - )) { - this.searchRoots = this.searchRoots.push(childID); - this.emit('searchRoots'); - this.highlightSearch(); - } - } - }); - } - this.emit(data.id); - } - - _updateComponentProfileTimes(data: DataType) { - var node = this.get(data.id); - if (!node) { - return; - } - this._nodes = this._nodes.mergeIn([data.id], Map(data)); - this.emit(data.id); - } - - _unmountComponent(id: ElementID) { - var pid = this._parents.get(id); - this._removeFromNodesByName(id); - this._parents = this._parents.delete(id); - this._nodes = this._nodes.delete(id); - if (pid) { - this.emit(pid); - } else { - var ix = this.roots.indexOf(id); - if (ix !== -1) { - this.roots = this.roots.delete(ix); - this.emit('roots'); - } - } - if (id === this.selected) { - var newsel = pid ? this.skipWrapper(pid, true) : this.roots.get(0); - this.selectTop(newsel, true); - } - if (this.searchRoots && this.searchRoots.contains(id)) { - // $FlowFixMe flow things searchRoots might be null - this.searchRoots = this.searchRoots.delete(this.searchRoots.indexOf(id)); - this.emit('searchRoots'); - this.highlightSearch(); - } - } - - _removeFromNodesByName(id: ElementID) { - var node = this._nodes.get(id); - if (node) { - this._nodesByName = this._nodesByName.set(node.get('name'), this._nodesByName.get(node.get('name')).delete(id)); - } - } -} - -module.exports = Store; diff --git a/frontend/SvgIcon.js b/frontend/SvgIcon.js deleted file mode 100644 index 47ad6ff3d8..0000000000 --- a/frontend/SvgIcon.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); - -type Props = { - path: string, - style?: Object, -}; - -const SvgIcon = ({ path, style }: Props) => ( - - - -); - -const DEFAULT_STYLE = { - flex: '0 0 1rem', - width: '1rem', - height: '1rem', - fill: 'currentColor', -}; - -module.exports = SvgIcon; diff --git a/frontend/TabbedPane.js b/frontend/TabbedPane.js deleted file mode 100644 index 06700be1a5..0000000000 --- a/frontend/TabbedPane.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); -const React = require('react'); -const decorate = require('./decorate'); -const {sansSerif} = require('./Themes/Fonts'); - -import type {Theme} from './types'; - -type Props = { - tabs: {[key: string]: () => React.Node}, - selected: string, - setSelectedTab: (name: string) => void, -}; - -class TabbedPane extends React.Component { - context: { - theme: Theme, - }; - - render() { - var {theme} = this.context; - var tabs = Object.keys(this.props.tabs); - if (tabs.length === 1) { - return this.props.tabs[tabs[0]](); - } - return ( -
    -
      - {tabs.map((name, i) => ( -
    • this.props.setSelectedTab(name)} - style={tabStyle(name === this.props.selected, theme)} - > - {name} -
    • - ))} -
    -
    - {this.props.tabs[this.props.selected]()} -
    -
    - ); - } -} - -TabbedPane.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const tabsStyle = (theme: Theme) => ({ - display: 'flex', - flexShrink: 0, - listStyle: 'none', - margin: 0, - backgroundColor: theme.base01, - borderBottom: `1px solid ${theme.base03}`, - padding: '0 0.25rem', -}); - -const tabStyle = (isSelected: boolean, theme: Theme) => { - return { - padding: '0.25rem 0.75rem', - lineHeight: '15px', - fontSize: sansSerif.sizes.normal, - fontFamily: sansSerif.family, - cursor: 'pointer', - borderTop: '1px solid transparent', - borderBottom: isSelected ? `2px solid ${theme.state00}` : 'none', - marginBottom: isSelected ? '-1px' : '1px', - }; -}; - -var styles = { - container:{ - flex: 1, - display: 'flex', - flexDirection: 'column', - width: '100%', - }, - body: { - flex: 1, - display: 'flex', - minHeight: 0, - }, -}; - -module.exports = decorate({ - listeners: () => ['selectedTab'], - shouldUpdate: (props, prevProps) => { - for (var name in props) { - if (props[name] !== prevProps[name]) { - return true; - } - } - return false; - }, - props(store) { - return { - selected: store.selectedTab, - setSelectedTab: name => store.setSelectedTab(name), - }; - }, -}, TabbedPane); diff --git a/frontend/Themes/Editor/ColorGroups.js b/frontend/Themes/Editor/ColorGroups.js deleted file mode 100644 index e55f9beb29..0000000000 --- a/frontend/Themes/Editor/ColorGroups.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const Base = { - base00: 'Default Background', - base01: 'Soft Background', - base02: 'Soft Middle', - base03: 'Strong Middle', - base04: 'Soft Foreground', - base05: 'Default Foreground', -}; - -const Syntax = { - special00: 'Custom Components', - special01: 'Integers, Booleans', - special02: 'Strings, Arrays', - special03: 'Details Pane Text', - special04: 'Functions, Objects', - special05: 'Special Text', - special06: 'XML Attributes', - special07: 'Host Components', -}; - -const Selection = { - state00: 'Focused Background', - state01: 'Blurred Background', - state03: 'Hovered Background', - state02: 'Focused Foreground', - state04: 'Search Background', - state05: 'Search Foreground', - state06: 'Interactive Hover', -}; - -module.exports = { - Base, - Selection, - Syntax, -}; diff --git a/frontend/Themes/Editor/ColorInput.js b/frontend/Themes/Editor/ColorInput.js deleted file mode 100644 index d23cc09070..0000000000 --- a/frontend/Themes/Editor/ColorInput.js +++ /dev/null @@ -1,223 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); -const Portal = require('react-portal'); -const nullthrows = require('nullthrows').default; -const ColorPicker = require('./ColorPicker'); -const Input = require('../../Input'); -const {monospace} = require('../Fonts'); -const {getBrightness, isBright} = require('../utils'); - -import type {Theme} from '../../types'; - -type Position = { - left: number, - top: number, -}; - -type Props = { - customTheme: Theme, - label: string, - propertyName: string, - theme: Theme, - udpatePreview: () => void, -}; - -type State = { - color: string, - isColorPickerOpen: boolean, - maxHeight: ?number, - targetPosition: Position, -}; - -class ColorInput extends React.Component { - props: Props; - state: State; - - _colorChipRef: ?HTMLDivElement; - _containerRef: ?HTMLDivElement; - - constructor(props: Props, context: any) { - super(props, context); - - const {customTheme, propertyName} = props; - - this.state = { - color: customTheme[propertyName], - isColorPickerOpen: false, - maxHeight: null, - targetPosition: { - left: 0, - top: 0, - }, - }; - } - - componentWillReceiveProps(nextProps: Props) { - const {customTheme, propertyName} = nextProps; - - this.setState({ - color: customTheme[propertyName], - }); - } - - render() { - const {label, theme} = this.props; - const {color, isColorPickerOpen, maxHeight, targetPosition} = this.state; - - const backgroundIsBright = isBright(theme.base00); - const chipIsBright = isBright(color); - const showColorChipBorder = backgroundIsBright === chipIsBright && - getBrightness(color) > getBrightness(theme.base03); - - return ( -
    - -
    -
    - -
    - -
    - -
    -
    -
    - ); - } - - _onChange: Function = ({ target }): void => { - this._updateColor(target.value); - }; - - _onClick: Function = (event): void => { - const container = nullthrows(this._containerRef); - const targetPosition = nullthrows( - this._colorChipRef, - ).getBoundingClientRect(); - - this.setState({ - isColorPickerOpen: true, - maxHeight: container.offsetHeight, - targetPosition, - }); - }; - - _onClose: Function = (): void => { - this.setState({ - isColorPickerOpen: false, - }); - }; - - _setColorChipRef = (ref: ?HTMLDivElement): void => { - this._colorChipRef = ref; - }; - - _setContainerRef = (ref: ?HTMLDivElement): void => { - this._containerRef = ref; - }; - - _updateColor: Function = (color: string): void => { - const {customTheme, propertyName, udpatePreview} = this.props; - - customTheme[propertyName] = color; - - this.setState({ - color, - }); - - udpatePreview(); - }; -} - -const colorPickerPosition = (position: Position) => ({ - position: 'absolute', - left: `${position.left}px`, - top: `${position.top}px`, -}); - -const containerStyle = (maxHeight: ?number) => ({ - margin: '0.25rem', - minWidth: '7.5rem', - maxHeight, -}); - -const colorChipStyle = (theme: Theme, color: string = '', showBorder: boolean = false) => ({ - height: '1.25rem', - width: '1.25rem', - borderRadius: '2px', - backgroundColor: color, - boxSizing: 'border-box', - border: showBorder ? `1px solid ${theme.base03}` : 'none', -}); - -const inputContainerStyle = (theme: Theme) => ({ - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - padding: '0.125rem', - flex: '0 0 1.25rem', - backgroundColor: theme.base00, - color: theme.base05, - border: `1px solid ${theme.base03}`, - borderRadius: '0.25rem', -}); - -const styles = { - input: { - width: '5rem', - flex: '1 0 auto', - textTransform: 'lowercase', - boxSizing: 'border-box', - background: 'transparent', - border: 'none', - marginLeft: '0.25rem', - outline: 'none', - color: 'inherit', - fontFamily: monospace.family, - fontSize: monospace.sizes.large, - }, - label: { - marginBottom: '0.25rem', - display: 'inline-block', - }, - small: { - fontWeight: 'normal', - }, -}; - -module.exports = ColorInput; diff --git a/frontend/Themes/Editor/ColorPicker.js b/frontend/Themes/Editor/ColorPicker.js deleted file mode 100644 index c26d129588..0000000000 --- a/frontend/Themes/Editor/ColorPicker.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); -const {CustomPicker} = require('react-color'); -const {Hue, Saturation} = require('react-color/lib/components/common'); - -import type {Theme} from '../../types'; - -type ColorPickerProps = { - color: string, - theme: Theme, - updateColor: (color: string) => void, -}; - -class ColorPicker extends React.Component { - _ref: any; - - render() { - const {color, theme} = this.props; - - return ( - - ); - } - - _onChangeComplete: Function = (color: any): void => { - this.props.updateColor(color.hex); - }; - - _setRef: Function = (ref: any): void => { - this._ref = ref; - }; -} - -type CustomColorPickerProps = { - color: string, - theme: Theme, - onChange: (color: any) => void, -}; -class CustomColorPicker extends React.Component { - render() { - return ( -
    -
    - -
    -
    - -
    -
    - ); - } -} - -const CustomPointer = () => ( -
    -); - -const DecoratedCustomColorPicker = CustomPicker(CustomColorPicker); - -const customColorPicker = (theme) => ({ - display: 'flex', - flexDirection: 'row', - padding: '0.125rem', - borderRadius: '0.25rem', - position: 'relative', - zIndex: 1, - background: theme.base00, - border: `1px solid ${theme.base03}`, -}); - -const styles = { - saturation: { - flex: '0 0 auto', - position: 'relative', - width: '6rem', - height: '6rem', - }, - hue: { - flex: '1 0 auto', - position: 'relative', - width: '0.75rem', - height: '6rem', - marginLeft: '0.125rem', - }, - pointer: { - width: '0.25rem', - height: '0.25rem', - borderRadius: '50%', - transform: 'translate(-0.125rem, -0.125rem)', - boxShadow: 'rgb(255, 255, 255) 0px 0px 0px 1.5px, rgba(0, 0, 0, 0.3) 0px 0px 1px 1px inset, rgba(0, 0, 0, 0.4) 0px 0px 1px 2px', - }, -}; - -module.exports = ColorPicker; diff --git a/frontend/Themes/Editor/Editor.js b/frontend/Themes/Editor/Editor.js deleted file mode 100644 index d7635c81c9..0000000000 --- a/frontend/Themes/Editor/Editor.js +++ /dev/null @@ -1,337 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const {copy} = require('clipboard-js'); -const decorate = require('../../decorate'); -const PropTypes = require('prop-types'); -const React = require('react'); -const ColorInput = require('./ColorInput'); -const ColorGroups = require('./ColorGroups'); -const Hoverable = require('../../Hoverable'); -const Icons = require('../../Icons'); -const Input = require('../../Input'); -const {monospace, sansSerif} = require('../Fonts'); -const Preview = require('../Preview'); -const SvgIcon = require('../../SvgIcon'); -const Themes = require('../Themes'); -const TimerSafe = require('../../TimerSafe'); -const {deserialize, serialize} = require('../Serializer'); -const {CUSTOM_THEME_NAME} = require('../constants'); - -import type {DOMEvent, Theme} from '../../types'; -import type {SetTimeout} from '../../TimerSafe'; - -const THEME_SITE_URL = 'http://facebook.github.io/react-devtools/?theme='; - -// The editor itself should use a known safe theme, -// In case a user messes up a custom theme and renders it unusable. -// The editor should remain usable in this case. -const safeTheme = Themes.ChromeDefault; - -const colors = Object.assign({}, - ColorGroups.Base, - ColorGroups.Selection, - ColorGroups.Syntax -); - -type Props = { - changeTheme: (themeName: string) => void, - defaultThemeName: string, - hide: () => {}, - saveTheme: (theme: Theme) => {}, - setTimeout: SetTimeout, - theme: Theme, -} - -type State = { - isResetEnabled: boolean, - showCopyConfirmation: boolean, - updateCounter: number, -} - -class Editor extends React.Component { - _customTheme: Theme; - _serializedPropsTheme: string; - - constructor(props, context) { - super(props, context); - - this.state = { - isResetEnabled: false, - showCopyConfirmation: false, - updateCounter: 0, - }; - - this._serializedPropsTheme = serialize(props.theme); - - this._reset(); - this._sanitizeCustomTheme(); - } - - getChildContext() { - return { - theme: this._customTheme, - }; - } - - render() { - const {hide} = this.props; - const {isResetEnabled, showCopyConfirmation, updateCounter} = this.state; - - return ( -
    event.stopPropagation()} - style={editorStyle(safeTheme)} - > -

    Custom Theme

    - -
    -
    - {Object.keys(colors).map(key => ( - - ))} -
    -
    - -
    -
    - -
    -
    - -
    - -
    - - - - -
    -
    -
    - ); - } - - _copyTheme = () => { - const serializedTheme = encodeURI(serialize(this._customTheme)); - - copy(THEME_SITE_URL + serializedTheme); - - // Give (temporary) UI confirmation that the URL has been copied. - this.setState( - {showCopyConfirmation: true}, - () => { - this.props.setTimeout( - () => this.setState({showCopyConfirmation: false}), - 2500, - ); - }, - ); - }; - - _onShareChange = (event: DOMEvent) => { - this._customTheme = deserialize(event.target.value, this.props.theme); - this._sanitizeCustomTheme(); - this._udpatePreview(); - }; - - _udpatePreview = () => { - this.setState(state => ({ - isResetEnabled: this._serializedPropsTheme !== serialize(this._customTheme), - updateCounter: state.updateCounter + 1, - })); - }; - - _reset = () => { - this._customTheme = Object.assign({}, this.props.theme); - this._udpatePreview(); - }; - - _sanitizeCustomTheme() { - this._customTheme.displayName = CUSTOM_THEME_NAME; - - delete this._customTheme.hidden; - } - - _save = () => { - const {changeTheme, hide, saveTheme} = this.props; - - saveTheme(this._customTheme); - changeTheme(CUSTOM_THEME_NAME); - hide(); - }; -} - -Editor.childContextTypes = { - theme: PropTypes.object, -}; - -const WrappedEditor = decorate({ - listeners() { - return []; - }, - props(store, props) { - return { - changeTheme: themeName => store.changeTheme(themeName), - defaultThemeName: store.themeStore.defaultThemeName, - saveTheme: (theme: Theme) => store.saveCustomTheme(theme), - }; - }, -}, TimerSafe(Editor)); - -const CopyThemeButton = Hoverable( - ({ isHovered, isPressed, onClick, onMouseDown, onMouseEnter, onMouseLeave, onMouseUp, showCopyConfirmation, theme }) => ( - - ) -); - -const editorStyle = (theme: Theme) => ({ - display: 'flex', - flexDirection: 'column', - maxWidth: 'calc(100vw - 2rem)', - maxHeight: 'calc(100vh - 2rem)', - boxSizing: 'border-box', - zIndex: 1, - padding: '0.5rem', - borderRadius: '0.25rem', - backgroundColor: theme.base01, - color: theme.base05, - border: `1px solid ${theme.base03}`, - fontFamily: sansSerif.family, - fontSize: sansSerif.sizes.normal, -}); - -const groupStyle = (theme: Theme) => ({ - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - overflowY: 'auto', - borderRadius: '0.25rem', -}); - -const previewWrapperStyle = (theme: Theme) => ({ - display: 'inline-flex', - flex: '1 0 auto', - marginLeft: '0.5rem', - alignItems: 'stretch', - borderRadius: '0.25rem', - border: `1px solid ${theme.base03}`, -}); - -const shareInput = (theme: Theme) => ({ - flex: '0 1 15rem', - padding: '0.25rem', - border: `1px solid ${theme.base03}`, - borderRadius: '0.25rem', - fontFamily: monospace.family, - fontSize: monospace.sizes.normal, - color: 'inherit', -}); - -const copyThemeButtonStyle = (isHovered: boolean, isPressed: boolean, theme: Theme) => ({ - flex: '0 0 auto', - display: 'flex', - alignItems: 'center', - padding: '0.25rem', - margin: '0 0.25rem', - height: '1.5rem', - background: isPressed ? theme.state01 : 'none', - border: 'none', - color: isHovered ? theme.state06 : 'inherit', -}); - -const styles = { - header: { - flex: '0 0 auto', - marginTop: 0, - marginBottom: '0.5rem', - }, - bottomRow: { - flex: '0 0 auto', - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - marginTop: '0.5rem', - }, - buttons: { - flex: '1 0 auto', - marginTop: '0.5rem', - }, - copyLabel: { - flex: '0 0 auto', - marginLeft: '0.25rem', - }, - middleRow: { - display: 'flex', - flexDirection: 'row', - flex: '0 1 auto', - overflowY: 'auto', - }, - column: { - display: 'flex', - flexDirection: 'column', - }, - importExportRow: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - flex: '0 0 auto', - marginTop: '0.5rem', - }, - shareLabel: { - flex: '0 0 auto', - margin: '0 0.25rem', - }, -}; - -module.exports = WrappedEditor; diff --git a/frontend/Themes/Fonts.js b/frontend/Themes/Fonts.js deleted file mode 100644 index 308cf50aee..0000000000 --- a/frontend/Themes/Fonts.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -module.exports = { - monospace: { - family: 'Menlo, Consolas, monospace', - sizes: { - normal: 11, - large: 14, - }, - }, - sansSerif: { - family: '"Helvetica Neue", "Lucida Grande", -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, sans-serif', - sizes: { - small: 10, - normal: 12, - large: 14, - }, - }, -}; diff --git a/frontend/Themes/Preview.js b/frontend/Themes/Preview.js deleted file mode 100644 index 9e6875779c..0000000000 --- a/frontend/Themes/Preview.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); -const PropTypes = require('prop-types'); -const {Map} = require('immutable'); - -const consts = require('../../agent/consts'); -const Node = require('../Node'); -const {monospace} = require('./Fonts'); - -import type {Theme} from '../types'; - -type Props = { - theme: Theme, -}; - -class Preview extends React.Component { - getChildContext() { - return { - scrollTo: () => {}, - store: fauxStore, - }; - } - - render() { - const {theme} = this.props; - - return ( -
    - -
    - ); - } -} - -Preview.childContextTypes = { - scrollTo: PropTypes.func, - store: PropTypes.object, -}; - -const fauxRef = { - [consts.type]: 'function', - [consts.name]: 'setRef', -}; - -const childNode = Map({ - id: 'child', - children: 'text', - name: 'div', - props: { - style: {color: 'red'}, - }, - ref: fauxRef, -}); - -const strictModeNode = Map({ - id: 'strictMode', - children: ['grandparent'], - name: 'StrictMode', - nodeType: 'Special', -}); - -const grandparentNode = Map({ - id: 'grandparent', - children: ['parent'], - name: 'Grandparent', - nodeType: 'Composite', - props: { - depth: 0, - }, -}); - -const parentNode = Map({ - id: 'parent', - children: ['child'], - name: 'Parent', - nodeType: 'Composite', - props: { - boolean: true, - integer: 123, - string: 'foobar', - }, -}); - -const nodes = { - child: childNode, - grandparent: grandparentNode, - parent: parentNode, - strictMode: strictModeNode, -}; - -const noop = () => {}; - -const fauxStore = { - hovered: 'parent', - selected: 'grandparent', - get: (id: any) => nodes[id], - off: noop, - on: noop, - onContextMenu: noop, - onHover: noop, - onHoverBottom: noop, - onSelect: noop, - onSelectBottom: noop, - onToggleCollapse: noop, - setHover: noop, - selectBottom: noop, - selectTop: noop, -}; - -const panelStyle = (theme: Theme) => ({ - maxWidth: '100%', - padding: '0.25rem 0', - zIndex: 1, - fontFamily: monospace.family, - fontSize: monospace.sizes.normal, - backgroundColor: theme.base00, - color: theme.base05, -}); - -module.exports = Preview; diff --git a/frontend/Themes/Serializer.js b/frontend/Themes/Serializer.js deleted file mode 100644 index fe099312bf..0000000000 --- a/frontend/Themes/Serializer.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const {ChromeDefault} = require('./Themes'); - -import type {Theme} from '../types'; - -const themeKeys = Object.keys(ChromeDefault); - -function deserialize(string: string, fallbackTheme: Theme = ChromeDefault): Theme { - const theme = {}; - - try { - const maybeTheme = JSON.parse(string); - - // Make sure serialized theme has no extra keys. - themeKeys.forEach(key => { - const maybeColor = maybeTheme[key]; - if (isColorSet(maybeColor)) { - theme[key] = maybeColor; - } - }); - - } catch (error) { - console.error('Could not deserialize theme', error); - } - - // Update outdated custom theme formats and set reasonable defaults. - if (!isColorSet(theme.state06)) { // Added in version > 2.3.0 - theme.state06 = theme.base05; - } - - // Make sure serialized theme has all of the required color values. - themeKeys.forEach(key => { - const maybeColor = theme[key]; - if (!isColorSet(maybeColor)) { - theme[key] = fallbackTheme[key]; - } - }); - - return ((theme: any): Theme); -} - -function isColorSet(maybeColor: any): boolean { - return typeof maybeColor === 'string' && maybeColor !== ''; -} - -function serialize(theme: Theme): string { - return JSON.stringify(theme, null, 0); -} - -module.exports = { - deserialize, - serialize, -}; diff --git a/frontend/Themes/Store.js b/frontend/Themes/Store.js deleted file mode 100644 index c1b569b8c5..0000000000 --- a/frontend/Themes/Store.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const {deserialize, serialize} = require('./Serializer'); -const Themes = require('./Themes'); -const {CUSTOM_THEME_NAME} = require('./constants'); -const {get, set} = require('../../utils/storage'); - -const LOCAL_STORAGE_CUSTOM_THEME_KEY = 'customTheme'; -const LOCAL_STORAGE_THEME_NAME_KEY = 'themeName'; - -import type {Theme} from '../types'; - -class Store { - customTheme: Theme; - defaultThemeName: string; - theme: Theme; - themeName: string; - themes: { [key: string]: Theme }; - - constructor(defaultThemeName: ?string) { - this.themes = Themes; - - // Load previous custom theme from localStorage. - // If there isn't one in local storage, start by cloning the default theme. - const customTheme = get(LOCAL_STORAGE_CUSTOM_THEME_KEY); - if (customTheme) { - this.customTheme = deserialize(customTheme); - } - - this.setDefaultTheme(defaultThemeName); - } - - setDefaultTheme(defaultThemeName: ?string) { - // Don't accept an invalid themeName as a default. - this.defaultThemeName = getSafeThemeName(defaultThemeName); - - // Don't restore an invalid themeName. - // This guards against themes being removed or renamed. - const themeName = getSafeThemeName( - get(LOCAL_STORAGE_THEME_NAME_KEY), - this.defaultThemeName, - ); - - // The user's active theme is either their custom one, - // Or one of the built-in sets (based on the default). - this.theme = themeName === CUSTOM_THEME_NAME ? this.customTheme : Themes[themeName]; - this.themeName = themeName; - } - - update(themeName: ?string) { - if (themeName === CUSTOM_THEME_NAME) { - this.theme = this.customTheme; - this.themeName = CUSTOM_THEME_NAME; - } else { - // Only apply a valid theme. - const safeThemeKey = getSafeThemeName(themeName, this.defaultThemeName); - - this.theme = this.themes[safeThemeKey]; - this.themeName = safeThemeKey; - } - - // But allow users to restore "default" mode by selecting an empty theme. - set(LOCAL_STORAGE_THEME_NAME_KEY, themeName || null); - } - - saveCustomTheme(theme: Theme) { - this.customTheme = theme; - this.theme = theme; - - set(LOCAL_STORAGE_CUSTOM_THEME_KEY, serialize(theme)); - } -} - -function getSafeThemeName(themeName: ?string, fallbackThemeName: ?string): string { - if (themeName === CUSTOM_THEME_NAME) { - return CUSTOM_THEME_NAME; - } else if ( - themeName && - Themes.hasOwnProperty(themeName) - ) { - return themeName; - } else if ( - fallbackThemeName && - Themes.hasOwnProperty(fallbackThemeName) - ) { - return fallbackThemeName; - } else { - return 'ChromeDefault'; - } -} - -module.exports = Store; diff --git a/frontend/Themes/Themes.js b/frontend/Themes/Themes.js deleted file mode 100644 index 68443fa197..0000000000 --- a/frontend/Themes/Themes.js +++ /dev/null @@ -1,690 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../types'; - -/** - * A theme is a color template used throughout devtools. - * All devtools coloring is declared by themes, with one minor exception: status colors. - * Themes are user-selectable (via the preferences panel) and peristed between sessions. - * - * New themes may be safely added without user-facing impact. - * Renaming or removing a theme may cause user preferences to be reset on next devtools launch however. - * - * A few of the themes below are special purpose (ChromeDefault and ChromeDark, FirefoxDark). - * These are flagged as "hidden", meaning that they are not directly exposed to the user. - * Rather if the user chooses the "default" theme- they will be used to match the browser UI. - * These themes can be exposed to the UI directly by passing a "showHiddenThemes" flag to . - * - * Before adding a new theme, refer to the Theme docs in frontend/types. - * Each theme key has a purpose and guidelines should be followed to ensure legibility. - */ - -const ApathyDark: Theme = { - displayName: 'Apathy (dark)', - base00: '#1b322e', - base01: '#30695f', - base02: '#184E45', - base03: '#2B685E', - base04: '#5F9C92', - base05: '#f5fcfb', - special00: '#3E9688', - special01: '#3E7996', - special02: '#883E96', - special03: '#963E4C', - special04: '#96883E', - special05: '#4C963E', - special06: '#3E965B', - special07: '#5F9C92', - state00: '#28423d', - state01: '#28423d', - state02: '#f5fcfb', - state03: '#28423d', - state04: '#3E4C96', - state05: '#f5fcfb', - state06: '#ffffff', -}; - -const ApathyLight: Theme = { - displayName: 'Apathy (light)', - base00: '#D2E7E4', - base01: '#fff', - base02: '#184E45', - base03: '#2B685E', - base04: '#5F9C92', - base05: 'rgba(0, 0, 0, .7)', - special00: '#3E9688', - special01: '#3E7996', - special02: '#883E96', - special03: '#963E4C', - special04: '#96883E', - special05: '#4C963E', - special06: '#3E965B', - special07: '#5F9C92', - state00: '#f5fcfb', - state01: '#f5fcfb', - state02: '#2b2d2d', - state03: '#f5fcfb', - state04: '#3E4C96', - state05: '#f5fcfb', - state06: '#000000', -}; - -const AtomDark: Theme = { - displayName: 'Atom (dark)', - hidden: true, - base00: '#1d1f21', - base01: '#292c2f', - base02: '#2e2e2e', - base03: '#303030', - base04: '#868989', - base05: '#c1c4c2', - special00: '#fffeba', - special01: '#f574f3', - special02: '#aafd6a', - special03: '#93c294', - special04: '#fed2aa', - special05: '#93c294', - special06: '#c5c5fb', - special07: '#98ccfc', - state00: '#4483c2', - state01: '#444444', - state02: '#ffffff', - state03: '#444444', - state04: '#4483c2', - state05: '#ffffff', - state06: '#e3e6e4', -}; - -const AtomLight: Theme = { - displayName: 'Atom (light)', - hidden: true, - base00: '#ffffff', - base01: '#f4f4f4', - base02: '#eeeeee', - base03: '#555555', - base04: '#999989', - base05: '#222222', - special00: '#11807f', - special01: '#db1847', - special02: '#db1847', - special03: '#11807f', - special04: '#97040c', - special05: '#616b9f', - special06: '#455686', - special07: '#11807f', - state00: '#b0c4d9', - state01: '#fffed8', - state02: '#222222', - state03: '#fffed8', - state04: '#6da6e7', - state05: '#ffffff', - state06: '#000000', -}; - -const ChromeDark: Theme = { - displayName: 'Chrome (dark)', - hidden: true, - base00: '#242424', - base01: '#2a2a2a', - base02: '#363636', - base03: '#404040', - base04: '#777777', - base05: '#a5a5a5', - special00: '#5db0d7', - special01: '#a1f7b5', - special02: '#f29766', - special03: '#d2c057', - special04: '#34d1c5', - special05: '#9a7fd5', - special06: '#9bbbdc', - special07: '#777777', - state00: '#c78626', - state01: '#363636', - state02: '#242424', - state03: '#342e24', - state04: '#66ff88', - state05: '#242424', - state06: '#cccccc', -}; - -const ChromeDefault: Theme = { - displayName: 'Chrome (default)', - hidden: true, - base00: '#ffffff', - base01: '#f3f3f3', - base02: '#eeeeee', - base03: '#dadada', - base04: '#888888', - base05: '#5a5a5a', - special00: '#881280', - special01: '#222222', - special02: '#1a1aa6', - special03: '#c80000', - special04: '#236e25', - special05: '#aa0d91', - special06: '#994500', - special07: '#888888', - state00: '#3879d9', - state01: '#dadada', - state02: '#ffffff', - state03: '#ebf1fb', - state04: '#FFFF00', - state05: '#222222', - state06: '#222222', -}; - -const Dracula: Theme = { - displayName: 'Dracula', - base00: '#282936', - base01: '#3a3c4e', - base02: '#4d4f68', - base03: '#626483', - base04: '#6f7191', - base05: '#e9e9f4', - special00: '#ff79c6', - special01: '#bd93f9', - special02: '#f1fa8c', - special03: '#a1efe4', - special04: '#4afa7b', - special05: '#ff79c6', - special06: '#f8f8f2', - special07: '#6f7191', - state00: '#181a21', - state01: '#323547', - state02: '#f7f7fb', - state03: '#323547', - state04: '#fafa8c', - state05: '#000000', - state06: '#ffffff', -}; - -const Eighties: Theme = { - displayName: 'Eighties', - base00: '#2d2d2d', - base01: '#393939', - base02: '#515151', - base03: '#747369', - base04: '#a09f93', - base05: '#d3d0c8', - special00: '#f2777a', - special01: '#f99157', - special02: '#99cc99', - special03: '#66cccc', - special04: '#4afa7b', - special05: '#cc99cc', - special06: '#d27b53', - special07: '#a09f93', - state00: '#f2f0ec', - state01: '#3f3e3e', - state02: '#2d2d2d', - state03: '#3f3e3e', - state04: '#4afa7b', - state05: '#121212', - state06: '#e3e0d8', -}; - -const FirefoxDark: Theme = { - displayName: 'Firefox (dark)', - hidden: true, - base00: '#393f4c', - base01: '#393f4c', - base02: '#454d5d', - base03: '#454D5D', - base04: '#8fa1b2', - base05: '#a9bacb', - special00: '#00ff7f', - special01: '#eb5368', - special02: '#e9f4fe', - special03: '#bcb8db', - special04: '#e9f4fe', - special05: '#e9f4fe', - special06: '#e9f4fe', - special07: '#8fa1b2', - state00: '#5675b9', - state01: '#475983', - state02: '#ffffff', - state03: '#475983', - state04: '#00ff7f', - state05: '#181d20', - state06: '#b9cadb', -}; - -const FirefoxFirebug: Theme = { - displayName: 'Firefox (firebug)', - hidden: true, - base00: '#ffffff', - base01: '#f5f5f5', - base02: '#dde1e4', - base03: '#c1c1c1', - base04: '#888888', - base05: '#2a2a2a', - special00: '#0000ff', - special01: '#ff0000', - special02: '#ff0000', - special03: '#292e33', - special04: '#ff0000', - special05: '#0000ff', - special06: '#000062', - special07: '#0000ff', - state00: '#3399ff', - state01: '#e4f1fa', - state02: '#ffffff', - state03: '#e6e6e6', - state04: '#ffee99', - state05: '#000000', - state06: '#000000', -}; - -const FirefoxLight: Theme = { - displayName: 'Firefox (light)', - hidden: true, - base00: '#ffffff', - base01: '#fcfcfc', - base02: '#dde1e4', - base03: '#c1c1c1', - base04: '#888888', - base05: '#767676', - special00: '#2e9dd5', - special01: '#676bff', - special02: '#5b5fff', - special03: '#393f4c', - special04: '#ed2655', - special05: '#4f88cc', - special06: '#393f4c', - special07: '#888888', - state00: '#4c9ed9', - state01: '#e4f1fa', - state02: '#f4f7fa', - state03: '#e4f1fa', - state04: '#FFFF00', - state05: '#585959', - state06: '#444444', -}; - -const Flat: Theme = { - displayName: 'Flat', - base00: '#2C3E50', - base01: '#34495E', - base02: '#7F8C8D', - base03: '#95A5A6', - base04: '#BDC3C7', - base05: '#e0e0e0', - special00: '#E74C3C', - special01: '#E67E22', - special02: '#2ECC71', - special03: '#1ABC9C', - special04: '#3498DB', - special05: '#b670d2', - special06: '#be643c', - special07: '#BDC3C7', - state00: '#6a8db1', - state01: '#364c62', - state02: '#2C3E50', - state03: '#364c62', - state04: '#64fa82', - state05: '#2C3E50', - state06: '#ffffff', -}; - -const GruvboxDark: Theme = { - displayName: 'Gruvbox (dark)', - base00: '#282828', - base01: '#3c3836', - base02: '#504945', - base03: '#928374', - base04: '#bdae93', - base05: '#ebdbb2', - special00: '#83a598', - special01: '#d3869b', - special02: '#b8bb26', - special03: '#689d6a', - special04: '#fabd2f', - special05: '#fe8019', - special06: '#fe8019', - special07: '#bdae93', - state00: '#504945', - state01: '#3c3836', - state02: '#fbf1c7', - state03: '#3c3836', - state04: '#7c6f64', - state05: '#fbf1c7', - state06: '#fbebc2', -}; - -const GruvboxLight: Theme = { - displayName: 'Gruvbox (light)', - base00: '#fbf1c7', - base01: '#ebdbb2', - base02: '#d5c4a1', - base03: '#928374', - base04: '#282828', - base05: '#3c3836', - special00: '#076678', - special01: '#8f3f71', - special02: '#98971a', - special03: '#af3a03', - special04: '#458588', - special05: '#fe8019', - special06: '#b57614', - special07: '#282828', - state00: '#bdae93', - state01: '#d5c4a1', - state02: '#282828', - state03: '#d5c4a1', - state04: '#d5c4a1', - state05: '#282828', - state06: '#000000', -}; - -const Halflife: Theme = { - displayName: 'Halflife', - base00: '#222222', - base01: '#f3f3f3', - base02: '#888888', - base03: '#282828', - base04: '#888888', - base05: '#aaaaaa', - special00: '#fc913a', - special01: '#f9d423', - special02: '#f9d423', - special03: '#cccccc', - special04: '#f9d423', - special05: '#3b3a32', - special06: '#cccccc', - special07: '#7d8991', - state00: '#f85931', - state01: '#dadada', - state02: '#ffffff', - state03: '#282828', - state04: '#ffe792', - state05: '#000000', - state06: '#222222', -}; - -const Materia: Theme = { - displayName: 'Materia', - base00: '#263238', - base01: '#2C393F', - base02: '#37474F', - base03: '#707880', - base04: '#C9CCD3', - base05: '#CDD3DE', - special00: '#EC5F67', - special01: '#EA9560', - special02: '#8BD649', - special03: '#80CBC4', - special04: '#89DDFF', - special05: '#82AAFF', - special06: '#EC5F67', - special07: '#C9CCD3', - state00: '#0084ff', - state01: '#314048', - state02: '#263238', - state03: '#314048', - state04: '#00ff84', - state05: '#263238', - state06: '#DDE3EE', -}; - -const MaterialDark: Theme = { - displayName: 'Material Dark', - base00: '#263239', - base01: '#373b41', - base02: '#3e4a51', - base03: '#445052', - base04: '#718184', - base05: '#B2CCD6', - special00: '#f95479', - special01: '#F78C6A', - special02: '#C3E88D', - special03: '#89DDF3', - special04: '#82AAFF', - special05: '#C792EA', - special06: '#FFCB6B', - special07: '#718184', - state00: '#4a55b9', - state01: '#3e4a50', - state02: '#ffffff', - state03: '#212b30', - state04: '#4a55b9', - state05: '#ffffff', - state06: '#D2ECF6', -}; - -const OceanDark: Theme = { - displayName: 'Ocean Dark', - base00: '#232730', - base01: '#2b303c', - base02: '#323943', - base03: '#414551', - base04: '#65727e', - base05: '#757984', - special00: '#b48bae', - special01: '#c0c5ce', - special02: '#a3c08a', - special03: '#ab7866', - special04: '#eccd88', - special05: '#d06a77', - special06: '#757a85', - special07: '#65727e', - state00: '#a0a4ae', - state01: '#314048', - state02: '#263238', - state03: '#314048', - state04: '#d06a77', - state05: '#1c1f27', - state06: '#A5A9B4', -}; - -const OneDark: Theme = { - displayName: 'One (dark)', - base00: '#282c34', - base01: '#21252b', - base02: '#568af2', - base03: '#3b4048', - base04: '#3e454f', - base05: '#abb2bf', - special00: '#e5c07b', - special01: '#abb2bf', - special02: '#98c379', - special03: '#9da5b4', - special04: '#e06c75', - special05: '#6b717e', - special06: '#d19a66', - special07: '#abb2bf', - state00: '#4d78cc', - state01: '#3e4450', - state02: '#ffffff', - state03: '#2c323c', - state04: '#4d78cc', - state05: '#ffffff', - state06: '#ffffff', -}; - -const OneLight: Theme = { - displayName: 'One (light)', - base00: '#fafafa', - base01: '#eaeaeb', - base02: '#eeeeee', - base03: '#dbdbdc', - base04: '#8e8e90', - base05: '#3e4048', - special00: '#c0831e', - special01: '#a42ea2', - special02: '#68ab68', - special03: '#447bef', - special04: '#e2574e', - special05: '#424242', - special06: '#976715', - special07: '#424242', - state00: '#447bef', - state01: '#f0f0f1', - state02: '#ffffff', - state03: '#f0f0f1', - state04: '#447bef', - state05: '#ffffff', - state06: '#1c2026', -}; - -const Phd: Theme = { - displayName: 'Phd', - base00: '#061229', - base01: '#2a3448', - base02: '#4d5666', - base03: '#717885', - base04: '#9a99a3', - base05: '#b8bbc2', - special00: '#d07346', - special01: '#f0a000', - special02: '#99bf52', - special03: '#72b9bf', - special04: '#5299bf', - special05: '#9989cc', - special06: '#b08060', - special07: '#9a99a3', - state00: '#4b73bf', - state01: '#112243', - state02: '#061229', - state03: '#112243', - state04: '#00c8fa', - state05: '#061229', - state06: '#d8dbe2', -}; - -const SolarizedDark: Theme = { - displayName: 'Solarized Dark', - base00: '#002b36', - base01: '#073642', - base02: '#586e75', - base03: '#657b83', - base04: '#93a1a1', - base05: '#839496', - special00: '#268bd2', - special01: '#268bd2', - special02: '#2aa198', - special03: '#839496', - special04: '#2aa198', - special05: '#b58900', - special06: '#859900', - special07: '#268bd2', - state00: '#073642', - state01: '#002b36', - state02: '#93a1a1', - state03: '#073642', - state04: '#859900', - state05: '#002b36', - state06: '#fdf6e3', -}; - -const SolarizedLight: Theme = { - 'displayName':'Solarized Light', - 'base00':'#fdf6e3', - 'base01':'#eee8d5', - 'base02':'#586e75', - 'base03':'#657b83', - 'base04':'#93a1a1', - 'base05':'#657b83', - 'special00':'#268bd2', - 'special01':'#268bd2', - 'special02':'#2aa198', - 'special03':'#839496', - 'special04':'#2aa198', - 'special05':'#b58900', - 'special06':'#859900', - 'special07':'#268bd2', - 'state00':'#eee8d5', - 'state01':'#eee8d5', - 'state02':'#586e75', - 'state03':'#eee8d5', - 'state04':'#859900', - 'state05':'#eee8d5', - 'state06':'#073642', -}; - -const Tomorrow: Theme = { - displayName: 'Tomorrow', - base00: '#ffffff', - base01: '#e0e0e0', - base02: '#d6d6d6', - base03: '#8e908c', - base04: '#969896', - base05: '#4d4d4c', - special00: '#c82829', - special01: '#f5871f', - special02: '#718c00', - special03: '#3e999f', - special04: '#4271ae', - special05: '#8959a8', - special06: '#a3685a', - special07: '#969896', - state00: '#4271ae', - state01: '#e0e0e0', - state02: '#ffffff', - state03: '#e0e0e0', - state04: '#eab700', - state05: '#1d1f21', - state06: '#222222', -}; - -const TomorrowNight: Theme = { - displayName: 'Tomorrow Night', - base00: '#1d1f21', - base01: '#282a2e', - base02: '#373b41', - base03: '#969896', - base04: '#b4b7b4', - base05: '#c5c8c6', - special00: '#cc6666', - special01: '#de935f', - special02: '#b5bd68', - special03: '#8abeb7', - special04: '#81a2be', - special05: '#b294bb', - special06: '#a3685a', - special07: '#b4b7b4', - state00: '#0084ff', - state01: '#e0e0e0', - state02: '#282a2e', - state03: '#373b41', - state04: '#f0c674', - state05: '#1d1f21', - state06: '#e5e8e6', -}; - -module.exports = { - ApathyDark, - ApathyLight, - AtomDark, - AtomLight, - ChromeDark, - ChromeDefault, - Dracula, - Eighties, - FirefoxDark, - FirefoxFirebug, - FirefoxLight, - Flat, - GruvboxDark, - GruvboxLight, - Halflife, - Materia, - MaterialDark, - OceanDark, - OneDark, - OneLight, - Phd, - SolarizedDark, - SolarizedLight, - Tomorrow, - TomorrowNight, -}; diff --git a/frontend/Themes/constants.js b/frontend/Themes/constants.js deleted file mode 100644 index 91f52cf49c..0000000000 --- a/frontend/Themes/constants.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const CUSTOM_THEME_NAME = 'Custom'; - -module.exports = { - CUSTOM_THEME_NAME, -}; diff --git a/frontend/Themes/utils.js b/frontend/Themes/utils.js deleted file mode 100644 index 4042d46779..0000000000 --- a/frontend/Themes/utils.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -type RGB = { - r: number, - g: number, - b: number, -}; - -function getBrightness(hex: string): number { - const {r, g, b} = getRgb(hex); - - // http://www.w3.org/TR/AERT#color-contrast - return Math.round(((r * 299) + (g * 587) + (b * 114)) / 1000); -} - -function getInvertedMid(hex: string): string { - return hexToRgba(hex, 0.8); -} - -function getInvertedWeak(hex: string): string { - return hexToRgba(hex, 0.65); -} - -function getRgb(hex: string = ''): RGB { - hex = hex.replace('#', ''); - - if (hex.length === 3) { - hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); - } - - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - - return {r, g, b}; -} - -function hexToRgba(hex: string, alpha: number): string { - const {r, g, b} = getRgb(hex); - - return `rgba(${r}, ${g}, ${b}, ${alpha})`; -} - -function isBright(hex: string): boolean { - // http://www.w3.org/TR/AERT#color-contrast - return getBrightness(hex) > 125; -} - -module.exports = { - getBrightness, - getInvertedMid, - getInvertedWeak, - getRgb, - hexToRgba, - isBright, -}; diff --git a/frontend/TimerSafe.js b/frontend/TimerSafe.js deleted file mode 100644 index 961633f327..0000000000 --- a/frontend/TimerSafe.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); - -export type ClearTimeout = (id: any) => void; -export type SetTimeout = (callback: () => void, delay: number) => any; - -const TimerSafe = (Component: any) => { - class TimerSafeImplementation extends React.Component { - _timeoutIds: {[key: any]: boolean} = {}; - - componentWillUnmount() { - Object.keys(this._timeoutIds).forEach(this._clearTimeout); - } - - render() { - return ( - - ); - } - - _clearTimeout: ClearTimeout = (id) => { - clearTimeout(id); - - delete this._timeoutIds[id]; - }; - - _setTimeout: SetTimeout = (callback, delay) => { - const id = setTimeout(() => { - delete this._timeoutIds[id]; - - callback(); - }, delay); - - this._timeoutIds[id] = true; - - return id; - }; - } - - return TimerSafeImplementation; -}; - -module.exports = TimerSafe; diff --git a/frontend/TreeView.js b/frontend/TreeView.js deleted file mode 100644 index 134f959644..0000000000 --- a/frontend/TreeView.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const Node = require('./Node'); -const PropTypes = require('prop-types'); -const React = require('react'); -const SearchUtils = require('./SearchUtils'); -const Breadcrumb = require('./Breadcrumb'); - -const decorate = require('./decorate'); -const {monospace, sansSerif} = require('./Themes/Fonts'); - -import type {List} from 'immutable'; -import type {Theme} from './types'; - -const MAX_SEARCH_ROOTS = 200; - -type Props = { - reload?: () => void, - roots: List, - searching: boolean, - searchText: string, -}; - -class TreeView extends React.Component { - node: ?HTMLElement; - - getChildContext() { - return { - scrollTo: this.scrollTo.bind(this), - }; - } - - scrollTo(toNode) { - if (!this.node) { - return; - } - let val = 0; - const height = toNode.offsetHeight; - while (toNode && this.node.contains(toNode)) { - val += toNode.offsetTop; - toNode = toNode.offsetParent; - } - const top = this.node.scrollTop; - const rel = val - this.node.offsetTop; - const margin = 40; - if (top > rel - margin) { - this.node.scrollTop = rel - margin; - } else if (top + this.node.offsetHeight < rel + height + margin) { - this.node.scrollTop = rel - this.node.offsetHeight + height + margin; - } - } - - render() { - const {theme} = this.context; - - if (!this.props.roots.count()) { - if (this.props.searching) { - return ( -
    - No search results -
    - ); - } else { - return ( -
    -
    this.node = n} style={styles.scroll}> -
    - Waiting for roots to load... - {this.props.reload && - - to reload the inspector - } -
    -
    -
    - ); - } - } - - // Convert search text into a case-insensitive regex for match-highlighting. - const searchText = this.props.searchText; - const searchRegExp = SearchUtils.isValidRegex(searchText) - ? SearchUtils.searchTextToRegExp(searchText) - : null; - - if (this.props.searching && this.props.roots.count() > MAX_SEARCH_ROOTS) { - return ( -
    -
    this.node = n} style={styles.scroll}> -
    - {this.props.roots.slice(0, MAX_SEARCH_ROOTS).map(id => ( - - )).toJS()} - Some results not shown. Narrow your search criteria to find them -
    -
    -
    - ); - } - - return ( -
    -
    this.node = n} style={styles.scroll}> -
    - {this.props.roots.map(id => ( - - )).toJS()} -
    -
    - -
    - ); - } -} - -TreeView.childContextTypes = { - scrollTo: PropTypes.func, -}; - -TreeView.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const noSearchResultsStyle = (theme: Theme) => ({ - color: theme.base04, - fontFamily: sansSerif.family, - fontSize: sansSerif.sizes.large, - fontStyle: 'italic', - padding: '0.5rem', -}); - -const styles = { - container: { - fontFamily: monospace.family, - fontSize: monospace.sizes.normal, - lineHeight: 1.5, - flex: 1, - display: 'flex', - flexDirection: 'column', - minHeight: 0, - - WebkitUserSelect: 'none', - MozUserSelect: 'none', - userSelect: 'none', - }, - - scroll: { - overflow: 'auto', - minHeight: 0, - flex: 1, - display: 'flex', - flexDirection: 'column', - alignItems: 'flex-start', - padding: '0.5rem 0.25rem', - }, - - scrollContents: { - flexDirection: 'column', - flex: 1, - display: 'flex', - alignItems: 'stretch', - width: '100%', - }, -}; - -const WrappedTreeView = decorate({ - listeners(props) { - return ['searchRoots', 'roots']; - }, - props(store, props) { - return { - roots: store.searchRoots || store.roots, - searching: !!store.searchRoots, - searchText: store.searchText, - }; - }, -}, TreeView); - -module.exports = WrappedTreeView; diff --git a/frontend/decorate.js b/frontend/decorate.js deleted file mode 100644 index 42c7d86e69..0000000000 --- a/frontend/decorate.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var PropTypes = require('prop-types'); -var React = require('react'); - -type Options = { - /** A function determining whether the component should rerender when the - * parent rerenders. Defaults to function returning false **/ - shouldUpdate?: (nextProps: Object, props: Object) => boolean, - /** A function returning a list of events to listen to **/ - listeners?: (props: Object, store: Object) => Array, - /** This is how you get data and action handlers from the store. The - * returned object will be spread in as props on the wrapped component. **/ - props: (store: Object, props: Object) => Object, -}; - -type State = {}; - -/** - * This Higher Order Component decorator function is the way the components - * communicate with the central Store. - * - * Example: - * - * class MyComp { - * render() { - * return ( - *
    - * Hello {this.props.name}. - * - *
    - * ); - * } - * } - * - * module.exports = decorate({ - * listeners: () => ['nameChanged'], - * props(store) { - * return { - * name: store.name, - * sayHi: () => store.sayHi(), - * }; - * }, - * }, MyComp); - */ -module.exports = function(options: Options, Component: any): any { - var storeKey = options.store || 'store'; - class Wrapper extends React.Component<*, State> { - _listeners: Array; - _update: () => void; - state: State; - - constructor(props) { - super(props); - this.state = {}; - } - - componentWillMount() { - if (!this.context[storeKey]) { - console.warn('no store on context...'); - return; - } - this._update = () => this.forceUpdate(); - if (!options.listeners) { - return; - } - this._listeners = options.listeners(this.props, this.context[storeKey]); - this._listeners.forEach(evt => { - this.context[storeKey].on(evt, this._update); - }); - } - - componentWillUnmount() { - if (!this.context[storeKey]) { - console.warn('no store on context...'); - return; - } - this._listeners.forEach(evt => { - this.context[storeKey].off(evt, this._update); - }); - } - - shouldComponentUpdate(nextProps, nextState) { - if (nextState !== this.state) { - return true; - } - if (options.shouldUpdate) { - return options.shouldUpdate(nextProps, this.props); - } - return false; - } - - componentWillUpdate(nextProps, nextState) { - if (!this.context[storeKey]) { - console.warn('no store on context...'); - return; - } - if (!options.listeners) { - return; - } - var listeners = options.listeners(this.props, this.context[storeKey]); - var diff = arrayDiff(listeners, this._listeners); - diff.missing.forEach(name => { - this.context[storeKey].off(name, this._update); - }); - diff.newItems.forEach(name => { - this.context[storeKey].on(name, this._update); - }); - this._listeners = listeners; - } - - render() { - var store = this.context[storeKey]; - var props = store && options.props(store, this.props); - return ; - } - } - - Wrapper.contextTypes = { - // $FlowFixMe - [storeKey]: PropTypes.object, - }; - - Wrapper.displayName = 'Wrapper(' + Component.name + ')'; - - return Wrapper; -}; - -function arrayDiff(array, oldArray) { - var names = new Set(); - var missing = []; - for (var i = 0; i < array.length; i++) { - names.add(array[i]); - } - for (var j = 0; j < oldArray.length; j++) { - if (!names.has(oldArray[j])) { - missing.push(oldArray[j]); - } else { - names.delete(oldArray[j]); - } - } - return { - missing, - newItems: setToArray(names), - }; -} - -function setToArray(set) { - var res = []; - for (var val of set) { - res.push(val); - } - return res; -} diff --git a/frontend/detail_pane/DetailPane.js b/frontend/detail_pane/DetailPane.js deleted file mode 100644 index da08be1c66..0000000000 --- a/frontend/detail_pane/DetailPane.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var {monospace} = require('../Themes/Fonts'); -var React = require('react'); - -type Props = { - children: React.Node, -} - -class DetailPane extends React.Component { - render() { - return ( -
    - {this.props.children} -
    - ); - } -} - -var styles = { - container: { - fontSize: monospace.sizes.normal, - fontFamily: monospace.family, - overflow: 'auto', - flex: 1, - display: 'flex', - flexDirection: 'column', - }, -}; - -module.exports = DetailPane; diff --git a/frontend/detail_pane/DetailPaneSection.js b/frontend/detail_pane/DetailPaneSection.js deleted file mode 100644 index 748d22a0e6..0000000000 --- a/frontend/detail_pane/DetailPaneSection.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../types'; - -const PropTypes = require('prop-types'); -const React = require('react'); - -type Props = { - title: string, - hint?: React.Node, - children: React.Node, -} - -class DetailPaneSection extends React.Component { - context: { - theme: Theme, - }; - - render() { - const {theme} = this.context; - const {children, hint} = this.props; - return ( -
    - {this.props.title} - {hint} - {children} -
    - ); - } -} - -DetailPaneSection.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const sectionStyle = (theme: Theme) => ({ - borderTop: `1px solid ${theme.base01}`, - padding: '0.5rem', - flexShrink: 0, -}); - -var styles = { - title: { - display: 'inline-block', - marginRight: '0.5rem', - lineHeight: '1.5rem', - }, -}; - -module.exports = DetailPaneSection; diff --git a/frontend/dirToDest.js b/frontend/dirToDest.js deleted file mode 100644 index 7aa8394a5b..0000000000 --- a/frontend/dirToDest.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * A helper method for the store, to deal with navigating the tree. - * - * @flow - */ -'use strict'; - -import type {Dir, Dest} from './types'; - -module.exports = function(dir: Dir, bottom: boolean, collapsed: boolean, hasChildren: boolean): ?Dest { - if (dir === 'down') { - if (bottom || collapsed || !hasChildren) { - return 'nextSibling'; - } - return 'firstChild'; - } - - if (dir === 'up') { - if (!bottom || collapsed || !hasChildren) { - return 'prevSibling'; - } - return 'lastChild'; - } - - if (dir === 'left') { - if (!collapsed && hasChildren) { - return bottom ? 'selectTop' : 'collapse'; - } - return 'parent'; - } - - if (dir === 'right') { - if (collapsed && hasChildren) { - return 'uncollapse'; - } - if (hasChildren) { - if (bottom) { - return null; - } else { - return 'firstChild'; - } - } - } - - return null; -}; diff --git a/frontend/flash.js b/frontend/flash.js deleted file mode 100644 index 2eefe7d810..0000000000 --- a/frontend/flash.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * Flash the background of a dom node to a different color and then fade back - * to a base color. - * - * @flow - */ -'use strict'; - -type DOMElement = { - style: Object, - offsetTop: number; -}; - -function flash(node: DOMElement, flashColor: string, baseColor: string, duration: number) { - node.style.transition = 'none'; - node.style.backgroundColor = flashColor; - // force recalc - void node.offsetTop; - node.style.transition = `background-color ${duration}s ease`; - node.style.backgroundColor = baseColor; -} - -module.exports = flash; diff --git a/frontend/invariant.js b/frontend/invariant.js deleted file mode 100644 index d47660db11..0000000000 --- a/frontend/invariant.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * TODO: this is copied from fbjs because fbjs doesn't play well with - * non-haste module systems :/. Look into how to fix this. - */ -'use strict'; - -/** - * Use invariant() to assert state which your program assumes to be true. - * - * Provide sprintf-style format (only %s is supported) and arguments - * to provide information about what broke and what you were - * expecting. - * - * The invariant message will be stripped in production, but the invariant - * will remain to ensure logic does not differ in production. - */ -var __DEV__ = process.env.NODE_ENV === 'development'; - -var invariant = function(condition, format, a, b, c, d, e, f) { - if (__DEV__) { - if (format === undefined) { - throw new Error('invariant requires an error message argument'); - } - } - - if (!condition) { - var error; - if (format === undefined) { - error = new Error( - 'Minified exception occurred; use the non-minified dev environment ' + - 'for the full error message and additional helpful warnings.' - ); - } else { - var args = [a, b, c, d, e, f]; - var argIndex = 0; - error = new Error( - 'Invariant Violation: ' + - format.replace(/%s/g, function() { - return args[argIndex++]; - }) - ); - } - - error.framesToPop = 1; // we don't care about invariant's own frame - throw error; - } -}; - -module.exports = invariant; diff --git a/frontend/keyboardNav.js b/frontend/keyboardNav.js deleted file mode 100644 index 58394d8108..0000000000 --- a/frontend/keyboardNav.js +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * A helper method for the store, to deal with navigating the tree. - * - * @flow - */ -'use strict'; - -var dirToDest = require('./dirToDest'); -import type Store from './Store'; -import type {DOMEvent, Dir, Dest, ElementID} from './types'; - -var keyCodes = { - '72': 'left', // 'h', - '74': 'down', // 'j', - '75': 'up', // 'k', - '76': 'right', // 'l', - - '37': 'left', - '38': 'up', - '39': 'right', - '40': 'down', -}; - -module.exports = function keyboardNav(store: Store, win: Object): (e: DOMEvent) => void { - win = win || window; - return function(e: DOMEvent) { - if (win.document.activeElement !== win.document.body) { - return; - } - if (e.shiftKey || e.metaKey) { - return; - } - - var direction = keyCodes[e.keyCode]; - if (!direction) { - return; - } - e.preventDefault(); - if (e.altKey && direction === 'right') { - store.toggleAllChildrenNodes(false); - return; - } - if (e.altKey && direction === 'left') { - store.toggleAllChildrenNodes(true); - return; - } - if (e.ctrlKey || e.altKey) { - return; - } - var dest = getDest(direction, store); - if (!dest) { - return; - } - var move = getNewSelection(dest, store); - if (move && move !== store.selected) { - store.select(move); - } - }; -}; - -function getDest(dir: Dir, store: Store): ?Dest { - var id = store.selected; - if (!id) { - return null; - } - var bottom = store.isBottomTagSelected; - var node = store.get(id); - var collapsed = node.get('collapsed'); - var children = node.get('children'); - if (node.get('nodeType') === 'NativeWrapper') { - children = store.get(children[0]).get('children'); - } - var hasChildren = children && typeof children !== 'string' && children.length; - - return dirToDest(dir, bottom, collapsed, hasChildren); -} - -function getRootSelection(dest, store, id) { - var roots = store.searchRoots || store.roots; - var ix = roots.indexOf(id); - if (ix === -1) { - ix = roots.indexOf(store._parents.get(id)); - } - if (dest === 'prevSibling') { - // prev root - if (ix === 0) { - return null; - } - var prev = store.skipWrapper(roots.get(ix - 1), false, true); - store.isBottomTagSelected = prev ? store.hasBottom(prev) : false; // flowtype requires the ternary - return prev; - } else if (dest === 'nextSibling') { - if (ix >= roots.size - 1) { - return null; - } - store.isBottomTagSelected = false; - return store.skipWrapper(roots.get(ix + 1)); - } - return null; -} - -function getNewSelection(dest: Dest, store: Store): ?ElementID { - var id = store.selected; - if (!id) { - return undefined; - } - var node = store.get(id); - var pid = store.getParent(id); - - if (pid) { - var lastId = id; - if (dest === 'parent') { - let parentNode = store.get(pid); - if (parentNode.get('nodeType') !== 'Wrapper') { - return pid; - } - while (parentNode.get('nodeType') === 'Wrapper') { - lastId = id; - id = pid; - pid = store.getParent(id); - // we keep traversing up each parent until we have - // a non wrapper. if we find an empty parent, that means - // we've hit the top of the tree, meaning we need to - // use the root as the pid (so we get the roots) - if (!pid) { - pid = id; - id = lastId; - break; - } - parentNode = store.get(pid); - } - dest = 'prevSibling'; - } else if (dest === 'parentBottom') { - let parentNode = store.get(pid); - if (parentNode.get('nodeType') !== 'Wrapper') { - store.isBottomTagSelected = true; - return pid; - } - while (parentNode.get('nodeType') === 'Wrapper') { - lastId = id; - id = pid; - pid = store.getParent(id); - // we keep traversing up each parent until we have - // a non wrapper. if we find an empty parent, that means - // we've hit the top of the tree, meaning we need to - // use the root as the pid (so we get the roots) - if (!pid) { - pid = id; - id = lastId; - break; - } - parentNode = store.get(pid); - } - dest = 'nextSibling'; - } - } - if (!id) { - return undefined; - } - // if the parent is a root node, we should set pid to null - // so we go through the getRootSelection() route below - if (store.searchRoots && store.searchRoots.contains(pid)) { - pid = null; - } - if (dest === 'collapse' || dest === 'uncollapse') { - if (dest === 'collapse') { - store.isBottomTagSelected = false; - } - store.toggleCollapse(id); - return undefined; - } - - if (dest === 'selectTop') { - store.selectTop(id); - } - - var children = node.get('children'); - if (node.get('nodeType') === 'NativeWrapper') { - children = store.get(children[0]).get('children'); - } - - // Children - var cid; - if (dest === 'firstChild') { - if (typeof children === 'string') { - return getNewSelection('nextSibling', store); - } - for (var i = 0; i < children.length; i++) { - cid = store.skipWrapper(children[i]); - if (cid) { - store.isBottomTagSelected = false; - return cid; - } - } - } - if (dest === 'lastChild') { - if (typeof children === 'string') { - return getNewSelection('prevSibling', store); - } - cid = store.skipWrapper(children[children.length - 1], false, true); - if (cid && !store.hasBottom(cid)) { - store.isBottomTagSelected = false; - } - return cid; - } - - // Siblings at the root node - if (!pid) { - return getRootSelection(dest, store, id); - } - - // Siblings - var parent = store.get(store.getParent(id)); - var pchildren = parent.get('children'); - var pix = pchildren.indexOf(id); - if (pix === -1) { - pix = pchildren.indexOf(store._parents.get(id)); - } - if (dest === 'prevSibling') { - while (pix > 0) { - const childId = pchildren[pix - 1]; - const child = store.get(childId); - const prevCid = store.skipWrapper( - childId, - false, - child.get('nodeType') === 'Wrapper' - ); - if (prevCid) { - if (store.hasBottom(prevCid)) { - store.isBottomTagSelected = true; - } - return prevCid; - } - pix--; - } - const roots = store.searchRoots || store.roots; - // if the the previous sibling is a root, we need - // to go the getRootSelection() route to select it - if (roots.indexOf(store.getParent(id)) > -1) { - return getRootSelection(dest, store, id); - } - const childId = pchildren[pix]; - const child = store.get(childId); - if (child.get('nodeType') === 'Wrapper') { - return store.getParent(id); - } - return getNewSelection('parent', store); - } - if (dest === 'nextSibling') { - // check if we're at the end of the children array - if (pix === pchildren.length - 1) { - const roots = store.searchRoots || store.roots; - // if the the next sibling is a root, we need - // to go the getRootSelection() route to select it - if (roots.indexOf(store.getParent(id)) > -1) { - return getRootSelection(dest, store, id); - } - const childId = pchildren[pix]; - const child = store.get(childId); - if (child.get('nodeType') === 'Wrapper') { - store.isBottomTagSelected = true; - return store.getParent(id); - } - return getNewSelection('parentBottom', store); - } - store.isBottomTagSelected = false; - return store.skipWrapper(pchildren[pix + 1]); - } - - return null; -} diff --git a/frontend/nodeMatchesText.js b/frontend/nodeMatchesText.js deleted file mode 100644 index 27aa30db04..0000000000 --- a/frontend/nodeMatchesText.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var SearchUtils = require('./SearchUtils'); - -import type {Map} from 'immutable'; -import type Store from './Store'; - -function nodeMatchesText(node: Map, needle: string, key: string, store: Store): boolean { - var name = node.get('name'); - var wrapper = store.get(store.getParent(key)); - if (node.get('nodeType') === 'Native' && wrapper && wrapper.get('nodeType') === 'NativeWrapper') { - return false; - } - var useRegex = SearchUtils.shouldSearchUseRegex(needle); - if (name) { - if (node.get('nodeType') !== 'Wrapper') { - return validString(name, needle, useRegex); - } - } - var text = node.get('text'); - if (text) { - return validString(text, needle, useRegex); - } - var children = node.get('children'); - if (typeof children === 'string') { - return validString(children, needle, useRegex); - } - return false; -} - -function validString(str: string, needle: string, regex: boolean): boolean { - if (regex) { - try { - var regExp = SearchUtils.searchTextToRegExp(needle); - return regExp.test(str.toLowerCase()); - } catch (error) { - return false; - } - } - return str.toLowerCase().indexOf(needle) !== -1; -} - -module.exports = nodeMatchesText; diff --git a/frontend/provideStore.js b/frontend/provideStore.js deleted file mode 100644 index 7884e719ee..0000000000 --- a/frontend/provideStore.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var PropTypes = require('prop-types'); -var React = require('react'); - -type Props = { - children: () => React.Node, - store: Object, -}; -module.exports = function(name: string): Object { - class Wrapper extends React.Component { - getChildContext() { - return {[name]: this.props.store}; - } - render() { - return this.props.children(); - } - } - Wrapper.childContextTypes = { - [name]: PropTypes.object, - }; - Wrapper.displayName = 'StoreProvider(' + name + ')'; - return Wrapper; -}; diff --git a/frontend/types.js b/frontend/types.js deleted file mode 100644 index d9aade5982..0000000000 --- a/frontend/types.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var {Record} = require('immutable'); - -export type Dir = 'up' | 'down' | 'left' | 'right'; -export type Dest = 'firstChild' | 'lastChild' | 'prevSibling' | 'nextSibling' | 'collapse' | 'uncollapse' | 'parent' | 'parentBottom' | 'selectTop'; - -export type ElementID = string; - -export type Window = { - frameElement: DOMNode | null, -}; - -export type Document = { - defaultView: Window | null, -}; - -export type DOMNode = { - appendChild: (child: DOMNode) => void, - childNodes: Array, - getBoundingClientRect: () => DOMRect, - innerHTML: string, - innerText: string, - nodeName: string, - nodeType: number, - offsetHeight: number, - offsetLeft: number, - offsetParent: ?DOMNode, - offsetTop: number, - offsetWidth: number, - onclick?: (evt: DOMEvent) => void, - parentNode: DOMNode, - removeChild: (child: DOMNode) => void, - removeListener: (evt: string, fn: () => void) => void, - selectionStart: number, - selectionEnd: number, - scrollLeft: number, - scrollTop: number, - style: Object, - textContent: string, - value: string, - ownerDocument: Document | null, -}; - -export type DOMEvent = { - cancelBubble: boolean, - key: string, - keyCode: number, - pageX: number, - pageY: number, - preventDefault: () => void, - stopPropagation: () => void, - target: DOMNode, -}; - -export type DOMRect = { - top: number, - left: number, - width: number, - height: number, - bottom: number, - right: number, -}; - -export type ControlState = { - enabled: boolean, -} & Record; - -/** - * A theme is a color template used throughout devtools. - * All devtools coloring is declared by themes, with one minor exception: status colors. - */ -export type Theme = { - displayName: string; // Display name (shown in PreferencesPanel) - - hidden?: boolean; // Special theme (eg Chrome or Firefox default) hidden from user in prefs panel - - /** - * Base colors should increase in light-to-dark (or dark-to-light) order. - * This is important for legibility/contrast because of how the colors are used. - */ - base00: string; // Default Background - base01: string; // Lighter Background (eg status bars) - base02: string; // Borders, Context Menu, etc. - base03: string; // Borders, Comments, etc. - base04: string; // Lighter Foreground - base05: string; // Default Foreground - - /** - * These colors are used to highlight specific parts of the UI. - * Typically they are used for syntax highlighting. - * Some have special one-off usage (eg invalid regex input highlight). - */ - special00: string; // Custom Coponents - special01: string; // Integers, Booleans, etc. - special02: string; // Strings, Arrays, etc. - special03: string; // Details Pane Text - special04: string; // Functions, Objects, etc. - special05: string; // Special Text (eg breadcrumbs) - special06: string; // XML Attributes - special07: string; // Host Components - - /** - * These colors are used for selection, hover, and filtering state. - */ - state00: string; // Focused Background - state01: string; // Blurred Background - state03: string; // Hovered Background - state02: string; // Focused Foreground - state04: string; // Search Background - state05: string; // Search Foreground - state06: string; // Interactive Hover -}; diff --git a/images/devtools-full.gif b/images/devtools-full.gif deleted file mode 100644 index fd7ed94938..0000000000 Binary files a/images/devtools-full.gif and /dev/null differ diff --git a/images/devtools-search-new.gif b/images/devtools-search-new.gif deleted file mode 100644 index af42c2d454..0000000000 Binary files a/images/devtools-search-new.gif and /dev/null differ diff --git a/images/devtools-side-pane.gif b/images/devtools-side-pane.gif deleted file mode 100644 index 381e3554ee..0000000000 Binary files a/images/devtools-side-pane.gif and /dev/null differ diff --git a/images/devtools-tree-view.png b/images/devtools-tree-view.png deleted file mode 100644 index 4c24a65b8b..0000000000 Binary files a/images/devtools-tree-view.png and /dev/null differ diff --git a/images/plain-shell.png b/images/plain-shell.png deleted file mode 100644 index 7a002de627..0000000000 Binary files a/images/plain-shell.png and /dev/null differ diff --git a/lerna.json b/lerna.json deleted file mode 100644 index 57a8396345..0000000000 --- a/lerna.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "lerna": "2.8.0", - "version": "3.4.1", - "npmClient": "yarn", - "useWorkspaces": true -} diff --git a/package.json b/package.json deleted file mode 100644 index c74be1b90d..0000000000 --- a/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "dependencies": { - "@babel/core": "^7.1.6", - "@babel/plugin-proposal-class-properties": "^7.1.0", - "@babel/plugin-transform-flow-strip-types": "^7.1.6", - "@babel/preset-env": "^7.1.6", - "@babel/preset-flow": "^7.0.0", - "@babel/preset-react": "^7.0.0", - "adm-zip": "^0.4.7", - "babel-core": "^7.0.0-bridge", - "babel-eslint": "6", - "babel-jest": "^23.6.0", - "babel-loader": "^8.0.4", - "child-process-promise": "^2.2.1", - "classnames": "2.2.1", - "cli-spinners": "^1.0.0", - "clipboard-js": "^0.3.3", - "es6-symbol": "3.0.2", - "eslint": "2.10.2", - "eslint-plugin-react": "5.1.1", - "fbjs": "0.5.1", - "fbjs-scripts": "0.7.0", - "flow-bin": "0.66.0", - "fs-extra": "^3.0.1", - "gh-pages": "^1.0.0", - "immutable": "3.7.6", - "jest": "22.1.4", - "log-update": "^2.0.0", - "lru-cache": "^4.1.3", - "memoize-one": "^3.1.1", - "node-libs-browser": "0.5.3", - "nullthrows": "^1.0.0", - "object-assign": "4.0.1", - "prop-types": "^15.6.1", - "react": "^16.5.2", - "react-color": "^2.11.7", - "react-dom": "^16.5.2", - "react-portal": "^3.1.0", - "react-virtualized-auto-sizer": "^1.0.2", - "react-window": "^1.1.1", - "semver": "^5.5.1", - "webpack": "^4.26.0", - "webpack-cli": "^3.1.2" - }, - "license": "BSD-3-Clause", - "repository": "facebook/react-devtools", - "private": true, - "workspaces": [ - "packages/*" - ], - "scripts": { - "build:extension": "yarn run build:extension:chrome && yarn run build:extension:firefox", - "build:extension:chrome": "node ./shells/chrome/build", - "build:extension:firefox": "node ./shells/firefox/build", - "build:standalone": "cd packages/react-devtools-core && yarn run build", - "build:example": "cd ./test/example && ./build.sh", - "deploy": "cd ./shells/theme-preview && ./build.sh && gh-pages -d .", - "lint": "eslint .", - "test": "jest", - "test:chrome": "node ./shells/chrome/test", - "test:firefox": "node ./shells/firefox/test", - "test:plain": "cd ./shells/plain && ./build.sh --watch", - "test:standalone": "cd packages/react-devtools && yarn start", - "typecheck": "flow check" - }, - "jest": { - "modulePathIgnorePatterns": [ - "/shells" - ] - }, - "devDependencies": { - "chrome-launch": "^1.1.4", - "firefox-profile": "^1.0.2", - "lerna": "^2.8.0", - "web-ext": "^1.10.1" - } -} diff --git a/packages/react-devtools-core/README.md b/packages/react-devtools-core/README.md deleted file mode 100644 index 16d85593bf..0000000000 --- a/packages/react-devtools-core/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# `react-devtools-core` - -A standalone React DevTools implementation. - -This is a low-level package. -**If you're looking for the Electron app you can run, use `react-devtools` package instead.** - -## Exports - -## `require('react-devtools-core').connectToDevTools(options)` - -This is similar to `require('react-devtools')` in another package but providing more control. -Unlike `require('react-devtools')`, it doesn't connect immediately, but exports a function. - -Run `connectToDevTools()` in the same context as React to set up a connection to DevTools. -Make sure this runs *before* any `react`, `react-dom`, or `react-native` imports. - -The `options` object may contain: - -* `host` (string), defaults to `'localhost'`. -* `port` (number), defaults to `8097`. -* `resolveRNStyle` (function), used by RN and `null` by default. - -None of the options are required. - -## `require('react-devtools-core/standalone')` - -Lets you render DevTools into a DOM node and have it listen to connections. - -For example: - -```js -require('react-devtools-core/standalone') - .setContentDOMNode(document.getElementById('container')) - .startServer(port); -``` - -You can check the Electron shell in `react-devtools` package for a complete integration example. diff --git a/packages/react-devtools-core/index.js b/packages/react-devtools-core/index.js deleted file mode 100644 index db4adcac3a..0000000000 --- a/packages/react-devtools-core/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./build/backend'); diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json deleted file mode 100644 index 363cf2089f..0000000000 --- a/packages/react-devtools-core/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "react-devtools-core", - "version": "3.4.3", - "description": "Use react-devtools outside of the browser", - "main": "./build/backend.js", - "repository": { - "url": "https://github.com/facebook/react-devtools.git", - "type": "git" - }, - "files": [ - "build", - "index.js", - "standalone.js", - "vendor" - ], - "scripts": { - "backend": "cross-env NODE_ENV=production ../../node_modules/.bin/webpack --config webpack.backend.js", - "backend:watch": "cross-env NODE_ENV=production ../../node_modules/.bin/webpack --config webpack.backend.js --watch", - "standalone": "cross-env NODE_ENV=production ../../node_modules/.bin/webpack --config webpack.standalone.js", - "standalone:watch": "cross-env NODE_ENV=production ../../node_modules/.bin/webpack --config webpack.standalone.js --watch", - "build": "yarn run backend && yarn run standalone", - "prepublish": "yarn run build" - }, - "author": "Jared Forsyth", - "license": "BSD-3-Clause", - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^3.3.1" - }, - "devDependencies": { - "cross-env": "^3.1.4" - } -} diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js deleted file mode 100644 index ce3afe7589..0000000000 --- a/packages/react-devtools-core/src/backend.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -type ConnectOptions = { - host?: string, - port?: number, - resolveRNStyle?: (style: number) => ?Object, - isAppActive?: () => boolean, - websocket?: ?WebSocket, -}; - -var Agent = require('../../../agent/Agent'); -var Bridge = require('../../../agent/Bridge'); -var ProfileCollector = require('../../../plugins/Profiler/ProfileCollector'); -var installGlobalHook = require('../../../backend/installGlobalHook'); -var inject = require('../../../agent/inject'); -var invariant = require('assert'); -var setupRNStyle = require('../../../plugins/ReactNativeStyle/setupBackend'); -var setupProfiler = require('../../../plugins/Profiler/backend'); - -installGlobalHook(window); - -if (window.document) { - // This shell is universal, and might be used inside a web app. - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('react-devtools', agent => { - var setupHighlighter = require('../../../frontend/Highlighter/setup'); - setupHighlighter(agent); - }); -} - -function connectToDevTools(options: ?ConnectOptions) { - var { - host = 'localhost', - port = 8097, - websocket, - resolveRNStyle = null, - isAppActive = () => true, - } = options || {}; - - function scheduleRetry() { - // Two seconds because RN had issues with too fast retries. - setTimeout(() => connectToDevTools(options), 2000); - } - - if (!isAppActive()) { - // If the app is in background, maybe retry later. - // Don't actually attempt to connect until we're in foreground. - scheduleRetry(); - return; - } - - var messageListeners = []; - var closeListeners = []; - var uri = 'ws://' + host + ':' + port; - // If existing websocket is passed, use it. - // This is necessary to support our custom integrations. - // See D6251744. - var ws = websocket ? websocket : new window.WebSocket(uri); - ws.onclose = handleClose; - ws.onerror = handleClose; - ws.onmessage = handleMessage; - ws.onopen = function() { - var wall = { - listen(fn) { - messageListeners.push(fn); - }, - onClose(fn) { - closeListeners.push(fn); - }, - send(data) { - ws.send(JSON.stringify(data)); - }, - }; - setupBackend(wall, resolveRNStyle); - }; - - var hasClosed = false; - function handleClose() { - if (!hasClosed) { - hasClosed = true; - scheduleRetry(); - closeListeners.forEach(fn => fn()); - } - } - - function handleMessage(evt) { - var data; - try { - invariant(typeof evt.data === 'string'); - data = JSON.parse(evt.data); - } catch (e) { - console.error('failed to parse json: ' + String(evt.data)); - return; - } - messageListeners.forEach(fn => { - try { - fn(data); - } catch (e) { - // jsc doesn't play so well with tracebacks that go into eval'd code, - // so the stack trace here will stop at the `eval()` call. Getting the - // message that caused the error is the best we can do for now. - console.log(data); - throw e; - } - }); - } -} - -function setupBackend(wall, resolveRNStyle) { - wall.onClose(() => { - if (agent) { - agent.emit('shutdown'); - } - // This appears necessary for plugin cleanup. - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.emit('shutdown'); - bridge = null; - agent = null; - console.log('closing devtools'); - }); - - var bridge = new Bridge(wall); - var agent = new Agent(window, { - rnStyle: !!resolveRNStyle, - rnStyleMeasure: !!resolveRNStyle, - }); - agent.addBridge(bridge); - - if (resolveRNStyle) { - setupRNStyle(bridge, agent, resolveRNStyle); - } - - setupProfiler(bridge, agent, window.__REACT_DEVTOOLS_GLOBAL_HOOK__); - - var _connectTimeout = setTimeout(() => { - console.warn('react-devtools agent got no connection'); - }, 20000); - - agent.once('connected', () => { - if (!agent) { - return; - } - inject(window.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent); - clearTimeout(_connectTimeout); - }); - - ProfileCollector.init(agent); -} - -module.exports = { connectToDevTools }; diff --git a/packages/react-devtools-core/src/launchEditor.js b/packages/react-devtools-core/src/launchEditor.js deleted file mode 100644 index d41a78dbe0..0000000000 --- a/packages/react-devtools-core/src/launchEditor.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var child_process = require('child_process'); -var shellQuote = require('shell-quote'); - -function isTerminalEditor(editor) { - switch (editor) { - case 'vim': - case 'emacs': - case 'nano': - return true; - } - return false; -} - -// Map from full process name to binary that starts the process -// We can't just re-use full process name, because it will spawn a new instance -// of the app every time -var COMMON_EDITORS = { - '/Applications/Atom.app/Contents/MacOS/Atom': 'atom', - '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta': - '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta', - '/Applications/Sublime Text.app/Contents/MacOS/Sublime Text': - '/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl', - '/Applications/Sublime Text 2.app/Contents/MacOS/Sublime Text 2': - '/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl', - '/Applications/Visual Studio Code.app/Contents/MacOS/Electron': 'code', -}; - -function getArgumentsForLineNumber(editor, filePath, lineNumber) { - switch (path.basename(editor)) { - case 'vim': - case 'mvim': - return [filePath, '+' + lineNumber]; - case 'atom': - case 'Atom': - case 'Atom Beta': - case 'subl': - case 'sublime': - case 'wstorm': - case 'appcode': - case 'charm': - case 'idea': - return [filePath + ':' + lineNumber]; - case 'joe': - case 'emacs': - case 'emacsclient': - return ['+' + lineNumber, filePath]; - case 'rmate': - case 'mate': - case 'mine': - return ['--line', lineNumber, filePath]; - case 'code': - return ['-g', filePath + ':' + lineNumber]; - } - - // For all others, drop the lineNumber until we have - // a mapping above, since providing the lineNumber incorrectly - // can result in errors or confusing behavior. - return [filePath]; -} - -function guessEditor() { - // Explicit config always wins - if (process.env.REACT_EDITOR) { - return shellQuote.parse(process.env.REACT_EDITOR); - } - - // Using `ps x` on OSX we can find out which editor is currently running. - // Potentially we could use similar technique for Windows and Linux - if (process.platform === 'darwin') { - try { - var output = child_process.execSync('ps x').toString(); - var processNames = Object.keys(COMMON_EDITORS); - for (var i = 0; i < processNames.length; i++) { - var processName = processNames[i]; - if (output.indexOf(processName) !== -1) { - return [COMMON_EDITORS[processName]]; - } - } - } catch (error) { - // Ignore... - } - } - - // Last resort, use old skool env vars - if (process.env.VISUAL) { - return [process.env.VISUAL]; - } else if (process.env.EDITOR) { - return [process.env.EDITOR]; - } - - return []; -} - -var _childProcess = null; -function launchEditor(maybeRelativePath, lineNumber, absoluteProjectRoots) { - // We use relative paths at Facebook we deterministic builds. - // This is why our internal tooling calls React DevTools with absoluteProjectRoots. - // If the filename is absolute then we don't need to care about this. - var filePath = [maybeRelativePath] - .concat( - absoluteProjectRoots.map(root => path.join(root, maybeRelativePath)) - ).find(combinedPath => - fs.existsSync(combinedPath) - ); - - if (!filePath) { - return; - } - - // Sanitize lineNumber to prevent malicious use on win32 - // via: https://github.com/nodejs/node/blob/c3bb4b1aa5e907d489619fb43d233c3336bfc03d/lib/child_process.js#L333 - if (lineNumber && isNaN(lineNumber)) { - return; - } - - var [editor, ...args] = guessEditor(); - if (!editor) { - return; - } - - if (lineNumber) { - args = args.concat(getArgumentsForLineNumber(editor, filePath, lineNumber)); - } else { - args.push(filePath); - } - - if (_childProcess && isTerminalEditor(editor)) { - // There's an existing editor process already and it's attached - // to the terminal, so go kill it. Otherwise two separate editor - // instances attach to the stdin/stdout which gets confusing. - _childProcess.kill('SIGKILL'); - } - - if (process.platform === 'win32') { - // On Windows, launch the editor in a shell because spawn can only - // launch .exe files. - _childProcess = child_process.spawn('cmd.exe', ['/C', editor].concat(args), {stdio: 'inherit'}); - } else { - _childProcess = child_process.spawn(editor, args, {stdio: 'inherit'}); - } - _childProcess.on('error', function() {}); - _childProcess.on('exit', function(errorCode) { - _childProcess = null; - }); -} - -module.exports = launchEditor; diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js deleted file mode 100644 index e89df7e073..0000000000 --- a/packages/react-devtools-core/src/standalone.js +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -var ws = require('ws'); -var fs = require('fs'); -var path = require('path'); - -var installGlobalHook = require('../../../backend/installGlobalHook'); -installGlobalHook(window); -var Panel = require('../../../frontend/Panel'); -var launchEditor = require('./launchEditor'); -var React = require('react'); -var ReactDOM = require('react-dom'); - -var node = null; -var onStatusChange = function noop() {}; -var projectRoots = []; -var wall = null; -var panel = null; - -var config = { - reload, - alreadyFoundReact: true, - showInspectButton: false, - showHiddenThemes: true, - inject(done) { - done(wall); - }, - showElementSource(source) { - launchEditor(source.fileName, source.lineNumber, projectRoots); - }, -}; - -var log = (...args) => console.log('[React DevTools]', ...args); -log.warn = (...args) => console.warn('[React DevTools]', ...args); -log.error = (...args) => console.error('[React DevTools]', ...args); - -function reload() { - ReactDOM.unmountComponentAtNode(node); - node.innerHTML = ''; - setTimeout(() => { - panel = ReactDOM.render(, node); - }, 100); -} - -function onDisconnected() { - panel = null; - ReactDOM.unmountComponentAtNode(node); - node.innerHTML = '

    Waiting for React to connect…

    '; -} - -function onError(e) { - panel = null; - ReactDOM.unmountComponentAtNode(node); - var message; - if (e.code === 'EADDRINUSE') { - message = 'Another instance of DevTools is running'; - } else { - message = `Unknown error (${e.message})`; - } - node.innerHTML = `

    ${message}

    `; -} - -function initialize(socket) { - var listeners = []; - socket.onmessage = (evt) => { - var data = JSON.parse(evt.data); - listeners.forEach((fn) => fn(data)); - }; - - wall = { - listen(fn) { - listeners.push(fn); - }, - send(data) { - if (socket.readyState === socket.OPEN) { - socket.send(JSON.stringify(data)); - } - }, - disconnect() { - socket.close(); - }, - }; - - log('Connected'); - reload(); -} - -var restartTimeout = null; - -function connectToSocket(socket) { - socket.onerror = (err) => { - onDisconnected(); - log.error('Error with websocket connection', err); - }; - socket.onclose = () => { - onDisconnected(); - log('Connection to RN closed'); - }; - initialize(socket); - - return { - close: function() { - onDisconnected(); - }, - }; -} - -function startServer(port = 8097) { - var httpServer = require('http').createServer(); - var server = new ws.Server({server: httpServer}); - var connected = false; - server.on('connection', (socket) => { - if (connected) { - connected.close(); - log.warn( - 'Only one connection allowed at a time.', - 'Closing the previous connection' - ); - } - connected = socket; - socket.onerror = (err) => { - connected = false; - onDisconnected(); - log.error('Error with websocket connection', err); - }; - socket.onclose = () => { - connected = false; - onDisconnected(); - log('Connection to RN closed'); - }; - initialize(socket); - }); - - server.on('error', (e) => { - onError(e); - log.error('Failed to start the DevTools server', e); - restartTimeout = setTimeout(() => startServer(port), 1000); - }); - - httpServer.on('request', (req, res) => { - // Serve a file that immediately sets up the connection. - var backendFile = fs.readFileSync( - path.join(__dirname, '../build/backend.js') - ); - res.end(backendFile + '\n;ReactDevToolsBackend.connectToDevTools();'); - }); - - httpServer.on('error', (e) => { - onError(e); - onStatusChange('Failed to start the server.'); - restartTimeout = setTimeout(() => startServer(port), 1000); - }); - - httpServer.listen(port, () => { - onStatusChange('The server is listening on the port ' + port + '.'); - }); - - return { - close: function() { - connected = false; - onDisconnected(); - clearTimeout(restartTimeout); - server.close(); - httpServer.close(); - }, - }; -} - -var DevtoolsUI = { - setContentDOMNode(_node) { - node = _node; - return DevtoolsUI; - }, - - setProjectRoots(_projectRoots) { - projectRoots = _projectRoots; - }, - - setStatusListener(_listener) { - onStatusChange = _listener; - return DevtoolsUI; - }, - - setDefaultThemeName(themeName) { - config.themeName = themeName; - if (panel) { - var {store} = panel.getChildContext(); - // Change default themeName if panel mounted - store.changeDefaultTheme(themeName); - } - return DevtoolsUI; - }, - - setBrowserName(name) { - config.browserName = name; - return DevtoolsUI; - }, - - startServer, - connectToSocket, -}; - -module.exports = DevtoolsUI; diff --git a/packages/react-devtools-core/standalone.js b/packages/react-devtools-core/standalone.js deleted file mode 100644 index 16a200e96c..0000000000 --- a/packages/react-devtools-core/standalone.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./build/standalone'); diff --git a/packages/react-devtools-core/webpack.backend.js b/packages/react-devtools-core/webpack.backend.js deleted file mode 100644 index cf61b82423..0000000000 --- a/packages/react-devtools-core/webpack.backend.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const {readFileSync} = require('fs'); -const {resolve} = require('path'); -const webpack = require('webpack'); - -const __DEV__ = process.env.NODE_ENV !== 'production'; - -module.exports = { - mode: __DEV__ ? 'development' : 'production', - devtool: __DEV__ ? 'source-map' : false, - entry: { - backend: './src/backend.js', - }, - output: { - path: __dirname + '/build', - filename: '[name].js', - }, - plugins: __DEV__ ? [] : [ - // Ensure we get production React - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': '"production"', - }), - ], - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), - }, - ], - }, -}; diff --git a/packages/react-devtools-core/webpack.standalone.js b/packages/react-devtools-core/webpack.standalone.js deleted file mode 100644 index 20dc77c131..0000000000 --- a/packages/react-devtools-core/webpack.standalone.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const {readFileSync} = require('fs'); -const {resolve} = require('path'); -const webpack = require('webpack'); - -const __DEV__ = process.env.NODE_ENV !== 'production'; - -module.exports = { - mode: __DEV__ ? 'development' : 'production', - devtool: __DEV__ ? 'source-map' : false, - target: 'electron-main', - externals: ['ws'], - entry: { - standalone: './src/standalone.js', - }, - output: { - path: __dirname + '/build', // eslint-disable-line no-path-concat - filename: '[name].js', - library: '[name]', - libraryTarget: 'commonjs2', - }, - plugins: __DEV__ ? [] : [ - // Ensure we get production React - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': '"production"', - }), - ], - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), - }, - ], - }, -}; diff --git a/packages/react-devtools-core/yarn.lock b/packages/react-devtools-core/yarn.lock deleted file mode 100644 index afb1440323..0000000000 --- a/packages/react-devtools-core/yarn.lock +++ /dev/null @@ -1,76 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -array-filter@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - -array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - -array-reduce@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - -cross-env@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.1.4.tgz#56e8bca96f17908a6eb1bc2012ca126f92842130" - dependencies: - cross-spawn "^3.0.1" - -cross-spawn@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - -isexe@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -lru-cache@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" - -pseudomap@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - -shell-quote@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - dependencies: - array-filter "~0.0.0" - array-map "~0.0.0" - array-reduce "~0.0.0" - jsonify "~0.0.0" - -ultron@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" - -which@^1.2.9: - version "1.2.12" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" - dependencies: - isexe "^1.1.1" - -ws@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-2.0.3.tgz#532fd499c3f7d7d720e543f1f807106cfc57d9cb" - dependencies: - ultron "~1.1.0" - -yallist@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.0.0.tgz#306c543835f09ee1a4cb23b7bce9ab341c91cdd4" diff --git a/packages/react-devtools/README.md b/packages/react-devtools/README.md deleted file mode 100644 index 66593d00f5..0000000000 --- a/packages/react-devtools/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# `react-devtools` - -If you need to debug a React page somewhere other than Chrome on desktop (a mobile browser, an embedded webview, Safari, etc), the `react-devtools` package is for you! It is also useful if your app is inside an iframe. - -It works both with React DOM and React Native. - -Screenshot of React DevTools running with React Native - -## Usage with React Native - -Install the `react-devtools` package globally: - -with yarn: - -``` -yarn global add react-devtools -``` - -or with npm: - -``` -npm install -g react-devtools -``` - -Now run `react-devtools` from the terminal to launch the standalone DevTools app: - -``` -react-devtools -``` - -If you're not in a simulator then you also need to run the following in a command prompt: - -``` -adb reverse tcp:8097 tcp:8097 -``` - -If you're using React Native 0.43 or higher, it should connect to your simulator within a few seconds. - -> Note: if you prefer to avoid global installations, you can add `react-devtools` as a project dependency. With Yarn, you can run `yarn add --dev react-devtools`, and then run `yarn react-devtools` from your project folder to open the DevTools. With npm, you can run `npm install --save-dev react-devtools`, add `"react-devtools": "react-devtools"` to the `scripts` section in your `package.json`, and then run `npm run react-devtools` from your project folder to open the DevTools. - -### Integration with React Native Inspector - -You can open the [in-app developer menu](https://facebook.github.io/react-native/docs/debugging.html#accessing-the-in-app-developer-menu) and choose "Show Inspector". It will bring up an overlay that lets you tap on any UI element and see information about it: - -![React Native Inspector](http://i.imgur.com/ReFhREb.gif) - -However, when `react-devtools` is running, Inspector will enter a special collapsed mode, and instead use the DevTools as primary UI. In this mode, clicking on something in the simulator will bring up the relevant components in the DevTools: - -![React DevTools Inspector Integration](http://i.imgur.com/wVgV9RP.gif) - -You can choose "Hide Inspector" in the same menu to exit this mode. - -### Inspecting Component Instances - -When debugging JavaScript in Chrome, you can inspect the props and state of the React components in the browser console. - -First, follow the [instructions for debugging in Chrome](https://facebook.github.io/react-native/docs/debugging.html#chrome-developer-tools) to open the Chrome console. - -Make sure that the dropdown in the top left corner of the Chrome console says `debuggerWorker.js`. **This step is essential.** - -Then select a React component in React DevTools. There is a search box at the top that helps you find one by name. As soon as you select it, it will be available as `$r` in the Chrome console, letting you inspect its props, state, and instance properties. - -![React DevTools Chrome Console Integration](http://i.imgur.com/Cpvhs8i.gif) - - -## Usage with React DOM - -The standalone shell can also be useful with React DOM (for example, to debug apps in Safari, or inside an iframe). - -Install the `react-devtools` package globally: - -with yarn: -``` -yarn global add react-devtools -``` - -or with npm: - -``` -npm install -g react-devtools -``` - -Now run `react-devtools` from the terminal to launch the standalone DevTools app: - -``` -react-devtools -``` - -Finally, add `` as the very first ` -``` - -This will ensure the developer tools are connected. -**Don’t forget to remove it before deploying to production!** - ->Note: if you prefer to avoid global installations, you can add `react-devtools` as a project dependency. With Yarn, you can run `yarn add --dev react-devtools`, and then run `yarn react-devtools` from your project folder to open the DevTools. With npm, you can run `npm install --save-dev react-devtools`, add `"react-devtools": "react-devtools"` to the `scripts` section in your `package.json`, and then run `npm run react-devtools` from your project folder to open the DevTools. - ->If you install `react-devtools` as a project dependency, you may also replace the ` - - diff --git a/packages/react-devtools/app.js b/packages/react-devtools/app.js deleted file mode 100644 index 65f7b069ff..0000000000 --- a/packages/react-devtools/app.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -var app = require('electron').app; // Module to control application life. -var BrowserWindow = require('electron').BrowserWindow; // Module to create native browser window. -var path = require('path'); - -var mainWindow = null; -var argv = require('minimist')(process.argv.slice(2)); -var projectRoots = argv._; -var defaultThemeName = argv.theme; - -app.on('window-all-closed', function() { - app.quit(); -}); - -app.on('ready', function() { - // Create the browser window. - mainWindow = new BrowserWindow({width: 800, height: 600, icon: path.join(__dirname, 'icons/icon128.png')}); - - // and load the index.html of the app. - mainWindow.loadURL('file://' + __dirname + '/app.html'); // eslint-disable-line no-path-concat - mainWindow.webContents.executeJavaScript( - // We use this so that RN can keep relative JSX __source filenames - // but "click to open in editor" still works. js1 passes project roots - // as the argument to DevTools. - 'window.devtools.setProjectRoots(' + JSON.stringify(projectRoots) + ')' - ); - - if (defaultThemeName) { - mainWindow.webContents.executeJavaScript( - 'window.devtools.setDefaultThemeName(' + JSON.stringify(defaultThemeName) + ')' - ); - } - - // Emitted when the window is closed. - mainWindow.on('closed', function() { - mainWindow = null; - }); -}); diff --git a/packages/react-devtools/bin.js b/packages/react-devtools/bin.js deleted file mode 100755 index b1d06e4dea..0000000000 --- a/packages/react-devtools/bin.js +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env node -var electron = require('electron'); -var spawn = require('cross-spawn'); -var argv = process.argv.slice(2); -var pkg = require('./package.json'); -var updateNotifier = require('update-notifier'); - -// notify if there's an update -updateNotifier({pkg}).notify({defer: false}); - -var result = spawn.sync( - electron, - [require.resolve('./app')].concat(argv), - {stdio: 'ignore'} -); -process.exit(result.status); diff --git a/packages/react-devtools/icons/icon128.png b/packages/react-devtools/icons/icon128.png deleted file mode 100644 index b932794644..0000000000 Binary files a/packages/react-devtools/icons/icon128.png and /dev/null differ diff --git a/packages/react-devtools/index.js b/packages/react-devtools/index.js deleted file mode 100644 index 3f179259f0..0000000000 --- a/packages/react-devtools/index.js +++ /dev/null @@ -1,4 +0,0 @@ -var {connectToDevTools} = require('react-devtools-core'); -// Connect immediately with default options. -// If you need more control, use `react-devtools-core` directly instead of `react-devtools`. -connectToDevTools(); diff --git a/packages/react-devtools/package.json b/packages/react-devtools/package.json deleted file mode 100644 index 401556c0af..0000000000 --- a/packages/react-devtools/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "react-devtools", - "version": "3.4.3", - "description": "Use react-devtools outside of the browser", - "repository": { - "url": "https://github.com/facebook/react-devtools.git", - "type": "git" - }, - "bin": { - "react-devtools": "./bin.js" - }, - "files": [ - "bin.js", - "app.html", - "app.js", - "index.js", - "icons" - ], - "scripts": { - "start": "node bin.js" - }, - "author": "Jared Forsyth", - "license": "BSD-3-Clause", - "dependencies": { - "cross-spawn": "^5.0.1", - "electron": "^1.8.7", - "ip": "^1.1.4", - "minimist": "^1.2.0", - "react-devtools-core": "^3.4.3", - "update-notifier": "^2.1.0" - } -} diff --git a/plugins/Colorizer/ColorizerFrontendControl.js b/plugins/Colorizer/ColorizerFrontendControl.js deleted file mode 100644 index 77f170c13c..0000000000 --- a/plugins/Colorizer/ColorizerFrontendControl.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -var decorate = require('../../frontend/decorate'); -var SettingsCheckbox = require('../../frontend/SettingsCheckbox'); - -var Wrapped = decorate({ - listeners() { - return ['colorizerchange']; - }, - props(store) { - return { - state: store.colorizerState, - text: 'Highlight Search', - onChange: state => store.changeColorizer(state), - }; - }, -}, SettingsCheckbox); - -module.exports = Wrapped; diff --git a/plugins/DepGraph/DepGraph.js b/plugins/DepGraph/DepGraph.js deleted file mode 100644 index a6fbc539a1..0000000000 --- a/plugins/DepGraph/DepGraph.js +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @ xx flow unused at the moment - */ -'use strict'; - -var React = require('react'); -var decorate = require('../../frontend/decorate'); -var crawlChildren = require('./crawlChildren'); -var dagre = require('dagre'); -var {sansSerif} = require('../../Themes/Fonts'); - -class DepGraph extends React.Component { - constructor(props: Object) { - super(props); - this.state = {renderCount: 0}; - } - render() { - if (this.state.renderCount > 0) { - return ( - this.setState({renderCount: 0})} - onReload={() => this.setState({renderCount: this.state.renderCount + 1})} - /> - ); - } - return ; - } -} - -class DisplayDeps extends React.Component { - props: Object; - componentWillReceiveProps(nextProps) { - if (nextProps.selected !== this.props.selected) { - this.props.onClose(); - } - } - render() { - return ( -
    -
    - -
    -
    - - -
    -
    - ); - } -} - -class SvgGraph extends React.Component { - props: Object; - render() { - var graph = this.props.graph; - if (!graph) { - return No graph to display. Select something else; - } - var transform = 'translate(10, 10)'; - return ( - - - {graph.edges().map(n => { - var edge = graph.edge(n); - return ( - p.x + ',' + p.y).join(' ')} - fill="none" - stroke="orange" - strokeWidth="2" - /> - ); - })} - - - {graph.nodes().map(n => { - var node = graph.node(n); - return ( - - ); - })} - - - {graph.nodes().map(n => { - var node = graph.node(n); - return ( - {node.label + ' ' + node.count} - ); - })} - - - ); - } -} - -var styles = { - container: { - border: '1px solid red', - position: 'relative', - minWidth: 0, - minHeight: 0, - flex: 1, - }, - - scrollParent: { - overflow: 'auto', - top: 0, - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - textAlign: 'center', - }, - - rect: { - cursor: 'pointer', - }, - - svg: { - flexShrink: 0, - }, - - buttons: { - position: 'absolute', - bottom: 3, - right: 3, - }, -}; - -function dagrize(graph) { - var g = new dagre.graphlib.Graph(); - g.setGraph({ - nodesep: 20, - ranksep: 50, - }); - g.setDefaultEdgeLabel(() => ({})); - var hasNodes = false; - for (var nodeName in graph.nodes) { - hasNodes = true; - g.setNode(nodeName, { - label: nodeName, - count: graph.nodes[nodeName], - width: nodeName.length * 7 + 20, - height: 20, - }); - } - if (!hasNodes) { - return false; - } - - for (var edgeName in graph.edges) { - var parts = edgeName.split('\x1f'); - if (parts[0] === '$root') { - continue; - } - g.setEdge(parts[0], parts[1], {label: graph[edgeName]}); - } - - dagre.layout(g); - return g; -} - -var DepWrapper = decorate({ - listeners: () => ['selected'], - shouldUpdate(nextProps, props) { - return nextProps.renderCount !== props.renderCount; - }, - props(store) { - var graph = { - edges: {}, - nodes: {}, - }; - crawlChildren('$root', [store.selected], store._nodes, 0, graph); - return { - selected: store.selected, - graph: dagrize(graph), - onHover: name => store.hoverClass(name), - onClick: name => store.selectFirstOfClass(name), - }; - }, -}, DisplayDeps); - -module.exports = DepGraph; diff --git a/plugins/DepGraph/crawlChildren.js b/plugins/DepGraph/crawlChildren.js deleted file mode 100644 index bcc7a0586a..0000000000 --- a/plugins/DepGraph/crawlChildren.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var sep = '\x1f'; // separator -// var MAX_DEPTH = 100; - -function crawlChildren( - ptype: string, - children: Array, - nodes: Map>, - depth: number, - graph: Object, -) { - var descendents = []; - var keepCrawling = true; // depth < MAX_DEPTH; - children.forEach(cid => { - var child = nodes.get(cid); - if (!child) { - return; - } - var isCustom = child.get('nodeType') === 'Composite'; - if (isCustom) { - var name = child.get('name'); - if (!name) { - return; - } - if (!graph.nodes[name]) { - graph.nodes[name] = 1; - } else { - graph.nodes[name] += 1; - } - var key = ptype + sep + name; - if (graph.edges[key]) { - graph.edges[key] += 1; - } else { - graph.edges[key] = 1; - } - } - if (keepCrawling && name) { - var grandChildren = child.get('children'); - if (grandChildren && Array.isArray(grandChildren)) { - if (isCustom) { - crawlChildren(name, grandChildren, nodes, depth + 1, graph); - } else { - descendents = descendents.concat(grandChildren); - } - } - } - }); - - if (keepCrawling && descendents.length) { - crawlChildren(ptype, descendents, nodes, depth + 1, graph); - } -} - -module.exports = crawlChildren; diff --git a/plugins/Profiler/ProfileCollector.js b/plugins/Profiler/ProfileCollector.js deleted file mode 100644 index d802f08e76..0000000000 --- a/plugins/Profiler/ProfileCollector.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -type Agent = any; - -import type {Interaction, StoreSnapshot} from './ProfilerTypes'; - -const hasNativePerformanceNow = - typeof performance === 'object' && - typeof performance.now === 'function'; - -const now = hasNativePerformanceNow - ? () => performance.now() - : () => Date.now(); - -/** - * The Profiler UI displays the entire React tree, with timing info, for each commit. - * The frontend store only has the latest tree at any given time though, - * So the ProfileCollector stores snapshots of the immutable tree for each commit, - * Along with timing information for nodes that were updated in that commit. - * This information is saved in the ProfilerStore. - */ -class ProfileCollector { - _agent: Agent; - _committedNodes: Set = new Set(); - _isRecording: boolean = false; - _maxActualDuration: number = 0; - _recordingStartTime: number = 0; - - constructor(agent: Agent) { - this._agent = agent; - - agent.on('isRecording', this._onIsRecording); - agent.on('mount', this._onMountOrUpdate); - agent.on('rootCommitted', this._onRootCommitted); - agent.on('unmount', this._onUnmount); - agent.on('update', this._onMountOrUpdate); - } - - _takeCommitSnapshotForRoot(id: string, data: any) { - const interactionsArray = data.memoizedInteractions != null - ? Array.from(data.memoizedInteractions) - : []; - - // Map interaction start times to when we started profiling. - // We clone (rather than mutate) the interactions in stateNode.memoizedInteractions, - // Because we don't want to affect user code that might be consuming these Interactions via Profiler. - const memoizedInteractions = interactionsArray.map(({name, timestamp}: Interaction) => ({ - name, - timestamp: timestamp - this._recordingStartTime, - })); - - const storeSnapshot: StoreSnapshot = { - memoizedInteractions, - committedNodes: Array.from(this._committedNodes), - commitTime: now() - this._recordingStartTime, - duration: this._maxActualDuration, - root: id, - }; - - this._agent.emit('storeSnapshot', storeSnapshot); - } - - _onIsRecording = isRecording => { - this._committedNodes = new Set(); - this._isRecording = isRecording; - this._recordingStartTime = isRecording ? now() : 0; - - if (isRecording) { - // Maybe in the future, we'll allow collecting multiple profiles and stepping through them. - // For now, clear old snapshots when we start recording new data though. - this._agent.emit('clearSnapshots'); - - // Note that the Profiler doesn't need to do anything to turn profiling on in React. - // Profiling-capable builds automatically profile all roots when DevTools is detected. - } - }; - - _onMountOrUpdate = (data: any) => { - if (!this._isRecording || data.actualDuration === undefined) { - return; - } - - this._committedNodes.add(data.id); - this._maxActualDuration = Math.max(this._maxActualDuration, data.actualDuration); - }; - - _onRootCommitted = (id: string, internalInstance: any, data: any) => { - if (!this._isRecording) { - return; - } - - // Once all roots have been committed, - // Take a snapshot of the current tree. - this._takeCommitSnapshotForRoot(id, data); - - // Then reset data for the next snapshot. - this._committedNodes = new Set(); - this._maxActualDuration = 0; - } - - _onUnmount = (id: string) => { - this._committedNodes.delete(id); - }; -} - -function init(agent: Agent): ProfileCollector { - return new ProfileCollector(agent); -} - -module.exports = { - init, -}; diff --git a/plugins/Profiler/ProfilerPlugin.js b/plugins/Profiler/ProfilerPlugin.js deleted file mode 100644 index 3dc5e2163d..0000000000 --- a/plugins/Profiler/ProfilerPlugin.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type Bridge from '../../agent/Bridge'; -import type Store from '../../frontend/Store'; - -const React = require('react'); -const provideStore = require('../../frontend/provideStore'); -const ProfilerStore = require('./ProfilerStore'); -const ProfilerTab = require('./views/ProfilerTab').default; -const StoreWrapper = provideStore('profilerStore'); - -class ProfilerPlugin { - bridge: Bridge; - profilingIsSupported: boolean; - store: Store; - profilerStore: ProfilerStore; - - constructor(store: Store, bridge: Bridge, refresh: () => void) { - this.bridge = bridge; - this.store = store; - this.profilingIsSupported = false; - this.profilerStore = new ProfilerStore(bridge, store); - - // The Profiler backend will notify us if/when it detects a profiling capable React build. - // This is an async check, because roots may not have been registered yet at this time. - bridge.onCall('profiler:update', (profilingIsSupported: boolean) => { - if (this.profilingIsSupported !== profilingIsSupported) { - this.profilingIsSupported = profilingIsSupported; - refresh(); - } - }); - } - - panes(): Array<(node: Object, id: string) => React$Element> { - return []; - } - - teardown() { - } - - tabs(): ?{[key: string]: () => React$Element} { - if (!this.profilingIsSupported) { - return null; - } - - return { - Profiler: () => ( - - {() => } - - ), - }; - } -} - -module.exports = ProfilerPlugin; diff --git a/plugins/Profiler/ProfilerStore.js b/plugins/Profiler/ProfilerStore.js deleted file mode 100644 index 818ff64548..0000000000 --- a/plugins/Profiler/ProfilerStore.js +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type Bridge from '../../agent/Bridge'; -import type {ChartType, Interaction, RootProfilerData, Snapshot} from './ProfilerTypes'; - -const {List} = require('immutable'); -const {EventEmitter} = require('events'); -const {get, set} = require('../../utils/storage'); -const LRU = require('lru-cache'); - -const LOCAL_STORAGE_COMMIT_THRESHOLD = 'profiler:commitThreshold'; -const LOCAL_STORAGE_HIDE_COMMITS_BELOW_THRESHOLD = 'profiler:hideCommitsBelowThreshold'; -const LOCAL_STORAGE_SHOW_NATIVE_NODES_KEY = 'profiler:showNativeNodes'; - -class ProfilerStore extends EventEmitter { - _bridge: Bridge; - _mainStore: Object; - - cachedData = LRU(50); // Evict items from the cache after this number - commitThreshold: number = ((get(LOCAL_STORAGE_COMMIT_THRESHOLD, 0): any): number); - hideCommitsBelowThreshold: boolean = ((get(LOCAL_STORAGE_HIDE_COMMITS_BELOW_THRESHOLD, false): any): boolean); - isRecording: boolean = false; - isSettingsPanelActive: boolean = false; - processedInteractions: {[id: string]: Interaction} = {}; - rootsToProfilerData: Map = new Map(); - roots: List = new List(); - selectedChartType: ChartType = 'flamegraph'; - selectedRoot: string | null = null; - showNativeNodes: boolean = ((get(LOCAL_STORAGE_SHOW_NATIVE_NODES_KEY, false): any): boolean); - - constructor(bridge: Bridge, mainStore: Object) { - super(); - - this._bridge = bridge; - this._mainStore = mainStore; - this._mainStore.on('clearSnapshots', this.clearSnapshots); - this._mainStore.on('roots', this.saveRoots); - this._mainStore.on('selected', this.updateSelected); - this._mainStore.on('storeSnapshot', this.storeSnapshot); - } - - off() { - // Noop - } - - cacheDataForSnapshot(snapshotIndex: number, snapshotRootID: string, key: string, data: any): void { - this.cachedData.set(`${snapshotIndex}-${snapshotRootID}-${key}`, data); - } - - cacheInteractionData(rootID: string, data: any): void { - this.cachedData.set(`${rootID}-interactions`, data); - } - - clearSnapshots = () => { - this.cachedData.reset(); - this.processedInteractions = {}; - this.rootsToProfilerData = new Map(); - this.emit('profilerData', this.rootsToProfilerData); - }; - - getCachedDataForSnapshot(snapshotIndex: number, snapshotRootID: string, key: string): any { - return this.cachedData.get(`${snapshotIndex}-${snapshotRootID}-${key}`) || null; - } - - getCachedInteractionData(rootID: string): any { - return this.cachedData.get(`${rootID}-interactions`) || null; - } - - processInteraction(interaction: Interaction): Interaction { - const key = `${interaction.name} at ${interaction.timestamp}`; - if (this.processedInteractions.hasOwnProperty(key)) { - return this.processedInteractions[key]; - } else { - this.processedInteractions[key] = interaction; - return interaction; - } - } - - saveRoots = () => { - this.roots = this._mainStore.roots; - this.emit('roots', this._mainStore.roots); - }; - - setCommitThrehsold = (commitThreshold: number) => { - this.commitThreshold = commitThreshold; - this.emit('commitThreshold', commitThreshold); - set(LOCAL_STORAGE_COMMIT_THRESHOLD, commitThreshold); - }; - - setHideCommitsBelowThreshold(hideCommitsBelowThreshold: boolean): void { - this.hideCommitsBelowThreshold = hideCommitsBelowThreshold; - this.emit('hideCommitsBelowThreshold', hideCommitsBelowThreshold); - set(LOCAL_STORAGE_HIDE_COMMITS_BELOW_THRESHOLD, hideCommitsBelowThreshold); - } - - setIsRecording(isRecording: boolean): void { - this.isRecording = isRecording; - this.emit('isRecording', isRecording); - this._mainStore.setIsRecording(isRecording); - } - - setIsSettingsPanelActive(isSettingsPanelActive: boolean): void { - this.isSettingsPanelActive = isSettingsPanelActive; - this.emit('isSettingsPanelActive', isSettingsPanelActive); - } - - setSelectedChartType(selectedChartType: ChartType) { - this.selectedChartType = selectedChartType; - this.emit('selectedChartType', selectedChartType); - } - - setShowNativeNodes(showNativeNodes: boolean) { - this.showNativeNodes = showNativeNodes; - this.emit('showNativeNodes', showNativeNodes); - set(LOCAL_STORAGE_SHOW_NATIVE_NODES_KEY, showNativeNodes); - } - - storeSnapshot = () => { - this._mainStore.snapshotQueue.forEach((snapshot: Snapshot) => { - const { root } = snapshot; - if (!this.rootsToProfilerData.has(root)) { - this.rootsToProfilerData.set(root, { - interactionsToSnapshots: new Map(), - snapshots: [], - timestampsToInteractions: new Map(), - }); - } - - const {interactionsToSnapshots, snapshots, timestampsToInteractions} = - ((this.rootsToProfilerData.get(root): any): RootProfilerData); - - snapshots.push(snapshot); - - // Restore Interaction instance equality between commits, - // Since this will be lost due to Bridge serialization. - snapshot.memoizedInteractions = snapshot.memoizedInteractions.map( - (interaction: Interaction) => this.processInteraction(interaction) - ); - - snapshot.memoizedInteractions.forEach((interaction: Interaction) => { - if (interactionsToSnapshots.has(interaction)) { - ((interactionsToSnapshots.get(interaction): any): Set).add(snapshot); - } else { - interactionsToSnapshots.set(interaction, new Set([snapshot])); - } - - if (timestampsToInteractions.has(interaction.timestamp)) { - ((timestampsToInteractions.get(interaction.timestamp): any): Set).add(interaction); - } else { - timestampsToInteractions.set(interaction.timestamp, new Set([interaction])); - } - }); - }); - - // Clear the queue once we've processed it. - this._mainStore.snapshotQueue.length = 0; - - this.emit('profilerData', this.rootsToProfilerData); - }; - - updateSelected = () => { - let currentID = this._mainStore.selected; - - while (true) { - const parentID = this._mainStore.getParent(currentID); - if (parentID != null) { - currentID = parentID; - } else { - break; - } - } - - this.selectedRoot = currentID; - this.emit('selectedRoot', this.selectedRoot); - }; -} - -module.exports = ProfilerStore; diff --git a/plugins/Profiler/ProfilerTypes.js b/plugins/Profiler/ProfilerTypes.js deleted file mode 100644 index c6cd20edea..0000000000 --- a/plugins/Profiler/ProfilerTypes.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -const {Map} = require('immutable'); - -export type ChartType = 'flamegraph' | 'interactions' | 'ranked'; - -export type CacheDataForSnapshot = ( - snapshotIndex: number, - snapshotRootID: string, - key: string, - data: any, -) => void; - -export type CacheInteractionData = ( - rootID: string, - data: any, -) => void; - -export type GetCachedDataForSnapshot = ( - snapshotIndex: number, - snapshotRootID: string, - key: string, -) => any | null; - -export type GetCachedInteractionData = ( - rootID: string, -) => any | null; - -export type Interaction = {| - name: string, - timestamp: number, -|}; - -export type RootProfilerData = {| - interactionsToSnapshots: Map>, - snapshots: Array, - timestampsToInteractions: Map>, -|}; - -export type Snapshot = {| - committedNodes: Array, - commitTime: number, - duration: number, - memoizedInteractions: Array, - nodes: Map, - root: string, -|}; - -export type StoreSnapshot = {| - committedNodes: Array, - commitTime: number, - duration: number, - memoizedInteractions: Array, - root: string, -|}; diff --git a/plugins/Profiler/backend.js b/plugins/Profiler/backend.js deleted file mode 100644 index 5d5756e4b7..0000000000 --- a/plugins/Profiler/backend.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type Bridge from '../../agent/Bridge'; -import type Agent from '../../agent/Agent'; - -const emptyFunction = () => {}; - -module.exports = (bridge: Bridge, agent: Agent, hook: Object) => { - const checkIfProfilingIsSupported = () => { - let profilingIsSupported = false; - - // Feature detection for profiling mode. - // The presence of an "treeBaseDuration" field signifies: - // 1) This is a new enough version of React (e.g. > 16.4 which was the initial profiling release) - // 2) This is a profiling capable bundle (e.g. DEV or PROFILING) - agent.roots.forEach((rootId: string) => { - const root = agent.internalInstancesById.get(rootId); - if ((root: any).hasOwnProperty('treeBaseDuration')) { - profilingIsSupported = true; - } - }); - - bridge.call('profiler:update', [profilingIsSupported], emptyFunction); - }; - - // Wait for roots to be registered. - // They might not yet exist at the time the plugin is initialized. - // Also while the first root(s) may not be capable of profiling, later ones might. - agent.on('root', checkIfProfilingIsSupported); - agent.on('rootUnmounted', checkIfProfilingIsSupported); - - // Check once in case some roots have already been registered: - checkIfProfilingIsSupported(); -}; diff --git a/plugins/Profiler/views/ChartNode.js b/plugins/Profiler/views/ChartNode.js deleted file mode 100644 index 7c73528a72..0000000000 --- a/plugins/Profiler/views/ChartNode.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../../../frontend/types'; - -import React from 'react'; -import { textHeight } from './constants'; - -type Props = {| - color: string, - height: number, - isDimmed?: boolean, - label: string, - onClick: Function, - onDoubleClick?: Function, - placeLabelAboveNode?: boolean, - theme: Theme, - title: string, - width: number, - x: number, - y: number, -|}; - -const minWidthToDisplay = 35; - -const ChartNode = ({ - color, - height, - isDimmed = false, - label, - onClick, - onDoubleClick, - theme, - title, - width, - x, - y, -}: Props) => ( - - {title} - - {width >= minWidthToDisplay && ( - -
    - {label} -
    -
    - )} -
    -); - -const ChartAnimatedNode = { - transition: 'all ease-in-out 250ms', -}; - -const ChartRect = (theme: Theme, isDimmed: boolean) => ({ - cursor: 'pointer', - opacity: isDimmed ? 0.5 : 1, - stroke: theme.base00, - ...ChartAnimatedNode, -}); - -const ChartLabel = { - pointerEvents: 'none', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - overflow: 'hidden', - fontSize: '12px', - fontFamily: 'sans-serif', - marginLeft: '4px', - marginRight: '4px', - lineHeight: '1.5', - padding: '0 0 0', - fontWeight: '400', - color: 'black', - textAlign: 'left', - ...ChartAnimatedNode, -}; - -export default ChartNode; diff --git a/plugins/Profiler/views/FiberRenderDurations.js b/plugins/Profiler/views/FiberRenderDurations.js deleted file mode 100644 index 5751befe5f..0000000000 --- a/plugins/Profiler/views/FiberRenderDurations.js +++ /dev/null @@ -1,256 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Snapshot} from '../ProfilerTypes'; -import type {Theme} from '../../../frontend/types'; - -import memoize from 'memoize-one'; -import React, { PureComponent } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { FixedSizeList as List } from 'react-window'; -import ChartNode from './ChartNode'; -import { getFilteredSnapshotData, getGradientColor, minBarHeight, minBarWidth, scale } from './constants'; -import NoRenderTimesMessage from './NoRenderTimesMessage'; - -type Node = {| - maxCommitValue: number, - parentSnapshot: Snapshot, - value: number, -|}; - -type ChartData = {| - itemSize: number, - maxValue: number, - nodes: Array, -|}; - -type ItemData = {| - height: number, - nodes: Array, - scaleY: (value: number, fallbackValue: number) => number, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - stopInspecting: Function, - theme: Theme, -|}; - -type SelectSnapshot = (snapshot: Snapshot) => void; - -type Props = {| - commitThreshold: number, - hideCommitsBelowThreshold: boolean, - selectedFiberID: string, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - snapshotIndex: number, - snapshots: Array, - stopInspecting: Function, - theme: Theme, -|}; - -export default ({ - commitThreshold, - hideCommitsBelowThreshold, - selectedFiberID, - selectedSnapshot, - selectSnapshot, - snapshotIndex, - snapshots, - stopInspecting, - theme, -}: Props) => { - const filteredData = getFilteredSnapshotData( - commitThreshold, - hideCommitsBelowThreshold, - true, // If we're viewing this component - selectedFiberID, - selectedSnapshot, - snapshotIndex, - snapshots, - ); - - return ( - - {({ height, width }) => ( - - )} - - ); -}; - -type RenderDurationsProps = {| - commitThreshold: number, - height: number, - hideCommitsBelowThreshold: boolean, - selectedFiberID: string, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - snapshots: Array, - stopInspecting: Function, - theme: Theme, - width: number, -|}; - -const RenderDurations = ({ - commitThreshold, - height, - hideCommitsBelowThreshold, - selectedFiberID, - selectedSnapshot, - selectSnapshot, - snapshots, - stopInspecting, - theme, - width, -}: RenderDurationsProps) => { - // getChartData() is memoized so it's okay to call them on every render. - const chartData = getChartData( - selectedFiberID, - snapshots, - width, - ); - - const { itemSize, maxValue, nodes } = chartData; - - if (maxValue === 0) { - return ( - - ); - } - - // Pass required contextual data down to the ListItem renderer. - // getItemData() is memoized so it's okay to call them on every render. - const itemData = getItemData( - height, - maxValue, - nodes, - selectedSnapshot, - selectSnapshot, - stopInspecting, - theme, - ); - - return ( - - {ListItem} - - ); -}; - -class ListItem extends PureComponent { - render() { - const { index, style } = this.props; - const itemData: ItemData = ((this.props.data: any): ItemData); - - const { height, nodes, scaleY, selectedSnapshot, selectSnapshot, stopInspecting, theme } = itemData; - - const node = nodes[index]; - const safeHeight = Math.max(minBarHeight, scaleY(node.value, minBarHeight)); - - // List items are absolutely positioned using the CSS "left" attribute. - // The "top" value will always be 0. - // Since the height is based on the node's duration, we can ignore it also. - const left = parseInt(style.left, 10); - const width = parseInt(style.width, 10); - - return ( - selectSnapshot(node.parentSnapshot)} - onDoubleClick={stopInspecting} - theme={theme} - title={`${node.value.toFixed(3)}ms`} - width={width} - x={left} - y={height - safeHeight} - /> - ); - } -} - -const getChartData = memoize(( - nodeID: string, - snapshots: Array, - width: number -): ChartData => { - let maxValue = 0; - - const nodes: Array = snapshots - .filter((snapshot: Snapshot) => snapshot.committedNodes.indexOf(nodeID) >= 0) - .map((snapshot: Snapshot) => { - // Filter out Text nodes; they won't have durations. - const maxCommitValue = snapshot.committedNodes.reduce((reduced, currentNodeID) => - Math.max(reduced, snapshot.nodes.getIn([currentNodeID, 'actualDuration']) || 0), - 0 - ); - const value = snapshot.nodes.getIn([nodeID, 'actualDuration']); - - maxValue = Math.max(maxValue, value); - - return { maxCommitValue, parentSnapshot: snapshot, value }; - }); - - const itemSize = Math.max(minBarWidth, width / nodes.length); - - return { - itemSize, - maxValue, - nodes, - }; -}); - -const getItemData = memoize(( - height: number, - maxValue: number, - nodes: Array, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - stopInspecting: Function, - theme: Theme, -): ItemData => ({ - height, - nodes, - scaleY: scale(0, maxValue, 0, height), - selectedSnapshot, - selectSnapshot, - stopInspecting, - theme, -})); diff --git a/plugins/Profiler/views/IconButton.js b/plugins/Profiler/views/IconButton.js deleted file mode 100644 index 102c510614..0000000000 --- a/plugins/Profiler/views/IconButton.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import React from 'react'; -import Hoverable from '../../../frontend/Hoverable'; -import SvgIcon from '../../../frontend/SvgIcon'; - -const IconButton = Hoverable( - ({ disabled, icon, isActive = false, isHovered, isTransparent = false, onClick, onMouseEnter, onMouseLeave, style, theme, title }) => ( - - ) -); - -export default IconButton; diff --git a/plugins/Profiler/views/InteractionTimeline.js b/plugins/Profiler/views/InteractionTimeline.js deleted file mode 100644 index 4ead6fe0d0..0000000000 --- a/plugins/Profiler/views/InteractionTimeline.js +++ /dev/null @@ -1,353 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {CacheInteractionData, GetCachedInteractionData, Interaction, Snapshot} from '../ProfilerTypes'; -import type {Theme} from '../../../frontend/types'; - -import memoize from 'memoize-one'; -import React, { PureComponent } from 'react'; -import { FixedSizeList as List } from 'react-window'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import NoInteractionsMessage from './NoInteractionsMessage'; -import { getGradientColor, scale } from './constants'; - -const INTERACTION_SIZE = 4; -const ITEM_SIZE = 25; -const SNAPSHOT_SIZE = 10; - -type SelectInteraction = (interaction: Interaction) => void; - -type ChartItem = {| - interaction: Interaction, - lastSnapshotCommitTime: number, - snapshots: Array, -|}; - -type ChartData = {| - items: Array, - maxDuration: number, - stopTime: number, -|}; - -type ItemData = {| - chartData: ChartData, - labelColumnWidth: number, - graphColumnWidth: number, - scaleX: (value: number, fallbackValue: number) => number, - selectedInteraction: Interaction | null, - selectedSnapshot: Snapshot, - selectInteraction: SelectInteraction, - theme: Theme, -|}; - -type Props = {| - cacheInteractionData: CacheInteractionData, - getCachedInteractionData: GetCachedInteractionData, - hasMultipleRoots: boolean, - interactionsToSnapshots: Map>, - maxDuration: number, - selectedInteraction: Interaction | null, - selectedSnapshot: Snapshot, - selectInteraction: SelectInteraction, - theme: Theme, - timestampsToInteractions: Map>, -|}; - -const InteractionTimeline = ({ - cacheInteractionData, - getCachedInteractionData, - hasMultipleRoots, - interactionsToSnapshots, - maxDuration, - selectedInteraction, - selectedSnapshot, - selectInteraction, - theme, - timestampsToInteractions, -}: Props) => { - // Cache data in ProfilerStore so we only have to compute it the first time the interactions tab is shown - let chartData = getCachedInteractionData(selectedSnapshot.root); - if (chartData === null) { - chartData = getChartData(interactionsToSnapshots, maxDuration, timestampsToInteractions); - cacheInteractionData(selectedSnapshot.root, chartData); - } - - return ( - - {({ height, width }) => ( - - )} - - ); -}; - -type InteractionsListProps = {| - chartData: ChartData, - hasMultipleRoots: boolean, - height: number, - selectedInteraction: Interaction | null, - selectedSnapshot: Snapshot, - selectInteraction: SelectInteraction, - theme: Theme, - width: number, -|}; - -class InteractionsList extends PureComponent { - handleKeyDown = event => { - if (event.keyCode === UP_ARROW || event.keyCode === DOWN_ARROW) { - // Don't let the main Elements tab change root selection for keyboard arrows. - event.preventDefault(); - - const { - chartData, - selectedInteraction, - selectInteraction, - } = this.props; - - const items = chartData.items; - const index = items.findIndex(({ interaction }) => selectedInteraction === interaction); - - // Select a new interaction... - let newIndex = 0; - if (event.keyCode === UP_ARROW) { - newIndex = index > 0 - ? index - 1 - : items.length - 1; - } else { - newIndex = index < items.length - 1 - ? index + 1 - : 0; - } - - selectInteraction(items[newIndex].interaction); - } - }; - - render() { - const { - chartData, - hasMultipleRoots, - height, - selectedInteraction, - selectedSnapshot, - selectInteraction, - theme, - width, - } = this.props; - - // If a commit contains no interactions, display a fallback message. - if (chartData.items.length === 0) { - return ( - - ); - } - - // The following conversion methods are memoized, - // So it's okay to call them on every render. - const itemData = getItemData( - chartData, - selectedInteraction, - selectedSnapshot, - selectInteraction, - theme, - width, - ); - - return ( -
    - - {ListItem} - -
    - ); - } -} - -const UP_ARROW = 38; -const DOWN_ARROW = 40; - -type ListItemProps = {| - data: ItemData, - index: number, - style: Object, -|}; -type ListItemState = {| - isHovered: boolean, -|}; - -class ListItem extends PureComponent { - state = { - isHovered: false, - }; - - handleMouseEnter = () => this.setState({isHovered: true}); - handleMouseLeave = () => this.setState({isHovered: false}); - - render() { - const { data: itemData, index: itemIndex, style } = this.props; - const { isHovered } = this.state; - - const { chartData, labelColumnWidth, scaleX, selectedInteraction, selectedSnapshot, theme } = itemData; - const { items, maxDuration } = chartData; - - const item: ChartItem = items[itemIndex]; - const { interaction, lastSnapshotCommitTime } = item; - - return ( -
    itemData.selectInteraction(interaction)} - onMouseEnter={this.handleMouseEnter} - onMouseLeave={this.handleMouseLeave} - style={{ - ...style, - display: 'flex', - alignItems: 'center', - backgroundColor: isHovered ? theme.state03 : (selectedInteraction === interaction ? theme.base01 : 'transparent'), - borderBottom: `1px solid ${theme.base01}`, - cursor: 'pointer', - }} - > -
    - {interaction.name} -
    -
    - {item.snapshots.map((snapshot, snapshotIndex) => { - // Guard against commits with duration 0 - const percentage = Math.min(1, Math.max(0, snapshot.duration / maxDuration)) || 0; - - return ( -
    - ); - })} -
    - ); - } -} - -const getChartData = memoize(( - interactionsToSnapshots: Map>, - maxDuration: number, - timestampsToInteractions: Map>, -): ChartData => { - const items: Array = []; - let stopTime = Number.MIN_VALUE; - - // eslint-disable-next-line no-unused-vars - for (const [timestamp, interactions] of timestampsToInteractions) { - for (const interaction of interactions) { - const snapshots = Array.from(((interactionsToSnapshots.get(interaction): any): Set)); - const lastSnapshotCommitTime = snapshots[snapshots.length - 1].commitTime; - - stopTime = Math.max(stopTime, lastSnapshotCommitTime); - - items.push({ - interaction, - lastSnapshotCommitTime, - snapshots, - }); - } - } - - return { - items, - maxDuration, - stopTime, - }; -}); - -const getItemData = memoize(( - chartData: ChartData, - selectedInteraction: Interaction | null, - selectedSnapshot: Snapshot, - selectInteraction: SelectInteraction, - theme: Theme, - width: number, -): ItemData => { - const labelColumnWidth = Math.min(200, width / 5); - const graphColumnWidth = width - labelColumnWidth - SNAPSHOT_SIZE; - - return { - chartData, - graphColumnWidth, - labelColumnWidth, - scaleX: scale(0, chartData.stopTime, 0, graphColumnWidth), - selectedInteraction, - selectedSnapshot, - selectInteraction, - theme, - }; -}); - -export default InteractionTimeline; diff --git a/plugins/Profiler/views/NoInteractionsMessage.js b/plugins/Profiler/views/NoInteractionsMessage.js deleted file mode 100644 index fbc93ead53..0000000000 --- a/plugins/Profiler/views/NoInteractionsMessage.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import React from 'react'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; - -type Props = {| - hasMultipleRoots: boolean, - height: number, - width: number, -|}; - -export default ({ hasMultipleRoots, height, width }: Props) => { - let headerText; - if (hasMultipleRoots) { - headerText = 'No interactions were recorded for the selected root.'; - } else { - headerText = 'No interactions were recorded.'; - } - - return ( -
    -

    - {headerText} -

    - {hasMultipleRoots && ( -

    - You may want to select a different root in the Elements panel. -

    - )} -

    - - Learn more about the interaction tracking API here - . -

    -
    - ); -}; diff --git a/plugins/Profiler/views/NoProfilingDataMessage.js b/plugins/Profiler/views/NoProfilingDataMessage.js deleted file mode 100644 index d69cd56e70..0000000000 --- a/plugins/Profiler/views/NoProfilingDataMessage.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../../../frontend/types'; - -import React, {Fragment} from 'react'; -import Icons from '../../../frontend/Icons'; -import SvgIcon from '../../../frontend/SvgIcon'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; - -type Props = {| - hasMultipleRoots: boolean, - startRecording: Function, - theme: Theme, -|}; - -export default ({ hasMultipleRoots, startRecording, theme }: Props) => { - let buttonPreMessage; - let headerText; - if (hasMultipleRoots) { - buttonPreMessage = ( - - Select a different root in the Elements panel, or click the record button - - ); - headerText = 'No profiling data has been recorded for the selected root.'; - } else { - buttonPreMessage = ( - - Click the record button - - ); - headerText = 'No profiling data has been recorded.'; - } - - return ( -
    -

    - {headerText} -

    -

    - {buttonPreMessage} - - to start a new recording. -

    -
    - ); -}; diff --git a/plugins/Profiler/views/NoRenderTimesMessage.js b/plugins/Profiler/views/NoRenderTimesMessage.js deleted file mode 100644 index 77c391966e..0000000000 --- a/plugins/Profiler/views/NoRenderTimesMessage.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import React from 'react'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; - -type Props = {| - commitThreshold: number, - height: number, - hideCommitsBelowThreshold: boolean, - stopInspecting: Function, - width: number, -|}; - -export default ({ - commitThreshold, - height, - hideCommitsBelowThreshold, - stopInspecting, - width, -}: Props) => ( -
    - {!hideCommitsBelowThreshold && ( -

    - No render times were recorded for the selected element. -

    - )} - {hideCommitsBelowThreshold && ( -

    - No render times were recorded for the selected element based on the current commit threshold. -

    - )} -

    - -

    -
    -); diff --git a/plugins/Profiler/views/NoSnapshotDataMessage.js b/plugins/Profiler/views/NoSnapshotDataMessage.js deleted file mode 100644 index 9c0b3cdb78..0000000000 --- a/plugins/Profiler/views/NoSnapshotDataMessage.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import React from 'react'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; - -type Props = {| - height: number, - width: number, -|}; - -export default ({ height, width }: Props) => ( -
    -

    - There is no timing data to display for the currently selected commit. -

    -

    - This can indicate that a render occurred too quickly for the timing API to measure. - Try selecting another commit in the upper, right-hand corner. -

    -
    -); diff --git a/plugins/Profiler/views/ProfilerFiberDetailPane.js b/plugins/Profiler/views/ProfilerFiberDetailPane.js deleted file mode 100644 index a777e941ba..0000000000 --- a/plugins/Profiler/views/ProfilerFiberDetailPane.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../../../frontend/types'; -import type {Snapshot} from '../ProfilerTypes'; - -import React, {Fragment} from 'react'; -import {monospace} from '../../../frontend/Themes/Fonts'; -import DataView from '../../../frontend/DataView/DataView'; -import DetailPane from '../../../frontend/detail_pane/DetailPane'; -import DetailPaneSection from '../../../frontend/detail_pane/DetailPaneSection'; -import Icons from '../../../frontend/Icons'; -import IconButton from './IconButton'; - -const emptyFunction = () => {}; - -type Props = {| - deselectFiber: Function, - isInspectingSelectedFiber: boolean, - name?: string, - snapshot: Snapshot, - snapshotFiber: any, - theme: Theme, - toggleInspectingSelectedFiber: Function, -|}; - -const ProfilerFiberDetailPane = ({ - deselectFiber, - isInspectingSelectedFiber, - name = 'Unknown', - snapshot, - snapshotFiber, - theme, - toggleInspectingSelectedFiber, -}: Props) => ( - -
    -
    -
    - {name} -
    - - -
    -
    - {snapshotFiber !== null && ( -
    -
    - Total renders: {snapshotFiber.get('renders')} -
    - - - - - {snapshotFiber.get('state') && ( - - - - )} - -
    - )} -
    -); - -export default ProfilerFiberDetailPane; diff --git a/plugins/Profiler/views/ProfilerInteractionDetailPane.js b/plugins/Profiler/views/ProfilerInteractionDetailPane.js deleted file mode 100644 index 52b39b1ef6..0000000000 --- a/plugins/Profiler/views/ProfilerInteractionDetailPane.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../../../frontend/types'; -import type {Interaction, Snapshot} from '../ProfilerTypes'; - -import React, {Fragment} from 'react'; -import { getGradientColor, formatDuration, formatTime } from './constants'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; -import Hoverable from '../../../frontend/Hoverable'; - -type ViewSnapshot = (snapshot: Snapshot) => void; - -type Props = {| - interaction: Interaction, - maxDuration: number, - selectedSnapshot: Snapshot | null, - snapshots: Set, - theme: Theme, - viewSnapshot: ViewSnapshot, -|}; - -const ProfilerInteractionDetailPane = ({ - interaction, - maxDuration, - selectedSnapshot, - snapshots, - theme, - viewSnapshot, -}: Props) => { - let currentTimestamp = interaction.timestamp; - - return ( - -
    - {interaction.name} at {formatTime(interaction.timestamp)}s -
    -
    -
    - Commits: -
    -
      - {Array.from(snapshots).map((snapshot, index) => { - const previousTimestamp = currentTimestamp; - currentTimestamp = snapshot.commitTime; - - return ( - viewSnapshot(snapshot)} - previousTimestamp={previousTimestamp} - selectedSnapshot={selectedSnapshot} - snapshot={snapshot} - theme={theme} - viewSnapshot={viewSnapshot} - /> - ); - })} -
    -
    -
    - ); -}; - -const SnapshotLink = Hoverable(({ - isHovered, - maxDuration, - onClick, - onMouseEnter, - onMouseLeave, - previousTimestamp, - selectedSnapshot, - snapshot, - theme, - viewSnapshot, -}) => { - return ( -
  • -
    -
      -
    • - Timestamp: {formatTime(snapshot.commitTime)}s -
    • -
    • - Duration: {formatDuration(snapshot.duration)}ms -
    • -
    -
  • - ); -}); - -export default ProfilerInteractionDetailPane; diff --git a/plugins/Profiler/views/ProfilerSettings.js b/plugins/Profiler/views/ProfilerSettings.js deleted file mode 100644 index 74af7ff8f9..0000000000 --- a/plugins/Profiler/views/ProfilerSettings.js +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -import PropTypes from 'prop-types'; -import React, { PureComponent } from 'react'; -import decorate from '../../../frontend/decorate'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; - -type Props = {| - commitThreshold: number, - isSettingsPanelActive: boolean, - hideCommitsBelowThreshold: boolean, - setCommitThrehsold: (value: number) => void, - showNativeNodes: boolean, - toggleHideCommitsBelowThreshold: Function, - toggleIsSettingsPanelActive: Function, - toggleShowNativeNodes: Function, -|}; - -class ProfilerSettings extends PureComponent { - static contextTypes = { - theme: PropTypes.object.isRequired, - }; - - handleCommitThresholdChange = event => { - const { hideCommitsBelowThreshold, setCommitThrehsold, toggleHideCommitsBelowThreshold } = this.props; - - const commitThreshold = parseFloat(event.currentTarget.value); - if (!Number.isNaN(commitThreshold)) { - setCommitThrehsold(commitThreshold); - - // For convenience, enable the hide-commits feature if the threshold is being changed. - // This seems likely to be what the user wants. - if (!hideCommitsBelowThreshold) { - toggleHideCommitsBelowThreshold(); - } - } - }; - - stopClickEventFromBubbling = event => event.stopPropagation(); - - render() { - const { theme } = this.context; - const { - commitThreshold, - hideCommitsBelowThreshold, - isSettingsPanelActive, - toggleHideCommitsBelowThreshold, - toggleIsSettingsPanelActive, - toggleShowNativeNodes, - showNativeNodes, - } = this.props; - - if (!isSettingsPanelActive) { - return null; - } - - return ( -
    -
    -

    - Profiler settings -

    - - - - -
    -
    - ); - } -} - -export default decorate({ - store: 'profilerStore', - listeners: () => [ - 'commitThreshold', - 'hideCommitsBelowThreshold', - 'isSettingsPanelActive', - 'showNativeNodes', - ], - props(store) { - return { - commitThreshold: store.commitThreshold, - hideCommitsBelowThreshold: store.hideCommitsBelowThreshold, - isSettingsPanelActive: store.isSettingsPanelActive, - showNativeNodes: store.showNativeNodes, - setCommitThrehsold: store.setCommitThrehsold, - toggleHideCommitsBelowThreshold: () => store.setHideCommitsBelowThreshold(!store.hideCommitsBelowThreshold), - toggleIsSettingsPanelActive: () => store.setIsSettingsPanelActive(!store.isSettingsPanelActive), - toggleShowNativeNodes: () => store.setShowNativeNodes(!store.showNativeNodes), - }; - }, -}, ProfilerSettings); diff --git a/plugins/Profiler/views/ProfilerSnapshotDetailPane.js b/plugins/Profiler/views/ProfilerSnapshotDetailPane.js deleted file mode 100644 index 53e909d410..0000000000 --- a/plugins/Profiler/views/ProfilerSnapshotDetailPane.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../../../frontend/types'; -import type {Interaction, Snapshot} from '../ProfilerTypes'; - -import React, {Fragment} from 'react'; -import {formatDuration, formatTime} from './constants'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; -import Hoverable from '../../../frontend/Hoverable'; - -type Props = {| - selectedInteraction: Interaction | null, - snapshot: Snapshot, - theme: Theme, - viewInteraction: (interaction: Interaction) => void, -|}; - -const ProfilerSnapshotDetailPane = ({ - selectedInteraction, - snapshot, - theme, - viewInteraction, -}: Props) => ( - -
    - Commit information -
    -
    -
    Committed at: {formatTime(snapshot.commitTime)}s
    -
    Render duration: {formatDuration(snapshot.duration)}ms
    -
    Interactions:
    -
      - {snapshot.memoizedInteractions.length === 0 && ( -
    • - None -
    • - )} - {snapshot.memoizedInteractions.map((interaction, index) => ( - viewInteraction(interaction)} - selectedInteraction={selectedInteraction} - theme={theme} - /> - ))} -
    -
    -
    -); - -const InteractionLink = Hoverable(({ - interaction, - isHovered, - onClick, - onMouseEnter, - onMouseLeave, - selectedInteraction, - theme, -}) => ( -
  • - "{interaction.name}" at {formatTime(interaction.timestamp)}s -
  • -)); - -export default ProfilerSnapshotDetailPane; diff --git a/plugins/Profiler/views/ProfilerTab.js b/plugins/Profiler/views/ProfilerTab.js deleted file mode 100644 index 9e8ff9b32a..0000000000 --- a/plugins/Profiler/views/ProfilerTab.js +++ /dev/null @@ -1,481 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type { - CacheDataForSnapshot, - CacheInteractionData, - ChartType, - GetCachedDataForSnapshot, - GetCachedInteractionData, - Interaction, - RootProfilerData, - Snapshot, -} from '../ProfilerTypes'; - -import PropTypes from 'prop-types'; -import React from 'react'; -import decorate from '../../../frontend/decorate'; -import {sansSerif} from '../../../frontend/Themes/Fonts'; -import { getMaxDuration } from './constants'; -import FiberRenderDurations from './FiberRenderDurations'; -import InteractionTimeline from './InteractionTimeline'; -import NoProfilingDataMessage from './NoProfilingDataMessage'; -import SnapshotFlamegraph from './SnapshotFlamegraph'; -import SnapshotRanked from './SnapshotRanked'; -import ProfilerTabToolbar from './ProfilerTabToolbar'; -import ProfilerFiberDetailPane from './ProfilerFiberDetailPane'; -import ProfilerSnapshotDetailPane from './ProfilerSnapshotDetailPane'; -import ProfilerInteractionDetailPane from './ProfilerInteractionDetailPane'; -import ProfilerSettings from './ProfilerSettings'; - -type Props = {| - cacheDataForSnapshot: CacheDataForSnapshot, - cacheInteractionData: CacheInteractionData, - commitThreshold: number, - getCachedDataForSnapshot: GetCachedDataForSnapshot, - getCachedInteractionData: GetCachedInteractionData, - hasMultipleRoots: boolean, - hideCommitsBelowThreshold: boolean, - interactionsToSnapshots: Map>, - isRecording: boolean, - profilerData: RootProfilerData, - selectedChartType: ChartType, - selectedRootID: string | null, - setSelectedChartType: (chartType: ChartType) => void, - showNativeNodes: boolean, - snapshots: Array, - timestampsToInteractions: Map>, - toggleIsRecording: Function, - toggleIsSettingsPanelActive: Function, -|}; - -type State = {| - isInspectingSelectedFiber: boolean, - prevIsRecording: boolean, - prevSelectedChartType: ChartType, - prevShowNativeNodes: boolean, - selectedFiberID: string | null, - selectedFiberName: string | null, - selectedInteraction: Interaction | null, - snapshotIndex: number, -|}; - -class ProfilerTab extends React.Component { - static contextTypes = { - theme: PropTypes.object.isRequired, - }; - - state: State = { - isInspectingSelectedFiber: false, - prevIsRecording: this.props.isRecording, - prevSelectedChartType: this.props.selectedChartType, - prevShowNativeNodes: this.props.showNativeNodes, - selectedFiberID: null, - selectedFiberName: null, - selectedInteraction: null, - snapshotIndex: 0, - }; - - static getDerivedStateFromProps(props: Props, state: State): $Shape { - if (props.isRecording !== state.prevIsRecording) { - return { - isInspectingSelectedFiber: false, - prevIsRecording: props.isRecording, - selectedFiberID: null, - selectedFiberName: null, - selectedInteraction: null, - snapshotIndex: 0, - }; - } - if (props.selectedChartType !== state.prevSelectedChartType) { - return { - isInspectingSelectedFiber: false, - prevSelectedChartType: props.selectedChartType, - selectedFiberID: null, - selectedFiberName: null, - }; - } - if (props.showNativeNodes !== state.prevShowNativeNodes) { - return { - isInspectingSelectedFiber: false, - prevShowNativeNodes: props.showNativeNodes, - selectedFiberID: null, - selectedFiberName: null, - }; - } - return null; - } - - deselectFiber = () => - this.setState({ - selectedFiberID: null, - selectedFiberName: null, - }); - - - handleSnapshotSliderChange = (event: SyntheticEvent) => - this.setState({ snapshotIndex: parseInt(event.currentTarget.value, 10) }); - - inspectFiber = (id: string, name: string) => - this.setState({ - isInspectingSelectedFiber: true, - selectedFiberID: id, - selectedFiberName: name, - }); - - // We store the ID and name separately, - // Because a Fiber may not exist in all snapshots. - // In that case, it's still important to show the selected fiber (name) in the details pane. - selectFiber = (id: string, name: string) => - this.setState({ - selectedFiberID: id, - selectedFiberName: name, - }); - - selectInteraction = (interaction: Interaction) => - this.setState({ - selectedInteraction: interaction, - }); - - selectSnapshot = (snapshot: Snapshot) => - this.setState({ - snapshotIndex: this.props.snapshots.indexOf(snapshot), - }); - - stopInspecting = () => this.setState({ isInspectingSelectedFiber: false }); - - toggleInspectingSelectedFiber = () => this.setState(state => ({ - isInspectingSelectedFiber: !state.isInspectingSelectedFiber, - })); - - viewInteraction = (interaction: Interaction) => - this.setState({ - selectedInteraction: interaction, - }, () => this.props.setSelectedChartType('interactions')); - - viewSnapshot = (snapshot: Snapshot) => - this.setState({ - isInspectingSelectedFiber: false, - selectedFiberID: null, - selectedFiberName: null, - snapshotIndex: this.props.snapshots.indexOf(snapshot), - }, () => this.props.setSelectedChartType('flamegraph')); - - render() { - const { theme } = this.context; - const { - cacheDataForSnapshot, - cacheInteractionData, - commitThreshold, - getCachedDataForSnapshot, - getCachedInteractionData, - hasMultipleRoots, - hideCommitsBelowThreshold, - interactionsToSnapshots, - isRecording, - profilerData, - selectedChartType, - selectedRootID, - showNativeNodes, - snapshots, - timestampsToInteractions, - toggleIsRecording, - toggleIsSettingsPanelActive, - } = this.props; - const { - isInspectingSelectedFiber, - selectedFiberID, - selectedFiberName, - selectedInteraction, - snapshotIndex, - } = this.state; - - const snapshot = snapshots[snapshotIndex]; - const snapshotFiber = selectedFiberID && snapshot.nodes.get(selectedFiberID) || null; - const maxDuration = getMaxDuration(snapshots); - - let content; - if (isRecording) { - content = ( - - ); - } else if (selectedRootID === null || profilerData === null) { - // Edge case where keyboard up/down arrows change selected root in the Elements tab. - // This is a bug that should be fixed separately from the Profiler plug-in. - content = ( - - ); - } else if (snapshots.length > 0) { - if (isInspectingSelectedFiber && selectedFiberID !== null) { - content = ( - - ); - } else if (selectedChartType === 'interactions') { - content = ( - - ); - } else { - const ChartComponent = selectedChartType === 'ranked' - ? SnapshotRanked - : SnapshotFlamegraph; - - content = ( - - ); - } - } else { - content = ( - - ); - } - - let details; - if (isRecording || selectedRootID === null || profilerData === null) { - // Edge case where keyboard up/down arrows change selected root in the Elements tab. - // This is a bug that should be fixed separately from the Profiler plug-in. - details = ( - - ); - } else if ((selectedChartType === 'flamegraph' || selectedChartType === 'ranked') && selectedFiberName === null) { - details = ( - - ); - } else if (selectedChartType === 'interactions' && selectedInteraction !== null) { - details = ( - )} - theme={theme} - viewSnapshot={this.viewSnapshot} - /> - ); - } else if (selectedChartType !== 'interactions' && selectedFiberName !== null) { - details = ( - - ); - } else { - details = ( - - ); - } - - return ( -
    -
    -
    - -
    -
    - {content} - - -
    -
    -
    - {details} -
    -
    - ); - } -} - -const DetailsNoData = ({ theme }) => ( -
    - Nothing selected -
    -); - -const RecordingInProgress = ({stopRecording, theme}) => ( - - Recording profiling data... - - -); - -export default decorate({ - store: 'profilerStore', - listeners: () => [ - 'commitThreshold', - 'hideCommitsBelowThreshold', - 'isRecording', - 'profilerData', - 'selectedChartType', - 'selectedRoot', - 'showNativeNodes', - ], - props(store) { - const profilerData: RootProfilerData | null = - store.rootsToProfilerData.has(store.selectedRoot) - ? ((store.rootsToProfilerData.get(store.selectedRoot): any): RootProfilerData) - : null; - - return { - cacheDataForSnapshot: (...args) => store.cacheDataForSnapshot(...args), - cacheInteractionData: (...args) => store.cacheInteractionData(...args), - getCachedDataForSnapshot: (...args) => store.getCachedDataForSnapshot(...args), - getCachedInteractionData: (...args) => store.getCachedInteractionData(...args), - commitThreshold: store.commitThreshold, - hasMultipleRoots: store.roots.size > 1, - hideCommitsBelowThreshold: store.hideCommitsBelowThreshold, - interactionsToSnapshots: profilerData !== null - ? profilerData.interactionsToSnapshots - : new Map(), - isRecording: !!store.isRecording, - profilerData, - selectedChartType: store.selectedChartType, - setSelectedChartType: (chartType: ChartType) => store.setSelectedChartType(chartType), - showNativeNodes: store.showNativeNodes, - snapshots: profilerData !== null - ? profilerData.snapshots - : [], - timestampsToInteractions: profilerData !== null - ? profilerData.timestampsToInteractions - : new Map(), - toggleIsRecording: () => store.setIsRecording(!store.isRecording), - toggleIsSettingsPanelActive: () => store.setIsSettingsPanelActive(!store.isSettingsPanelActive), - }; - }, -}, ProfilerTab); diff --git a/plugins/Profiler/views/ProfilerTabToolbar.js b/plugins/Profiler/views/ProfilerTabToolbar.js deleted file mode 100644 index 5d9012ecf8..0000000000 --- a/plugins/Profiler/views/ProfilerTabToolbar.js +++ /dev/null @@ -1,292 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Theme} from '../../../frontend/types'; -import type {ChartType, Snapshot} from '../ProfilerTypes'; - -import React, { Fragment } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import Hoverable from '../../../frontend/Hoverable'; -import SvgIcon from '../../../frontend/SvgIcon'; -import Icons from '../../../frontend/Icons'; -import IconButton from './IconButton'; -import SnapshotSelector from './SnapshotSelector'; - -const CHART_RADIO_LABEL_WIDTH_THRESHOLD = 650; - -type SelectSnapshot = (snapshot: Snapshot) => void; - -type Props = {| - commitThreshold: number, - hideCommitsBelowThreshold: boolean, - interactionsCount: number, - isInspectingSelectedFiber: boolean, - isRecording: boolean, - selectChart: (chart: ChartType) => void, - selectedChartType: ChartType, - selectedFiberID: string | null, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - snapshotIndex: number, - snapshots: Array, - theme: Theme, - toggleIsRecording: Function, - toggleIsSettingsPanelActive: Function, -|}; - -export default (props: Props) => ( - - {({ width }) => ( - - )} - -); - -type ProfilerTabToolbarProps = { - commitThreshold: number, - hideCommitsBelowThreshold: boolean, - interactionsCount: number, - isInspectingSelectedFiber: boolean, - isRecording: boolean, - selectChart: (chart: ChartType) => void, - selectedChartType: ChartType, - selectedFiberID: string | null, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - snapshotIndex: number, - snapshots: Array, - theme: Theme, - toggleIsRecording: Function, - toggleIsSettingsPanelActive: Function, - width: number, -}; - -const ProfilerTabToolbar = ({ - commitThreshold, - hideCommitsBelowThreshold, - interactionsCount, - isInspectingSelectedFiber, - isRecording, - selectChart, - selectedChartType, - selectedFiberID, - selectedSnapshot, - selectSnapshot, - snapshotIndex, - snapshots, - theme, - toggleIsRecording, - toggleIsSettingsPanelActive, - width, -}: ProfilerTabToolbarProps) => ( -
    - - - - - {isRecording || snapshots.length === 0 && ( - -
    - - - - )} - - {!isRecording && snapshots.length > 0 && ( - - selectChart('flamegraph')} - theme={theme} - width={width} - /> -   - selectChart('ranked')} - theme={theme} - width={width} - /> -   - selectChart('interactions')} - theme={theme} - width={width} - /> - -
    - - - - {selectedChartType !== 'interactions' && ( - - - - - - )} - - )} -
    -); - -const HRule = ({ theme }) => ( -
    -); - -type RadioOptionProps = {| - icon: string, - isChecked: boolean, - isDisabled: boolean, - label: string, - isHovered: boolean, - onChange: Function, - onMouseEnter: Function, - onMouseLeave: Function, - theme: Theme, - width: number, -|}; - -const RadioOption = Hoverable(({ - icon, - isChecked, - isDisabled = false, - isHovered, - label, - onChange, - onMouseEnter, - onMouseLeave, - theme, - width, -}: RadioOptionProps) => ( - -)); - -const RecordButton = Hoverable( - ({ isActive, isHovered, onClick, onMouseEnter, onMouseLeave, theme }) => ( - - ) -); diff --git a/plugins/Profiler/views/SnapshotFlamegraph.js b/plugins/Profiler/views/SnapshotFlamegraph.js deleted file mode 100644 index dc6055a20a..0000000000 --- a/plugins/Profiler/views/SnapshotFlamegraph.js +++ /dev/null @@ -1,494 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {CacheDataForSnapshot, GetCachedDataForSnapshot, Snapshot} from '../ProfilerTypes'; -import type {Theme} from '../../../frontend/types'; - -import memoize from 'memoize-one'; -import React, { Fragment, PureComponent } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { FixedSizeList as List } from 'react-window'; -import ChartNode from './ChartNode'; -import NoSnapshotDataMessage from './NoSnapshotDataMessage'; -import { barHeight, barWidthThreshold, calculateSelfDuration, didNotRender, getGradientColor, scale } from './constants'; - -// Mapping of depth (i.e. List row index) to flame graph Nodes. -// Flamegraphs may contain a lot of data and pre-processing it all would be expensive. -// This mapping is lazily created, so that we can defer most of the work until a row is needed. -// Each row builds on the values of the previous row, (e.g. initial left offset, child Fiber ids, etc.) -type LazyIDToDepthMap = {[id: string]: number}; -type LazyIDToXMap = {[id: string]: number}; -type LazyIDsByDepth = Array>; -type IDToSelfDuration = {[id: string]: number}; - -type FlamegraphData = {| - // Number of rows in the flamegraph List. - flameGraphDepth: number, - // Lazily constructed map of id to the depth of the fiber within the flamegraph. - // This is used to quickly determine if a given row is "above" a focused Fiber, - // In which case it should be rendered differently (with a dim color). - lazyIDToDepthMap: LazyIDToDepthMap, - // Lazily constructed map of id to the x offset of the fiber within the flamegraph. - // Each fiber position in the flamegraph is relative to its parent. - // This mapp enables quick lookup of the x offset of the parent. - lazyIDToXMap: LazyIDToXMap, - // Lazily constructed array of ids per row within the flamegraph. - // This enables quick rendering of all fibers in a specific row. - lazyIDsByDepth: LazyIDsByDepth, - // Maximum self duration within the tree for this commit. - // Nodes are colored relatived to this value. - maxSelfDuration: number, - // Eagerly constructed map of id to actual duration excluding descendants. - // This value is used to determine the color of each node. - selfDurations: IDToSelfDuration, - // Native nodes (e.g. div, span) should be included in the flamegraph. - // If this value is false, these nodes are filtered out of the flamegraph view. - showNativeNodes: boolean, -|}; - -type SelectOrInspectFiber = (id: string, name: string) => void; - -// List-level data that's cached (memoized) and passed to individual item renderers. -type ItemData = {| - flamegraphData: FlamegraphData, - inspectFiber: SelectOrInspectFiber, - // Tree base time value for either the root fiber in the tree or the current selected fiber. - // This value determins the horizontal (time) scale, - // Which in turn determines which fibers are rendered in the flamegraph. - maxTreeBaseDuration: number, - // Scales horizontal values (left offset and width) based on the selected fiber's tree base time. - scaleX: (value: number, fallbackValue: number) => number, - selectedFiberID: string | null, - selectFiber: SelectOrInspectFiber, - snapshot: Snapshot, - theme: Theme, - width: number, -|}; - -type Props = {| - cacheDataForSnapshot: CacheDataForSnapshot, - deselectFiber: Function, - getCachedDataForSnapshot: GetCachedDataForSnapshot, - inspectFiber: SelectOrInspectFiber, - selectedFiberID: string | null, - selectFiber: SelectOrInspectFiber, - showNativeNodes: boolean, - snapshot: Snapshot, - snapshotIndex: number, - theme: Theme, -|}; - -const SnapshotFlamegraph = ({ - cacheDataForSnapshot, - deselectFiber, - getCachedDataForSnapshot, - inspectFiber, - selectedFiberID, - selectFiber, - showNativeNodes, - snapshot, - snapshotIndex, - theme, -}: Props) => { - // Cache data in ProfilerStore so we only have to compute it the first time a Snapshot is shown. - const dataKey = showNativeNodes ? 'SnapshotFlamegraphWithNativeNodes' : 'SnapshotFlamegraphWithoutNativeNodes'; - let flamegraphData = getCachedDataForSnapshot(snapshotIndex, snapshot.root, dataKey); - if (flamegraphData === null) { - flamegraphData = convertSnapshotToChartData(showNativeNodes, snapshot); - cacheDataForSnapshot(snapshotIndex, snapshot.root, dataKey, flamegraphData); - } - - return ( - - {({ height, width }) => ( - - )} - - ); -}; - -type FlamegraphProps = {| - deselectFiber: Function, - flamegraphData: FlamegraphData, - height: number, - inspectFiber: SelectOrInspectFiber, - selectedFiberID: string | null, - selectFiber: SelectOrInspectFiber, - showNativeNodes: boolean, - snapshot: Snapshot, - theme: Theme, - width: number, -|}; - -const Flamegraph = ({ - deselectFiber, - flamegraphData, - height, - inspectFiber, - selectedFiberID, - selectFiber, - showNativeNodes, - snapshot, - theme, - width, -}: FlamegraphProps) => { - const { flameGraphDepth, lazyIDToDepthMap, lazyIDsByDepth } = flamegraphData; - - // Initialize enough of the flamegraph to include the focused Fiber. - // Otherwise the horizontal scale will be off. - if (selectedFiberID !== null) { - let index = lazyIDsByDepth.length; - while (lazyIDToDepthMap[selectedFiberID] === undefined && index < flameGraphDepth) { - calculateFibersAtDepth(flamegraphData, index, snapshot); - index++; - } - } - - // Pass required contextual data down to the ListItem renderer. - // (This method is memoized so it's safe to call on every render.) - const itemData = getItemData( - flamegraphData, - inspectFiber, - selectedFiberID, - selectFiber, - snapshot, - theme, - width, - ); - - // If a commit is small and fast enough, it's possible for it to contain no base time values > 0. - // In this case, we could only display an empty graph. - if (flameGraphDepth === 0) { - return ; - } - - return ( -
    - - {ListItem} - -
    - ); -}; - -class ListItem extends PureComponent { - handleClick = (id, name, event) => { - event.stopPropagation(); - const itemData: ItemData = ((this.props.data: any): ItemData); - itemData.selectFiber(id, name); - }; - - handleDoubleClick = (id, name, event) => { - event.stopPropagation(); - const itemData: ItemData = ((this.props.data: any): ItemData); - itemData.inspectFiber(id, name); - }; - - render() { - const { index, style } = this.props; - const itemData: ItemData = ((this.props.data: any): ItemData); - - const { flamegraphData, scaleX, selectedFiberID, snapshot, width } = itemData; - const { lazyIDToDepthMap, lazyIDToXMap, maxSelfDuration, selfDurations } = flamegraphData; - const { committedNodes, nodes } = snapshot; - - // List items are absolutely positioned using the CSS "top" attribute. - // The "left" value will always be 0. - // Since height is fixed, and width is based on the node's duration, - // We can ignore those values as well. - const top = parseInt(style.top, 10); - - const ids = calculateFibersAtDepth(flamegraphData, index, snapshot); - - let focusedNodeIndex = 0; - let focusedNodeX = 0; - if (selectedFiberID !== null) { - focusedNodeIndex = lazyIDToDepthMap[selectedFiberID] || 0; - focusedNodeX = scaleX(lazyIDToXMap[selectedFiberID], 0) || 0; - } - - return ( - - {ids.map(id => { - const fiber = nodes.get(id); - const treeBaseDuration = fiber.get('treeBaseDuration'); - const nodeWidth = scaleX(treeBaseDuration, width); - - // Filter out nodes that are too small to see or click. - // This also helps render large trees faster. - if (nodeWidth < barWidthThreshold) { - return null; - } - - const nodeX = scaleX(lazyIDToXMap[id], 0); - - // Filter out nodes that are outside of the horizontal window. - if ( - nodeX + nodeWidth < focusedNodeX || - nodeX > focusedNodeX + width - ) { - return null; - } - - const actualDuration = fiber.get('actualDuration') || 0; - const selfDuration = selfDurations[id] || 0; - const name = fiber.get('name') || 'Unknown'; - const didRender = committedNodes.includes(id); - - let color = didNotRender; - let label = name; - if (didRender) { - color = getGradientColor(selfDuration / maxSelfDuration); - label = `${name} (${selfDuration.toFixed(1)}ms of ${actualDuration.toFixed(1)}ms)`; - } - - return ( - - ); - })} - - ); - } -} - -const convertSnapshotToChartData = ( - showNativeNodes: boolean, - snapshot: Snapshot, -): FlamegraphData => { - const [selfDurations, maxSelfDuration] = computeSelfDurations(snapshot, showNativeNodes); - - const flamegraphData: FlamegraphData = { - flameGraphDepth: calculateFlameGraphDepth(showNativeNodes, snapshot), - lazyIDToDepthMap: {}, - lazyIDToXMap: {}, - lazyIDsByDepth: [], - maxSelfDuration, - selfDurations, - showNativeNodes, - }; - - // Pre-calculate the first row in the List. - // Later calls to calculateFibersAtDepth() depend on this being initialized. - flamegraphData.lazyIDsByDepth[0] = calculateFibersAtDepthCrawler( - 0, - flamegraphData, - snapshot.root, - 0, - [], - snapshot, - ); - - return flamegraphData; -}; - -const calculateFlameGraphDepth = (showNativeNodes: boolean, snapshot: Snapshot): number => { - let maxDepth = 0; - - const walkTree = (nodeID: string, currentDepth: number = 0) => { - const nodeType = snapshot.nodes.getIn([nodeID, 'nodeType']); - - if (nodeType === undefined) { - return; - } else if (nodeType === 'Composite' || showNativeNodes && nodeType === 'Native') { - currentDepth++; - - maxDepth = Math.max(maxDepth, currentDepth); - } - - const children = snapshot.nodes.getIn([nodeID, 'children']); - if (Array.isArray(children)) { - children.forEach(childID => walkTree(childID, currentDepth)); - } else if (children != null) { - walkTree(children, currentDepth); - } - }; - - walkTree(snapshot.root); - - return maxDepth; -}; - -const getItemData = memoize(( - flamegraphData: FlamegraphData, - inspectFiber: SelectOrInspectFiber, - selectedFiberID: string | null, - selectFiber: SelectOrInspectFiber, - snapshot: Snapshot, - theme: Theme, - width: number, -): ItemData => { - const maxTreeBaseDuration = getMaxTreeBaseDuration(flamegraphData, selectedFiberID, snapshot); - return { - flamegraphData, - inspectFiber, - maxTreeBaseDuration, - scaleX: scale(0, maxTreeBaseDuration, 0, width), - selectedFiberID, - selectFiber, - snapshot, - theme, - width, - }; -}); - -const getMaxTreeBaseDuration = ( - flamegraphData: FlamegraphData, - selectedFiberID: string | null, - snapshot: Snapshot, -): number => { - const baseNodeID = flamegraphData.lazyIDsByDepth[0][0]; - - // If the selected fiber is in a different root, - // Just scale everything to the base node in this flamegraph. - // Even if the root matches, it's possible the Fiber won't be included in this commit though. - // (For example, it may have been removed.) - // In that case, we should still fallback to the base node time. - return snapshot.nodes.getIn([selectedFiberID, 'treeBaseDuration']) || snapshot.nodes.getIn([baseNodeID, 'treeBaseDuration']); -}; - -const computeSelfDurations = memoize((snapshot: Snapshot, showNativeNodes: boolean): [IDToSelfDuration, number] => { - const { committedNodes, nodes } = snapshot; - const selfDurations: IDToSelfDuration = {}; - - let maxSelfDuration = 0; - - committedNodes - .filter(nodeID => { - const nodeType = nodes.getIn([nodeID, 'nodeType']); - return (nodeType === 'Composite' || (nodeType === 'Native' && showNativeNodes)); - }) - .forEach(nodeID => { - const selfDuration = calculateSelfDuration(snapshot, nodeID); - - selfDurations[nodeID] = selfDuration; - maxSelfDuration = Math.max(maxSelfDuration, selfDuration); - }); - - return [selfDurations, maxSelfDuration]; -}); - -// This method depends on rows being initialized in-order. -const calculateFibersAtDepth = (flamegraphData: FlamegraphData, depth: number, snapshot: Snapshot): Array => { - const { lazyIDsByDepth, lazyIDToXMap } = flamegraphData; - - for (let index = lazyIDsByDepth.length; index <= depth; index++) { - const nodesAtPreviousDepth = lazyIDsByDepth[index - 1]; - lazyIDsByDepth[index] = nodesAtPreviousDepth.reduce( - (nodesAtDepth: Array, parentID: string) => - calculateFibersAtDepthCrawler( - index, - flamegraphData, - parentID, - lazyIDToXMap[parentID], - nodesAtDepth, - snapshot, - ), [] - ); - } - - return lazyIDsByDepth[depth]; -}; - -const calculateFibersAtDepthCrawler = ( - depth: number, - flamegraphData: FlamegraphData, - id: string, - leftOffset: number = 0, - nodesAtDepth: Array, - snapshot: Snapshot, -): Array => { - const { lazyIDToDepthMap, lazyIDToXMap, showNativeNodes } = flamegraphData; - const { nodes } = snapshot; - - const children = nodes.getIn([id, 'children']); - let childArray = null; - if (Array.isArray(children)) { - childArray = children; - } else if (children != null) { - childArray = [children]; - } - - if (childArray !== null) { - childArray.forEach(childID => { - const fiber = nodes.get(childID); - if (fiber === undefined) { - // Bailout on Text nodes - return; - } - - const nodeType = fiber.get('nodeType'); - - if (nodeType !== 'Composite' && (nodeType !== 'Native' || !showNativeNodes)) { - // Skip over native fibers if they are being filtered from the view - calculateFibersAtDepthCrawler( - depth, - flamegraphData, - childID, - leftOffset, - nodesAtDepth, - snapshot, - ); - } else { - const prevID = nodesAtDepth.length - ? nodesAtDepth[nodesAtDepth.length - 1] - : null; - const prevNodeTreeBaseDuration = nodes.getIn([prevID, 'treeBaseDuration']) || 0; - const prevNodeX = prevID !== null ? lazyIDToXMap[prevID] : 0; - - const x = Math.max(leftOffset, prevNodeX + prevNodeTreeBaseDuration); - - nodesAtDepth.push(childID); - - lazyIDToDepthMap[childID] = depth; - lazyIDToXMap[childID] = x; - } - }); - } - - return nodesAtDepth; -}; - -export default SnapshotFlamegraph; diff --git a/plugins/Profiler/views/SnapshotRanked.js b/plugins/Profiler/views/SnapshotRanked.js deleted file mode 100644 index 55edf64219..0000000000 --- a/plugins/Profiler/views/SnapshotRanked.js +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {CacheDataForSnapshot, GetCachedDataForSnapshot, Snapshot} from '../ProfilerTypes'; -import type {Theme} from '../../../frontend/types'; - -import memoize from 'memoize-one'; -import React, { PureComponent } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { FixedSizeList as List } from 'react-window'; -import ChartNode from './ChartNode'; -import NoSnapshotDataMessage from './NoSnapshotDataMessage'; -import { barHeight, calculateSelfDuration, getGradientColor, minBarWidth, scale } from './constants'; - -type Node = {| - id: any, - label: string, - name: string, - title: string, - value: number, -|}; - -type SelectOrInspectFiber = (id: string, name: string) => void; - -type ItemData = {| - focusedNode: Node, - focusedNodeIndex: number, - inspectFiber: SelectOrInspectFiber, - nodes: Array, - maxValue: number, - scaleX: (value: number, fallbackValue: number) => number, - selectFiber: SelectOrInspectFiber, - snapshot: Snapshot, - theme: Theme, - width: number, -|}; - -type Props = {| - cacheDataForSnapshot: CacheDataForSnapshot, - deselectFiber: Function, - getCachedDataForSnapshot: GetCachedDataForSnapshot, - inspectFiber: SelectOrInspectFiber, - selectedFiberID: string | null, - selectFiber: SelectOrInspectFiber, - showNativeNodes: boolean, - snapshot: Snapshot, - snapshotIndex: number, - theme: Theme, -|}; - -const SnapshotRanked = ({ - cacheDataForSnapshot, - deselectFiber, - getCachedDataForSnapshot, - inspectFiber, - selectedFiberID, - selectFiber, - showNativeNodes, - snapshot, - snapshotIndex, - theme, -}: Props) => { - // Cache data in ProfilerStore so we only have to compute it the first time a Snapshot is shown. - const dataKey = showNativeNodes ? 'SnapshotRankedDataWithNativeNodes' : 'SnapshotRankedDataWithoutNativeNodes'; - let rankedData = getCachedDataForSnapshot(snapshotIndex, snapshot.root, dataKey); - if (rankedData === null) { - rankedData = convertSnapshotToChartData(snapshot, showNativeNodes); - cacheDataForSnapshot(snapshotIndex, snapshot.root, dataKey, rankedData); - } - - return ( - - {({ height, width }) => ( - - )} - - ); -}; - -type RankedData = {| - maxValue: number, - nodes: Array, -|}; - -type SnapshotRankedInnerProps = {| - deselectFiber: Function, - height: number, - inspectFiber: SelectOrInspectFiber, - rankedData: RankedData, - selectedFiberID: string | null, - selectFiber: SelectOrInspectFiber, - snapshot: Snapshot, - theme: Theme, - width: number, -|}; - -const SnapshotRankedInner = ({ - deselectFiber, - height, - inspectFiber, - rankedData, - selectedFiberID, - selectFiber, - snapshot, - theme, - width, -}: SnapshotRankedInnerProps) => { - // If a commit contains no fibers with an actualDuration > 0, - // Display a fallback message. - if (rankedData.nodes.length === 0) { - return ; - } - - const focusedNodeIndex = getNodeIndex(rankedData, selectedFiberID); - - // The following conversion methods are memoized, - // So it's okay to call them on every render. - const itemData = getItemData( - focusedNodeIndex, - inspectFiber, - rankedData, - selectFiber, - snapshot, - theme, - width, - ); - - return ( -
    - - {ListItem} - -
    - ); -}; - -class ListItem extends PureComponent { - handleClick = event => { - event.stopPropagation(); - const { data, index } = this.props; - const node = data.nodes[index]; - data.selectFiber(node.id, node.name, data.snapshot.root); - }; - - handleDoubleClick = event => { - event.stopPropagation(); - const { data, index } = this.props; - const node = data.nodes[index]; - data.inspectFiber(node.id, node.name, data.snapshot.root); - }; - - render() { - const { data, index, style } = this.props; - - const node = data.nodes[index]; - - const { scaleX, width } = data; - - // List items are absolutely positioned using the CSS "top" attribute. - // The "left" value will always be 0. - // Since height is fixed, and width is based on the node's duration, - // We can ignore those values as well. - const top = parseInt(style.top, 10); - - return ( - - ); - } -} - -const getItemData = memoize(( - focusedNodeIndex: number, - inspectFiber: SelectOrInspectFiber, - rankedData: RankedData, - selectFiber: SelectOrInspectFiber, - snapshot: Snapshot, - theme: Theme, - width: number, -): ItemData => ({ - focusedNode: rankedData.nodes[focusedNodeIndex], - focusedNodeIndex, - inspectFiber, - nodes: rankedData.nodes, - maxValue: rankedData.maxValue, - scaleX: scale(0, rankedData.nodes[focusedNodeIndex].value, 0, width), - selectFiber, - snapshot, - theme, - width, -})); - -const getNodeIndex = memoize((rankedData: RankedData, id: string | null): number => { - if (id === null) { - return 0; - } - const { nodes } = rankedData; - for (let index = 0; index < nodes.length; index++) { - if (nodes[index].id === id) { - return index; - } - } - return 0; -}); - -const convertSnapshotToChartData = (snapshot: Snapshot, showNativeNodes: boolean): RankedData => { - let maxSelfDuration = 0; - - const nodes = snapshot.committedNodes - .filter(nodeID => { - const node = snapshot.nodes.get(nodeID); - const nodeType = node && node.get('nodeType'); - return (nodeType === 'Composite' || (nodeType === 'Native' && showNativeNodes)); - }) - .map((nodeID, index) => { - const selfDuration = calculateSelfDuration(snapshot, nodeID); - maxSelfDuration = Math.max(maxSelfDuration, selfDuration); - - const name = snapshot.nodes.getIn([nodeID, 'name']) || 'Unknown'; - const label = `${name} (${selfDuration.toFixed(1)}ms)`; - return { - id: nodeID, - label, - name, - title: label, - value: selfDuration, - }; - }) - .sort((a, b) => b.value - a.value); - - return { - maxValue: maxSelfDuration, - nodes, - }; -}; - -export default SnapshotRanked; diff --git a/plugins/Profiler/views/SnapshotSelector.js b/plugins/Profiler/views/SnapshotSelector.js deleted file mode 100644 index f35daf509e..0000000000 --- a/plugins/Profiler/views/SnapshotSelector.js +++ /dev/null @@ -1,419 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Snapshot} from '../ProfilerTypes'; -import type {Theme} from '../../../frontend/types'; - -import memoize from 'memoize-one'; -import React, {PureComponent} from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { FixedSizeList as List } from 'react-window'; -import { - didNotRender, - formatDuration, - formatTime, - getGradientColor, - getFilteredSnapshotData, - minBarHeight, - minBarWidth, -} from './constants'; -import Icons from '../../../frontend/Icons'; -import IconButton from './IconButton'; - -const HEIGHT = 20; - -type SelectSnapshot = (snapshot: Snapshot) => void; - -type ListData = {| - itemSize: number, - maxDuration: number, -|}; - -type ItemData = {| - isMouseDown: boolean, - maxDuration: number, - selectSnapshot: SelectSnapshot, - selectedSnapshot: Snapshot, - snapshots: Array, - theme: Theme, -|}; - -type Props = {| - commitThreshold: number, - hideCommitsBelowThreshold: boolean, - isInspectingSelectedFiber: boolean, - selectedFiberID: string | null, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - snapshotIndex: number, - snapshots: Array, - theme: Theme, -|}; - -export default ({ - commitThreshold, - hideCommitsBelowThreshold, - isInspectingSelectedFiber, - selectedFiberID, - selectedSnapshot, - selectSnapshot, - snapshotIndex, - snapshots, - theme, -}: Props) => { - const filteredData = getFilteredSnapshotData( - commitThreshold, - hideCommitsBelowThreshold, - isInspectingSelectedFiber, - selectedFiberID, - selectedSnapshot, - snapshotIndex, - snapshots, - ); - - return ( - - ); -}; - -class SnapshotSelectorWrapper extends PureComponent { - handleKeyDown = event => { - if (event.keyCode === LEFT_ARROW || event.keyCode === RIGHT_ARROW) { - event.preventDefault(); - - if (event.keyCode === LEFT_ARROW) { - this.selectPreviousSnapshotIndex(); - } else { - this.selectNextSnapshotIndex(); - } - } - }; - - selectNextSnapshotIndex = () => { - const { - selectSnapshot, - snapshotIndex, - snapshots, - } = this.props; - - if ( - snapshots.length > 0 && - snapshotIndex < snapshots.length - 1 - ) { - const newIndex = snapshotIndex + 1; - selectSnapshot(snapshots[newIndex]); - } - }; - - selectPreviousSnapshotIndex = () => { - const { - selectSnapshot, - snapshotIndex, - snapshots, - } = this.props; - - if ( - snapshots.length > 0 && - snapshotIndex > 0 - ) { - const newIndex = snapshotIndex - 1; - selectSnapshot(snapshots[newIndex]); - } - }; - - render() { - const { - commitThreshold, - hideCommitsBelowThreshold, - isInspectingSelectedFiber, - selectedFiberID, - selectedSnapshot, - selectSnapshot, - snapshotIndex, - snapshots, - theme, - } = this.props; - - const numSnapshots = snapshots.length; - - return ( -
    - {numSnapshots === 0 && ( - - 0 / 0 - - )} - {numSnapshots > 0 && ( - - {`${snapshotIndex >= 0 ? snapshotIndex + 1 : '-'}`.padStart(`${numSnapshots}`.length, '0')} / {numSnapshots} - - )} - - - = numSnapshots - 1} - icon={Icons.FORWARD} - isTransparent={true} - onClick={this.selectNextSnapshotIndex} - theme={theme} - title="Next render" - /> -
    - ); - } -} - -const AutoSizedSnapshotSelector = ({ - isInspectingSelectedFiber, - selectedFiberID, - selectedSnapshot, - selectSnapshot, - snapshotIndex, - snapshots, - theme, -}: Props) => ( -
    - - {({ height, width }) => ( - - )} - -
    -); - -const LEFT_ARROW = 37; -const RIGHT_ARROW = 39; - -type SnapshotSelectorProps = {| - height: number, - selectedFiberID: string | null, - selectedSnapshot: Snapshot, - selectSnapshot: SelectSnapshot, - snapshotIndex: number, - snapshots: Array, - theme: Theme, - width: number, -|}; - -type SnapshotSelectorState = {| - isMouseDown: boolean, -|}; - -class SnapshotSelector extends PureComponent { - // $FlowFixMe createRef() - listRef = React.createRef(); - - state: SnapshotSelectorState = { - isMouseDown: false, - }; - - componentDidUpdate(prevProps) { - // Make sure any newly selected snapshot is visible within the list. - if ( - this.props.snapshotIndex !== prevProps.snapshotIndex && - this.listRef.current !== null - ) { - this.listRef.current.scrollToItem(this.props.snapshotIndex); - } - } - - componentWillUnmount() { - window.removeEventListener('mouseup', this.handleMouseUp); - } - - handleMouseDown = event => this.setState({ isMouseDown: true }, () => { - window.addEventListener('mouseup', this.handleMouseUp); - }); - handleMouseUp = event => this.setState({ isMouseDown: false }); - - render() { - const { - height, - selectedSnapshot, - selectSnapshot, - snapshots, - theme, - width, - } = this.props; - const {isMouseDown} = this.state; - - const listData = getListData(snapshots, width); - - // Pass required contextual data down to the ListItem renderer. - // (This method is memoized so it's safe to call on every render.) - const itemData = getItemData( - isMouseDown, - listData.maxDuration, - selectSnapshot, - selectedSnapshot, - snapshots, - theme, - ); - - const numSnapshots = snapshots.length; - - return ( -
    - {numSnapshots > 0 && ( - - {ListItem} - - )} -
    - ); - } -} - -class ListItem extends PureComponent { - handleMouseEnter = () => { - const itemData: ItemData = ((this.props.data: any): ItemData); - if (itemData.isMouseDown) { - itemData.selectSnapshot(itemData.snapshots[this.props.index]); - } - } - - render() { - const { index, style } = this.props; - const itemData: ItemData = ((this.props.data: any): ItemData); - - const { - maxDuration, - selectedSnapshot, - selectSnapshot, - snapshots, - theme, - } = itemData; - - const snapshot = snapshots[index]; - // Guard against commits with duration 0 - const percentage = Math.min(1, Math.max(0, snapshot.duration / maxDuration)) || 0; - const isSelected = selectedSnapshot === snapshot; - - const width = parseFloat(style.width) - 1; - - return ( -
    selectSnapshot(snapshot)} - onMouseEnter={this.handleMouseEnter} - style={{ - ...style, - width, - backgroundColor: isSelected ? theme.base01 : 'transparent', - userSelect: 'none', - cursor: 'pointer', - }} - title={`Duration ${formatDuration(snapshot.duration)}ms at ${formatTime(snapshot.commitTime)}s`} - > -
    -
    - ); - } -} - -const getListData = memoize(( - snapshots: Array, - width: number, -): ListData => ({ - itemSize: Math.max(minBarWidth, width / snapshots.length), - maxDuration: snapshots.reduce((maxDuration, snapshot) => Math.max(maxDuration, snapshot.duration), 0), -})); - -const getItemData = memoize(( - isMouseDown: boolean, - maxDuration: number, - selectSnapshot: SelectSnapshot, - selectedSnapshot: Snapshot, - snapshots: Array, - theme: Theme, -): ItemData => ({ - isMouseDown, - maxDuration, - selectSnapshot, - selectedSnapshot, - snapshots, - theme, -})); diff --git a/plugins/Profiler/views/ViewTypes.js b/plugins/Profiler/views/ViewTypes.js deleted file mode 100644 index 64759524a4..0000000000 --- a/plugins/Profiler/views/ViewTypes.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -export type Chart = 'flamegraph' | 'interactions' | 'ranked'; diff --git a/plugins/Profiler/views/constants.js b/plugins/Profiler/views/constants.js deleted file mode 100644 index 663ed04e2a..0000000000 --- a/plugins/Profiler/views/constants.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Snapshot} from '../ProfilerTypes'; - -import memoize from 'memoize-one'; - -// http://gka.github.io/palettes/#colors=#37AFA9,#FEBC38|steps=10|bez=0|coL=0 -export const gradient = [ - '#37afa9', '#63b19e', '#80b393', '#97b488', '#abb67d', '#beb771', '#cfb965', '#dfba57', '#efbb49', '#febc38', -]; - -export const didNotRender = '#ddd'; - -export const barHeight = 20; -export const barWidth = 100; -export const barWidthThreshold = 2; -export const minBarHeight = 5; -export const minBarWidth = 5; -export const textHeight = 18; - -export const scale = (minValue: number, maxValue: number, minRange: number, maxRange: number) => - (value: number, fallbackValue: number) => - maxValue - minValue === 0 - ? fallbackValue - : ((value - minValue) / (maxValue - minValue)) * (maxRange - minRange); - -const gradientMaxIndex = gradient.length - 1; -export const getGradientColor = (value: number) => { - let index; - // Guard against commits with duration 0 - if (Number.isNaN(value)) { - index = 0; - } else if (!Number.isFinite(value)) { - index = gradient.length - 1; - } else { - index = Math.max(0, Math.min(gradientMaxIndex, value)) * gradientMaxIndex; - } - return gradient[Math.round(index)]; -}; - -export const formatDuration = (duration: number) => Math.round(duration * 10) / 10; -export const formatPercentage = (percentage: number) => Math.round(percentage * 100); -export const formatTime = (timestamp: number) => Math.round(Math.round(timestamp) / 100) / 10; - -export const getMaxDuration = (snapshots: Array): number => - snapshots.reduce((maxDuration: number, snapshot: Snapshot) => - Math.max(maxDuration, snapshot.duration || 0), 0); - -type FilteredSnapshotData = {| - snapshotIndex: number, - snapshots: Array, -|}; - -/** - * Helper method to filter snapshots based on the current store state. - * This is a helper util so that its calculations are memoized and shared between multiple components. - */ -export const getFilteredSnapshotData = memoize(( - commitThreshold: number, - hideCommitsBelowThreshold: boolean, - isInspectingSelectedFiber: boolean, - selectedFiberID: string | null, - selectedSnapshot: Snapshot, - snapshotIndex: number, - snapshots: Array, -): FilteredSnapshotData => { - let filteredSnapshots = snapshots; - if (isInspectingSelectedFiber) { - filteredSnapshots = filteredSnapshots.filter(snapshot => snapshot.committedNodes.includes(selectedFiberID)); - } - if (hideCommitsBelowThreshold) { - filteredSnapshots = filteredSnapshots.filter(snapshot => snapshot.duration >= commitThreshold); - } - - const filteredSnapshotIndex = filteredSnapshots.indexOf(selectedSnapshot); - - return { - snapshotIndex: filteredSnapshotIndex, - snapshots: filteredSnapshots, - }; -}); - -export const calculateSelfDuration = (snapshot: Snapshot, nodeID: string): number => { - const {nodes} = snapshot; - const node = nodes.get(nodeID); - const actualDuration = node.get('actualDuration'); - - let selfDuration = actualDuration; - - const children = node.get('children'); - if (Array.isArray(children)) { - children.forEach(childID => { - const childActualDuration = nodes.getIn([childID, 'actualDuration']); - if (childActualDuration > 0) { - selfDuration -= childActualDuration; - } - }); - } - - return selfDuration; -}; diff --git a/plugins/ReactNativeStyle/AutoSizeInput.js b/plugins/ReactNativeStyle/AutoSizeInput.js deleted file mode 100644 index 191c24e19c..0000000000 --- a/plugins/ReactNativeStyle/AutoSizeInput.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); - -var React = require('react'); -var nullthrows = require('nullthrows').default; -var {monospace} = require('../../frontend/Themes/Fonts'); -var Input = require('../../frontend/Input'); - -import type {Theme} from '../../frontend/types'; - -type Context = { - theme: Theme, -}; -type Props = { - onChange: (text: string|number) => any, - value: string|number, - type?: string, - isNew?: boolean, -}; -type DefaultProps = {}; -type State = { - text: string; - inputWidth: number; -}; - -class AutoSizeInput extends React.Component { - context: Context; - defaultProps: DefaultProps; - input: HTMLInputElement; - sizer: ?HTMLDivElement; - - constructor(props: Props, context: Context) { - super(props, context); - - this.state = { - text: '' + this.props.value, - inputWidth: 1, - }; - } - - componentDidMount() { - this.copyInputStyles(); - this.updateInputWidth(); - if (this.props.isNew) { - this.input.focus(); - } - } - - componentDidUpdate(prevProps: Props, prevState: State) { - this.updateInputWidth(); - } - - componentWillReceiveProps(nextProps: Props) { - this.setState({text: '' + nextProps.value}); - } - - copyInputStyles() { - if (!window.getComputedStyle) { - return; - } - const inputStyle = this.input && window.getComputedStyle(this.input); - if (!inputStyle) { - return; - } - const sizerNode = nullthrows(this.sizer); - sizerNode.style.fontSize = inputStyle.fontSize; - sizerNode.style.fontFamily = inputStyle.fontFamily; - sizerNode.style.fontWeight = inputStyle.fontWeight; - sizerNode.style.fontStyle = inputStyle.fontStyle; - sizerNode.style.letterSpacing = inputStyle.letterSpacing; - } - - updateInputWidth() { - if (!this.sizer || typeof this.sizer.scrollWidth === 'undefined') { - return; - } - const width = this.sizer.scrollWidth + 1; - if (width !== this.state.inputWidth) { - this.setState({ - inputWidth: width, - }); - } - } - - onKeyDown(e: KeyboardEvent) { - if (e.key === 'Enter' || e.key === 'Escape') { - this.done(); - return; - } else if (e.key === 'ArrowUp') { - if (+this.state.text + '' === this.state.text) { - this.props.onChange(+this.state.text + 1); - } - } else if (e.key === 'ArrowDown') { - if (+this.state.text + '' === this.state.text) { - this.props.onChange(+this.state.text - 1); - } - } - } - - onFocus() { - const {theme} = this.context; - - const input = this.input; - input.selectionStart = 0; - input.selectionEnd = input.value.length; - input.style.color = theme.base05; - input.style.boxShadow = `0 0 3px ${theme.base03}`; - input.style.border = `1px solid ${theme.base03}`; - input.style.padding = '0px 1px'; - } - - done() { - const input = this.input; - input.style.color = this.getColor(); - input.style.boxShadow = 'none'; - input.style.border = 'none'; - input.style.padding = '1px 2px'; - if (this.state.text !== '' + this.props.value || this.props.isNew) { - this.props.onChange(this.state.text); - } - } - - getColor() { - const {theme} = this.context; - return this.props.type === 'attr' ? theme.special06 : theme.base05; - } - - render() { - const style = (inputStyle(this.state.text): any); - style.color = this.getColor(); - style.width = this.state.inputWidth + 'px'; - return ( -
    - this.input = i} - value={this.state.text} - style={style} - onChange={e => this.setState({text: e.target.value})} - onFocus={() => this.onFocus()} - onBlur={() => this.done()} - onKeyDown={e => this.onKeyDown(e)} - /> -
    this.sizer = el} style={styles.sizer}>{this.state.text}
    -
    - ); - } -} - -AutoSizeInput.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const inputStyle = (text: ?string) => ({ - fontFamily: monospace.family, - fontSize: monospace.sizes.normal, - boxSizing: 'content-box', - border: 'none', - padding: '1px 2px', - marginLeft: '0.75rem', - outline: 'none', - width: '0px', - minWidth: text ? '0' : '1rem', // Make it easier to click initially -}); - -var styles = { - wrapper: { - display: 'inline-block', - }, - sizer: { - position: 'absolute', - top: 0, - left: 0, - visibility: 'hidden', - height: 0, - overflow: 'scroll', - whiteSpace: 'pre', - }, -}; - -module.exports = AutoSizeInput; diff --git a/plugins/ReactNativeStyle/BlurInput.js b/plugins/ReactNativeStyle/BlurInput.js deleted file mode 100644 index 17b94a590a..0000000000 --- a/plugins/ReactNativeStyle/BlurInput.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var React = require('react'); -var Input = require('../../frontend/Input'); - -import type {DOMEvent, DOMNode} from '../../frontend/types'; - -type Props = { - onChange: (text: string|number) => any; - value: string|number; -}; -type DefaultProps = {}; -type State = { - text: string; -}; - -class BlurInput extends React.Component { - defaultProps: DefaultProps; - node: ?DOMNode; - - constructor(props: Props) { - super(props); - this.state = {text: '' + this.props.value}; - } - - componentWillReceiveProps(nextProps: Props) { - this.setState({text: '' + nextProps.value}); - } - - done() { - if (this.state.text !== '' + this.props.value) { - this.props.onChange(this.state.text); - } - } - - onKeyDown(e: DOMEvent) { - if (e.key === 'Enter') { - this.done(); - return; - } else if (e.key === 'ArrowUp') { - if (+this.state.text + '' === this.state.text) { - this.props.onChange(+this.state.text + 1); - } - } else if (e.key === 'ArrowDown') { - if (+this.state.text + '' === this.state.text) { - this.props.onChange(+this.state.text - 1); - } - } - } - - render() { - return ( - this.node = i} - onChange={e => this.setState({text: e.target.value})} - onBlur={this.done.bind(this)} - onKeyDown={e => this.onKeyDown(e)} - size={1 /* Allow to shrink */} - style={styles.input} - /> - ); - } -} - -var styles = { - input: { - width: '100%', - }, -}; - -module.exports = BlurInput; diff --git a/plugins/ReactNativeStyle/BoxInspector.js b/plugins/ReactNativeStyle/BoxInspector.js deleted file mode 100644 index 195c372872..0000000000 --- a/plugins/ReactNativeStyle/BoxInspector.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); - -var React = require('react'); -var {sansSerif} = require('../../frontend/Themes/Fonts'); - -import type {Theme} from '../../frontend/types'; - -type BoxMeasurements = { - top: number, - left: number, - right: number, - bottom: number, -} - -type BoxProps = BoxMeasurements & { - title: string, - children: React.Node, - theme: Theme, -}; - -var Box = (props: BoxProps) => { - var {title, children, top, left, right, bottom, theme} = props; - return ( -
    - {title} -
    {+top.toFixed(3)}
    -
    - {+left.toFixed(3)} - {children} - {+right.toFixed(3)} -
    -
    {+bottom.toFixed(3)}
    -
    - ); -}; - -type BoxInspectorProps = { - left: number, - top: number, - width: number, - height: number, - margin: BoxMeasurements, - padding: BoxMeasurements, -} - -class BoxInspector extends React.Component { - context: { - theme: Theme, - }; - - render() { - const {theme} = this.context; - const {left, top, width, height, margin, padding} = this.props; - return ( - - -
    - - ({+left.toFixed(3)}, {+top.toFixed(3)}) - - - {+width.toFixed(3)} × {+height.toFixed(3)} - -
    -
    -
    - ); - } -} - -BoxInspector.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const labelStyle = (theme: Theme) => ({ - flex: 1, - color: theme.special03, -}); - -const positionTextStyle = (theme: Theme) => ({ - color: theme.base03, - fontSize: sansSerif.sizes.normal, - textAlign: 'center', -}); - -const dimenTextStyle = (theme: Theme) => ({ - color: theme.special02, - textAlign: 'center', -}); - -const boxStyle = (theme: Theme) => ({ - position: 'relative', - padding: 8, - margin: 8, - width: 184, - border: `1px dashed ${theme.base05}`, - alignItems: 'center', - alignSelf: 'center', -}); - -var styles = { - row: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - }, - measureLayout: { - display: 'flex', - flexDirection: 'column', - margin: 4, - }, - boxText: { - textAlign: 'center', - }, -}; - -module.exports = BoxInspector; diff --git a/plugins/ReactNativeStyle/ReactNativeStyle.js b/plugins/ReactNativeStyle/ReactNativeStyle.js deleted file mode 100644 index 63226d0dc1..0000000000 --- a/plugins/ReactNativeStyle/ReactNativeStyle.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var React = require('react'); -var StyleEdit = require('./StyleEdit'); -var BoxInspector = require('./BoxInspector'); - -function shallowClone(obj) { - var nobj = {}; - for (var n in obj) { - nobj[n] = obj[n]; - } - return nobj; -} - -type Props = { - // TODO: typecheck bridge interface - bridge: any; - id: any; - supportsMeasure: ?boolean; -}; - -type DefaultProps = {}; - -type State = { - style: ?Object; - measuredLayout: ?Object; -}; - -type StyleResult = { - style: Object; - measuredLayout: ?Object; -}; - -class NativeStyler extends React.Component { - defaultProps: DefaultProps; - _styleGet: (result: StyleResult) => void; - - constructor(props: Object) { - super(props); - this.state = {style: null, measuredLayout: null}; - } - - componentWillMount() { - this._styleGet = this._styleGet.bind(this); - if (this.props.supportsMeasure) { - this.props.bridge.on('rn-style:measure', this._styleGet); - this.props.bridge.send('rn-style:measure', this.props.id); - } else { - this.props.bridge.call('rn-style:get', this.props.id, style => { - this.setState({style}); - }); - } - } - - componentWillUnmount() { - if (this.props.supportsMeasure) { - this.props.bridge.off('rn-style:measure', this._styleGet); - } - } - - componentWillReceiveProps(nextProps: Object) { - if (nextProps.id === this.props.id) { - return; - } - this.setState({style: null}); - this.props.bridge.send('rn-style:get', nextProps.id); - - if (this.props.supportsMeasure) { - this.props.bridge.send('rn-style:measure', nextProps.id); - } else { - this.props.bridge.call('rn-style:get', nextProps.id, style => { - this.setState({style}); - }); - } - } - - _styleGet(result: StyleResult) { - var {style, measuredLayout} = result; - this.setState({style, measuredLayout}); - } - - _handleStyleChange(attr: string, val: string | number) { - if (this.state.style) { - this.state.style[attr] = val; - } - this.props.bridge.send('rn-style:set', {id: this.props.id, attr, val}); - this.setState({style: this.state.style}); - } - - _handleStyleRename(oldName: string, newName: string, val: string | number) { - var style = shallowClone(this.state.style); - delete style[oldName]; - style[newName] = val; - this.props.bridge.send('rn-style:rename', {id: this.props.id, oldName, newName, val}); - this.setState({style}); - } - - render() { - if (!this.state.style) { - return loading; - } - return ( -
    - {this.state.measuredLayout && } - -
    - ); - } -} - -var styles = { - container: { - display: 'flex', - flexDirection: 'column', - }, -}; - -module.exports = NativeStyler; diff --git a/plugins/ReactNativeStyle/StyleEdit.js b/plugins/ReactNativeStyle/StyleEdit.js deleted file mode 100644 index ee59cc5350..0000000000 --- a/plugins/ReactNativeStyle/StyleEdit.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); - -var React = require('react'); -var AutoSizeInput = require('./AutoSizeInput'); - -import type {Theme} from '../../frontend/types'; - -type Context = { - theme: Theme, -}; - -type Props = { - style: Object, - onChange: (attr: string, val: string | number) => void, - onRename: (oldName: string, newName: string, val: string | number) => void, -}; - -type DefaultProps = {}; - -type State = { - showNew: boolean, - newAttr: string, - newValue: string|number, -}; - -class StyleEdit extends React.Component { - context: Context; - defaultProps: DefaultProps; - - constructor(props: Props) { - super(props); - this.state = {showNew: false, newAttr: '', newValue: ''}; - } - - onChange(name: string, val: string | number) { - var num = Number(val); - this.props.onChange(name, num === Number(val) ? num : val); - } - - onNewSubmit(val: string | number) { - this.onChange(this.state.newAttr, val); - this.setState({showNew: false, newAttr: '', newValue: ''}); - } - - onNewAttr(attr: string | number) { - if (attr === '') { - this.setState({showNew: false}); - } else { - this.setState({newAttr: '' + attr}); - } - } - - onListClick(e: Event) { - if (e.target instanceof Element) { - if (e.target.tagName === 'INPUT') { - return; - } - } - this.setState({showNew: true}); - } - - render() { - var attrs = Object.keys(this.props.style); - return ( -
      this.onListClick(e)}> - style - {' {'} - {attrs.map(name => ( -
    • - this.props.onRename(name, '' + newName, this.props.style[name])} - /> - : - this.onChange(name, val)} - /> - ; -
    • - ))} - {this.state.showNew && -
    • - this.onNewAttr(newAttr)} - /> - : - this.onNewSubmit(val)} - /> - ; -
    • } - {'}'} -
    - ); - } -} - -StyleEdit.contextTypes = { - theme: PropTypes.object.isRequired, -}; - -const blockClick = event => event.stopPropagation(); - -const tagStyle = (theme: Theme) => ({ - color: theme.base04, -}); - -const styles = { - list: { - listStyle: 'none', - padding: 0, - margin: '5px 0px', - cursor: 'text', - }, - colon: { - margin: '-3px', - }, - listItem: { - margin: 0, - display: 'flex', - alignItems: 'center', - cursor: 'default', - }, -}; - -module.exports = StyleEdit; diff --git a/plugins/ReactNativeStyle/resolveBoxStyle.js b/plugins/ReactNativeStyle/resolveBoxStyle.js deleted file mode 100644 index 16f724dada..0000000000 --- a/plugins/ReactNativeStyle/resolveBoxStyle.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -/** - * This is mirror from - * https://github.com/facebook/react-native/blob/master/Libraries/Inspector/resolveBoxStyle.js - * - * Resolve a style property into it's component parts, e.g. - * - * resolveBoxStyle('margin', {margin: 5, marginBottom: 10}) - * -> - * {top: 5, left: 5, right: 5, bottom: 10} - * - * If none are set, returns false. - */ -function resolveBoxStyle(prefix: string, style: Object): ?Object { - var res = {}; - var subs = ['top', 'left', 'bottom', 'right']; - var set = false; - subs.forEach(sub => { - res[sub] = style[prefix] || 0; - }); - if (style[prefix]) { - set = true; - } - if (style[prefix + 'Vertical']) { - res.top = res.bottom = style[prefix + 'Vertical']; - set = true; - } - if (style[prefix + 'Horizontal']) { - res.left = res.right = style[prefix + 'Horizontal']; - set = true; - } - subs.forEach(sub => { - var val = style[prefix + capFirst(sub)]; - if (val) { - res[sub] = val; - set = true; - } - }); - if (!set) { - return null; - } - return res; -} - -function capFirst(text) { - return text[0].toUpperCase() + text.slice(1); -} - -module.exports = resolveBoxStyle; diff --git a/plugins/ReactNativeStyle/setupBackend.js b/plugins/ReactNativeStyle/setupBackend.js deleted file mode 100644 index 0ad2ce4878..0000000000 --- a/plugins/ReactNativeStyle/setupBackend.js +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var resolveBoxStyle = require('./resolveBoxStyle'); - -import type Bridge from '../../agent/Bridge'; -import type Agent from '../../agent/Agent'; - -var styleOverridesByHostComponentId = {}; - -module.exports = function setupRNStyle( - bridge: Bridge, - agent: Agent, - resolveRNStyle: (style: number) => ?Object, -) { - bridge.onCall('rn-style:get', id => { - var node = agent.elementData.get(id); - if (!node || !node.props) { - return null; - } - return resolveRNStyle(node.props.style); - }); - - bridge.on('rn-style:measure', id => { - measureStyle(agent, bridge, resolveRNStyle, id); - }); - - bridge.on('rn-style:rename', ({id, oldName, newName, val}) => { - renameStyle(agent, id, oldName, newName, val); - setTimeout(() => measureStyle(agent, bridge, resolveRNStyle, id)); - }); - - bridge.on('rn-style:set', ({id, attr, val}) => { - setStyle(agent, id, attr, val); - setTimeout(() => measureStyle(agent, bridge, resolveRNStyle, id)); - }); -}; - -var blank = { - top: 0, - left: 0, - right: 0, - bottom: 0, -}; - -function measureStyle(agent, bridge, resolveRNStyle, id) { - var node = agent.elementData.get(id); - if (!node || !node.props) { - bridge.send('rn-style:measure', {}); - return; - } - - let style = resolveRNStyle(node.props.style); - // If it's a host component we edited before, amend styles. - if (styleOverridesByHostComponentId[id]) { - style = Object.assign({}, style, styleOverridesByHostComponentId[id]); - } - - var instance = node.publicInstance; - if (!instance || !instance.measure) { - bridge.send('rn-style:measure', {style}); - return; - } - - instance.measure((x, y, width, height, left, top) => { - // RN Android sometimes returns undefined here. Don't send measurements in this case. - // https://github.com/jhen0409/react-native-debugger/issues/84#issuecomment-304611817 - if (typeof x !== 'number') { - bridge.send('rn-style:measure', {style}); - return; - } - var margin = (style && resolveBoxStyle('margin', style)) || blank; - var padding = (style && resolveBoxStyle('padding', style)) || blank; - bridge.send('rn-style:measure', { - style, - measuredLayout: { - x, - y, - width, - height, - left, - top, - margin, - padding, - }, - }); - }); -} - -function shallowClone(obj) { - var nobj = {}; - for (var n in obj) { - nobj[n] = obj[n]; - } - return nobj; -} - -function renameStyle(agent, id, oldName, newName, val) { - var data = agent.elementData.get(id); - var newStyle = newName - ? {[oldName]: undefined, [newName]: val} - : {[oldName]: undefined}; - - if (data && data.updater && typeof data.updater.setInProps === 'function') { - // First attempt: use setInProps(). - // We do this for composite components, and it works relatively well. - var style = data && data.props && data.props.style; - var customStyle; - if (Array.isArray(style)) { - var lastLength = style.length - 1; - if (typeof style[lastLength] === 'object' && !Array.isArray(style[lastLength])) { - customStyle = shallowClone(style[lastLength]); - delete customStyle[oldName]; - if (newName) { - customStyle[newName] = val; - } else { - customStyle[oldName] = undefined; - } - // $FlowFixMe we know that updater is not null here - data.updater.setInProps(['style', lastLength], customStyle); - } else { - style = style.concat([newStyle]); - // $FlowFixMe we know that updater is not null here - data.updater.setInProps(['style'], style); - } - } else { - if (typeof style === 'object') { - customStyle = shallowClone(style); - delete customStyle[oldName]; - if (newName) { - customStyle[newName] = val; - } else { - customStyle[oldName] = undefined; - } - // $FlowFixMe we know that updater is not null here - data.updater.setInProps(['style'], customStyle); - } else { - style = [style, newStyle]; - data.updater.setInProps(['style'], style); - } - } - } else if (data && data.updater && typeof data.updater.setNativeProps === 'function') { - // Fallback: use setNativeProps(). We're dealing with a host component. - // Remember to "correct" resolved styles when we read them next time. - if (!styleOverridesByHostComponentId[id]) { - styleOverridesByHostComponentId[id] = newStyle; - } else { - Object.assign(styleOverridesByHostComponentId[id], newStyle); - } - data.updater.setNativeProps({ style: newStyle }); - } else { - return; - } - agent.emit('hideHighlight'); -} - -function setStyle(agent, id, attr, val) { - var data = agent.elementData.get(id); - var newStyle = {[attr]: val}; - - if (data && data.updater && typeof data.updater.setInProps === 'function') { - // First attempt: use setInProps(). - // We do this for composite components, and it works relatively well. - var style = data.props && data.props.style; - if (Array.isArray(style)) { - var lastLength = style.length - 1; - if (typeof style[lastLength] === 'object' && !Array.isArray(style[lastLength])) { - // $FlowFixMe we know that updater is not null here - data.updater.setInProps(['style', lastLength, attr], val); - } else { - style = style.concat([newStyle]); - // $FlowFixMe we know that updater is not null here - data.updater.setInProps(['style'], style); - } - } else { - style = [style, newStyle]; - data.updater.setInProps(['style'], style); - } - } else if (data && data.updater && typeof data.updater.setNativeProps === 'function') { - // Fallback: use setNativeProps(). We're dealing with a host component. - // Remember to "correct" resolved styles when we read them next time. - if (!styleOverridesByHostComponentId[id]) { - styleOverridesByHostComponentId[id] = newStyle; - } else { - Object.assign(styleOverridesByHostComponentId[id], newStyle); - } - data.updater.setNativeProps({ style: newStyle }); - } else { - return; - } - agent.emit('hideHighlight'); -} diff --git a/plugins/TraceUpdates/TraceUpdatesAbstractNodeMeasurer.js b/plugins/TraceUpdates/TraceUpdatesAbstractNodeMeasurer.js deleted file mode 100644 index 1cac91cfb1..0000000000 --- a/plugins/TraceUpdates/TraceUpdatesAbstractNodeMeasurer.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const requestAnimationFrame = require('fbjs/lib/requestAnimationFrame'); -const immutable = require('immutable'); - -import type {Measurement, Measurer} from './TraceUpdatesTypes'; - -// How long the measurement can be cached in ms. -const DURATION = 800; - -const {Record, Map, Set} = immutable; - -const MeasurementRecord = Record({ - bottom: 0, - expiration: 0, - height: 0, - id: '', - left: 0, - right: 0, - scrollX: 0, - scrollY: 0, - top: 0, - width: 0, -}); - -var _id = 100; - -class TraceUpdatesAbstractNodeMeasurer implements Measurer { - _callbacks: Map void>; - _ids: Map; - _isRequesting: boolean; - _measureNodes: () => void; - _measurements: Map; - _nodes: Map; - - constructor() { - // pending nodes to measure. - this._nodes = new Map(); - - // ids of pending nodes. - this._ids = new Map(); - - // cached measurements. - this._measurements = new Map(); - - // callbacks for pending nodes. - this._callbacks = new Map(); - - this._isRequesting = false; - - // non-auto-binds. - this._measureNodes = this._measureNodes.bind(this); - } - - request(node: Node, callback: (v: Measurement) => void): string { - var requestID = this._nodes.has(node) ? - this._nodes.get(node) : - String(_id++); - - this._nodes = this._nodes.set(node, requestID); - this._ids = this._ids.set(requestID, node); - - var callbacks = this._callbacks.has(node) ? - this._callbacks.get(node) : - new Set(); - - callbacks = callbacks.add(callback); - this._callbacks = this._callbacks.set(node, callbacks); - - if (this._isRequesting) { - return requestID; - } - - this._isRequesting = true; - requestAnimationFrame(this._measureNodes); - return requestID; - } - - cancel(requestID: string): void { - if (this._ids.has(requestID)) { - var node = this._ids.get(requestID); - this._ids = this._ids.delete(requestID); - this._nodes = this._nodes.delete(node); - this._callbacks = this._callbacks.delete(node); - } - } - - measureImpl(node: Node): Measurement { - // sub-class must overwrite this. - return new MeasurementRecord(); - } - - _measureNodes(): void { - var now = Date.now(); - - this._measurements = this._measurements.withMutations(_measurements => { - for (const node of this._nodes.keys()) { - const measurement = this._measureNode(now, node); - // cache measurement. - _measurements.set(node, measurement); - } - }); - - // execute callbacks. - for (const node of this._nodes.keys()) { - const measurement = this._measurements.get(node); - this._callbacks.get(node).forEach(callback => callback(measurement)); - } - - // clear stale measurement. - this._measurements = this._measurements.withMutations(_measurements => { - for (const [node, measurement] of _measurements.entries()) { - if (measurement.expiration < now) { - _measurements.delete(node); - } - } - }); - - this._ids = this._ids.clear(); - this._nodes = this._nodes.clear(); - this._callbacks = this._callbacks.clear(); - this._isRequesting = false; - } - - _measureNode(timestamp: number, node: Node): Measurement { - var measurement; - var data; - - if (this._measurements.has(node)) { - measurement = this._measurements.get(node); - if (measurement.expiration < timestamp) { - // measurement expires. measure again. - data = this.measureImpl(node); - measurement = measurement.merge({ - ...data, - expiration: timestamp + DURATION, - }); - } - } else { - data = this.measureImpl(node); - measurement = new MeasurementRecord({ - ...data, - expiration: timestamp + DURATION, - id: 'm_' + String(_id++), - }); - } - return measurement; - } -} - -module.exports = TraceUpdatesAbstractNodeMeasurer; diff --git a/plugins/TraceUpdates/TraceUpdatesAbstractNodePresenter.js b/plugins/TraceUpdates/TraceUpdatesAbstractNodePresenter.js deleted file mode 100644 index 8f3de20329..0000000000 --- a/plugins/TraceUpdates/TraceUpdatesAbstractNodePresenter.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const immutable = require('immutable'); -const requestAnimationFrame = require('fbjs/lib/requestAnimationFrame'); - -import type { - Measurement, - MetaData as MetaDataType, - Presenter, -} from './TraceUpdatesTypes'; - -// How long the measurement should be presented for. -const DURATION = 250; - -const {Record, Map} = immutable; - -const MetaData: MetaDataType = Record({ - expiration: 0, - hit: 0, -}); - -class TraceUpdatesAbstractNodePresenter implements Presenter { - // eslint shouldn't error on type positions. TODO: update eslint - // eslint-disable-next-line no-undef - _clearTimer: ?TimeoutID; - _draw: () => void; - _drawing: boolean; - _enabled: boolean; - _pool: Map; - _redraw: () => void; - - constructor() { - this._pool = new Map(); - this._drawing = false; - this._enabled = false; - this._clearTimer = null; - - this._draw = this._draw.bind(this); - this._redraw = this._redraw.bind(this); - } - - present(measurement: Measurement): void { - if (!this._enabled) { - return; - } - var data; - if (this._pool.has(measurement)) { - data = this._pool.get(measurement); - } else { - // $FlowIssue - data = new MetaData(); - } - - data = data.merge({ - expiration: Date.now() + DURATION, - hit: data.hit + 1, - }); - - this._pool = this._pool.set(measurement, data); - - if (this._drawing) { - return; - } - - this._drawing = true; - requestAnimationFrame(this._draw); - } - - setEnabled(enabled: boolean): void { - // console.log('setEnabled', enabled); - if (this._enabled === enabled) { - return; - } - - this._enabled = enabled; - - if (enabled) { - return; - } - - if (this._clearTimer) { - clearTimeout(this._clearTimer); - this._clearTimer = null; - } - - this._pool = this._pool.clear(); - this._drawing = false; - this.clearImpl(); - } - - drawImpl(measurements: Map): void { - // sub-class should implement this. - } - - clearImpl(): void { - // sub-class should implement this. - } - - _redraw(): void { - this._clearTimer = null; - if (!this._drawing && this._pool.size > 0) { - this._drawing = true; - this._draw(); - } - } - - _draw(): void { - if (!this._enabled) { - this._drawing = false; - return; - } - - var now = Date.now(); - var minExpiration = Number.MAX_VALUE; - - this._pool = this._pool.withMutations(_pool => { - for (const [measurement, data] of _pool.entries()) { - if (data.expiration < now) { - // already passed the expiration time. - _pool.delete(measurement); - } else { - minExpiration = Math.min(data.expiration, minExpiration); - } - } - }); - - this.drawImpl(this._pool); - - if (this._pool.size > 0) { - if (this._clearTimer != null) { - clearTimeout(this._clearTimer); - } - this._clearTimer = setTimeout(this._redraw, minExpiration - now); - } - - this._drawing = false; - } -} - -module.exports = TraceUpdatesAbstractNodePresenter; diff --git a/plugins/TraceUpdates/TraceUpdatesBackendManager.js b/plugins/TraceUpdates/TraceUpdatesBackendManager.js deleted file mode 100644 index 9f7fb50461..0000000000 --- a/plugins/TraceUpdates/TraceUpdatesBackendManager.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -const TraceUpdatesAbstractNodeMeasurer = require('./TraceUpdatesAbstractNodeMeasurer'); -const TraceUpdatesAbstractNodePresenter = require('./TraceUpdatesAbstractNodePresenter'); -const TraceUpdatesWebNodeMeasurer = require('./TraceUpdatesWebNodeMeasurer'); -const TraceUpdatesWebNodePresenter = require('./TraceUpdatesWebNodePresenter'); - -import type { - Agent, - Measurement, - Measurer, - Presenter, -} from './TraceUpdatesTypes'; - -import type {ControlState} from '../../frontend/types.js'; - -const NODE_TYPE_COMPOSITE = 'Composite'; -const NODE_TYPE_SPECIAL = 'Special'; - -class TraceUpdatesBackendManager { - _onMeasureNode: () => void; - _measurer: Measurer; - _presenter: Presenter; - _isActive: boolean; - - constructor(agent: Agent) { - this._onMeasureNode = this._onMeasureNode.bind(this); - - var useDOM = agent.capabilities.dom; - - this._measurer = useDOM ? - new TraceUpdatesWebNodeMeasurer() : - new TraceUpdatesAbstractNodeMeasurer(); - - this._presenter = useDOM ? - new TraceUpdatesWebNodePresenter() : - new TraceUpdatesAbstractNodePresenter(); - - this._isActive = false; - agent.on('traceupdatesstatechange', this._onTraceUpdatesStateChange.bind(this)); - agent.on('update', this._onUpdate.bind(this, agent)); - agent.on('shutdown', this._shutdown.bind(this)); - } - - _onUpdate(agent: Agent, obj: any) { - if (!this._isActive || !obj.id) { - return; - } - - // Highlighting every host node would be too noisy. - // We highlight user components and context consumers - // (without consumers, a context update that renders - // only host nodes directly wouldn't highlight at all). - const shouldHighlight = obj.nodeType === NODE_TYPE_COMPOSITE || ( - obj.nodeType === NODE_TYPE_SPECIAL && - obj.name.endsWith('.Consumer') - ); - - if (!shouldHighlight) { - return; - } - - var node = agent.getNodeForID(obj.id); - if (!node) { - return; - } - - this._measurer.request(node, this._onMeasureNode); - } - - _onMeasureNode(measurement: Measurement): void { - this._presenter.present(measurement); - } - - _onTraceUpdatesStateChange(state: ControlState): void { - this._isActive = state.enabled; - this._presenter.setEnabled(state.enabled); - } - - _shutdown(): void { - this._isActive = false; - this._presenter.setEnabled(false); - } -} - -function init(agent: Agent): TraceUpdatesBackendManager { - return new TraceUpdatesBackendManager(agent); -} - -module.exports = { - init, -}; diff --git a/plugins/TraceUpdates/TraceUpdatesFrontendControl.js b/plugins/TraceUpdates/TraceUpdatesFrontendControl.js deleted file mode 100644 index 59ce7ed0bb..0000000000 --- a/plugins/TraceUpdates/TraceUpdatesFrontendControl.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -var decorate = require('../../frontend/decorate'); -var SettingsCheckbox = require('../../frontend/SettingsCheckbox'); - -var Wrapped = decorate({ - listeners() { - return ['traceupdatesstatechange']; - }, - props(store) { - return { - state: store.traceupdatesState, - text: 'Highlight Updates', - onChange: state => store.changeTraceUpdates(state), - }; - }, -}, SettingsCheckbox); - -module.exports = Wrapped; diff --git a/plugins/TraceUpdates/TraceUpdatesTypes.js b/plugins/TraceUpdates/TraceUpdatesTypes.js deleted file mode 100644 index 4734cd8658..0000000000 --- a/plugins/TraceUpdates/TraceUpdatesTypes.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -export type Agent = any; - -export type Node = any; - -export type Measurement = { - bottom: number, - expiration: number, - height: number, - id: string, - left: number, - right: number, - scrollX: number, - scrollY: number, - top: number, - width: number, -}; - -export type MetaData = { - expiration: number, - hit: number, -} - -export type onMeasureNode = (m: Measurement) => void; - - -// eslint shouldn't error on type positions. TODO: update eslint -// eslint-disable-next-line no-undef -export interface Measurer { - +request:(n: Node, c: onMeasureNode) => string, -} - -// eslint shouldn't error on type positions. TODO: update eslint -// eslint-disable-next-line no-undef -export interface Presenter { - +present: (m: Measurement) => void, - +setEnabled: (b: boolean) => void, -} diff --git a/plugins/TraceUpdates/TraceUpdatesWebNodeMeasurer.js b/plugins/TraceUpdates/TraceUpdatesWebNodeMeasurer.js deleted file mode 100644 index d71277b79f..0000000000 --- a/plugins/TraceUpdates/TraceUpdatesWebNodeMeasurer.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -const TraceUpdatesAbstractNodeMeasurer = require('./TraceUpdatesAbstractNodeMeasurer'); - -import type { - Measurement, -} from './TraceUpdatesTypes'; - -const DUMMY = { - bottom: 0, - expiration: 0, - height: 0, - id: '', - left: 0, - right: 0, - scrollX: 0, - scrollY: 0, - top: 0, - width: 0, -}; - -class TraceUpdatesWebNodeMeasurer extends TraceUpdatesAbstractNodeMeasurer { - measureImpl(node: any): Measurement { - if (!node || typeof node.getBoundingClientRect !== 'function') { - return DUMMY; - } - - var rect = node.getBoundingClientRect(); - var scrollX = Math.max( - document.body ? document.body.scrollLeft : 0, - document.documentElement ? document.documentElement.scrollLeft : 0, - window.pageXOffset || 0, - window.scrollX || 0, - ); - - var scrollY = Math.max( - document.body ? document.body.scrollTop : 0, - document.documentElement ? document.documentElement.scrollTop : 0, - window.pageYOffset || 0, - window.scrollY || 0, - ); - - return { - bottom: rect.bottom, - expiration: 0, - height: rect.height, - id: '', - left: rect.left, - right: rect.right, - scrollX, - scrollY, - top: rect.top, - width: rect.width, - }; - } -} - -module.exports = TraceUpdatesWebNodeMeasurer; diff --git a/plugins/TraceUpdates/TraceUpdatesWebNodePresenter.js b/plugins/TraceUpdates/TraceUpdatesWebNodePresenter.js deleted file mode 100644 index b895d7b091..0000000000 --- a/plugins/TraceUpdates/TraceUpdatesWebNodePresenter.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -import type {Measurement, MetaData} from './TraceUpdatesTypes'; - -const TraceUpdatesAbstractNodePresenter = require('./TraceUpdatesAbstractNodePresenter'); - -const OUTLINE_COLOR = '#f0f0f0'; - -const COLORS = [ - // coolest - '#55cef6', - '#55f67b', - '#a5f655', - '#f4f655', - '#f6a555', - '#f66855', - // hottest - '#ff0000', -]; - -const HOTTEST_COLOR = COLORS[COLORS.length - 1]; - -function drawBorder(ctx, measurement, borderWidth, borderColor) { - // outline - ctx.lineWidth = 1; - ctx.strokeStyle = OUTLINE_COLOR; - - ctx.strokeRect( - measurement.left- 1, - measurement.top - 1, - measurement.width + 2, - measurement.height + 2, - ); - - // inset - ctx.lineWidth = 1; - ctx.strokeStyle = OUTLINE_COLOR; - ctx.strokeRect( - measurement.left + borderWidth, - measurement.top + borderWidth, - measurement.width - borderWidth, - measurement.height - borderWidth, - ); - ctx.strokeStyle = borderColor; - - - if (measurement.should_update) { - ctx.setLineDash([2]); - } else { - ctx.setLineDash([0]); - } - - // border - ctx.lineWidth = '' + borderWidth; - ctx.strokeRect( - measurement.left + Math.floor(borderWidth / 2), - measurement.top + Math.floor(borderWidth / 2), - measurement.width - borderWidth, - measurement.height - borderWidth, - ); - - ctx.setLineDash([0]); -} - -const CANVAS_NODE_ID = 'TraceUpdatesWebNodePresenter'; - -class TraceUpdatesWebNodePresenter extends TraceUpdatesAbstractNodePresenter { - _canvas: any; - - constructor() { - super(); - this._canvas = null; - } - - drawImpl(pool: Map): void { - this._ensureCanvas(); - var canvas = this._canvas; - var ctx = canvas.getContext('2d'); - ctx.clearRect( - 0, - 0, - canvas.width, - canvas.height - ); - for (const [measurement, data] of pool.entries()) { - const color = COLORS[data.hit - 1] || HOTTEST_COLOR; - drawBorder(ctx, measurement, 1, color); - } - } - - clearImpl(): void { - var canvas = this._canvas; - if (canvas === null) { - return; - } - - if (!canvas.parentNode) { - return; - } - - var ctx = canvas.getContext('2d'); - ctx.clearRect( - 0, - 0, - canvas.width, - canvas.height - ); - - canvas.parentNode.removeChild(canvas); - this._canvas = null; - } - - _ensureCanvas(): void { - var canvas = this._canvas; - if (canvas === null) { - canvas = - window.document.getElementById(CANVAS_NODE_ID) || - window.document.createElement('canvas'); - - canvas.id = CANVAS_NODE_ID; - canvas.width = window.screen.availWidth; - canvas.height = window.screen.availHeight; - canvas.style.cssText = ` - xx-background-color: red; - xx-opacity: 0.5; - bottom: 0; - left: 0; - pointer-events: none; - position: fixed; - right: 0; - top: 0; - z-index: 1000000000; - `; - } - - if (!canvas.parentNode) { - var root = window.document.documentElement; - root.insertBefore(canvas, root.firstChild); - } - this._canvas = canvas; - } -} - -module.exports = TraceUpdatesWebNodePresenter; diff --git a/plugins/package.json b/plugins/package.json deleted file mode 100644 index 79ee71b91d..0000000000 --- a/plugins/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "dagre": "^0.7.3" - } -} diff --git a/shells/chrome/README.md b/shells/chrome/README.md deleted file mode 100644 index dd77d072c8..0000000000 --- a/shells/chrome/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# The Chrome extension - -The source code for this extension has moved to `shells/webextension`. - -Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:chrome` from the root directory. - -## Testing in Chrome - -You can test a local build of the web extension like so: - - 1. Build the extension: `node build` - 1. Follow the on-screen instructions. diff --git a/shells/chrome/build.js b/shells/chrome/build.js deleted file mode 100644 index 1286160708..0000000000 --- a/shells/chrome/build.js +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright 2004-present Facebook. All Rights Reserved. - */ - -'use strict'; - -const chalk = require('chalk'); -const {join} = require('path'); -const build = require('../webextension/build'); - -const main = async () => { - await build( - 'chrome', - join(__dirname, 'manifest.json'), - join(__dirname, 'build') - ); - - console.log(chalk.green('\nThe Chrome extension has been built!')); - console.log(chalk.green('You can test this build by running:')); - console.log(chalk.gray('\n# From the react-devtools root directory:')); - console.log('yarn run test:chrome'); -}; - -main(); diff --git a/shells/chrome/manifest.json b/shells/chrome/manifest.json deleted file mode 100644 index b8b93e4589..0000000000 --- a/shells/chrome/manifest.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "manifest_version": 2, - "name": "React Developer Tools", - "description": "Adds React debugging tools to the Chrome Developer Tools.", - "version": "3.4.3", - - "minimum_chrome_version": "49", - - "icons": { - "16": "icons/16-production.png", - "32": "icons/32-production.png", - "48": "icons/48-production.png", - "128": "icons/128-production.png" - }, - - "browser_action": { - "default_icon": { - "16": "icons/16-disabled.png", - "32": "icons/32-disabled.png", - "48": "icons/48-disabled.png", - "128": "icons/128-disabled.png" - }, - - "default_popup": "popups/disabled.html" - }, - - "devtools_page": "main.html", - - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", - "web_accessible_resources": [ "main.html", "panel.html", "build/backend.js"], - - "background": { - "scripts": [ "build/background.js" ], - "persistent": false - }, - - "permissions": [ - "file:///*", - "http://*/*", - "https://*/*" - ], - - "content_scripts": [ - { - "matches": [""], - "js": ["build/inject.js"], - "run_at": "document_start" - } - ] -} diff --git a/shells/chrome/test.js b/shells/chrome/test.js deleted file mode 100644 index 3876c5d5a1..0000000000 --- a/shells/chrome/test.js +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node - -const chromeLaunch = require('chrome-launch'); // eslint-disable-line import/no-extraneous-dependencies -const {resolve} = require('path'); - -const EXTENSION_PATH = resolve('shells/chrome/build/unpacked'); -const START_URL = 'https://facebook.github.io/react/'; - -chromeLaunch(START_URL, { - args: [`--load-extension=${EXTENSION_PATH}`], -}); diff --git a/shells/electron/Readme.md b/shells/electron/Readme.md deleted file mode 100644 index 132744148d..0000000000 --- a/shells/electron/Readme.md +++ /dev/null @@ -1,7 +0,0 @@ -# The Stand-Alone App - -This is useful for working on a React Native app *without* debugging in -chrome. The js runs in jscore, and the devtools talk to it through a -websocket. - -The shell is located in `packages/react-devtools` in this repository. diff --git a/shells/firefox/README.md b/shells/firefox/README.md deleted file mode 100644 index c4bbc8d2de..0000000000 --- a/shells/firefox/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# The Firefox extension - -The source code for this extension has moved to `shells/webextension`. - -Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:firefox` from the root directory. - -## Testing in Firefox - - 1. Build the extension: `node build` - 1. Follow the on-screen instructions. - -You can test upcoming releases of Firefox by downloading the Beta or Nightly build from the [Firefox releases](https://www.mozilla.org/en-US/firefox/channel/desktop/) page and then following the on-screen instructions after building. diff --git a/shells/firefox/build.js b/shells/firefox/build.js deleted file mode 100644 index 5954b2c23d..0000000000 --- a/shells/firefox/build.js +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright 2004-present Facebook. All Rights Reserved. - */ - -'use strict'; - -const chalk = require('chalk'); -const {join} = require('path'); -const build = require('../webextension/build'); - -const main = async () => { - await build( - 'firefox', - join(__dirname, 'manifest.json'), - join(__dirname, 'build') - ); - - console.log(chalk.green('\nThe Firefox extension has been built!')); - console.log(chalk.green('You can test this build by running:')); - console.log(chalk.gray('\n# From the react-devtools root directory:')); - console.log('yarn run test:firefox'); - console.log(chalk.gray('\n# You can also test against upcoming Firefox releases.')); - console.log(chalk.gray('# First download a release from https://www.mozilla.org/en-US/firefox/channel/desktop/')); - console.log(chalk.gray('# And then tell web-ext which release to use (eg firefoxdeveloperedition, nightly, beta):')); - console.log('WEB_EXT_FIREFOX=nightly yarn run test:firefox'); - console.log(chalk.gray('\n# You can test against older versions too:')); - console.log('WEB_EXT_FIREFOX=/Applications/Firefox52.app/Contents/MacOS/firefox-bin yarn run test:firefox'); -}; - -main(); diff --git a/shells/firefox/manifest.json b/shells/firefox/manifest.json deleted file mode 100644 index ebe72365b4..0000000000 --- a/shells/firefox/manifest.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "manifest_version": 2, - "name": "React Developer Tools", - "description": "Adds React debugging tools to the Firefox Developer Tools.", - "version": "3.4.3", - - "applications": { - "gecko": { - "id": "@react-devtools", - "strict_min_version": "54.0" - } - }, - - "icons": { - "16": "icons/16-production.png", - "32": "icons/32-production.png", - "48": "icons/48-production.png", - "128": "icons/128-production.png" - }, - - "browser_action": { - "default_icon": { - "16": "icons/16-disabled.png", - "32": "icons/32-disabled.png", - "48": "icons/48-disabled.png", - "128": "icons/128-disabled.png" - }, - - "default_popup": "popups/disabled.html", - "browser_style": true - }, - - "devtools_page": "main.html", - - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", - "web_accessible_resources": [ "main.html", "panel.html", "build/backend.js"], - - "background": { - "scripts": [ "build/background.js" ], - "persistent": false - }, - - "permissions": [ - "file:///*", - "http://*/*", - "https://*/*" - ], - - "content_scripts": [ - { - "matches": [""], - "js": ["build/inject.js"], - "run_at": "document_start" - } - ] -} \ No newline at end of file diff --git a/shells/firefox/test.js b/shells/firefox/test.js deleted file mode 100644 index d8c7e23400..0000000000 --- a/shells/firefox/test.js +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright 2004-present Facebook. All Rights Reserved. - */ - -'use strict'; - -const {exec} = require('child-process-promise'); -const {Finder} = require('firefox-profile'); -const {resolve} = require('path'); - -const EXTENSION_PATH = resolve('shells/firefox/build/unpacked'); -const START_URL = 'https://facebook.github.io/react/'; - -const main = async () => { - const finder = new Finder(); - - // Use default Firefox profile for testing purposes. - // This prevents users from having to re-login-to sites before testing. - const findPathPromise = new Promise((resolvePromise, rejectPromise) => { - finder.getPath('default', (error, profile) => { - if (error) { - rejectPromise(error); - } else { - resolvePromise(profile); - } - }); - }); - - const options = [ - `--source-dir=${EXTENSION_PATH}`, - `--start-url=${START_URL}`, - '--browser-console', - ]; - - try { - const path = await findPathPromise; - const trimmedPath = path.replace(' ', '\\ '); - options.push(`--firefox-profile=${trimmedPath}`); - } catch (err) { - console.warn('Could not find default profile, using temporary profile.'); - } - - try { - await exec(`web-ext run ${options.join(' ')}`); - } catch (err) { - console.error('`web-ext run` failed', err.stdout, err.stderr); - } -}; - -main(); diff --git a/shells/plain/Readme.md b/shells/plain/Readme.md deleted file mode 100644 index da0f534c0f..0000000000 --- a/shells/plain/Readme.md +++ /dev/null @@ -1,22 +0,0 @@ -# Pure HTML Example - -![Screenshot](/images/plain-shell.png) - -This is an example of using the devtools outside of chrome/firefox/etc. In -order to simulate the same network restrictions found in a plugin environment, -the inspection target is within an iframe, and communication is done via -`.postMessage`. - -You have to run `./build.sh` (or `./build.sh --watch`) to be able to run this. Then -open `index.html`. - -Here's an overview of how things work: - -- the devtools ui loads -- an iframe is created, and the global (`__REACT_DEVTOOLS_GLOBAL_HOOK__`) is - setup. -- the inspection target script is added to the iframe (from `/test/example/`) -- the devtools backend is added to the iframe - -And yes, you can use this to inspect the inspector :) but remember to enable -file access for your browser's react-devtools extension. \ No newline at end of file diff --git a/shells/plain/backend.js b/shells/plain/backend.js deleted file mode 100644 index fe3804e7b9..0000000000 --- a/shells/plain/backend.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var Agent = require('../../agent/Agent'); -var ProfileCollector = require('../../plugins/Profiler/ProfileCollector'); -var TraceUpdatesBackendManager = require('../../plugins/TraceUpdates/TraceUpdatesBackendManager'); -var Bridge = require('../../agent/Bridge'); -var setupHighlighter = require('../../frontend/Highlighter/setup'); -var setupProfiler = require('../../plugins/Profiler/backend'); -var inject = require('../../agent/inject'); - -var wall = { - listen(fn) { - window.addEventListener('message', evt => { - if (evt.source === window.parent) { - fn(evt.data); - } - }); - }, - send(data) { - window.parent.postMessage(data, '*'); - }, -}; - -var bridge = new Bridge(wall); -var agent = new Agent(window); -agent.addBridge(bridge); - -inject(window.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent); - -setupHighlighter(agent); -setupProfiler(bridge, agent, window.__REACT_DEVTOOLS_GLOBAL_HOOK__); - -ProfileCollector.init(agent); -TraceUpdatesBackendManager.init(agent); diff --git a/shells/plain/build.sh b/shells/plain/build.sh deleted file mode 100755 index c9cb1b3082..0000000000 --- a/shells/plain/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -ex - -../../node_modules/.bin/webpack --config webpack.config.js $@ diff --git a/shells/plain/container.js b/shells/plain/container.js deleted file mode 100644 index ae8504d994..0000000000 --- a/shells/plain/container.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var React = require('react'); -var ReactDOM = require('react-dom'); -var invariant = require('assert'); -var nullthrows = require('nullthrows').default; - -var installGlobalHook = require('../../backend/installGlobalHook'); - -window.React = React; - -var Panel = require('../../frontend/Panel'); - -var target = document.getElementById('target'); -invariant(target instanceof HTMLIFrameElement); - -var appSrc = target.getAttribute('data-app-src') || '../../test/example/build/target.js'; -var devtoolsSrc = target.getAttribute('data-devtools-src') || './build/backend.js'; - -var win = target.contentWindow; -installGlobalHook(win); - -var iframeSrc = document.getElementById('iframe-src'); -if (iframeSrc) { - win.document.documentElement.innerHTML = iframeSrc.textContent.replace(/>/g, '>'); -} - -window.addEventListener('keydown', function(e) { - const body = nullthrows(document.body); - if (e.altKey && e.keyCode === 68) { // Alt + D - if (body.className === 'devtools-bottom') { - body.className = 'devtools-right'; - } else { - body.className = 'devtools-bottom'; - } - } -}); - -var config = { - alreadyFoundReact: true, - showHiddenThemes: true, - inject(done) { - inject(devtoolsSrc, () => { - var wall = { - listen(fn) { - win.parent.addEventListener('message', evt => { - if (evt.source === win) { - fn(evt.data); - } - }); - }, - send(data) { - win.postMessage(data, '*'); - }, - }; - done(wall); - }); - }, -}; - -function inject(src, done) { - if (!src || src === 'false') { - done(); - return; - } - invariant(target instanceof HTMLIFrameElement); - var script = target.contentDocument.createElement('script'); - script.src = src; - script.onload = done; - nullthrows(target.contentDocument.body).appendChild(script); -} - -function injectMany(sources, done) { - if (sources.length === 1) { - inject(sources[0], done); - return; - } - inject(sources[0], () => injectMany(sources.slice(1), done)); -} - -var sources = appSrc.split('|'); - -injectMany(sources, () => { - var node = nullthrows(document.getElementById('container')); - - ReactDOM.render(, node); -}); diff --git a/shells/plain/index.html b/shells/plain/index.html deleted file mode 100644 index 1e3398d027..0000000000 --- a/shells/plain/index.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - Container Example - - - - -
    - - - diff --git a/shells/plain/webpack.config.js b/shells/plain/webpack.config.js deleted file mode 100644 index 15878d8498..0000000000 --- a/shells/plain/webpack.config.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const {readFileSync} = require('fs'); -const {resolve} = require('path'); - -module.exports = { - mode: 'development', - devtool: false, - entry: { - backend: './backend.js', - container: './container.js', - }, - output: { - path: __dirname + '/build', - filename: '[name].js', - }, - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), - }, - ], - }, -}; diff --git a/shells/theme-preview/README.md b/shells/theme-preview/README.md deleted file mode 100644 index 2f0c6ab7a3..0000000000 --- a/shells/theme-preview/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Theme Preview site - -This is the source for the theme preview site running at [facebook.github.io/react-devtools](http://facebook.github.io/react-devtools). - -### Development - -To build a local version of the preview site run: `build.sh` - -If you're making frequent changes, you can also run in "watch" mode: `../../node_modules/.bin/webpack --config webpack.config.js --watch` - -After the site has been built, open the `index.html` file (in this directory) in your browser. - -### Deployment - -To deploy this site to `gh-pages` run `npm run deploy` from the root directory. - diff --git a/shells/theme-preview/application.js b/shells/theme-preview/application.js deleted file mode 100644 index 701b6aaaf0..0000000000 --- a/shells/theme-preview/application.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); -const ReactDOM = require('react-dom'); -const nullthrows = require('nullthrows').default; - -const Application = require('./source/Application'); -const {deserialize, serialize} = require('../../frontend/Themes/Serializer'); -const Themes = require('../../frontend/Themes/Themes'); - -const {location} = window; - -let theme; - -function updateTheme(updatedTheme) { - const serialized = encodeURI(serialize(updatedTheme)); - - if (history.pushState) { - const url = `${location.protocol}//${location.host}${location.pathname}?theme=${serialized}`; - - window.history.pushState({path: url}, '', url); - } -} - -function parseTheme() { - const match = location.href.match(/theme=(.+)/); - - theme = match - ? deserialize(decodeURI(match[1]), Themes.ChromeDefault) - : Themes.ChromeDefault; -} - -function renderApplication() { - ReactDOM.render( - , - nullthrows(document.getElementById('application')) - ); -} - -window.addEventListener('popstate', () => { - parseTheme(); - renderApplication(); -}); - -parseTheme(); -renderApplication(); diff --git a/shells/theme-preview/build.sh b/shells/theme-preview/build.sh deleted file mode 100755 index c9cb1b3082..0000000000 --- a/shells/theme-preview/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -ex - -../../node_modules/.bin/webpack --config webpack.config.js $@ diff --git a/shells/theme-preview/index.html b/shells/theme-preview/index.html deleted file mode 100644 index c39dddf51a..0000000000 --- a/shells/theme-preview/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - Theme - - - -
    - - - - diff --git a/shells/theme-preview/source/Application.js b/shells/theme-preview/source/Application.js deleted file mode 100644 index 8581d08b21..0000000000 --- a/shells/theme-preview/source/Application.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); - -const React = require('react'); - -const LeftPane = require('./LeftPane'); -const {sansSerif} = require('../../../frontend/Themes/Fonts'); -const RightPane = require('./RightPane'); -const SplitPane = require('../../../frontend/SplitPane'); -const {serialize} = require('../../../frontend/Themes/Serializer'); - -import type {Theme} from '../../../frontend/types'; - -type Props = { - theme: Theme, - updateTheme: (Theme) => void, -}; - -class Application extends React.Component { - _onInput: (event: Event) => void; - - constructor(props: Props, context: any) { - super(props, context); - - this._onInput = this._onInput.bind(this); - } - - getChildContext() { - return { - theme: this.props.theme, - }; - } - - render() { - const {theme} = this.props; - - return ( -
    -
    - theme for React DevTools -
    -
    - Theme preview: -
    -
    - } - right={() => } - /> -
    -
    - Paste this text into React DevTools to import the theme: -
    -
    - {serialize(theme)} -
    -
    - ); - } - - _onInput(event: Event) { - const {theme, updateTheme} = this.props; - - updateTheme({ - ...theme, - displayName: (event.target: any).innerText, - }); - } -} - -Application.childContextTypes = { - store: PropTypes.object, - theme: PropTypes.object, -}; -Application.propTypes = { - theme: PropTypes.object, - updateTheme: PropTypes.func, -}; - -const applicationStyle = (theme: Theme) => ({ - height: '100%', - width: '600px', - maxWidth: '100%', - margin: '0 auto', - fontFamily: sansSerif.family, - fontSize: sansSerif.sizes.normal, -}); - -const themeWrapper = (theme: Theme) => ({ - borderRadius: '0.25rem', - overflow: 'hidden', - backgroundColor: theme.base00, - color: theme.base05, -}); - -const staticStyles = { - createdByRow: { - margin: '0 0 0.25rem', - }, - header: { - marginTop: '1rem', - marginBottom: '0.5rem', - fontSize: sansSerif.sizes.large, - }, - label: { - marginTop: '1rem', - marginBottom: '0.25rem', - }, - themeText: { - padding: '0.25rem', - backgroundColor: '#ffe', - border: '1px solid #ddd', - borderRadius: '0.25rem', - wordWrap: 'break-word', - }, -}; - -module.exports = Application; diff --git a/shells/theme-preview/source/ExampleIconButton.js b/shells/theme-preview/source/ExampleIconButton.js deleted file mode 100644 index f108665357..0000000000 --- a/shells/theme-preview/source/ExampleIconButton.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const React = require('react'); - -const Hoverable = require('../../../frontend/Hoverable'); -const SvgIcon = require('../../../frontend/SvgIcon'); - -import type {Theme} from '../../../frontend/types'; - -const ExampleIconButton = Hoverable( - ({ isHovered, onMouseEnter, onMouseLeave, path, theme }) => ( - - ) -); - -const iconButtonStyle = (isHovered: boolean, theme: Theme) => ({ - display: 'flex', - background: 'none', - border: 'none', - color: isHovered ? theme.state06 : 'inherit', -}); - -module.exports = ExampleIconButton; diff --git a/shells/theme-preview/source/LeftPane.js b/shells/theme-preview/source/LeftPane.js deleted file mode 100644 index 49d0ba5808..0000000000 --- a/shells/theme-preview/source/LeftPane.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const PropTypes = require('prop-types'); - -const React = require('react'); - -const ExampleIconButton = require('./ExampleIconButton'); -const Icons = require('../../../frontend/Icons'); -const Input = require('../../../frontend/Input'); -const Preview = require('../../../frontend/Themes/Preview'); -const SettingsCheckbox = require('../../../frontend/SettingsCheckbox'); - -import type {Theme} from '../../../frontend/types'; - -type Context = { - theme: Theme, -}; - -const LeftPane = (_: any, {theme}: Context) => ( -
    -
    -
    - - - -
    - -
    - -
      -
    • div
    • -
    • Grandparent
    • -
    • Parent
    • -
    • div
    • -
    -
    -); - -LeftPane.contextTypes = { - theme: PropTypes.object, -}; - -const noop = () => {}; - -const inputStyle = (theme: Theme) => ({ - padding: '0.25rem', - border: `1px solid ${theme.base02}`, - outline: 'none', - borderRadius: '0.25rem', -}); - -const listItemStyle = (isSelected: boolean, isComposite: boolean, theme: Theme) => { - let color; - if (isSelected) { - color = theme.state02; - } else if (isComposite) { - color = theme.special05; - } - - return { - backgroundColor: isSelected ? theme.state00 : 'transparent', - color, - cursor: isSelected ? 'default' : 'pointer', - padding: '0.25rem 0.5rem', - WebkitUserSelect: 'none', - MozUserSelect: 'none', - userSelect: 'none', - display: 'inline-block', - marginRight: '2px', - }; -}; - -const listStyle = (theme: Theme) => ({ - listStyle: 'none', - padding: 0, - margin: 0, - maxHeight: '80px', - overflow: 'auto', - backgroundColor: theme.base01, - borderTop: `1px solid ${theme.base03}`, -}); - -const menuRowStyle = (theme: Theme) => ({ - display: 'flex', - direction: 'row', - padding: '0.25rem', - borderBottom: `1px solid ${theme.base03}`, - backgroundColor: theme.base01, -}); - - -const staticStyles = { - container: { - width: '100%', - }, - iconsColumn: { - display: 'flex', - direction: 'row', - alignItems: 'center', - flexGrow: '1', - }, -}; - -module.exports = LeftPane; diff --git a/shells/theme-preview/source/RightPane.js b/shells/theme-preview/source/RightPane.js deleted file mode 100644 index 7ee0b37e60..0000000000 --- a/shells/theme-preview/source/RightPane.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -const Immutable = require('immutable'); -const PropTypes = require('prop-types'); -const React = require('react'); - -const PropState = require('../../../frontend/PropState'); - -type Props = {}; - -class RightPane extends React.Component { - getChildContext() { - return { - onChange: noop, - }; - } - - render() { - return ( - - ); - } -} - -RightPane.childContextTypes = { - onChange: PropTypes.func, -}; - -const fauxNode = Immutable.Map({ - canUpdate: false, - children: ['grandparent'], - name: 'div', - nodeType: 'Composite', - props: { - boolean: true, - integer: 123, - string: 'foobar', - }, - source: { - fileName: 'grandparent.js', - lineNumber: '10', - }, - state: {}, -}); - -const noop = () => {}; - -module.exports = RightPane; diff --git a/shells/theme-preview/webpack.config.js b/shells/theme-preview/webpack.config.js deleted file mode 100644 index e3bcffe8ee..0000000000 --- a/shells/theme-preview/webpack.config.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const {readFileSync} = require('fs'); -const {resolve} = require('path'); - -module.exports = { - mode: 'development', - devtool: 'cheap-module-eval-source-map', - entry: { - application: './application.js', - }, - output: { - path: __dirname + '/build', - filename: '[name].js', - }, - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), - }, - ], - }, -}; diff --git a/shells/webextension/Readme.md b/shells/webextension/Readme.md deleted file mode 100644 index b583c45ce1..0000000000 --- a/shells/webextension/Readme.md +++ /dev/null @@ -1,24 +0,0 @@ -# WebExtension Plugin - -This is shared source code for the Chrome and Firefox extensions. - -Changes to this code should be tested in both browsers before being submitted. Refer to the instructions in `shells/chrome` and `shells/firefox` for how to test each browser. - -## Insulating the environment - -React DevTools has part of the code (the backend + agent) running in the same javascript context as the inspected page, which makes the code vulnerable to environmental inconsistencies. For example, the backend uses the es6 `Map` class and normally expects it to be available in the global scope. If a user script has overridden this, the backend breaks. - -To prevent this, the content script [`src/GlobalHook.js`](src/GlobalHook.js), which runs before any user js, saves the native values we depend on to the `__REACT_DEVTOOLS_GLOBAL_HOOK__` global. These are: - -- Set -- Map -- WeakMap -- Object.create - -Then in `webpack.backend.js`, these saved values are substituted for the globally referenced name (e.g. `Map` gets replaced with `window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap`). - -## Fixing document.create - -React Native sets `document.createElement` to `null` in order to convince js libs that they are not running in a browser environment while `debug in chrome` is enabled. - -To deal with this, [`src/inject.js`](src/inject.js) calls `document.constructor.prototype.createElement` when it needs to create a ` - - - - diff --git a/shells/webextension/panel.html b/shells/webextension/panel.html deleted file mode 100644 index 60fd1bdf13..0000000000 --- a/shells/webextension/panel.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - -
    Unable to find React on the page.
    - - - diff --git a/shells/webextension/popups/deadcode.html b/shells/webextension/popups/deadcode.html deleted file mode 100644 index 5e74dc06c4..0000000000 --- a/shells/webextension/popups/deadcode.html +++ /dev/null @@ -1,32 +0,0 @@ - - -

    - This page includes an extra development build of React. 🚧 -

    -

    - The React build on this page includes both development and production versions because dead code elimination has not been applied correctly. -
    -
    - This makes its size larger, and causes React to run slower. -
    -
    - Make sure to set up dead code elimination before deployment. -

    -
    -

    - Open the developer tools, and the React tab will appear to the right. -

    diff --git a/shells/webextension/popups/development.html b/shells/webextension/popups/development.html deleted file mode 100644 index 9c2089cc2f..0000000000 --- a/shells/webextension/popups/development.html +++ /dev/null @@ -1,28 +0,0 @@ - - -

    - This page is using the development build of React. 🚧 -

    -

    - Note that the development build is not suitable for production. -
    - Make sure to use the production build before deployment. -

    -
    -

    - Open the developer tools, and the React tab will appear to the right. -

    diff --git a/shells/webextension/popups/disabled.html b/shells/webextension/popups/disabled.html deleted file mode 100644 index a89b178d49..0000000000 --- a/shells/webextension/popups/disabled.html +++ /dev/null @@ -1,21 +0,0 @@ - - -

    - This page doesn’t appear to be using React. -
    - If this seems wrong, follow the troubleshooting instructions. -

    diff --git a/shells/webextension/popups/outdated.html b/shells/webextension/popups/outdated.html deleted file mode 100644 index a6ec12bcaf..0000000000 --- a/shells/webextension/popups/outdated.html +++ /dev/null @@ -1,29 +0,0 @@ - - -

    - This page is using an outdated version of React. ⌛ -

    -

    - We recommend updating React to ensure that you receive important bugfixes and performance improvements. -
    -
    - You can find the upgrade instructions on the React blog. -

    -
    -

    - Open the developer tools, and the React tab will appear to the right. -

    diff --git a/shells/webextension/popups/production.html b/shells/webextension/popups/production.html deleted file mode 100644 index 1b65eb5b21..0000000000 --- a/shells/webextension/popups/production.html +++ /dev/null @@ -1,21 +0,0 @@ - - -

    - This page is using the production build of React. ✅ -
    - Open the developer tools, and the React tab will appear to the right. -

    diff --git a/shells/webextension/popups/shared.js b/shells/webextension/popups/shared.js deleted file mode 100644 index e4f5eaa9fc..0000000000 --- a/shells/webextension/popups/shared.js +++ /dev/null @@ -1,22 +0,0 @@ -/* globals chrome */ - -document.addEventListener('DOMContentLoaded', function() { - // Make links work - var links = document.getElementsByTagName('a'); - for (var i = 0; i < links.length; i++) { - (function() { - var ln = links[i]; - var location = ln.href; - ln.onclick = function() { - chrome.tabs.create({active: true, url: location}); - }; - })(); - } - - // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=428044 - document.body.style.opacity = 0; - document.body.style.transition = 'opacity ease-out .4s'; - requestAnimationFrame(function() { - document.body.style.opacity = 1; - }); -}); diff --git a/shells/webextension/popups/unminified.html b/shells/webextension/popups/unminified.html deleted file mode 100644 index 553c9ac6ac..0000000000 --- a/shells/webextension/popups/unminified.html +++ /dev/null @@ -1,31 +0,0 @@ - - -

    - This page is using an unminified build of React. 🚧 -

    -

    - The React build on this page appears to be unminified. -
    - This makes its size larger, and causes React to run slower. -
    -
    - Make sure to set up minification before deployment. -

    -
    -

    - Open the developer tools, and the React tab will appear to the right. -

    diff --git a/shells/webextension/src/GlobalHook.js b/shells/webextension/src/GlobalHook.js deleted file mode 100644 index 719ed2fa66..0000000000 --- a/shells/webextension/src/GlobalHook.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* globals chrome */ - -// Inject a `__REACT_DEVTOOLS_GLOBAL_HOOK__` global so that React can detect that the -// devtools are installed (and skip its suggestion to install the devtools). - -var installGlobalHook = require('../../../backend/installGlobalHook.js'); -var nullthrows = require('nullthrows').default; - -var lastDetectionResult; - -// We want to detect when a renderer attaches, and notify the "background -// page" (which is shared between tabs and can highlight the React icon). -// Currently we are in "content script" context, so we can't listen -// to the hook directly (it will be injected directly into the page). -// So instead, the hook will use postMessage() to pass message to us here. -// And when this happens, we'll send a message to the "background page". -window.addEventListener('message', function(evt) { - if (evt.source === window && evt.data && evt.data.source === 'react-devtools-detector') { - lastDetectionResult = { - hasDetectedReact: true, - reactBuildType: evt.data.reactBuildType, - }; - chrome.runtime.sendMessage(lastDetectionResult); - } -}); - -// NOTE: Firefox WebExtensions content scripts are still alive and not re-injected -// while navigating the history to a document that has not been destroyed yet, -// replay the last detection result if the content script is active and the -// document has been hidden and shown again. -window.addEventListener('pageshow', function(evt) { - if (!lastDetectionResult || evt.target !== window.document) { - return; - } - chrome.runtime.sendMessage(lastDetectionResult); -}); - -var detectReact = ` -window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function(evt) { - window.postMessage({ - source: 'react-devtools-detector', - reactBuildType: evt.reactBuildType, - }, '*'); -}); -`; -var saveNativeValues = ` -window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create; -window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map; -window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap; -window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set; -`; - -var js = ( - ';(' + installGlobalHook.toString() + '(window))' + - saveNativeValues + - detectReact -); - -// This script runs before the element is created, so we add the script -// to instead. -var script = document.createElement('script'); -script.textContent = js; -nullthrows(document.documentElement).appendChild(script); -nullthrows(script.parentNode).removeChild(script); diff --git a/shells/webextension/src/backend.js b/shells/webextension/src/backend.js deleted file mode 100644 index 4f91541578..0000000000 --- a/shells/webextension/src/backend.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -// Do not add requires here! -// Running module factories is intentionally delayed until we know the hook exists. -// This is to avoid issues like: https://github.com/facebook/react-devtools/issues/1039 - -window.addEventListener('message', welcome); -function welcome(evt) { - if (evt.source !== window || evt.data.source !== 'react-devtools-content-script') { - return; - } - - window.removeEventListener('message', welcome); - setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__); -} - -function setup(hook) { - var Agent = require('../../../agent/Agent'); - var ProfileCollector = require('../../../plugins/Profiler/ProfileCollector'); - var TraceUpdatesBackendManager = require('../../../plugins/TraceUpdates/TraceUpdatesBackendManager'); - var Bridge = require('../../../agent/Bridge'); - var inject = require('../../../agent/inject'); - var setupRNStyle = require('../../../plugins/ReactNativeStyle/setupBackend'); - var setupHighlighter = require('../../../frontend/Highlighter/setup'); - var setupProfiler = require('../../../plugins/Profiler/backend'); - - var listeners = []; - - var wall = { - listen(fn) { - var listener = evt => { - if (evt.source !== window || !evt.data || evt.data.source !== 'react-devtools-content-script' || !evt.data.payload) { - return; - } - fn(evt.data.payload); - }; - listeners.push(listener); - window.addEventListener('message', listener); - }, - send(data) { - window.postMessage({ - source: 'react-devtools-bridge', - payload: data, - }, '*'); - }, - }; - - // Note: this is only useful for react-native-web (and equivalents). - // They would have to set this field directly on the hook. - var isRNStyleEnabled = !!hook.resolveRNStyle; - - var bridge = new Bridge(wall); - var agent = new Agent(window, { - rnStyle: isRNStyleEnabled, - }); - agent.addBridge(bridge); - - agent.once('connected', () => { - inject(hook, agent); - }); - - if (isRNStyleEnabled) { - setupRNStyle(bridge, agent, hook.resolveRNStyle); - } - - setupProfiler(bridge, agent, hook); - - agent.on('shutdown', () => { - hook.emit('shutdown'); - listeners.forEach(fn => { - window.removeEventListener('message', fn); - }); - listeners = []; - }); - - setupHighlighter(agent); - ProfileCollector.init(agent); - TraceUpdatesBackendManager.init(agent); -} diff --git a/shells/webextension/src/background.js b/shells/webextension/src/background.js deleted file mode 100644 index deede1f2da..0000000000 --- a/shells/webextension/src/background.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* global chrome */ -var ports = {}; - -var IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0; - -chrome.runtime.onConnect.addListener(function(port) { - var tab = null; - var name = null; - if (isNumeric(port.name)) { - tab = port.name; - name = 'devtools'; - installContentScript(+port.name); - } else { - tab = port.sender.tab.id; - name = 'content-script'; - } - - if (!ports[tab]) { - ports[tab] = { - devtools: null, - 'content-script': null, - }; - } - ports[tab][name] = port; - - if (ports[tab].devtools && ports[tab]['content-script']) { - doublePipe(ports[tab].devtools, ports[tab]['content-script']); - } -}); - -function isNumeric(str: string): boolean { - return +str + '' === str; -} - -function installContentScript(tabId: number) { - chrome.tabs.executeScript(tabId, {file: '/build/contentScript.js'}, function() { - }); -} - -function doublePipe(one, two) { - one.onMessage.addListener(lOne); - function lOne(message) { - // console.log('dv -> rep', message); - two.postMessage(message); - } - two.onMessage.addListener(lTwo); - function lTwo(message) { - // console.log('rep -> dv', message); - one.postMessage(message); - } - function shutdown() { - one.onMessage.removeListener(lOne); - two.onMessage.removeListener(lTwo); - one.disconnect(); - two.disconnect(); - } - one.onDisconnect.addListener(shutdown); - two.onDisconnect.addListener(shutdown); -} - -function setIconAndPopup(reactBuildType, tabId) { - chrome.browserAction.setIcon({ - tabId: tabId, - path: { - '16': 'icons/16-' + reactBuildType + '.png', - '32': 'icons/32-' + reactBuildType + '.png', - '48': 'icons/48-' + reactBuildType + '.png', - '128': 'icons/128-' + reactBuildType + '.png', - }, - }); - chrome.browserAction.setPopup({ - tabId: tabId, - popup: 'popups/' + reactBuildType + '.html', - }); -} - -// Listen to URL changes on the active tab and reset the DevTools icon. -// This prevents non-disabled icons from sticking in Firefox. -// Don't listen to this event in Chrome though. -// It fires more frequently, often after onMessage() has been called. -if (IS_FIREFOX) { - chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { - if (tab.active && changeInfo.status === 'loading') { - setIconAndPopup('disabled', tabId); - } - }); -} - -chrome.runtime.onMessage.addListener((req, sender) => { - // This is sent from the hook content script. - // It tells us a renderer has attached. - if (req.hasDetectedReact && sender.tab) { - // We use browserAction instead of pageAction because this lets us - // display a custom default popup when React is *not* detected. - // It is specified in the manifest. - var reactBuildType = req.reactBuildType; - if (sender.url.indexOf('facebook.github.io/react') !== -1) { - // Cheat: We use the development version on the website because - // it is better for interactive examples. However we're going - // to get misguided bug reports if the extension highlights it - // as using the dev version. We're just going to special case - // our own documentation and cheat. It is acceptable to use dev - // version of React in React docs, but not in any other case. - reactBuildType = 'production'; - } - - setIconAndPopup(reactBuildType, sender.tab.id); - } -}); diff --git a/shells/webextension/src/checkForReact.js b/shells/webextension/src/checkForReact.js deleted file mode 100644 index 3b81019203..0000000000 --- a/shells/webextension/src/checkForReact.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* global chrome */ - -module.exports = function(done: (pageHasReact: boolean) => void) { - chrome.devtools.inspectedWindow.eval(`!!( - (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && Object.keys(window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers).length) || window.React || (window.require && (require('react') || require('React'))) - )`, function(pageHasReact, err) { - done(pageHasReact); - }); -}; diff --git a/shells/webextension/src/contentScript.js b/shells/webextension/src/contentScript.js deleted file mode 100644 index 193484470e..0000000000 --- a/shells/webextension/src/contentScript.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* global chrome */ - -// proxy from main page to devtools (via the background page) -var port = chrome.runtime.connect({ - name: 'content-script', -}); - -port.onMessage.addListener(handleMessageFromDevtools); -port.onDisconnect.addListener(handleDisconnect); -window.addEventListener('message', handleMessageFromPage); - -window.postMessage({ - source: 'react-devtools-content-script', - hello: true, -}, '*'); - -function handleMessageFromDevtools(message) { - window.postMessage({ - source: 'react-devtools-content-script', - payload: message, - }, '*'); -} - -function handleMessageFromPage(evt) { - if (evt.source === window && evt.data && evt.data.source === 'react-devtools-bridge') { - // console.log('page -> rep -> dev', evt.data); - port.postMessage(evt.data.payload); - } -} - -function handleDisconnect() { - window.removeEventListener('message', handleMessageFromPage); - window.postMessage({ - source: 'react-devtools-content-script', - payload: { - type: 'event', - evt: 'shutdown', - }, - }, '*'); -} diff --git a/shells/webextension/src/inject.js b/shells/webextension/src/inject.js deleted file mode 100644 index 4587201a8a..0000000000 --- a/shells/webextension/src/inject.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* global chrome */ - -module.exports = function(scriptName: string, done: () => void) { - var src = ` - // the prototype stuff is in case document.createElement has been modified - (function () { - var script = document.constructor.prototype.createElement.call(document, 'script'); - script.src = "${scriptName}"; - script.charset = "utf-8"; - document.documentElement.appendChild(script); - script.parentNode.removeChild(script); - })() - `; - - chrome.devtools.inspectedWindow.eval(src, function(res, err) { - if (err) { - console.log(err); - } - done(); - }); -}; diff --git a/shells/webextension/src/main.js b/shells/webextension/src/main.js deleted file mode 100644 index fdcc64a81b..0000000000 --- a/shells/webextension/src/main.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* global chrome */ - -var panelCreated = false; - -function createPanelIfReactLoaded() { - if (panelCreated) { - return; - } - chrome.devtools.inspectedWindow.eval(`!!( - (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && Object.keys(window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers).length) || window.React - )`, function(pageHasReact, err) { - if (!pageHasReact || panelCreated) { - return; - } - - clearInterval(loadCheckInterval); - panelCreated = true; - chrome.devtools.panels.create('React', '', 'panel.html', function(panel) { - var reactPanel = null; - panel.onShown.addListener(function(window) { - // when the user switches to the panel, check for an elements tab - // selection - window.panel.getNewSelection(); - reactPanel = window.panel; - reactPanel.resumeTransfer(); - }); - panel.onHidden.addListener(function() { - if (reactPanel) { - reactPanel.hideHighlight(); - reactPanel.pauseTransfer(); - } - }); - }); - }); -} - -chrome.devtools.network.onNavigated.addListener(function() { - createPanelIfReactLoaded(); -}); - -// Check to see if React has loaded once per second in case React is added -// after page load -var loadCheckInterval = setInterval(function() { - createPanelIfReactLoaded(); -}, 1000); - -createPanelIfReactLoaded(); diff --git a/shells/webextension/src/panel.js b/shells/webextension/src/panel.js deleted file mode 100644 index 98a500c3fd..0000000000 --- a/shells/webextension/src/panel.js +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/* global chrome */ - -var checkForReact = require('./checkForReact'); -var inject = require('./inject'); - -import type {Props} from '../../../frontend/Panel'; - -const IS_CHROME = navigator.userAgent.indexOf('Firefox') < 0; - -let browserName; -let themeName; - -if (IS_CHROME) { - browserName = 'Chrome'; - - // chrome.devtools.panels added in Chrome 18. - // chrome.devtools.panels.themeName added in Chrome 54. - themeName = chrome.devtools.panels.themeName === 'dark' - ? 'ChromeDark' - : 'ChromeDefault'; -} else { - browserName = 'Firefox'; - themeName = 'FirefoxLight'; - - // chrome.devtools.panels.themeName added in Firefox 55. - // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.panels/themeName - if (chrome.devtools && chrome.devtools.panels) { - switch (chrome.devtools.panels.themeName) { - case 'dark': - themeName = 'FirefoxDark'; - break; - case 'firebug': - themeName = 'FirefoxFirebug'; - break; - } - } -} - -var config: Props = { - alreadyFoundReact: false, - browserName, - checkForReact, - reload, - themeName, - reloadSubscribe(reloadFn) { - chrome.devtools.network.onNavigated.addListener(reloadFn); - return () => { - chrome.devtools.network.onNavigated.removeListener(reloadFn); - }; - }, - getNewSelection(bridge) { - chrome.devtools.inspectedWindow.eval('window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0'); - bridge.send('checkSelection'); - }, - selectElement(id, bridge) { - bridge.send('putSelectedNode', id); - setTimeout(() => { - chrome.devtools.inspectedWindow.eval('inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$node)'); - }, 100); - }, - showComponentSource(globalPathToInst, globalPathToType) { - var code = ` - if ( - window.${globalPathToType} && - window.${globalPathToType}.prototype && - window.${globalPathToType}.prototype.isReactComponent - ) { - inspect(window.${globalPathToInst}.render); - } else { - inspect(window.${globalPathToType}); - } - `; - chrome.devtools.inspectedWindow.eval(code, (res, err) => { - if (err) { - console.error('Failed to inspect component', err); - } - }); - }, - showAttrSource(path) { - var attrs = '[' + path.map(m => JSON.stringify(m)).join('][') + ']'; - var code = 'inspect(window.$r' + attrs + ')'; - chrome.devtools.inspectedWindow.eval(code, (res, err) => { - if (err) { - console.error('Failed to inspect source', err); - } - }); - }, - executeFn(path) { - var attrs = '[' + path.map(m => JSON.stringify(m)).join('][') + ']'; - var code = 'window.$r' + attrs + '()'; - chrome.devtools.inspectedWindow.eval(code, (res, err) => { - if (err) { - console.error('Failed to call function', err); - } - }); - }, - inject(done) { - inject(chrome.runtime.getURL('build/backend.js'), () => { - var port = chrome.runtime.connect({ - name: '' + chrome.devtools.inspectedWindow.tabId, - }); - var disconnected = false; - - var wall = { - listen(fn) { - port.onMessage.addListener(message => fn(message)); - }, - send(data) { - if (disconnected) { - return; - } - port.postMessage(data); - }, - }; - - port.onDisconnect.addListener(() => { - disconnected = true; - }); - done(wall, () => port.disconnect()); - }); - }, -}; - -var Panel = require('../../../frontend/Panel'); -var React = require('react'); -var ReactDOM = require('react-dom'); -var nullthrows = require('nullthrows').default; - -var node = nullthrows(document.getElementById('container')); - -function reload() { - setTimeout(() => { - ReactDOM.unmountComponentAtNode(node); - node.innerHTML = ''; - ReactDOM.render(, node); - }, 100); -} - -ReactDOM.render(, node); diff --git a/shells/webextension/webpack.backend.js b/shells/webextension/webpack.backend.js deleted file mode 100644 index b313c907af..0000000000 --- a/shells/webextension/webpack.backend.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const {readFileSync} = require('fs'); -const {resolve} = require('path'); - -const __DEV__ = process.env.NODE_ENV !== 'production'; - -module.exports = { - mode: __DEV__ ? 'development' : 'production', - devtool: __DEV__ ? 'cheap-module-eval-source-map' : false, - entry: { - backend: './src/backend.js', - }, - output: { - path: __dirname + '/build', - filename: '[name].js', - }, - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), - }, - ], - }, -}; diff --git a/shells/webextension/webpack.config.js b/shells/webextension/webpack.config.js deleted file mode 100644 index 64429a8ada..0000000000 --- a/shells/webextension/webpack.config.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -const {readFileSync} = require('fs'); -const {resolve} = require('path'); -const webpack = require('webpack'); - -const __DEV__ = process.env.NODE_ENV !== 'production'; - -module.exports = { - mode: __DEV__ ? 'development' : 'production', - devtool: __DEV__ ? 'cheap-module-eval-source-map' : false, - entry: { - background: './src/background.js', - contentScript: './src/contentScript.js', - inject: './src/GlobalHook.js', - main: './src/main.js', - panel: './src/panel.js', - }, - output: { - path: __dirname + '/build', - filename: '[name].js', - }, - plugins: __DEV__ ? [] : [ - // Ensure we get production React - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': '"production"', - }), - ], - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), - }, - ], - }, -}; diff --git a/test/example/build.sh b/test/example/build.sh deleted file mode 100755 index c9cb1b3082..0000000000 --- a/test/example/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -ex - -../../node_modules/.bin/webpack --config webpack.config.js $@ diff --git a/test/example/build/sink.js b/test/example/build/sink.js deleted file mode 100644 index 4d72745273..0000000000 --- a/test/example/build/sink.js +++ /dev/null @@ -1,293 +0,0 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // create a fake namespace object -/******/ // mode & 1: value is a module id, require it -/******/ // mode & 2: merge all properties of value into the ns -/******/ // mode & 4: return value when already ns object -/******/ // mode & 8|1: behave like require -/******/ __webpack_require__.t = function(value, mode) { -/******/ if(mode & 1) value = __webpack_require__(value); -/******/ if(mode & 8) return value; -/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; -/******/ var ns = Object.create(null); -/******/ __webpack_require__.r(ns); -/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); -/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); -/******/ return ns; -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = "./sink.js"); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "../../node_modules/object-assign/index.js": -/*!***************************************************************************************!*\ - !*** /Users/bvaughn/Documents/git/react-devtools/node_modules/object-assign/index.js ***! - \***************************************************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("/* eslint-disable no-unused-vars */\n\n\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar propIsEnumerable = Object.prototype.propertyIsEnumerable;\n\nfunction toObject(val) {\n if (val === null || val === undefined) {\n throw new TypeError('Object.assign cannot be called with null or undefined');\n }\n\n return Object(val);\n}\n\nmodule.exports = Object.assign || function (target, source) {\n var from;\n var to = toObject(target);\n var symbols;\n\n for (var s = 1; s < arguments.length; s++) {\n from = Object(arguments[s]);\n\n for (var key in from) {\n if (hasOwnProperty.call(from, key)) {\n to[key] = from[key];\n }\n }\n\n if (Object.getOwnPropertySymbols) {\n symbols = Object.getOwnPropertySymbols(from);\n\n for (var i = 0; i < symbols.length; i++) {\n if (propIsEnumerable.call(from, symbols[i])) {\n to[symbols[i]] = from[symbols[i]];\n }\n }\n }\n }\n\n return to;\n};//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi4vLi4vbm9kZV9tb2R1bGVzL29iamVjdC1hc3NpZ24vaW5kZXguanMuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vL1VzZXJzL2J2YXVnaG4vRG9jdW1lbnRzL2dpdC9yZWFjdC1kZXZ0b29scy9ub2RlX21vZHVsZXMvb2JqZWN0LWFzc2lnbi9pbmRleC5qcz8wNzM5Il0sInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIG5vLXVudXNlZC12YXJzICovXG4ndXNlIHN0cmljdCc7XG52YXIgaGFzT3duUHJvcGVydHkgPSBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5O1xudmFyIHByb3BJc0VudW1lcmFibGUgPSBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlO1xuXG5mdW5jdGlvbiB0b09iamVjdCh2YWwpIHtcblx0aWYgKHZhbCA9PT0gbnVsbCB8fCB2YWwgPT09IHVuZGVmaW5lZCkge1xuXHRcdHRocm93IG5ldyBUeXBlRXJyb3IoJ09iamVjdC5hc3NpZ24gY2Fubm90IGJlIGNhbGxlZCB3aXRoIG51bGwgb3IgdW5kZWZpbmVkJyk7XG5cdH1cblxuXHRyZXR1cm4gT2JqZWN0KHZhbCk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gT2JqZWN0LmFzc2lnbiB8fCBmdW5jdGlvbiAodGFyZ2V0LCBzb3VyY2UpIHtcblx0dmFyIGZyb207XG5cdHZhciB0byA9IHRvT2JqZWN0KHRhcmdldCk7XG5cdHZhciBzeW1ib2xzO1xuXG5cdGZvciAodmFyIHMgPSAxOyBzIDwgYXJndW1lbnRzLmxlbmd0aDsgcysrKSB7XG5cdFx0ZnJvbSA9IE9iamVjdChhcmd1bWVudHNbc10pO1xuXG5cdFx0Zm9yICh2YXIga2V5IGluIGZyb20pIHtcblx0XHRcdGlmIChoYXNPd25Qcm9wZXJ0eS5jYWxsKGZyb20sIGtleSkpIHtcblx0XHRcdFx0dG9ba2V5XSA9IGZyb21ba2V5XTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRpZiAoT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scykge1xuXHRcdFx0c3ltYm9scyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMoZnJvbSk7XG5cdFx0XHRmb3IgKHZhciBpID0gMDsgaSA8IHN5bWJvbHMubGVuZ3RoOyBpKyspIHtcblx0XHRcdFx0aWYgKHByb3BJc0VudW1lcmFibGUuY2FsbChmcm9tLCBzeW1ib2xzW2ldKSkge1xuXHRcdFx0XHRcdHRvW3N5bWJvbHNbaV1dID0gZnJvbVtzeW1ib2xzW2ldXTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblx0fVxuXG5cdHJldHVybiB0bztcbn07XG4iXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///../../node_modules/object-assign/index.js\n"); - -/***/ }), - -/***/ "../../node_modules/react-dom/cjs/react-dom.development.js": -/*!*******************************************************************************************************!*\ - !*** /Users/bvaughn/Documents/git/react-devtools/node_modules/react-dom/cjs/react-dom.development.js ***! - \*******************************************************************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("/** @license React v16.5.2\n * react-dom.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */function _typeof(obj){if(typeof Symbol===\"function\"&&typeof Symbol.iterator===\"symbol\"){_typeof=function _typeof(obj){return typeof obj;};}else{_typeof=function _typeof(obj){return obj&&typeof Symbol===\"function\"&&obj.constructor===Symbol&&obj!==Symbol.prototype?\"symbol\":typeof obj;};}return _typeof(obj);}if(true){(function(){'use strict';var React=__webpack_require__(/*! react */ \"../../node_modules/react/index.js\");var _assign=__webpack_require__(/*! object-assign */ \"../../node_modules/react-dom/node_modules/object-assign/index.js\");var checkPropTypes=__webpack_require__(/*! prop-types/checkPropTypes */ \"../../node_modules/react-dom/node_modules/prop-types/checkPropTypes.js\");var schedule=__webpack_require__(/*! schedule */ \"../../node_modules/schedule/index.js\");var tracing=__webpack_require__(/*! schedule/tracing */ \"../../node_modules/schedule/tracing.js\");/**\n * Use invariant() to assert state which your program assumes to be true.\n *\n * Provide sprintf-style format (only %s is supported) and arguments\n * to provide information about what broke and what you were\n * expecting.\n *\n * The invariant message will be stripped in production, but the invariant\n * will remain to ensure logic does not differ in production.\n */var validateFormat=function validateFormat(){};{validateFormat=function validateFormat(format){if(format===undefined){throw new Error('invariant requires an error message argument');}};}function invariant(condition,format,a,b,c,d,e,f){validateFormat(format);if(!condition){var error=void 0;if(format===undefined){error=new Error('Minified exception occurred; use the non-minified dev environment '+'for the full error message and additional helpful warnings.');}else{var args=[a,b,c,d,e,f];var argIndex=0;error=new Error(format.replace(/%s/g,function(){return args[argIndex++];}));error.name='Invariant Violation';}error.framesToPop=1;// we don't care about invariant's own frame\nthrow error;}}// Relying on the `invariant()` implementation lets us\n// preserve the format and params in the www builds.\n!React?invariant(false,'ReactDOM was loaded before React. Make sure you load the React package before loading ReactDOM.'):void 0;var invokeGuardedCallbackImpl=function invokeGuardedCallbackImpl(name,func,context,a,b,c,d,e,f){var funcArgs=Array.prototype.slice.call(arguments,3);try{func.apply(context,funcArgs);}catch(error){this.onError(error);}};{// In DEV mode, we swap out invokeGuardedCallback for a special version\n// that plays more nicely with the browser's DevTools. The idea is to preserve\n// \"Pause on exceptions\" behavior. Because React wraps all user-provided\n// functions in invokeGuardedCallback, and the production version of\n// invokeGuardedCallback uses a try-catch, all user exceptions are treated\n// like caught exceptions, and the DevTools won't pause unless the developer\n// takes the extra step of enabling pause on caught exceptions. This is\n// untintuitive, though, because even though React has caught the error, from\n// the developer's perspective, the error is uncaught.\n//\n// To preserve the expected \"Pause on exceptions\" behavior, we don't use a\n// try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake\n// DOM node, and call the user-provided callback from inside an event handler\n// for that fake event. If the callback throws, the error is \"captured\" using\n// a global event handler. But because the error happens in a different\n// event loop context, it does not interrupt the normal program flow.\n// Effectively, this gives us try-catch behavior without actually using\n// try-catch. Neat!\n// Check that the browser supports the APIs we need to implement our special\n// DEV version of invokeGuardedCallback\nif(typeof window!=='undefined'&&typeof window.dispatchEvent==='function'&&typeof document!=='undefined'&&typeof document.createEvent==='function'){var fakeNode=document.createElement('react');var invokeGuardedCallbackDev=function invokeGuardedCallbackDev(name,func,context,a,b,c,d,e,f){// If document doesn't exist we know for sure we will crash in this method\n// when we call document.createEvent(). However this can cause confusing\n// errors: https://github.com/facebookincubator/create-react-app/issues/3482\n// So we preemptively throw with a better message instead.\n!(typeof document!=='undefined')?invariant(false,'The `document` global was defined when React was initialized, but is not defined anymore. This can happen in a test environment if a component schedules an update from an asynchronous callback, but the test has already finished running. To solve this, you can either unmount the component at the end of your test (and ensure that any asynchronous operations get canceled in `componentWillUnmount`), or you can change the test itself to be asynchronous.'):void 0;var evt=document.createEvent('Event');// Keeps track of whether the user-provided callback threw an error. We\n// set this to true at the beginning, then set it to false right after\n// calling the function. If the function errors, `didError` will never be\n// set to false. This strategy works even if the browser is flaky and\n// fails to call our global error handler, because it doesn't rely on\n// the error event at all.\nvar didError=true;// Keeps track of the value of window.event so that we can reset it\n// during the callback to let user code access window.event in the\n// browsers that support it.\nvar windowEvent=window.event;// Create an event handler for our fake event. We will synchronously\n// dispatch our fake event using `dispatchEvent`. Inside the handler, we\n// call the user-provided callback.\nvar funcArgs=Array.prototype.slice.call(arguments,3);function callCallback(){// We immediately remove the callback from event listeners so that\n// nested `invokeGuardedCallback` calls do not clash. Otherwise, a\n// nested call would trigger the fake event handlers of any call higher\n// in the stack.\nfakeNode.removeEventListener(evtType,callCallback,false);// We check for window.hasOwnProperty('event') to prevent the\n// window.event assignment in both IE <= 10 as they throw an error\n// \"Member not found\" in strict mode, and in Firefox which does not\n// support window.event.\nif(typeof window.event!=='undefined'&&window.hasOwnProperty('event')){window.event=windowEvent;}func.apply(context,funcArgs);didError=false;}// Create a global error event handler. We use this to capture the value\n// that was thrown. It's possible that this error handler will fire more\n// than once; for example, if non-React code also calls `dispatchEvent`\n// and a handler for that event throws. We should be resilient to most of\n// those cases. Even if our error event handler fires more than once, the\n// last error event is always used. If the callback actually does error,\n// we know that the last error event is the correct one, because it's not\n// possible for anything else to have happened in between our callback\n// erroring and the code that follows the `dispatchEvent` call below. If\n// the callback doesn't error, but the error event was fired, we know to\n// ignore it because `didError` will be false, as described above.\nvar error=void 0;// Use this to track whether the error event is ever called.\nvar didSetError=false;var isCrossOriginError=false;function handleWindowError(event){error=event.error;didSetError=true;if(error===null&&event.colno===0&&event.lineno===0){isCrossOriginError=true;}if(event.defaultPrevented){// Some other error handler has prevented default.\n// Browsers silence the error report if this happens.\n// We'll remember this to later decide whether to log it or not.\nif(error!=null&&_typeof(error)==='object'){try{error._suppressLogging=true;}catch(inner){// Ignore.\n}}}}// Create a fake event type.\nvar evtType='react-'+(name?name:'invokeguardedcallback');// Attach our event handlers\nwindow.addEventListener('error',handleWindowError);fakeNode.addEventListener(evtType,callCallback,false);// Synchronously dispatch our fake event. If the user-provided function\n// errors, it will trigger our global error handler.\nevt.initEvent(evtType,false,false);fakeNode.dispatchEvent(evt);if(didError){if(!didSetError){// The callback errored, but the error event never fired.\nerror=new Error('An error was thrown inside one of your components, but React '+\"doesn't know what it was. This is likely due to browser \"+'flakiness. React does its best to preserve the \"Pause on '+'exceptions\" behavior of the DevTools, which requires some '+\"DEV-mode only tricks. It's possible that these don't work in \"+'your browser. Try triggering the error in production mode, '+'or switching to a modern browser. If you suspect that this is '+'actually an issue with React, please file an issue.');}else if(isCrossOriginError){error=new Error(\"A cross-origin error was thrown. React doesn't have access to \"+'the actual error object in development. '+'See https://fb.me/react-crossorigin-error for more information.');}this.onError(error);}// Remove our event listeners\nwindow.removeEventListener('error',handleWindowError);};invokeGuardedCallbackImpl=invokeGuardedCallbackDev;}}var invokeGuardedCallbackImpl$1=invokeGuardedCallbackImpl;// Used by Fiber to simulate a try-catch.\nvar hasError=false;var caughtError=null;// Used by event system to capture/rethrow the first error.\nvar hasRethrowError=false;var rethrowError=null;var reporter={onError:function onError(error){hasError=true;caughtError=error;}};/**\n * Call a function while guarding against errors that happens within it.\n * Returns an error if it throws, otherwise null.\n *\n * In production, this is implemented using a try-catch. The reason we don't\n * use a try-catch directly is so that we can swap out a different\n * implementation in DEV mode.\n *\n * @param {String} name of the guard to use for logging or debugging\n * @param {Function} func The function to invoke\n * @param {*} context The context to use when calling the function\n * @param {...*} args Arguments for function\n */function invokeGuardedCallback(name,func,context,a,b,c,d,e,f){hasError=false;caughtError=null;invokeGuardedCallbackImpl$1.apply(reporter,arguments);}/**\n * Same as invokeGuardedCallback, but instead of returning an error, it stores\n * it in a global so it can be rethrown by `rethrowCaughtError` later.\n * TODO: See if caughtError and rethrowError can be unified.\n *\n * @param {String} name of the guard to use for logging or debugging\n * @param {Function} func The function to invoke\n * @param {*} context The context to use when calling the function\n * @param {...*} args Arguments for function\n */function invokeGuardedCallbackAndCatchFirstError(name,func,context,a,b,c,d,e,f){invokeGuardedCallback.apply(this,arguments);if(hasError){var error=clearCaughtError();if(!hasRethrowError){hasRethrowError=true;rethrowError=error;}}}/**\n * During execution of guarded functions we will capture the first error which\n * we will rethrow to be handled by the top level error handler.\n */function rethrowCaughtError(){if(hasRethrowError){var error=rethrowError;hasRethrowError=false;rethrowError=null;throw error;}}function hasCaughtError(){return hasError;}function clearCaughtError(){if(hasError){var error=caughtError;hasError=false;caughtError=null;return error;}else{invariant(false,'clearCaughtError was called but no error was captured. This error is likely caused by a bug in React. Please file an issue.');}}/**\n * Injectable ordering of event plugins.\n */var eventPluginOrder=null;/**\n * Injectable mapping from names to event plugin modules.\n */var namesToPlugins={};/**\n * Recomputes the plugin list using the injected plugins and plugin ordering.\n *\n * @private\n */function recomputePluginOrdering(){if(!eventPluginOrder){// Wait until an `eventPluginOrder` is injected.\nreturn;}for(var pluginName in namesToPlugins){var pluginModule=namesToPlugins[pluginName];var pluginIndex=eventPluginOrder.indexOf(pluginName);!(pluginIndex>-1)?invariant(false,'EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `%s`.',pluginName):void 0;if(plugins[pluginIndex]){continue;}!pluginModule.extractEvents?invariant(false,'EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `%s` does not.',pluginName):void 0;plugins[pluginIndex]=pluginModule;var publishedEvents=pluginModule.eventTypes;for(var eventName in publishedEvents){!publishEventForPlugin(publishedEvents[eventName],pluginModule,eventName)?invariant(false,'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',eventName,pluginName):void 0;}}}/**\n * Publishes an event so that it can be dispatched by the supplied plugin.\n *\n * @param {object} dispatchConfig Dispatch configuration for the event.\n * @param {object} PluginModule Plugin publishing the event.\n * @return {boolean} True if the event was successfully published.\n * @private\n */function publishEventForPlugin(dispatchConfig,pluginModule,eventName){!!eventNameDispatchConfigs.hasOwnProperty(eventName)?invariant(false,'EventPluginHub: More than one plugin attempted to publish the same event name, `%s`.',eventName):void 0;eventNameDispatchConfigs[eventName]=dispatchConfig;var phasedRegistrationNames=dispatchConfig.phasedRegistrationNames;if(phasedRegistrationNames){for(var phaseName in phasedRegistrationNames){if(phasedRegistrationNames.hasOwnProperty(phaseName)){var phasedRegistrationName=phasedRegistrationNames[phaseName];publishRegistrationName(phasedRegistrationName,pluginModule,eventName);}}return true;}else if(dispatchConfig.registrationName){publishRegistrationName(dispatchConfig.registrationName,pluginModule,eventName);return true;}return false;}/**\n * Publishes a registration name that is used to identify dispatched events.\n *\n * @param {string} registrationName Registration name to add.\n * @param {object} PluginModule Plugin publishing the event.\n * @private\n */function publishRegistrationName(registrationName,pluginModule,eventName){!!registrationNameModules[registrationName]?invariant(false,'EventPluginHub: More than one plugin attempted to publish the same registration name, `%s`.',registrationName):void 0;registrationNameModules[registrationName]=pluginModule;registrationNameDependencies[registrationName]=pluginModule.eventTypes[eventName].dependencies;{var lowerCasedName=registrationName.toLowerCase();possibleRegistrationNames[lowerCasedName]=registrationName;if(registrationName==='onDoubleClick'){possibleRegistrationNames.ondblclick=registrationName;}}}/**\n * Registers plugins so that they can extract and dispatch events.\n *\n * @see {EventPluginHub}\n */ /**\n * Ordered list of injected plugins.\n */var plugins=[];/**\n * Mapping from event name to dispatch config\n */var eventNameDispatchConfigs={};/**\n * Mapping from registration name to plugin module\n */var registrationNameModules={};/**\n * Mapping from registration name to event name\n */var registrationNameDependencies={};/**\n * Mapping from lowercase registration names to the properly cased version,\n * used to warn in the case of missing event handlers. Available\n * only in true.\n * @type {Object}\n */var possibleRegistrationNames={};// Trust the developer to only use possibleRegistrationNames in true\n/**\n * Injects an ordering of plugins (by plugin name). This allows the ordering\n * to be decoupled from injection of the actual plugins so that ordering is\n * always deterministic regardless of packaging, on-the-fly injection, etc.\n *\n * @param {array} InjectedEventPluginOrder\n * @internal\n * @see {EventPluginHub.injection.injectEventPluginOrder}\n */function injectEventPluginOrder(injectedEventPluginOrder){!!eventPluginOrder?invariant(false,'EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React.'):void 0;// Clone the ordering so it cannot be dynamically mutated.\neventPluginOrder=Array.prototype.slice.call(injectedEventPluginOrder);recomputePluginOrdering();}/**\n * Injects plugins to be used by `EventPluginHub`. The plugin names must be\n * in the ordering injected by `injectEventPluginOrder`.\n *\n * Plugins can be injected as part of page initialization or on-the-fly.\n *\n * @param {object} injectedNamesToPlugins Map from names to plugin modules.\n * @internal\n * @see {EventPluginHub.injection.injectEventPluginsByName}\n */function injectEventPluginsByName(injectedNamesToPlugins){var isOrderingDirty=false;for(var pluginName in injectedNamesToPlugins){if(!injectedNamesToPlugins.hasOwnProperty(pluginName)){continue;}var pluginModule=injectedNamesToPlugins[pluginName];if(!namesToPlugins.hasOwnProperty(pluginName)||namesToPlugins[pluginName]!==pluginModule){!!namesToPlugins[pluginName]?invariant(false,'EventPluginRegistry: Cannot inject two different event plugins using the same name, `%s`.',pluginName):void 0;namesToPlugins[pluginName]=pluginModule;isOrderingDirty=true;}}if(isOrderingDirty){recomputePluginOrdering();}}/**\n * Similar to invariant but only logs a warning if the condition is not met.\n * This can be used to log issues in development environments in critical\n * paths. Removing the logging code for production environments will keep the\n * same logic and follow the same code paths.\n */var warningWithoutStack=function warningWithoutStack(){};{warningWithoutStack=function warningWithoutStack(condition,format){for(var _len=arguments.length,args=Array(_len>2?_len-2:0),_key=2;_key<_len;_key++){args[_key-2]=arguments[_key];}if(format===undefined){throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning '+'message argument');}if(args.length>8){// Check before the condition to catch violations early.\nthrow new Error('warningWithoutStack() currently supports at most 8 arguments.');}if(condition){return;}if(typeof console!=='undefined'){var _args$map=args.map(function(item){return''+item;}),a=_args$map[0],b=_args$map[1],c=_args$map[2],d=_args$map[3],e=_args$map[4],f=_args$map[5],g=_args$map[6],h=_args$map[7];var message='Warning: '+format;// We intentionally don't use spread (or .apply) because it breaks IE9:\n// https://github.com/facebook/react/issues/13610\nswitch(args.length){case 0:console.error(message);break;case 1:console.error(message,a);break;case 2:console.error(message,a,b);break;case 3:console.error(message,a,b,c);break;case 4:console.error(message,a,b,c,d);break;case 5:console.error(message,a,b,c,d,e);break;case 6:console.error(message,a,b,c,d,e,f);break;case 7:console.error(message,a,b,c,d,e,f,g);break;case 8:console.error(message,a,b,c,d,e,f,g,h);break;default:throw new Error('warningWithoutStack() currently supports at most 8 arguments.');}}try{// --- Welcome to debugging React ---\n// This error was thrown as a convenience so that you can use this stack\n// to find the callsite that caused this warning to fire.\nvar argIndex=0;var _message='Warning: '+format.replace(/%s/g,function(){return args[argIndex++];});throw new Error(_message);}catch(x){}};}var warningWithoutStack$1=warningWithoutStack;var getFiberCurrentPropsFromNode=null;var getInstanceFromNode=null;var getNodeFromInstance=null;function setComponentTree(getFiberCurrentPropsFromNodeImpl,getInstanceFromNodeImpl,getNodeFromInstanceImpl){getFiberCurrentPropsFromNode=getFiberCurrentPropsFromNodeImpl;getInstanceFromNode=getInstanceFromNodeImpl;getNodeFromInstance=getNodeFromInstanceImpl;{!(getNodeFromInstance&&getInstanceFromNode)?warningWithoutStack$1(false,'EventPluginUtils.setComponentTree(...): Injected '+'module is missing getNodeFromInstance or getInstanceFromNode.'):void 0;}}var validateEventDispatches=void 0;{validateEventDispatches=function validateEventDispatches(event){var dispatchListeners=event._dispatchListeners;var dispatchInstances=event._dispatchInstances;var listenersIsArr=Array.isArray(dispatchListeners);var listenersLen=listenersIsArr?dispatchListeners.length:dispatchListeners?1:0;var instancesIsArr=Array.isArray(dispatchInstances);var instancesLen=instancesIsArr?dispatchInstances.length:dispatchInstances?1:0;!(instancesIsArr===listenersIsArr&&instancesLen===listenersLen)?warningWithoutStack$1(false,'EventPluginUtils: Invalid `event`.'):void 0;};}/**\n * Dispatch the event to the listener.\n * @param {SyntheticEvent} event SyntheticEvent to handle\n * @param {boolean} simulated If the event is simulated (changes exn behavior)\n * @param {function} listener Application-level callback\n * @param {*} inst Internal component instance\n */function executeDispatch(event,simulated,listener,inst){var type=event.type||'unknown-event';event.currentTarget=getNodeFromInstance(inst);invokeGuardedCallbackAndCatchFirstError(type,listener,undefined,event);event.currentTarget=null;}/**\n * Standard/simple iteration through an event's collected dispatches.\n */function executeDispatchesInOrder(event,simulated){var dispatchListeners=event._dispatchListeners;var dispatchInstances=event._dispatchInstances;{validateEventDispatches(event);}if(Array.isArray(dispatchListeners)){for(var i=0;i} An accumulation of items.\n */function accumulateInto(current,next){!(next!=null)?invariant(false,'accumulateInto(...): Accumulated items must not be null or undefined.'):void 0;if(current==null){return next;}// Both are not empty. Warning: Never call x.concat(y) when you are not\n// certain that x is an Array (x could be a string with concat method).\nif(Array.isArray(current)){if(Array.isArray(next)){current.push.apply(current,next);return current;}current.push(next);return current;}if(Array.isArray(next)){// A bit too dangerous to mutate `next`.\nreturn[current].concat(next);}return[current,next];}/**\n * @param {array} arr an \"accumulation\" of items which is either an Array or\n * a single item. Useful when paired with the `accumulate` module. This is a\n * simple utility that allows us to reason about a collection of items, but\n * handling the case when there is exactly one item (and we do not need to\n * allocate an array).\n * @param {function} cb Callback invoked with each element or a collection.\n * @param {?} [scope] Scope used as `this` in a callback.\n */function forEachAccumulated(arr,cb,scope){if(Array.isArray(arr)){arr.forEach(cb,scope);}else if(arr){cb.call(scope,arr);}}/**\n * Internal queue of events that have accumulated their dispatches and are\n * waiting to have their dispatches executed.\n */var eventQueue=null;/**\n * Dispatches an event and releases it back into the pool, unless persistent.\n *\n * @param {?object} event Synthetic event to be dispatched.\n * @param {boolean} simulated If the event is simulated (changes exn behavior)\n * @private\n */var executeDispatchesAndRelease=function executeDispatchesAndRelease(event,simulated){if(event){executeDispatchesInOrder(event,simulated);if(!event.isPersistent()){event.constructor.release(event);}}};var executeDispatchesAndReleaseSimulated=function executeDispatchesAndReleaseSimulated(e){return executeDispatchesAndRelease(e,true);};var executeDispatchesAndReleaseTopLevel=function executeDispatchesAndReleaseTopLevel(e){return executeDispatchesAndRelease(e,false);};function isInteractive(tag){return tag==='button'||tag==='input'||tag==='select'||tag==='textarea';}function shouldPreventMouseEvent(name,type,props){switch(name){case'onClick':case'onClickCapture':case'onDoubleClick':case'onDoubleClickCapture':case'onMouseDown':case'onMouseDownCapture':case'onMouseMove':case'onMouseMoveCapture':case'onMouseUp':case'onMouseUpCapture':return!!(props.disabled&&isInteractive(type));default:return false;}}/**\n * This is a unified interface for event plugins to be installed and configured.\n *\n * Event plugins can implement the following properties:\n *\n * `extractEvents` {function(string, DOMEventTarget, string, object): *}\n * Required. When a top-level event is fired, this method is expected to\n * extract synthetic events that will in turn be queued and dispatched.\n *\n * `eventTypes` {object}\n * Optional, plugins that fire events must publish a mapping of registration\n * names that are used to register listeners. Values of this mapping must\n * be objects that contain `registrationName` or `phasedRegistrationNames`.\n *\n * `executeDispatch` {function(object, function, string)}\n * Optional, allows plugins to override how an event gets dispatched. By\n * default, the listener is simply invoked.\n *\n * Each plugin that is injected into `EventsPluginHub` is immediately operable.\n *\n * @public\n */ /**\n * Methods for injecting dependencies.\n */var injection={/**\n * @param {array} InjectedEventPluginOrder\n * @public\n */injectEventPluginOrder:injectEventPluginOrder,/**\n * @param {object} injectedNamesToPlugins Map from names to plugin modules.\n */injectEventPluginsByName:injectEventPluginsByName};/**\n * @param {object} inst The instance, which is the source of events.\n * @param {string} registrationName Name of listener (e.g. `onClick`).\n * @return {?function} The stored callback.\n */function getListener(inst,registrationName){var listener=void 0;// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not\n// live here; needs to be moved to a better place soon\nvar stateNode=inst.stateNode;if(!stateNode){// Work in progress (ex: onload events in incremental mode).\nreturn null;}var props=getFiberCurrentPropsFromNode(stateNode);if(!props){// Work in progress.\nreturn null;}listener=props[registrationName];if(shouldPreventMouseEvent(registrationName,inst.type,props)){return null;}!(!listener||typeof listener==='function')?invariant(false,'Expected `%s` listener to be a function, instead got a value of `%s` type.',registrationName,_typeof(listener)):void 0;return listener;}/**\n * Allows registered plugins an opportunity to extract events from top-level\n * native browser events.\n *\n * @return {*} An accumulation of synthetic events.\n * @internal\n */function extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget){var events=null;for(var i=0;i0){instA=getParent(instA);depthA--;}// If B is deeper, crawl up.\nwhile(depthB-depthA>0){instB=getParent(instB);depthB--;}// Walk in lockstep until we find a match.\nvar depth=depthA;while(depth--){if(instA===instB||instA===instB.alternate){return instA;}instA=getParent(instA);instB=getParent(instB);}return null;}/**\n * Return if A is an ancestor of B.\n */ /**\n * Return the parent instance of the passed-in instance.\n */ /**\n * Simulates the traversal of a two-phase, capture/bubble event dispatch.\n */function traverseTwoPhase(inst,fn,arg){var path=[];while(inst){path.push(inst);inst=getParent(inst);}var i=void 0;for(i=path.length;i-->0;){fn(path[i],'captured',arg);}for(i=0;i0;){fn(pathTo[_i],'captured',argTo);}}/**\n * Some event types have a notion of different registration names for different\n * \"phases\" of propagation. This finds listeners by a given phase.\n */function listenerAtPhase(inst,event,propagationPhase){var registrationName=event.dispatchConfig.phasedRegistrationNames[propagationPhase];return getListener(inst,registrationName);}/**\n * A small set of propagation patterns, each of which will accept a small amount\n * of information, and generate a set of \"dispatch ready event objects\" - which\n * are sets of events that have already been annotated with a set of dispatched\n * listener functions/ids. The API is designed this way to discourage these\n * propagation strategies from actually executing the dispatches, since we\n * always want to collect the entire set of dispatches before executing even a\n * single one.\n */ /**\n * Tags a `SyntheticEvent` with dispatched listeners. Creating this function\n * here, allows us to not have to bind or create functions for each event.\n * Mutating the event's members allows us to not have to create a wrapping\n * \"dispatch\" object that pairs the event with the listener.\n */function accumulateDirectionalDispatches(inst,phase,event){{!inst?warningWithoutStack$1(false,'Dispatching inst must not be null'):void 0;}var listener=listenerAtPhase(inst,event,phase);if(listener){event._dispatchListeners=accumulateInto(event._dispatchListeners,listener);event._dispatchInstances=accumulateInto(event._dispatchInstances,inst);}}/**\n * Collect dispatches (must be entirely collected before dispatching - see unit\n * tests). Lazily allocate the array to conserve memory. We must loop through\n * each event and perform the traversal for each one. We cannot perform a\n * single traversal for the entire collection of events because each event may\n * have a different target.\n */function accumulateTwoPhaseDispatchesSingle(event){if(event&&event.dispatchConfig.phasedRegistrationNames){traverseTwoPhase(event._targetInst,accumulateDirectionalDispatches,event);}}/**\n * Accumulates without regard to direction, does not look for phased\n * registration names. Same as `accumulateDirectDispatchesSingle` but without\n * requiring that the `dispatchMarker` be the same as the dispatched ID.\n */function accumulateDispatches(inst,ignoredDirection,event){if(inst&&event&&event.dispatchConfig.registrationName){var registrationName=event.dispatchConfig.registrationName;var listener=getListener(inst,registrationName);if(listener){event._dispatchListeners=accumulateInto(event._dispatchListeners,listener);event._dispatchInstances=accumulateInto(event._dispatchInstances,inst);}}}/**\n * Accumulates dispatches on an `SyntheticEvent`, but only for the\n * `dispatchMarker`.\n * @param {SyntheticEvent} event\n */function accumulateDirectDispatchesSingle(event){if(event&&event.dispatchConfig.registrationName){accumulateDispatches(event._targetInst,null,event);}}function accumulateTwoPhaseDispatches(events){forEachAccumulated(events,accumulateTwoPhaseDispatchesSingle);}function accumulateEnterLeaveDispatches(leave,enter,from,to){traverseEnterLeave(from,to,accumulateDispatches,leave,enter);}function accumulateDirectDispatches(events){forEachAccumulated(events,accumulateDirectDispatchesSingle);}var canUseDOM=!!(typeof window!=='undefined'&&window.document&&window.document.createElement);// Do not uses the below two methods directly!\n// Instead use constants exported from DOMTopLevelEventTypes in ReactDOM.\n// (It is the only module that is allowed to access these methods.)\nfunction unsafeCastStringToDOMTopLevelType(topLevelType){return topLevelType;}function unsafeCastDOMTopLevelTypeToString(topLevelType){return topLevelType;}/**\n * Generate a mapping of standard vendor prefixes using the defined style property and event name.\n *\n * @param {string} styleProp\n * @param {string} eventName\n * @returns {object}\n */function makePrefixMap(styleProp,eventName){var prefixes={};prefixes[styleProp.toLowerCase()]=eventName.toLowerCase();prefixes['Webkit'+styleProp]='webkit'+eventName;prefixes['Moz'+styleProp]='moz'+eventName;return prefixes;}/**\n * A list of event names to a configurable list of vendor prefixes.\n */var vendorPrefixes={animationend:makePrefixMap('Animation','AnimationEnd'),animationiteration:makePrefixMap('Animation','AnimationIteration'),animationstart:makePrefixMap('Animation','AnimationStart'),transitionend:makePrefixMap('Transition','TransitionEnd')};/**\n * Event names that have already been detected and prefixed (if applicable).\n */var prefixedEventNames={};/**\n * Element to check for prefixes on.\n */var style={};/**\n * Bootstrap if a DOM exists.\n */if(canUseDOM){style=document.createElement('div').style;// On some platforms, in particular some releases of Android 4.x,\n// the un-prefixed \"animation\" and \"transition\" properties are defined on the\n// style object but the events that fire will still be prefixed, so we need\n// to check if the un-prefixed events are usable, and if not remove them from the map.\nif(!('AnimationEvent'in window)){delete vendorPrefixes.animationend.animation;delete vendorPrefixes.animationiteration.animation;delete vendorPrefixes.animationstart.animation;}// Same as above\nif(!('TransitionEvent'in window)){delete vendorPrefixes.transitionend.transition;}}/**\n * Attempts to determine the correct vendor prefixed event name.\n *\n * @param {string} eventName\n * @returns {string}\n */function getVendorPrefixedEventName(eventName){if(prefixedEventNames[eventName]){return prefixedEventNames[eventName];}else if(!vendorPrefixes[eventName]){return eventName;}var prefixMap=vendorPrefixes[eventName];for(var styleProp in prefixMap){if(prefixMap.hasOwnProperty(styleProp)&&styleProp in style){return prefixedEventNames[eventName]=prefixMap[styleProp];}}return eventName;}/**\n * To identify top level events in ReactDOM, we use constants defined by this\n * module. This is the only module that uses the unsafe* methods to express\n * that the constants actually correspond to the browser event names. This lets\n * us save some bundle size by avoiding a top level type -> event name map.\n * The rest of ReactDOM code should import top level types from this file.\n */var TOP_ABORT=unsafeCastStringToDOMTopLevelType('abort');var TOP_ANIMATION_END=unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('animationend'));var TOP_ANIMATION_ITERATION=unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('animationiteration'));var TOP_ANIMATION_START=unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('animationstart'));var TOP_BLUR=unsafeCastStringToDOMTopLevelType('blur');var TOP_CAN_PLAY=unsafeCastStringToDOMTopLevelType('canplay');var TOP_CAN_PLAY_THROUGH=unsafeCastStringToDOMTopLevelType('canplaythrough');var TOP_CANCEL=unsafeCastStringToDOMTopLevelType('cancel');var TOP_CHANGE=unsafeCastStringToDOMTopLevelType('change');var TOP_CLICK=unsafeCastStringToDOMTopLevelType('click');var TOP_CLOSE=unsafeCastStringToDOMTopLevelType('close');var TOP_COMPOSITION_END=unsafeCastStringToDOMTopLevelType('compositionend');var TOP_COMPOSITION_START=unsafeCastStringToDOMTopLevelType('compositionstart');var TOP_COMPOSITION_UPDATE=unsafeCastStringToDOMTopLevelType('compositionupdate');var TOP_CONTEXT_MENU=unsafeCastStringToDOMTopLevelType('contextmenu');var TOP_COPY=unsafeCastStringToDOMTopLevelType('copy');var TOP_CUT=unsafeCastStringToDOMTopLevelType('cut');var TOP_DOUBLE_CLICK=unsafeCastStringToDOMTopLevelType('dblclick');var TOP_AUX_CLICK=unsafeCastStringToDOMTopLevelType('auxclick');var TOP_DRAG=unsafeCastStringToDOMTopLevelType('drag');var TOP_DRAG_END=unsafeCastStringToDOMTopLevelType('dragend');var TOP_DRAG_ENTER=unsafeCastStringToDOMTopLevelType('dragenter');var TOP_DRAG_EXIT=unsafeCastStringToDOMTopLevelType('dragexit');var TOP_DRAG_LEAVE=unsafeCastStringToDOMTopLevelType('dragleave');var TOP_DRAG_OVER=unsafeCastStringToDOMTopLevelType('dragover');var TOP_DRAG_START=unsafeCastStringToDOMTopLevelType('dragstart');var TOP_DROP=unsafeCastStringToDOMTopLevelType('drop');var TOP_DURATION_CHANGE=unsafeCastStringToDOMTopLevelType('durationchange');var TOP_EMPTIED=unsafeCastStringToDOMTopLevelType('emptied');var TOP_ENCRYPTED=unsafeCastStringToDOMTopLevelType('encrypted');var TOP_ENDED=unsafeCastStringToDOMTopLevelType('ended');var TOP_ERROR=unsafeCastStringToDOMTopLevelType('error');var TOP_FOCUS=unsafeCastStringToDOMTopLevelType('focus');var TOP_GOT_POINTER_CAPTURE=unsafeCastStringToDOMTopLevelType('gotpointercapture');var TOP_INPUT=unsafeCastStringToDOMTopLevelType('input');var TOP_INVALID=unsafeCastStringToDOMTopLevelType('invalid');var TOP_KEY_DOWN=unsafeCastStringToDOMTopLevelType('keydown');var TOP_KEY_PRESS=unsafeCastStringToDOMTopLevelType('keypress');var TOP_KEY_UP=unsafeCastStringToDOMTopLevelType('keyup');var TOP_LOAD=unsafeCastStringToDOMTopLevelType('load');var TOP_LOAD_START=unsafeCastStringToDOMTopLevelType('loadstart');var TOP_LOADED_DATA=unsafeCastStringToDOMTopLevelType('loadeddata');var TOP_LOADED_METADATA=unsafeCastStringToDOMTopLevelType('loadedmetadata');var TOP_LOST_POINTER_CAPTURE=unsafeCastStringToDOMTopLevelType('lostpointercapture');var TOP_MOUSE_DOWN=unsafeCastStringToDOMTopLevelType('mousedown');var TOP_MOUSE_MOVE=unsafeCastStringToDOMTopLevelType('mousemove');var TOP_MOUSE_OUT=unsafeCastStringToDOMTopLevelType('mouseout');var TOP_MOUSE_OVER=unsafeCastStringToDOMTopLevelType('mouseover');var TOP_MOUSE_UP=unsafeCastStringToDOMTopLevelType('mouseup');var TOP_PASTE=unsafeCastStringToDOMTopLevelType('paste');var TOP_PAUSE=unsafeCastStringToDOMTopLevelType('pause');var TOP_PLAY=unsafeCastStringToDOMTopLevelType('play');var TOP_PLAYING=unsafeCastStringToDOMTopLevelType('playing');var TOP_POINTER_CANCEL=unsafeCastStringToDOMTopLevelType('pointercancel');var TOP_POINTER_DOWN=unsafeCastStringToDOMTopLevelType('pointerdown');var TOP_POINTER_MOVE=unsafeCastStringToDOMTopLevelType('pointermove');var TOP_POINTER_OUT=unsafeCastStringToDOMTopLevelType('pointerout');var TOP_POINTER_OVER=unsafeCastStringToDOMTopLevelType('pointerover');var TOP_POINTER_UP=unsafeCastStringToDOMTopLevelType('pointerup');var TOP_PROGRESS=unsafeCastStringToDOMTopLevelType('progress');var TOP_RATE_CHANGE=unsafeCastStringToDOMTopLevelType('ratechange');var TOP_RESET=unsafeCastStringToDOMTopLevelType('reset');var TOP_SCROLL=unsafeCastStringToDOMTopLevelType('scroll');var TOP_SEEKED=unsafeCastStringToDOMTopLevelType('seeked');var TOP_SEEKING=unsafeCastStringToDOMTopLevelType('seeking');var TOP_SELECTION_CHANGE=unsafeCastStringToDOMTopLevelType('selectionchange');var TOP_STALLED=unsafeCastStringToDOMTopLevelType('stalled');var TOP_SUBMIT=unsafeCastStringToDOMTopLevelType('submit');var TOP_SUSPEND=unsafeCastStringToDOMTopLevelType('suspend');var TOP_TEXT_INPUT=unsafeCastStringToDOMTopLevelType('textInput');var TOP_TIME_UPDATE=unsafeCastStringToDOMTopLevelType('timeupdate');var TOP_TOGGLE=unsafeCastStringToDOMTopLevelType('toggle');var TOP_TOUCH_CANCEL=unsafeCastStringToDOMTopLevelType('touchcancel');var TOP_TOUCH_END=unsafeCastStringToDOMTopLevelType('touchend');var TOP_TOUCH_MOVE=unsafeCastStringToDOMTopLevelType('touchmove');var TOP_TOUCH_START=unsafeCastStringToDOMTopLevelType('touchstart');var TOP_TRANSITION_END=unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('transitionend'));var TOP_VOLUME_CHANGE=unsafeCastStringToDOMTopLevelType('volumechange');var TOP_WAITING=unsafeCastStringToDOMTopLevelType('waiting');var TOP_WHEEL=unsafeCastStringToDOMTopLevelType('wheel');// List of events that need to be individually attached to media elements.\n// Note that events in this list will *not* be listened to at the top level\n// unless they're explicitly whitelisted in `ReactBrowserEventEmitter.listenTo`.\nvar mediaEventTypes=[TOP_ABORT,TOP_CAN_PLAY,TOP_CAN_PLAY_THROUGH,TOP_DURATION_CHANGE,TOP_EMPTIED,TOP_ENCRYPTED,TOP_ENDED,TOP_ERROR,TOP_LOADED_DATA,TOP_LOADED_METADATA,TOP_LOAD_START,TOP_PAUSE,TOP_PLAY,TOP_PLAYING,TOP_PROGRESS,TOP_RATE_CHANGE,TOP_SEEKED,TOP_SEEKING,TOP_STALLED,TOP_SUSPEND,TOP_TIME_UPDATE,TOP_VOLUME_CHANGE,TOP_WAITING];function getRawEventName(topLevelType){return unsafeCastDOMTopLevelTypeToString(topLevelType);}/**\n * These variables store information about text content of a target node,\n * allowing comparison of content before and after a given event.\n *\n * Identify the node where selection currently begins, then observe\n * both its text content and its current position in the DOM. Since the\n * browser may natively replace the target node during composition, we can\n * use its position to find its replacement.\n *\n *\n */var root=null;var startText=null;var fallbackText=null;function initialize(nativeEventTarget){root=nativeEventTarget;startText=getText();return true;}function reset(){root=null;startText=null;fallbackText=null;}function getData(){if(fallbackText){return fallbackText;}var start=void 0;var startValue=startText;var startLength=startValue.length;var end=void 0;var endValue=getText();var endLength=endValue.length;for(start=0;start1?1-end:undefined;fallbackText=endValue.slice(start,sliceTail);return fallbackText;}function getText(){if('value'in root){return root.value;}return root.textContent;}/* eslint valid-typeof: 0 */var EVENT_POOL_SIZE=10;/**\n * @interface Event\n * @see http://www.w3.org/TR/DOM-Level-3-Events/\n */var EventInterface={type:null,target:null,// currentTarget is set when dispatching; no use in copying it here\ncurrentTarget:function currentTarget(){return null;},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function timeStamp(event){return event.timeStamp||Date.now();},defaultPrevented:null,isTrusted:null};function functionThatReturnsTrue(){return true;}function functionThatReturnsFalse(){return false;}/**\n * Synthetic events are dispatched by event plugins, typically in response to a\n * top-level event delegation handler.\n *\n * These systems should generally use pooling to reduce the frequency of garbage\n * collection. The system should check `isPersistent` to determine whether the\n * event should be released into the pool after being dispatched. Users that\n * need a persisted event should invoke `persist`.\n *\n * Synthetic events (and subclasses) implement the DOM Level 3 Events API by\n * normalizing browser quirks. Subclasses do not necessarily have to implement a\n * DOM interface; custom application-specific events can also subclass this.\n *\n * @param {object} dispatchConfig Configuration used to dispatch this event.\n * @param {*} targetInst Marker identifying the event target.\n * @param {object} nativeEvent Native browser event.\n * @param {DOMEventTarget} nativeEventTarget Target node.\n */function SyntheticEvent(dispatchConfig,targetInst,nativeEvent,nativeEventTarget){{// these have a getter/setter for warnings\ndelete this.nativeEvent;delete this.preventDefault;delete this.stopPropagation;delete this.isDefaultPrevented;delete this.isPropagationStopped;}this.dispatchConfig=dispatchConfig;this._targetInst=targetInst;this.nativeEvent=nativeEvent;var Interface=this.constructor.Interface;for(var propName in Interface){if(!Interface.hasOwnProperty(propName)){continue;}{delete this[propName];// this has a getter/setter for warnings\n}var normalize=Interface[propName];if(normalize){this[propName]=normalize(nativeEvent);}else{if(propName==='target'){this.target=nativeEventTarget;}else{this[propName]=nativeEvent[propName];}}}var defaultPrevented=nativeEvent.defaultPrevented!=null?nativeEvent.defaultPrevented:nativeEvent.returnValue===false;if(defaultPrevented){this.isDefaultPrevented=functionThatReturnsTrue;}else{this.isDefaultPrevented=functionThatReturnsFalse;}this.isPropagationStopped=functionThatReturnsFalse;return this;}_assign(SyntheticEvent.prototype,{preventDefault:function preventDefault(){this.defaultPrevented=true;var event=this.nativeEvent;if(!event){return;}if(event.preventDefault){event.preventDefault();}else if(typeof event.returnValue!=='unknown'){event.returnValue=false;}this.isDefaultPrevented=functionThatReturnsTrue;},stopPropagation:function stopPropagation(){var event=this.nativeEvent;if(!event){return;}if(event.stopPropagation){event.stopPropagation();}else if(typeof event.cancelBubble!=='unknown'){// The ChangeEventPlugin registers a \"propertychange\" event for\n// IE. This event does not support bubbling or cancelling, and\n// any references to cancelBubble throw \"Member not found\". A\n// typeof check of \"unknown\" circumvents this issue (and is also\n// IE specific).\nevent.cancelBubble=true;}this.isPropagationStopped=functionThatReturnsTrue;},/**\n * We release all dispatched `SyntheticEvent`s after each event loop, adding\n * them back into the pool. This allows a way to hold onto a reference that\n * won't be added back into the pool.\n */persist:function persist(){this.isPersistent=functionThatReturnsTrue;},/**\n * Checks if this event should be released back into the pool.\n *\n * @return {boolean} True if this should not be released, false otherwise.\n */isPersistent:functionThatReturnsFalse,/**\n * `PooledClass` looks for `destructor` on each instance it releases.\n */destructor:function destructor(){var Interface=this.constructor.Interface;for(var propName in Interface){{Object.defineProperty(this,propName,getPooledWarningPropertyDefinition(propName,Interface[propName]));}}this.dispatchConfig=null;this._targetInst=null;this.nativeEvent=null;this.isDefaultPrevented=functionThatReturnsFalse;this.isPropagationStopped=functionThatReturnsFalse;this._dispatchListeners=null;this._dispatchInstances=null;{Object.defineProperty(this,'nativeEvent',getPooledWarningPropertyDefinition('nativeEvent',null));Object.defineProperty(this,'isDefaultPrevented',getPooledWarningPropertyDefinition('isDefaultPrevented',functionThatReturnsFalse));Object.defineProperty(this,'isPropagationStopped',getPooledWarningPropertyDefinition('isPropagationStopped',functionThatReturnsFalse));Object.defineProperty(this,'preventDefault',getPooledWarningPropertyDefinition('preventDefault',function(){}));Object.defineProperty(this,'stopPropagation',getPooledWarningPropertyDefinition('stopPropagation',function(){}));}}});SyntheticEvent.Interface=EventInterface;/**\n * Helper to reduce boilerplate when creating subclasses.\n */SyntheticEvent.extend=function(Interface){var Super=this;var E=function E(){};E.prototype=Super.prototype;var prototype=new E();function Class(){return Super.apply(this,arguments);}_assign(prototype,Class.prototype);Class.prototype=prototype;Class.prototype.constructor=Class;Class.Interface=_assign({},Super.Interface,Interface);Class.extend=Super.extend;addEventPoolingTo(Class);return Class;};addEventPoolingTo(SyntheticEvent);/**\n * Helper to nullify syntheticEvent instance properties when destructing\n *\n * @param {String} propName\n * @param {?object} getVal\n * @return {object} defineProperty object\n */function getPooledWarningPropertyDefinition(propName,getVal){var isFunction=typeof getVal==='function';return{configurable:true,set:set,get:get};function set(val){var action=isFunction?'setting the method':'setting the property';warn(action,'This is effectively a no-op');return val;}function get(){var action=isFunction?'accessing the method':'accessing the property';var result=isFunction?'This is a no-op function':'This is set to null';warn(action,result);return getVal;}function warn(action,result){var warningCondition=false;!warningCondition?warningWithoutStack$1(false,\"This synthetic event is reused for performance reasons. If you're seeing this, \"+\"you're %s `%s` on a released/nullified synthetic event. %s. \"+'If you must keep the original synthetic event around, use event.persist(). '+'See https://fb.me/react-event-pooling for more information.',action,propName,result):void 0;}}function getPooledEvent(dispatchConfig,targetInst,nativeEvent,nativeInst){var EventConstructor=this;if(EventConstructor.eventPool.length){var instance=EventConstructor.eventPool.pop();EventConstructor.call(instance,dispatchConfig,targetInst,nativeEvent,nativeInst);return instance;}return new EventConstructor(dispatchConfig,targetInst,nativeEvent,nativeInst);}function releasePooledEvent(event){var EventConstructor=this;!(event instanceof EventConstructor)?invariant(false,'Trying to release an event instance into a pool of a different type.'):void 0;event.destructor();if(EventConstructor.eventPool.length8&&documentMode<=11);var SPACEBAR_CODE=32;var SPACEBAR_CHAR=String.fromCharCode(SPACEBAR_CODE);// Events and their corresponding property names.\nvar eventTypes={beforeInput:{phasedRegistrationNames:{bubbled:'onBeforeInput',captured:'onBeforeInputCapture'},dependencies:[TOP_COMPOSITION_END,TOP_KEY_PRESS,TOP_TEXT_INPUT,TOP_PASTE]},compositionEnd:{phasedRegistrationNames:{bubbled:'onCompositionEnd',captured:'onCompositionEndCapture'},dependencies:[TOP_BLUR,TOP_COMPOSITION_END,TOP_KEY_DOWN,TOP_KEY_PRESS,TOP_KEY_UP,TOP_MOUSE_DOWN]},compositionStart:{phasedRegistrationNames:{bubbled:'onCompositionStart',captured:'onCompositionStartCapture'},dependencies:[TOP_BLUR,TOP_COMPOSITION_START,TOP_KEY_DOWN,TOP_KEY_PRESS,TOP_KEY_UP,TOP_MOUSE_DOWN]},compositionUpdate:{phasedRegistrationNames:{bubbled:'onCompositionUpdate',captured:'onCompositionUpdateCapture'},dependencies:[TOP_BLUR,TOP_COMPOSITION_UPDATE,TOP_KEY_DOWN,TOP_KEY_PRESS,TOP_KEY_UP,TOP_MOUSE_DOWN]}};// Track whether we've ever handled a keypress on the space key.\nvar hasSpaceKeypress=false;/**\n * Return whether a native keypress event is assumed to be a command.\n * This is required because Firefox fires `keypress` events for key commands\n * (cut, copy, select-all, etc.) even though no character is inserted.\n */function isKeypressCommand(nativeEvent){return(nativeEvent.ctrlKey||nativeEvent.altKey||nativeEvent.metaKey)&&// ctrlKey && altKey is equivalent to AltGr, and is not a command.\n!(nativeEvent.ctrlKey&&nativeEvent.altKey);}/**\n * Translate native top level events into event types.\n *\n * @param {string} topLevelType\n * @return {object}\n */function getCompositionEventType(topLevelType){switch(topLevelType){case TOP_COMPOSITION_START:return eventTypes.compositionStart;case TOP_COMPOSITION_END:return eventTypes.compositionEnd;case TOP_COMPOSITION_UPDATE:return eventTypes.compositionUpdate;}}/**\n * Does our fallback best-guess model think this event signifies that\n * composition has begun?\n *\n * @param {string} topLevelType\n * @param {object} nativeEvent\n * @return {boolean}\n */function isFallbackCompositionStart(topLevelType,nativeEvent){return topLevelType===TOP_KEY_DOWN&&nativeEvent.keyCode===START_KEYCODE;}/**\n * Does our fallback mode think that this event is the end of composition?\n *\n * @param {string} topLevelType\n * @param {object} nativeEvent\n * @return {boolean}\n */function isFallbackCompositionEnd(topLevelType,nativeEvent){switch(topLevelType){case TOP_KEY_UP:// Command keys insert or clear IME input.\nreturn END_KEYCODES.indexOf(nativeEvent.keyCode)!==-1;case TOP_KEY_DOWN:// Expect IME keyCode on each keydown. If we get any other\n// code we must have exited earlier.\nreturn nativeEvent.keyCode!==START_KEYCODE;case TOP_KEY_PRESS:case TOP_MOUSE_DOWN:case TOP_BLUR:// Events are not possible without cancelling IME.\nreturn true;default:return false;}}/**\n * Google Input Tools provides composition data via a CustomEvent,\n * with the `data` property populated in the `detail` object. If this\n * is available on the event object, use it. If not, this is a plain\n * composition event and we have nothing special to extract.\n *\n * @param {object} nativeEvent\n * @return {?string}\n */function getDataFromCustomEvent(nativeEvent){var detail=nativeEvent.detail;if(_typeof(detail)==='object'&&'data'in detail){return detail.data;}return null;}/**\n * Check if a composition event was triggered by Korean IME.\n * Our fallback mode does not work well with IE's Korean IME,\n * so just use native composition events when Korean IME is used.\n * Although CompositionEvent.locale property is deprecated,\n * it is available in IE, where our fallback mode is enabled.\n *\n * @param {object} nativeEvent\n * @return {boolean}\n */function isUsingKoreanIME(nativeEvent){return nativeEvent.locale==='ko';}// Track the current IME composition status, if any.\nvar isComposing=false;/**\n * @return {?object} A SyntheticCompositionEvent.\n */function extractCompositionEvent(topLevelType,targetInst,nativeEvent,nativeEventTarget){var eventType=void 0;var fallbackData=void 0;if(canUseCompositionEvent){eventType=getCompositionEventType(topLevelType);}else if(!isComposing){if(isFallbackCompositionStart(topLevelType,nativeEvent)){eventType=eventTypes.compositionStart;}}else if(isFallbackCompositionEnd(topLevelType,nativeEvent)){eventType=eventTypes.compositionEnd;}if(!eventType){return null;}if(useFallbackCompositionData&&!isUsingKoreanIME(nativeEvent)){// The current composition is stored statically and must not be\n// overwritten while composition continues.\nif(!isComposing&&eventType===eventTypes.compositionStart){isComposing=initialize(nativeEventTarget);}else if(eventType===eventTypes.compositionEnd){if(isComposing){fallbackData=getData();}}}var event=SyntheticCompositionEvent.getPooled(eventType,targetInst,nativeEvent,nativeEventTarget);if(fallbackData){// Inject data generated from fallback path into the synthetic event.\n// This matches the property of native CompositionEventInterface.\nevent.data=fallbackData;}else{var customData=getDataFromCustomEvent(nativeEvent);if(customData!==null){event.data=customData;}}accumulateTwoPhaseDispatches(event);return event;}/**\n * @param {TopLevelType} topLevelType Number from `TopLevelType`.\n * @param {object} nativeEvent Native browser event.\n * @return {?string} The string corresponding to this `beforeInput` event.\n */function getNativeBeforeInputChars(topLevelType,nativeEvent){switch(topLevelType){case TOP_COMPOSITION_END:return getDataFromCustomEvent(nativeEvent);case TOP_KEY_PRESS:/**\n * If native `textInput` events are available, our goal is to make\n * use of them. However, there is a special case: the spacebar key.\n * In Webkit, preventing default on a spacebar `textInput` event\n * cancels character insertion, but it *also* causes the browser\n * to fall back to its default spacebar behavior of scrolling the\n * page.\n *\n * Tracking at:\n * https://code.google.com/p/chromium/issues/detail?id=355103\n *\n * To avoid this issue, use the keypress event as if no `textInput`\n * event is available.\n */var which=nativeEvent.which;if(which!==SPACEBAR_CODE){return null;}hasSpaceKeypress=true;return SPACEBAR_CHAR;case TOP_TEXT_INPUT:// Record the characters to be added to the DOM.\nvar chars=nativeEvent.data;// If it's a spacebar character, assume that we have already handled\n// it at the keypress level and bail immediately. Android Chrome\n// doesn't give us keycodes, so we need to ignore it.\nif(chars===SPACEBAR_CHAR&&hasSpaceKeypress){return null;}return chars;default:// For other native event types, do nothing.\nreturn null;}}/**\n * For browsers that do not provide the `textInput` event, extract the\n * appropriate string to use for SyntheticInputEvent.\n *\n * @param {number} topLevelType Number from `TopLevelEventTypes`.\n * @param {object} nativeEvent Native browser event.\n * @return {?string} The fallback string for this `beforeInput` event.\n */function getFallbackBeforeInputChars(topLevelType,nativeEvent){// If we are currently composing (IME) and using a fallback to do so,\n// try to extract the composed characters from the fallback object.\n// If composition event is available, we extract a string only at\n// compositionevent, otherwise extract it at fallback events.\nif(isComposing){if(topLevelType===TOP_COMPOSITION_END||!canUseCompositionEvent&&isFallbackCompositionEnd(topLevelType,nativeEvent)){var chars=getData();reset();isComposing=false;return chars;}return null;}switch(topLevelType){case TOP_PASTE:// If a paste event occurs after a keypress, throw out the input\n// chars. Paste events should not lead to BeforeInput events.\nreturn null;case TOP_KEY_PRESS:/**\n * As of v27, Firefox may fire keypress events even when no character\n * will be inserted. A few possibilities:\n *\n * - `which` is `0`. Arrow keys, Esc key, etc.\n *\n * - `which` is the pressed key code, but no char is available.\n * Ex: 'AltGr + d` in Polish. There is no modified character for\n * this key combination and no character is inserted into the\n * document, but FF fires the keypress for char code `100` anyway.\n * No `input` event will occur.\n *\n * - `which` is the pressed key code, but a command combination is\n * being used. Ex: `Cmd+C`. No character is inserted, and no\n * `input` event will occur.\n */if(!isKeypressCommand(nativeEvent)){// IE fires the `keypress` event when a user types an emoji via\n// Touch keyboard of Windows. In such a case, the `char` property\n// holds an emoji character like `\\uD83D\\uDE0A`. Because its length\n// is 2, the property `which` does not represent an emoji correctly.\n// In such a case, we directly return the `char` property instead of\n// using `which`.\nif(nativeEvent.char&&nativeEvent.char.length>1){return nativeEvent.char;}else if(nativeEvent.which){return String.fromCharCode(nativeEvent.which);}}return null;case TOP_COMPOSITION_END:return useFallbackCompositionData&&!isUsingKoreanIME(nativeEvent)?null:nativeEvent.data;default:return null;}}/**\n * Extract a SyntheticInputEvent for `beforeInput`, based on either native\n * `textInput` or fallback behavior.\n *\n * @return {?object} A SyntheticInputEvent.\n */function extractBeforeInputEvent(topLevelType,targetInst,nativeEvent,nativeEventTarget){var chars=void 0;if(canUseTextInputEvent){chars=getNativeBeforeInputChars(topLevelType,nativeEvent);}else{chars=getFallbackBeforeInputChars(topLevelType,nativeEvent);}// If no characters are being inserted, no BeforeInput event should\n// be fired.\nif(!chars){return null;}var event=SyntheticInputEvent.getPooled(eventTypes.beforeInput,targetInst,nativeEvent,nativeEventTarget);event.data=chars;accumulateTwoPhaseDispatches(event);return event;}/**\n * Create an `onBeforeInput` event to match\n * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.\n *\n * This event plugin is based on the native `textInput` event\n * available in Chrome, Safari, Opera, and IE. This event fires after\n * `onKeyPress` and `onCompositionEnd`, but before `onInput`.\n *\n * `beforeInput` is spec'd but not implemented in any browsers, and\n * the `input` event does not provide any useful information about what has\n * actually been added, contrary to the spec. Thus, `textInput` is the best\n * available event to identify the characters that have actually been inserted\n * into the target node.\n *\n * This plugin is also responsible for emitting `composition` events, thus\n * allowing us to share composition fallback code for both `beforeInput` and\n * `composition` event types.\n */var BeforeInputEventPlugin={eventTypes:eventTypes,extractEvents:function extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget){var composition=extractCompositionEvent(topLevelType,targetInst,nativeEvent,nativeEventTarget);var beforeInput=extractBeforeInputEvent(topLevelType,targetInst,nativeEvent,nativeEventTarget);if(composition===null){return beforeInput;}if(beforeInput===null){return composition;}return[composition,beforeInput];}};// Use to restore controlled state after a change event has fired.\nvar restoreImpl=null;var restoreTarget=null;var restoreQueue=null;function restoreStateOfTarget(target){// We perform this translation at the end of the event loop so that we\n// always receive the correct fiber here\nvar internalInstance=getInstanceFromNode(target);if(!internalInstance){// Unmounted\nreturn;}!(typeof restoreImpl==='function')?invariant(false,'setRestoreImplementation() needs to be called to handle a target for controlled events. This error is likely caused by a bug in React. Please file an issue.'):void 0;var props=getFiberCurrentPropsFromNode(internalInstance.stateNode);restoreImpl(internalInstance.stateNode,internalInstance.type,props);}function setRestoreImplementation(impl){restoreImpl=impl;}function enqueueStateRestore(target){if(restoreTarget){if(restoreQueue){restoreQueue.push(target);}else{restoreQueue=[target];}}else{restoreTarget=target;}}function needsStateRestore(){return restoreTarget!==null||restoreQueue!==null;}function restoreStateIfNeeded(){if(!restoreTarget){return;}var target=restoreTarget;var queuedTargets=restoreQueue;restoreTarget=null;restoreQueue=null;restoreStateOfTarget(target);if(queuedTargets){for(var i=0;i element events #4963\nif(target.correspondingUseElement){target=target.correspondingUseElement;}// Safari may fire events on text nodes (Node.TEXT_NODE is 3).\n// @see http://www.quirksmode.org/js/events_properties.html\nreturn target.nodeType===TEXT_NODE?target.parentNode:target;}/**\n * Checks if an event is supported in the current execution environment.\n *\n * NOTE: This will not work correctly for non-generic events such as `change`,\n * `reset`, `load`, `error`, and `select`.\n *\n * Borrows from Modernizr.\n *\n * @param {string} eventNameSuffix Event name, e.g. \"click\".\n * @return {boolean} True if the event is supported.\n * @internal\n * @license Modernizr 3.0.0pre (Custom Build) | MIT\n */function isEventSupported(eventNameSuffix){if(!canUseDOM){return false;}var eventName='on'+eventNameSuffix;var isSupported=eventName in document;if(!isSupported){var element=document.createElement('div');element.setAttribute(eventName,'return;');isSupported=typeof element[eventName]==='function';}return isSupported;}function isCheckable(elem){var type=elem.type;var nodeName=elem.nodeName;return nodeName&&nodeName.toLowerCase()==='input'&&(type==='checkbox'||type==='radio');}function getTracker(node){return node._valueTracker;}function detachTracker(node){node._valueTracker=null;}function getValueFromNode(node){var value='';if(!node){return value;}if(isCheckable(node)){value=node.checked?'true':'false';}else{value=node.value;}return value;}function trackValueOnNode(node){var valueField=isCheckable(node)?'checked':'value';var descriptor=Object.getOwnPropertyDescriptor(node.constructor.prototype,valueField);var currentValue=''+node[valueField];// if someone has already defined a value or Safari, then bail\n// and don't track value will cause over reporting of changes,\n// but it's better then a hard failure\n// (needed for certain tests that spyOn input values and Safari)\nif(node.hasOwnProperty(valueField)||typeof descriptor==='undefined'||typeof descriptor.get!=='function'||typeof descriptor.set!=='function'){return;}var _get=descriptor.get,_set=descriptor.set;Object.defineProperty(node,valueField,{configurable:true,get:function get(){return _get.call(this);},set:function set(value){currentValue=''+value;_set.call(this,value);}});// We could've passed this the first time\n// but it triggers a bug in IE11 and Edge 14/15.\n// Calling defineProperty() again should be equivalent.\n// https://github.com/facebook/react/issues/11768\nObject.defineProperty(node,valueField,{enumerable:descriptor.enumerable});var tracker={getValue:function getValue(){return currentValue;},setValue:function setValue(value){currentValue=''+value;},stopTracking:function stopTracking(){detachTracker(node);delete node[valueField];}};return tracker;}function track(node){if(getTracker(node)){return;}// TODO: Once it's just Fiber we can move this to node._wrapperState\nnode._valueTracker=trackValueOnNode(node);}function updateValueIfChanged(node){if(!node){return false;}var tracker=getTracker(node);// if there is no tracker at this point it's unlikely\n// that trying again will succeed\nif(!tracker){return true;}var lastValue=tracker.getValue();var nextValue=getValueFromNode(node);if(nextValue!==lastValue){tracker.setValue(nextValue);return true;}return false;}var ReactSharedInternals=React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;var BEFORE_SLASH_RE=/^(.*)[\\\\\\/]/;var describeComponentFrame=function describeComponentFrame(name,source,ownerName){var sourceInfo='';if(source){var path=source.fileName;var fileName=path.replace(BEFORE_SLASH_RE,'');{// In DEV, include code for a common special case:\n// prefer \"folder/index.js\" instead of just \"index.js\".\nif(/^index\\./.test(fileName)){var match=path.match(BEFORE_SLASH_RE);if(match){var pathBeforeSlash=match[1];if(pathBeforeSlash){var folderName=pathBeforeSlash.replace(BEFORE_SLASH_RE,'');fileName=folderName+'/'+fileName;}}}}sourceInfo=' (at '+fileName+':'+source.lineNumber+')';}else if(ownerName){sourceInfo=' (created by '+ownerName+')';}return'\\n in '+(name||'Unknown')+sourceInfo;};// The Symbol used to tag the ReactElement-like types. If there is no native Symbol\n// nor polyfill, then a plain number is used for performance.\nvar hasSymbol=typeof Symbol==='function'&&Symbol.for;var REACT_ELEMENT_TYPE=hasSymbol?Symbol.for('react.element'):0xeac7;var REACT_PORTAL_TYPE=hasSymbol?Symbol.for('react.portal'):0xeaca;var REACT_FRAGMENT_TYPE=hasSymbol?Symbol.for('react.fragment'):0xeacb;var REACT_STRICT_MODE_TYPE=hasSymbol?Symbol.for('react.strict_mode'):0xeacc;var REACT_PROFILER_TYPE=hasSymbol?Symbol.for('react.profiler'):0xead2;var REACT_PROVIDER_TYPE=hasSymbol?Symbol.for('react.provider'):0xeacd;var REACT_CONTEXT_TYPE=hasSymbol?Symbol.for('react.context'):0xeace;var REACT_ASYNC_MODE_TYPE=hasSymbol?Symbol.for('react.async_mode'):0xeacf;var REACT_FORWARD_REF_TYPE=hasSymbol?Symbol.for('react.forward_ref'):0xead0;var REACT_PLACEHOLDER_TYPE=hasSymbol?Symbol.for('react.placeholder'):0xead1;var MAYBE_ITERATOR_SYMBOL=typeof Symbol==='function'&&Symbol.iterator;var FAUX_ITERATOR_SYMBOL='@@iterator';function getIteratorFn(maybeIterable){if(maybeIterable===null||_typeof(maybeIterable)!=='object'){return null;}var maybeIterator=MAYBE_ITERATOR_SYMBOL&&maybeIterable[MAYBE_ITERATOR_SYMBOL]||maybeIterable[FAUX_ITERATOR_SYMBOL];if(typeof maybeIterator==='function'){return maybeIterator;}return null;}var Pending=0;var Resolved=1;var Rejected=2;function getResultFromResolvedThenable(thenable){return thenable._reactResult;}function refineResolvedThenable(thenable){return thenable._reactStatus===Resolved?thenable._reactResult:null;}function getComponentName(type){if(type==null){// Host root, text node or just invalid type.\nreturn null;}{if(typeof type.tag==='number'){warningWithoutStack$1(false,'Received an unexpected object in getComponentName(). '+'This is likely a bug in React. Please file an issue.');}}if(typeof type==='function'){return type.displayName||type.name||null;}if(typeof type==='string'){return type;}switch(type){case REACT_ASYNC_MODE_TYPE:return'AsyncMode';case REACT_FRAGMENT_TYPE:return'Fragment';case REACT_PORTAL_TYPE:return'Portal';case REACT_PROFILER_TYPE:return'Profiler';case REACT_STRICT_MODE_TYPE:return'StrictMode';case REACT_PLACEHOLDER_TYPE:return'Placeholder';}if(_typeof(type)==='object'){switch(type.$$typeof){case REACT_CONTEXT_TYPE:return'Context.Consumer';case REACT_PROVIDER_TYPE:return'Context.Provider';case REACT_FORWARD_REF_TYPE:var renderFn=type.render;var functionName=renderFn.displayName||renderFn.name||'';return type.displayName||(functionName!==''?'ForwardRef('+functionName+')':'ForwardRef');}if(typeof type.then==='function'){var thenable=type;var resolvedThenable=refineResolvedThenable(thenable);if(resolvedThenable){return getComponentName(resolvedThenable);}}}return null;}var ReactDebugCurrentFrame=ReactSharedInternals.ReactDebugCurrentFrame;function describeFiber(fiber){switch(fiber.tag){case IndeterminateComponent:case FunctionalComponent:case FunctionalComponentLazy:case ClassComponent:case ClassComponentLazy:case HostComponent:case Mode:var owner=fiber._debugOwner;var source=fiber._debugSource;var name=getComponentName(fiber.type);var ownerName=null;if(owner){ownerName=getComponentName(owner.type);}return describeComponentFrame(name,source,ownerName);default:return'';}}function getStackByFiberInDevAndProd(workInProgress){var info='';var node=workInProgress;do{info+=describeFiber(node);node=node.return;}while(node);return info;}var current=null;var phase=null;function getCurrentFiberOwnerNameInDevOrNull(){{if(current===null){return null;}var owner=current._debugOwner;if(owner!==null&&typeof owner!=='undefined'){return getComponentName(owner.type);}}return null;}function getCurrentFiberStackInDev(){{if(current===null){return'';}// Safe because if current fiber exists, we are reconciling,\n// and it is guaranteed to be the work-in-progress version.\nreturn getStackByFiberInDevAndProd(current);}return'';}function resetCurrentFiber(){{ReactDebugCurrentFrame.getCurrentStack=null;current=null;phase=null;}}function setCurrentFiber(fiber){{ReactDebugCurrentFrame.getCurrentStack=getCurrentFiberStackInDev;current=fiber;phase=null;}}function setCurrentPhase(lifeCyclePhase){{phase=lifeCyclePhase;}}/**\n * Similar to invariant but only logs a warning if the condition is not met.\n * This can be used to log issues in development environments in critical\n * paths. Removing the logging code for production environments will keep the\n * same logic and follow the same code paths.\n */var warning=warningWithoutStack$1;{warning=function warning(condition,format){if(condition){return;}var ReactDebugCurrentFrame=ReactSharedInternals.ReactDebugCurrentFrame;var stack=ReactDebugCurrentFrame.getStackAddendum();// eslint-disable-next-line react-internal/warning-and-invariant-args\nfor(var _len=arguments.length,args=Array(_len>2?_len-2:0),_key=2;_key<_len;_key++){args[_key-2]=arguments[_key];}warningWithoutStack$1.apply(undefined,[false,format+'%s'].concat(args,[stack]));};}var warning$1=warning;// A reserved attribute.\n// It is handled by React separately and shouldn't be written to the DOM.\nvar RESERVED=0;// A simple string attribute.\n// Attributes that aren't in the whitelist are presumed to have this type.\nvar STRING=1;// A string attribute that accepts booleans in React. In HTML, these are called\n// \"enumerated\" attributes with \"true\" and \"false\" as possible values.\n// When true, it should be set to a \"true\" string.\n// When false, it should be set to a \"false\" string.\nvar BOOLEANISH_STRING=2;// A real boolean attribute.\n// When true, it should be present (set either to an empty string or its name).\n// When false, it should be omitted.\nvar BOOLEAN=3;// An attribute that can be used as a flag as well as with a value.\n// When true, it should be present (set either to an empty string or its name).\n// When false, it should be omitted.\n// For any other value, should be present with that value.\nvar OVERLOADED_BOOLEAN=4;// An attribute that must be numeric or parse as a numeric.\n// When falsy, it should be removed.\nvar NUMERIC=5;// An attribute that must be positive numeric or parse as a positive numeric.\n// When falsy, it should be removed.\nvar POSITIVE_NUMERIC=6;/* eslint-disable max-len */var ATTRIBUTE_NAME_START_CHAR=\":A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD\";/* eslint-enable max-len */var ATTRIBUTE_NAME_CHAR=ATTRIBUTE_NAME_START_CHAR+\"\\\\-.0-9\\\\u00B7\\\\u0300-\\\\u036F\\\\u203F-\\\\u2040\";var ROOT_ATTRIBUTE_NAME='data-reactroot';var VALID_ATTRIBUTE_NAME_REGEX=new RegExp('^['+ATTRIBUTE_NAME_START_CHAR+']['+ATTRIBUTE_NAME_CHAR+']*$');var hasOwnProperty=Object.prototype.hasOwnProperty;var illegalAttributeNameCache={};var validatedAttributeNameCache={};function isAttributeNameSafe(attributeName){if(hasOwnProperty.call(validatedAttributeNameCache,attributeName)){return true;}if(hasOwnProperty.call(illegalAttributeNameCache,attributeName)){return false;}if(VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)){validatedAttributeNameCache[attributeName]=true;return true;}illegalAttributeNameCache[attributeName]=true;{warning$1(false,'Invalid attribute name: `%s`',attributeName);}return false;}function shouldIgnoreAttribute(name,propertyInfo,isCustomComponentTag){if(propertyInfo!==null){return propertyInfo.type===RESERVED;}if(isCustomComponentTag){return false;}if(name.length>2&&(name[0]==='o'||name[0]==='O')&&(name[1]==='n'||name[1]==='N')){return true;}return false;}function shouldRemoveAttributeWithWarning(name,value,propertyInfo,isCustomComponentTag){if(propertyInfo!==null&&propertyInfo.type===RESERVED){return false;}switch(_typeof(value)){case'function':// $FlowIssue symbol is perfectly valid here\ncase'symbol':// eslint-disable-line\nreturn true;case'boolean':{if(isCustomComponentTag){return false;}if(propertyInfo!==null){return!propertyInfo.acceptsBooleans;}else{var prefix=name.toLowerCase().slice(0,5);return prefix!=='data-'&&prefix!=='aria-';}}default:return false;}}function shouldRemoveAttribute(name,value,propertyInfo,isCustomComponentTag){if(value===null||typeof value==='undefined'){return true;}if(shouldRemoveAttributeWithWarning(name,value,propertyInfo,isCustomComponentTag)){return true;}if(isCustomComponentTag){return false;}if(propertyInfo!==null){switch(propertyInfo.type){case BOOLEAN:return!value;case OVERLOADED_BOOLEAN:return value===false;case NUMERIC:return isNaN(value);case POSITIVE_NUMERIC:return isNaN(value)||value<1;}}return false;}function getPropertyInfo(name){return properties.hasOwnProperty(name)?properties[name]:null;}function PropertyInfoRecord(name,type,mustUseProperty,attributeName,attributeNamespace){this.acceptsBooleans=type===BOOLEANISH_STRING||type===BOOLEAN||type===OVERLOADED_BOOLEAN;this.attributeName=attributeName;this.attributeNamespace=attributeNamespace;this.mustUseProperty=mustUseProperty;this.propertyName=name;this.type=type;}// When adding attributes to this list, be sure to also add them to\n// the `possibleStandardNames` module to ensure casing and incorrect\n// name warnings.\nvar properties={};// These props are reserved by React. They shouldn't be written to the DOM.\n['children','dangerouslySetInnerHTML',// TODO: This prevents the assignment of defaultValue to regular\n// elements (not just inputs). Now that ReactDOMInput assigns to the\n// defaultValue property -- do we need this?\n'defaultValue','defaultChecked','innerHTML','suppressContentEditableWarning','suppressHydrationWarning','style'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,RESERVED,false,// mustUseProperty\nname,// attributeName\nnull);}// attributeNamespace\n);// A few React string attributes have a different name.\n// This is a mapping from React prop names to the attribute names.\n[['acceptCharset','accept-charset'],['className','class'],['htmlFor','for'],['httpEquiv','http-equiv']].forEach(function(_ref){var name=_ref[0],attributeName=_ref[1];properties[name]=new PropertyInfoRecord(name,STRING,false,// mustUseProperty\nattributeName,// attributeName\nnull);}// attributeNamespace\n);// These are \"enumerated\" HTML attributes that accept \"true\" and \"false\".\n// In React, we let users pass `true` and `false` even though technically\n// these aren't boolean attributes (they are coerced to strings).\n['contentEditable','draggable','spellCheck','value'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,BOOLEANISH_STRING,false,// mustUseProperty\nname.toLowerCase(),// attributeName\nnull);}// attributeNamespace\n);// These are \"enumerated\" SVG attributes that accept \"true\" and \"false\".\n// In React, we let users pass `true` and `false` even though technically\n// these aren't boolean attributes (they are coerced to strings).\n// Since these are SVG attributes, their attribute names are case-sensitive.\n['autoReverse','externalResourcesRequired','focusable','preserveAlpha'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,BOOLEANISH_STRING,false,// mustUseProperty\nname,// attributeName\nnull);}// attributeNamespace\n);// These are HTML boolean attributes.\n['allowFullScreen','async',// Note: there is a special case that prevents it from being written to the DOM\n// on the client side because the browsers are inconsistent. Instead we call focus().\n'autoFocus','autoPlay','controls','default','defer','disabled','formNoValidate','hidden','loop','noModule','noValidate','open','playsInline','readOnly','required','reversed','scoped','seamless',// Microdata\n'itemScope'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,BOOLEAN,false,// mustUseProperty\nname.toLowerCase(),// attributeName\nnull);}// attributeNamespace\n);// These are the few React props that we set as DOM properties\n// rather than attributes. These are all booleans.\n['checked',// Note: `option.selected` is not updated if `select.multiple` is\n// disabled with `removeAttribute`. We have special logic for handling this.\n'multiple','muted','selected'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,BOOLEAN,true,// mustUseProperty\nname,// attributeName\nnull);}// attributeNamespace\n);// These are HTML attributes that are \"overloaded booleans\": they behave like\n// booleans, but can also accept a string value.\n['capture','download'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,OVERLOADED_BOOLEAN,false,// mustUseProperty\nname,// attributeName\nnull);}// attributeNamespace\n);// These are HTML attributes that must be positive numbers.\n['cols','rows','size','span'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,POSITIVE_NUMERIC,false,// mustUseProperty\nname,// attributeName\nnull);}// attributeNamespace\n);// These are HTML attributes that must be numbers.\n['rowSpan','start'].forEach(function(name){properties[name]=new PropertyInfoRecord(name,NUMERIC,false,// mustUseProperty\nname.toLowerCase(),// attributeName\nnull);}// attributeNamespace\n);var CAMELIZE=/[\\-\\:]([a-z])/g;var capitalize=function capitalize(token){return token[1].toUpperCase();};// This is a list of all SVG attributes that need special casing, namespacing,\n// or boolean value assignment. Regular attributes that just accept strings\n// and have the same names are omitted, just like in the HTML whitelist.\n// Some of these attributes can be hard to find. This list was created by\n// scrapping the MDN documentation.\n['accent-height','alignment-baseline','arabic-form','baseline-shift','cap-height','clip-path','clip-rule','color-interpolation','color-interpolation-filters','color-profile','color-rendering','dominant-baseline','enable-background','fill-opacity','fill-rule','flood-color','flood-opacity','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','glyph-name','glyph-orientation-horizontal','glyph-orientation-vertical','horiz-adv-x','horiz-origin-x','image-rendering','letter-spacing','lighting-color','marker-end','marker-mid','marker-start','overline-position','overline-thickness','paint-order','panose-1','pointer-events','rendering-intent','shape-rendering','stop-color','stop-opacity','strikethrough-position','strikethrough-thickness','stroke-dasharray','stroke-dashoffset','stroke-linecap','stroke-linejoin','stroke-miterlimit','stroke-opacity','stroke-width','text-anchor','text-decoration','text-rendering','underline-position','underline-thickness','unicode-bidi','unicode-range','units-per-em','v-alphabetic','v-hanging','v-ideographic','v-mathematical','vector-effect','vert-adv-y','vert-origin-x','vert-origin-y','word-spacing','writing-mode','xmlns:xlink','x-height'].forEach(function(attributeName){var name=attributeName.replace(CAMELIZE,capitalize);properties[name]=new PropertyInfoRecord(name,STRING,false,// mustUseProperty\nattributeName,null);}// attributeNamespace\n);// String SVG attributes with the xlink namespace.\n['xlink:actuate','xlink:arcrole','xlink:href','xlink:role','xlink:show','xlink:title','xlink:type'].forEach(function(attributeName){var name=attributeName.replace(CAMELIZE,capitalize);properties[name]=new PropertyInfoRecord(name,STRING,false,// mustUseProperty\nattributeName,'http://www.w3.org/1999/xlink');});// String SVG attributes with the xml namespace.\n['xml:base','xml:lang','xml:space'].forEach(function(attributeName){var name=attributeName.replace(CAMELIZE,capitalize);properties[name]=new PropertyInfoRecord(name,STRING,false,// mustUseProperty\nattributeName,'http://www.w3.org/XML/1998/namespace');});// Special case: this attribute exists both in HTML and SVG.\n// Its \"tabindex\" attribute name is case-sensitive in SVG so we can't just use\n// its React `tabIndex` name, like we do for attributes that exist only in HTML.\nproperties.tabIndex=new PropertyInfoRecord('tabIndex',STRING,false,// mustUseProperty\n'tabindex',// attributeName\nnull);/**\n * Get the value for a property on a node. Only used in DEV for SSR validation.\n * The \"expected\" argument is used as a hint of what the expected value is.\n * Some properties have multiple equivalent values.\n */function getValueForProperty(node,name,expected,propertyInfo){{if(propertyInfo.mustUseProperty){var propertyName=propertyInfo.propertyName;return node[propertyName];}else{var attributeName=propertyInfo.attributeName;var stringValue=null;if(propertyInfo.type===OVERLOADED_BOOLEAN){if(node.hasAttribute(attributeName)){var value=node.getAttribute(attributeName);if(value===''){return true;}if(shouldRemoveAttribute(name,expected,propertyInfo,false)){return value;}if(value===''+expected){return expected;}return value;}}else if(node.hasAttribute(attributeName)){if(shouldRemoveAttribute(name,expected,propertyInfo,false)){// We had an attribute but shouldn't have had one, so read it\n// for the error message.\nreturn node.getAttribute(attributeName);}if(propertyInfo.type===BOOLEAN){// If this was a boolean, it doesn't matter what the value is\n// the fact that we have it is the same as the expected.\nreturn expected;}// Even if this property uses a namespace we use getAttribute\n// because we assume its namespaced name is the same as our config.\n// To use getAttributeNS we need the local name which we don't have\n// in our config atm.\nstringValue=node.getAttribute(attributeName);}if(shouldRemoveAttribute(name,expected,propertyInfo,false)){return stringValue===null?expected:stringValue;}else if(stringValue===''+expected){return expected;}else{return stringValue;}}}}/**\n * Get the value for a attribute on a node. Only used in DEV for SSR validation.\n * The third argument is used as a hint of what the expected value is. Some\n * attributes have multiple equivalent values.\n */function getValueForAttribute(node,name,expected){{if(!isAttributeNameSafe(name)){return;}if(!node.hasAttribute(name)){return expected===undefined?undefined:null;}var value=node.getAttribute(name);if(value===''+expected){return expected;}return value;}}/**\n * Sets the value for a property on a node.\n *\n * @param {DOMElement} node\n * @param {string} name\n * @param {*} value\n */function setValueForProperty(node,name,value,isCustomComponentTag){var propertyInfo=getPropertyInfo(name);if(shouldIgnoreAttribute(name,propertyInfo,isCustomComponentTag)){return;}if(shouldRemoveAttribute(name,value,propertyInfo,isCustomComponentTag)){value=null;}// If the prop isn't in the special list, treat it as a simple attribute.\nif(isCustomComponentTag||propertyInfo===null){if(isAttributeNameSafe(name)){var _attributeName=name;if(value===null){node.removeAttribute(_attributeName);}else{node.setAttribute(_attributeName,''+value);}}return;}var mustUseProperty=propertyInfo.mustUseProperty;if(mustUseProperty){var propertyName=propertyInfo.propertyName;if(value===null){var type=propertyInfo.type;node[propertyName]=type===BOOLEAN?false:'';}else{// Contrary to `setAttribute`, object properties are properly\n// `toString`ed by IE8/9.\nnode[propertyName]=value;}return;}// The rest are treated as attributes with special cases.\nvar attributeName=propertyInfo.attributeName,attributeNamespace=propertyInfo.attributeNamespace;if(value===null){node.removeAttribute(attributeName);}else{var _type=propertyInfo.type;var attributeValue=void 0;if(_type===BOOLEAN||_type===OVERLOADED_BOOLEAN&&value===true){attributeValue='';}else{// `setAttribute` with objects becomes only `[object]` in IE8/9,\n// ('' + value) makes it output the correct toString()-value.\nattributeValue=''+value;}if(attributeNamespace){node.setAttributeNS(attributeNamespace,attributeName,attributeValue);}else{node.setAttribute(attributeName,attributeValue);}}}// Flow does not allow string concatenation of most non-string types. To work\n// around this limitation, we use an opaque type that can only be obtained by\n// passing the value through getToStringValue first.\nfunction toString(value){return''+value;}function getToStringValue(value){switch(_typeof(value)){case'boolean':case'number':case'object':case'string':case'undefined':return value;default:// function, symbol are assigned as empty strings\nreturn'';}}var ReactDebugCurrentFrame$1=null;var ReactControlledValuePropTypes={checkPropTypes:null};{ReactDebugCurrentFrame$1=ReactSharedInternals.ReactDebugCurrentFrame;var hasReadOnlyValue={button:true,checkbox:true,image:true,hidden:true,radio:true,reset:true,submit:true};var propTypes={value:function value(props,propName,componentName){if(hasReadOnlyValue[props.type]||props.onChange||props.readOnly||props.disabled||props[propName]==null){return null;}return new Error('You provided a `value` prop to a form field without an '+'`onChange` handler. This will render a read-only field. If '+'the field should be mutable use `defaultValue`. Otherwise, '+'set either `onChange` or `readOnly`.');},checked:function checked(props,propName,componentName){if(props.onChange||props.readOnly||props.disabled||props[propName]==null){return null;}return new Error('You provided a `checked` prop to a form field without an '+'`onChange` handler. This will render a read-only field. If '+'the field should be mutable use `defaultChecked`. Otherwise, '+'set either `onChange` or `readOnly`.');}};/**\n * Provide a linked `value` attribute for controlled forms. You should not use\n * this outside of the ReactDOM controlled form components.\n */ReactControlledValuePropTypes.checkPropTypes=function(tagName,props){checkPropTypes(propTypes,props,'prop',tagName,ReactDebugCurrentFrame$1.getStackAddendum);};}// Exports ReactDOM.createRoot\nvar enableUserTimingAPI=true;// Experimental error-boundary API that can recover from errors within a single\n// render phase\nvar enableGetDerivedStateFromCatch=false;// Suspense\nvar enableSuspense=false;// Helps identify side effects in begin-phase lifecycle hooks and setState reducers:\nvar debugRenderPhaseSideEffects=false;// In some cases, StrictMode should also double-render lifecycles.\n// This can be confusing for tests though,\n// And it can be bad for performance in production.\n// This feature flag can be used to control the behavior:\nvar debugRenderPhaseSideEffectsForStrictMode=true;// To preserve the \"Pause on caught exceptions\" behavior of the debugger, we\n// replay the begin phase of a failed component inside invokeGuardedCallback.\nvar replayFailedUnitOfWorkWithInvokeGuardedCallback=true;// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:\nvar warnAboutDeprecatedLifecycles=false;// Warn about legacy context API\nvar warnAboutLegacyContextAPI=false;// Gather advanced timing metrics for Profiler subtrees.\nvar enableProfilerTimer=true;// Trace which interactions trigger each commit.\nvar enableSchedulerTracing=true;// Only used in www builds.\n// Only used in www builds.\n// React Fire: prevent the value and checked attributes from syncing\n// with their related DOM properties\nvar disableInputAttributeSyncing=false;// TODO: direct imports like some-package/src/* are bad. Fix me.\nvar didWarnValueDefaultValue=false;var didWarnCheckedDefaultChecked=false;var didWarnControlledToUncontrolled=false;var didWarnUncontrolledToControlled=false;function isControlled(props){var usesChecked=props.type==='checkbox'||props.type==='radio';return usesChecked?props.checked!=null:props.value!=null;}/**\n * Implements an host component that allows setting these optional\n * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.\n *\n * If `checked` or `value` are not supplied (or null/undefined), user actions\n * that affect the checked state or value will trigger updates to the element.\n *\n * If they are supplied (and not null/undefined), the rendered element will not\n * trigger updates to the element. Instead, the props must change in order for\n * the rendered element to be updated.\n *\n * The rendered element will be initialized as unchecked (or `defaultChecked`)\n * with an empty value (or `defaultValue`).\n *\n * See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html\n */function getHostProps(element,props){var node=element;var checked=props.checked;var hostProps=_assign({},props,{defaultChecked:undefined,defaultValue:undefined,value:undefined,checked:checked!=null?checked:node._wrapperState.initialChecked});return hostProps;}function initWrapperState(element,props){{ReactControlledValuePropTypes.checkPropTypes('input',props);if(props.checked!==undefined&&props.defaultChecked!==undefined&&!didWarnCheckedDefaultChecked){warning$1(false,'%s contains an input of type %s with both checked and defaultChecked props. '+'Input elements must be either controlled or uncontrolled '+'(specify either the checked prop, or the defaultChecked prop, but not '+'both). Decide between using a controlled or uncontrolled input '+'element and remove one of these props. More info: '+'https://fb.me/react-controlled-components',getCurrentFiberOwnerNameInDevOrNull()||'A component',props.type);didWarnCheckedDefaultChecked=true;}if(props.value!==undefined&&props.defaultValue!==undefined&&!didWarnValueDefaultValue){warning$1(false,'%s contains an input of type %s with both value and defaultValue props. '+'Input elements must be either controlled or uncontrolled '+'(specify either the value prop, or the defaultValue prop, but not '+'both). Decide between using a controlled or uncontrolled input '+'element and remove one of these props. More info: '+'https://fb.me/react-controlled-components',getCurrentFiberOwnerNameInDevOrNull()||'A component',props.type);didWarnValueDefaultValue=true;}}var node=element;var defaultValue=props.defaultValue==null?'':props.defaultValue;node._wrapperState={initialChecked:props.checked!=null?props.checked:props.defaultChecked,initialValue:getToStringValue(props.value!=null?props.value:defaultValue),controlled:isControlled(props)};}function updateChecked(element,props){var node=element;var checked=props.checked;if(checked!=null){setValueForProperty(node,'checked',checked,false);}}function updateWrapper(element,props){var node=element;{var _controlled=isControlled(props);if(!node._wrapperState.controlled&&_controlled&&!didWarnUncontrolledToControlled){warning$1(false,'A component is changing an uncontrolled input of type %s to be controlled. '+'Input elements should not switch from uncontrolled to controlled (or vice versa). '+'Decide between using a controlled or uncontrolled input '+'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',props.type);didWarnUncontrolledToControlled=true;}if(node._wrapperState.controlled&&!_controlled&&!didWarnControlledToUncontrolled){warning$1(false,'A component is changing a controlled input of type %s to be uncontrolled. '+'Input elements should not switch from controlled to uncontrolled (or vice versa). '+'Decide between using a controlled or uncontrolled input '+'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',props.type);didWarnControlledToUncontrolled=true;}}updateChecked(element,props);var value=getToStringValue(props.value);var type=props.type;if(value!=null){if(type==='number'){if(value===0&&node.value===''||// We explicitly want to coerce to number here if possible.\n// eslint-disable-next-line\nnode.value!=value){node.value=toString(value);}}else if(node.value!==toString(value)){node.value=toString(value);}}else if(type==='submit'||type==='reset'){// Submit/reset inputs need the attribute removed completely to avoid\n// blank-text buttons.\nnode.removeAttribute('value');return;}if(disableInputAttributeSyncing){// When not syncing the value attribute, React only assigns a new value\n// whenever the defaultValue React prop has changed. When not present,\n// React does nothing\nif(props.hasOwnProperty('defaultValue')){setDefaultValue(node,props.type,getToStringValue(props.defaultValue));}}else{// When syncing the value attribute, the value comes from a cascade of\n// properties:\n// 1. The value React property\n// 2. The defaultValue React property\n// 3. Otherwise there should be no change\nif(props.hasOwnProperty('value')){setDefaultValue(node,props.type,value);}else if(props.hasOwnProperty('defaultValue')){setDefaultValue(node,props.type,getToStringValue(props.defaultValue));}}if(disableInputAttributeSyncing){// When not syncing the checked attribute, the attribute is directly\n// controllable from the defaultValue React property. It needs to be\n// updated as new props come in.\nif(props.defaultChecked==null){node.removeAttribute('checked');}else{node.defaultChecked=!!props.defaultChecked;}}else{// When syncing the checked attribute, it only changes when it needs\n// to be removed, such as transitioning from a checkbox into a text input\nif(props.checked==null&&props.defaultChecked!=null){node.defaultChecked=!!props.defaultChecked;}}}function postMountWrapper(element,props,isHydrating){var node=element;// Do not assign value if it is already set. This prevents user text input\n// from being lost during SSR hydration.\nif(props.hasOwnProperty('value')||props.hasOwnProperty('defaultValue')){var type=props.type;var isButton=type==='submit'||type==='reset';// Avoid setting value attribute on submit/reset inputs as it overrides the\n// default value provided by the browser. See: #12872\nif(isButton&&(props.value===undefined||props.value===null)){return;}var _initialValue=toString(node._wrapperState.initialValue);// Do not assign value if it is already set. This prevents user text input\n// from being lost during SSR hydration.\nif(!isHydrating){if(disableInputAttributeSyncing){var value=getToStringValue(props.value);// When not syncing the value attribute, the value property points\n// directly to the React prop. Only assign it if it exists.\nif(value!=null){// Always assign on buttons so that it is possible to assign an\n// empty string to clear button text.\n//\n// Otherwise, do not re-assign the value property if is empty. This\n// potentially avoids a DOM write and prevents Firefox (~60.0.1) from\n// prematurely marking required inputs as invalid. Equality is compared\n// to the current value in case the browser provided value is not an\n// empty string.\nif(isButton||value!==node.value){node.value=toString(value);}}}else{// When syncing the value attribute, the value property should use\n// the the wrapperState._initialValue property. This uses:\n//\n// 1. The value React property when present\n// 2. The defaultValue React property when present\n// 3. An empty string\nif(_initialValue!==node.value){node.value=_initialValue;}}}if(disableInputAttributeSyncing){// When not syncing the value attribute, assign the value attribute\n// directly from the defaultValue React property (when present)\nvar defaultValue=getToStringValue(props.defaultValue);if(defaultValue!=null){node.defaultValue=toString(defaultValue);}}else{// Otherwise, the value attribute is synchronized to the property,\n// so we assign defaultValue to the same thing as the value property\n// assignment step above.\nnode.defaultValue=_initialValue;}}// Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug\n// this is needed to work around a chrome bug where setting defaultChecked\n// will sometimes influence the value of checked (even after detachment).\n// Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416\n// We need to temporarily unset name to avoid disrupting radio button groups.\nvar name=node.name;if(name!==''){node.name='';}if(disableInputAttributeSyncing){// When not syncing the checked attribute, the checked property\n// never gets assigned. It must be manually set. We don't want\n// to do this when hydrating so that existing user input isn't\n// modified\nif(!isHydrating){updateChecked(element,props);}// Only assign the checked attribute if it is defined. This saves\n// a DOM write when controlling the checked attribute isn't needed\n// (text inputs, submit/reset)\nif(props.hasOwnProperty('defaultChecked')){node.defaultChecked=!node.defaultChecked;node.defaultChecked=!!props.defaultChecked;}}else{// When syncing the checked attribute, both the the checked property and\n// attribute are assigned at the same time using defaultChecked. This uses:\n//\n// 1. The checked React property when present\n// 2. The defaultChecked React property when present\n// 3. Otherwise, false\nnode.defaultChecked=!node.defaultChecked;node.defaultChecked=!!node._wrapperState.initialChecked;}if(name!==''){node.name=name;}}function restoreControlledState(element,props){var node=element;updateWrapper(node,props);updateNamedCousins(node,props);}function updateNamedCousins(rootNode,props){var name=props.name;if(props.type==='radio'&&name!=null){var queryRoot=rootNode;while(queryRoot.parentNode){queryRoot=queryRoot.parentNode;}// If `rootNode.form` was non-null, then we could try `form.elements`,\n// but that sometimes behaves strangely in IE8. We could also try using\n// `form.getElementsByName`, but that will only return direct children\n// and won't include inputs that use the HTML5 `form=` attribute. Since\n// the input might not even be in a form. It might not even be in the\n// document. Let's just use the local `querySelectorAll` to ensure we don't\n// miss anything.\nvar group=queryRoot.querySelectorAll('input[name='+JSON.stringify(''+name)+'][type=\"radio\"]');for(var i=0;i is not a valid email address\".\n//\n// Here we check to see if the defaultValue has actually changed, avoiding these problems\n// when the user is inputting text\n//\n// https://github.com/facebook/react/issues/7253\nfunction setDefaultValue(node,type,value){if(// Focused number inputs synchronize on blur. See ChangeEventPlugin.js\ntype!=='number'||node.ownerDocument.activeElement!==node){if(value==null){node.defaultValue=toString(node._wrapperState.initialValue);}else if(node.defaultValue!==toString(value)){node.defaultValue=toString(value);}}}var eventTypes$1={change:{phasedRegistrationNames:{bubbled:'onChange',captured:'onChangeCapture'},dependencies:[TOP_BLUR,TOP_CHANGE,TOP_CLICK,TOP_FOCUS,TOP_INPUT,TOP_KEY_DOWN,TOP_KEY_UP,TOP_SELECTION_CHANGE]}};function createAndAccumulateChangeEvent(inst,nativeEvent,target){var event=SyntheticEvent.getPooled(eventTypes$1.change,inst,nativeEvent,target);event.type='change';// Flag this event loop as needing state restore.\nenqueueStateRestore(target);accumulateTwoPhaseDispatches(event);return event;}/**\n * For IE shims\n */var activeElement=null;var activeElementInst=null;/**\n * SECTION: handle `change` event\n */function shouldUseChangeEvent(elem){var nodeName=elem.nodeName&&elem.nodeName.toLowerCase();return nodeName==='select'||nodeName==='input'&&elem.type==='file';}function manualDispatchChangeEvent(nativeEvent){var event=createAndAccumulateChangeEvent(activeElementInst,nativeEvent,getEventTarget(nativeEvent));// If change and propertychange bubbled, we'd just bind to it like all the\n// other events and have it go through ReactBrowserEventEmitter. Since it\n// doesn't, we manually listen for the events and so we have to enqueue and\n// process the abstract event manually.\n//\n// Batching is necessary here in order to ensure that all event handlers run\n// before the next rerender (including event handlers attached to ancestor\n// elements instead of directly on the input). Without this, controlled\n// components don't work properly in conjunction with event bubbling because\n// the component is rerendered and the value reverted before all the event\n// handlers can run. See https://github.com/facebook/react/issues/708.\nbatchedUpdates(runEventInBatch,event);}function runEventInBatch(event){runEventsInBatch(event,false);}function getInstIfValueChanged(targetInst){var targetNode=getNodeFromInstance$1(targetInst);if(updateValueIfChanged(targetNode)){return targetInst;}}function getTargetInstForChangeEvent(topLevelType,targetInst){if(topLevelType===TOP_CHANGE){return targetInst;}}/**\n * SECTION: handle `input` event\n */var isInputEventSupported=false;if(canUseDOM){// IE9 claims to support the input event but fails to trigger it when\n// deleting text, so we ignore its input events.\nisInputEventSupported=isEventSupported('input')&&(!document.documentMode||document.documentMode>9);}/**\n * (For IE <=9) Starts tracking propertychange events on the passed-in element\n * and override the value property so that we can distinguish user events from\n * value changes in JS.\n */function startWatchingForValueChange(target,targetInst){activeElement=target;activeElementInst=targetInst;activeElement.attachEvent('onpropertychange',handlePropertyChange);}/**\n * (For IE <=9) Removes the event listeners from the currently-tracked element,\n * if any exists.\n */function stopWatchingForValueChange(){if(!activeElement){return;}activeElement.detachEvent('onpropertychange',handlePropertyChange);activeElement=null;activeElementInst=null;}/**\n * (For IE <=9) Handles a propertychange event, sending a `change` event if\n * the value of the active element has changed.\n */function handlePropertyChange(nativeEvent){if(nativeEvent.propertyName!=='value'){return;}if(getInstIfValueChanged(activeElementInst)){manualDispatchChangeEvent(nativeEvent);}}function handleEventsForInputEventPolyfill(topLevelType,target,targetInst){if(topLevelType===TOP_FOCUS){// In IE9, propertychange fires for most input events but is buggy and\n// doesn't fire when text is deleted, but conveniently, selectionchange\n// appears to fire in all of the remaining cases so we catch those and\n// forward the event if the value has changed\n// In either case, we don't want to call the event handler if the value\n// is changed from JS so we redefine a setter for `.value` that updates\n// our activeElementValue variable, allowing us to ignore those changes\n//\n// stopWatching() should be a noop here but we call it just in case we\n// missed a blur event somehow.\nstopWatchingForValueChange();startWatchingForValueChange(target,targetInst);}else if(topLevelType===TOP_BLUR){stopWatchingForValueChange();}}// For IE8 and IE9.\nfunction getTargetInstForInputEventPolyfill(topLevelType,targetInst){if(topLevelType===TOP_SELECTION_CHANGE||topLevelType===TOP_KEY_UP||topLevelType===TOP_KEY_DOWN){// On the selectionchange event, the target is just document which isn't\n// helpful for us so just check activeElement instead.\n//\n// 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire\n// propertychange on the first input event after setting `value` from a\n// script and fires only keydown, keypress, keyup. Catching keyup usually\n// gets it and catching keydown lets us fire an event for the first\n// keystroke if user does a key repeat (it'll be a little delayed: right\n// before the second keystroke). Other input methods (e.g., paste) seem to\n// fire selectionchange normally.\nreturn getInstIfValueChanged(activeElementInst);}}/**\n * SECTION: handle `click` event\n */function shouldUseClickEvent(elem){// Use the `click` event to detect changes to checkbox and radio inputs.\n// This approach works across all browsers, whereas `change` does not fire\n// until `blur` in IE8.\nvar nodeName=elem.nodeName;return nodeName&&nodeName.toLowerCase()==='input'&&(elem.type==='checkbox'||elem.type==='radio');}function getTargetInstForClickEvent(topLevelType,targetInst){if(topLevelType===TOP_CLICK){return getInstIfValueChanged(targetInst);}}function getTargetInstForInputOrChangeEvent(topLevelType,targetInst){if(topLevelType===TOP_INPUT||topLevelType===TOP_CHANGE){return getInstIfValueChanged(targetInst);}}function handleControlledInputBlur(node){var state=node._wrapperState;if(!state||!state.controlled||node.type!=='number'){return;}if(!disableInputAttributeSyncing){// If controlled, assign the value attribute to the current value on blur\nsetDefaultValue(node,'number',node.value);}}/**\n * This plugin creates an `onChange` event that normalizes change events\n * across form elements. This event fires at a time when it's possible to\n * change the element's value without seeing a flicker.\n *\n * Supported elements are:\n * - input (see `isTextInputElement`)\n * - textarea\n * - select\n */var ChangeEventPlugin={eventTypes:eventTypes$1,_isInputEventSupported:isInputEventSupported,extractEvents:function extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget){var targetNode=targetInst?getNodeFromInstance$1(targetInst):window;var getTargetInstFunc=void 0,handleEventFunc=void 0;if(shouldUseChangeEvent(targetNode)){getTargetInstFunc=getTargetInstForChangeEvent;}else if(isTextInputElement(targetNode)){if(isInputEventSupported){getTargetInstFunc=getTargetInstForInputOrChangeEvent;}else{getTargetInstFunc=getTargetInstForInputEventPolyfill;handleEventFunc=handleEventsForInputEventPolyfill;}}else if(shouldUseClickEvent(targetNode)){getTargetInstFunc=getTargetInstForClickEvent;}if(getTargetInstFunc){var inst=getTargetInstFunc(topLevelType,targetInst);if(inst){var event=createAndAccumulateChangeEvent(inst,nativeEvent,nativeEventTarget);return event;}}if(handleEventFunc){handleEventFunc(topLevelType,targetNode,targetInst);}// When blurring, set the value attribute for number inputs\nif(topLevelType===TOP_BLUR){handleControlledInputBlur(targetNode);}}};/**\n * Module that is injectable into `EventPluginHub`, that specifies a\n * deterministic ordering of `EventPlugin`s. A convenient way to reason about\n * plugins, without having to package every one of them. This is better than\n * having plugins be ordered in the same order that they are injected because\n * that ordering would be influenced by the packaging order.\n * `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that\n * preventing default on events is convenient in `SimpleEventPlugin` handlers.\n */var DOMEventPluginOrder=['ResponderEventPlugin','SimpleEventPlugin','EnterLeaveEventPlugin','ChangeEventPlugin','SelectEventPlugin','BeforeInputEventPlugin'];var SyntheticUIEvent=SyntheticEvent.extend({view:null,detail:null});/**\n * Translation from modifier key to the associated property in the event.\n * @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers\n */var modifierKeyToProp={Alt:'altKey',Control:'ctrlKey',Meta:'metaKey',Shift:'shiftKey'};// IE8 does not implement getModifierState so we simply map it to the only\n// modifier keys exposed by the event itself, does not support Lock-keys.\n// Currently, all major browsers except Chrome seems to support Lock-keys.\nfunction modifierStateGetter(keyArg){var syntheticEvent=this;var nativeEvent=syntheticEvent.nativeEvent;if(nativeEvent.getModifierState){return nativeEvent.getModifierState(keyArg);}var keyProp=modifierKeyToProp[keyArg];return keyProp?!!nativeEvent[keyProp]:false;}function getEventModifierState(nativeEvent){return modifierStateGetter;}var previousScreenX=0;var previousScreenY=0;// Use flags to signal movementX/Y has already been set\nvar isMovementXSet=false;var isMovementYSet=false;/**\n * @interface MouseEvent\n * @see http://www.w3.org/TR/DOM-Level-3-Events/\n */var SyntheticMouseEvent=SyntheticUIEvent.extend({screenX:null,screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:getEventModifierState,button:null,buttons:null,relatedTarget:function relatedTarget(event){return event.relatedTarget||(event.fromElement===event.srcElement?event.toElement:event.fromElement);},movementX:function movementX(event){if('movementX'in event){return event.movementX;}var screenX=previousScreenX;previousScreenX=event.screenX;if(!isMovementXSet){isMovementXSet=true;return 0;}return event.type==='mousemove'?event.screenX-screenX:0;},movementY:function movementY(event){if('movementY'in event){return event.movementY;}var screenY=previousScreenY;previousScreenY=event.screenY;if(!isMovementYSet){isMovementYSet=true;return 0;}return event.type==='mousemove'?event.screenY-screenY:0;}});/**\n * @interface PointerEvent\n * @see http://www.w3.org/TR/pointerevents/\n */var SyntheticPointerEvent=SyntheticMouseEvent.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null});var eventTypes$2={mouseEnter:{registrationName:'onMouseEnter',dependencies:[TOP_MOUSE_OUT,TOP_MOUSE_OVER]},mouseLeave:{registrationName:'onMouseLeave',dependencies:[TOP_MOUSE_OUT,TOP_MOUSE_OVER]},pointerEnter:{registrationName:'onPointerEnter',dependencies:[TOP_POINTER_OUT,TOP_POINTER_OVER]},pointerLeave:{registrationName:'onPointerLeave',dependencies:[TOP_POINTER_OUT,TOP_POINTER_OVER]}};var EnterLeaveEventPlugin={eventTypes:eventTypes$2,/**\n * For almost every interaction we care about, there will be both a top-level\n * `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that\n * we do not extract duplicate events. However, moving the mouse into the\n * browser from outside will not fire a `mouseout` event. In this case, we use\n * the `mouseover` top-level event.\n */extractEvents:function extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget){var isOverEvent=topLevelType===TOP_MOUSE_OVER||topLevelType===TOP_POINTER_OVER;var isOutEvent=topLevelType===TOP_MOUSE_OUT||topLevelType===TOP_POINTER_OUT;if(isOverEvent&&(nativeEvent.relatedTarget||nativeEvent.fromElement)){return null;}if(!isOutEvent&&!isOverEvent){// Must not be a mouse or pointer in or out - ignoring.\nreturn null;}var win=void 0;if(nativeEventTarget.window===nativeEventTarget){// `nativeEventTarget` is probably a window object.\nwin=nativeEventTarget;}else{// TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.\nvar doc=nativeEventTarget.ownerDocument;if(doc){win=doc.defaultView||doc.parentWindow;}else{win=window;}}var from=void 0;var to=void 0;if(isOutEvent){from=targetInst;var related=nativeEvent.relatedTarget||nativeEvent.toElement;to=related?getClosestInstanceFromNode(related):null;}else{// Moving to a node from outside the window.\nfrom=null;to=targetInst;}if(from===to){// Nothing pertains to our managed components.\nreturn null;}var eventInterface=void 0,leaveEventType=void 0,enterEventType=void 0,eventTypePrefix=void 0;if(topLevelType===TOP_MOUSE_OUT||topLevelType===TOP_MOUSE_OVER){eventInterface=SyntheticMouseEvent;leaveEventType=eventTypes$2.mouseLeave;enterEventType=eventTypes$2.mouseEnter;eventTypePrefix='mouse';}else if(topLevelType===TOP_POINTER_OUT||topLevelType===TOP_POINTER_OVER){eventInterface=SyntheticPointerEvent;leaveEventType=eventTypes$2.pointerLeave;enterEventType=eventTypes$2.pointerEnter;eventTypePrefix='pointer';}var fromNode=from==null?win:getNodeFromInstance$1(from);var toNode=to==null?win:getNodeFromInstance$1(to);var leave=eventInterface.getPooled(leaveEventType,from,nativeEvent,nativeEventTarget);leave.type=eventTypePrefix+'leave';leave.target=fromNode;leave.relatedTarget=toNode;var enter=eventInterface.getPooled(enterEventType,to,nativeEvent,nativeEventTarget);enter.type=eventTypePrefix+'enter';enter.target=toNode;enter.relatedTarget=fromNode;accumulateEnterLeaveDispatches(leave,enter,from,to);return[leave,enter];}};/*eslint-disable no-self-compare */var hasOwnProperty$1=Object.prototype.hasOwnProperty;/**\n * inlined Object.is polyfill to avoid requiring consumers ship their own\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\n */function is(x,y){// SameValue algorithm\nif(x===y){// Steps 1-5, 7-10\n// Steps 6.b-6.e: +0 != -0\n// Added the nonzero y check to make Flow happy, but it is redundant\nreturn x!==0||y!==0||1/x===1/y;}else{// Step 6.a: NaN == NaN\nreturn x!==x&&y!==y;}}/**\n * Performs equality by iterating through keys on an object and returning false\n * when any key has values which are not strictly equal between the arguments.\n * Returns true when the values of all keys are strictly equal.\n */function shallowEqual(objA,objB){if(is(objA,objB)){return true;}if(_typeof(objA)!=='object'||objA===null||_typeof(objB)!=='object'||objB===null){return false;}var keysA=Object.keys(objA);var keysB=Object.keys(objB);if(keysA.length!==keysB.length){return false;}// Test for A's keys different from B.\nfor(var i=0;i=32||charCode===13){return charCode;}return 0;}/**\n * Normalization of deprecated HTML5 `key` values\n * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names\n */var normalizeKey={Esc:'Escape',Spacebar:' ',Left:'ArrowLeft',Up:'ArrowUp',Right:'ArrowRight',Down:'ArrowDown',Del:'Delete',Win:'OS',Menu:'ContextMenu',Apps:'ContextMenu',Scroll:'ScrollLock',MozPrintableKey:'Unidentified'};/**\n * Translation from legacy `keyCode` to HTML5 `key`\n * Only special keys supported, all others depend on keyboard layout or browser\n * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names\n */var translateToKey={'8':'Backspace','9':'Tab','12':'Clear','13':'Enter','16':'Shift','17':'Control','18':'Alt','19':'Pause','20':'CapsLock','27':'Escape','32':' ','33':'PageUp','34':'PageDown','35':'End','36':'Home','37':'ArrowLeft','38':'ArrowUp','39':'ArrowRight','40':'ArrowDown','45':'Insert','46':'Delete','112':'F1','113':'F2','114':'F3','115':'F4','116':'F5','117':'F6','118':'F7','119':'F8','120':'F9','121':'F10','122':'F11','123':'F12','144':'NumLock','145':'ScrollLock','224':'Meta'};/**\n * @param {object} nativeEvent Native browser event.\n * @return {string} Normalized `key` property.\n */function getEventKey(nativeEvent){if(nativeEvent.key){// Normalize inconsistent values reported by browsers due to\n// implementations of a working draft specification.\n// FireFox implements `key` but returns `MozPrintableKey` for all\n// printable characters (normalized to `Unidentified`), ignore it.\nvar key=normalizeKey[nativeEvent.key]||nativeEvent.key;if(key!=='Unidentified'){return key;}}// Browser does not implement `key`, polyfill as much of it as we can.\nif(nativeEvent.type==='keypress'){var charCode=getEventCharCode(nativeEvent);// The enter-key is technically both printable and non-printable and can\n// thus be captured by `keypress`, no other non-printable key should.\nreturn charCode===13?'Enter':String.fromCharCode(charCode);}if(nativeEvent.type==='keydown'||nativeEvent.type==='keyup'){// While user keyboard layout determines the actual meaning of each\n// `keyCode` value, almost all function keys have a universal value.\nreturn translateToKey[nativeEvent.keyCode]||'Unidentified';}return'';}/**\n * @interface KeyboardEvent\n * @see http://www.w3.org/TR/DOM-Level-3-Events/\n */var SyntheticKeyboardEvent=SyntheticUIEvent.extend({key:getEventKey,location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:getEventModifierState,// Legacy Interface\ncharCode:function charCode(event){// `charCode` is the result of a KeyPress event and represents the value of\n// the actual printable character.\n// KeyPress is deprecated, but its replacement is not yet final and not\n// implemented in any major browser. Only KeyPress has charCode.\nif(event.type==='keypress'){return getEventCharCode(event);}return 0;},keyCode:function keyCode(event){// `keyCode` is the result of a KeyDown/Up event and represents the value of\n// physical keyboard key.\n// The actual meaning of the value depends on the users' keyboard layout\n// which cannot be detected. Assuming that it is a US keyboard layout\n// provides a surprisingly accurate mapping for US and European users.\n// Due to this, it is left to the user to implement at this time.\nif(event.type==='keydown'||event.type==='keyup'){return event.keyCode;}return 0;},which:function which(event){// `which` is an alias for either `keyCode` or `charCode` depending on the\n// type of the event.\nif(event.type==='keypress'){return getEventCharCode(event);}if(event.type==='keydown'||event.type==='keyup'){return event.keyCode;}return 0;}});/**\n * @interface DragEvent\n * @see http://www.w3.org/TR/DOM-Level-3-Events/\n */var SyntheticDragEvent=SyntheticMouseEvent.extend({dataTransfer:null});/**\n * @interface TouchEvent\n * @see http://www.w3.org/TR/touch-events/\n */var SyntheticTouchEvent=SyntheticUIEvent.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:getEventModifierState});/**\n * @interface Event\n * @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-\n * @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent\n */var SyntheticTransitionEvent=SyntheticEvent.extend({propertyName:null,elapsedTime:null,pseudoElement:null});/**\n * @interface WheelEvent\n * @see http://www.w3.org/TR/DOM-Level-3-Events/\n */var SyntheticWheelEvent=SyntheticMouseEvent.extend({deltaX:function deltaX(event){return'deltaX'in event?event.deltaX:// Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).\n'wheelDeltaX'in event?-event.wheelDeltaX:0;},deltaY:function deltaY(event){return'deltaY'in event?event.deltaY:// Fallback to `wheelDeltaY` for Webkit and normalize (down is positive).\n'wheelDeltaY'in event?-event.wheelDeltaY:// Fallback to `wheelDelta` for IE<9 and normalize (down is positive).\n'wheelDelta'in event?-event.wheelDelta:0;},deltaZ:null,// Browsers without \"deltaMode\" is reporting in raw wheel delta where one\n// notch on the scroll is always +/- 120, roughly equivalent to pixels.\n// A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or\n// ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size.\ndeltaMode:null});/**\n * Turns\n * ['abort', ...]\n * into\n * eventTypes = {\n * 'abort': {\n * phasedRegistrationNames: {\n * bubbled: 'onAbort',\n * captured: 'onAbortCapture',\n * },\n * dependencies: [TOP_ABORT],\n * },\n * ...\n * };\n * topLevelEventsToDispatchConfig = new Map([\n * [TOP_ABORT, { sameConfig }],\n * ]);\n */var interactiveEventTypeNames=[[TOP_BLUR,'blur'],[TOP_CANCEL,'cancel'],[TOP_CLICK,'click'],[TOP_CLOSE,'close'],[TOP_CONTEXT_MENU,'contextMenu'],[TOP_COPY,'copy'],[TOP_CUT,'cut'],[TOP_AUX_CLICK,'auxClick'],[TOP_DOUBLE_CLICK,'doubleClick'],[TOP_DRAG_END,'dragEnd'],[TOP_DRAG_START,'dragStart'],[TOP_DROP,'drop'],[TOP_FOCUS,'focus'],[TOP_INPUT,'input'],[TOP_INVALID,'invalid'],[TOP_KEY_DOWN,'keyDown'],[TOP_KEY_PRESS,'keyPress'],[TOP_KEY_UP,'keyUp'],[TOP_MOUSE_DOWN,'mouseDown'],[TOP_MOUSE_UP,'mouseUp'],[TOP_PASTE,'paste'],[TOP_PAUSE,'pause'],[TOP_PLAY,'play'],[TOP_POINTER_CANCEL,'pointerCancel'],[TOP_POINTER_DOWN,'pointerDown'],[TOP_POINTER_UP,'pointerUp'],[TOP_RATE_CHANGE,'rateChange'],[TOP_RESET,'reset'],[TOP_SEEKED,'seeked'],[TOP_SUBMIT,'submit'],[TOP_TOUCH_CANCEL,'touchCancel'],[TOP_TOUCH_END,'touchEnd'],[TOP_TOUCH_START,'touchStart'],[TOP_VOLUME_CHANGE,'volumeChange']];var nonInteractiveEventTypeNames=[[TOP_ABORT,'abort'],[TOP_ANIMATION_END,'animationEnd'],[TOP_ANIMATION_ITERATION,'animationIteration'],[TOP_ANIMATION_START,'animationStart'],[TOP_CAN_PLAY,'canPlay'],[TOP_CAN_PLAY_THROUGH,'canPlayThrough'],[TOP_DRAG,'drag'],[TOP_DRAG_ENTER,'dragEnter'],[TOP_DRAG_EXIT,'dragExit'],[TOP_DRAG_LEAVE,'dragLeave'],[TOP_DRAG_OVER,'dragOver'],[TOP_DURATION_CHANGE,'durationChange'],[TOP_EMPTIED,'emptied'],[TOP_ENCRYPTED,'encrypted'],[TOP_ENDED,'ended'],[TOP_ERROR,'error'],[TOP_GOT_POINTER_CAPTURE,'gotPointerCapture'],[TOP_LOAD,'load'],[TOP_LOADED_DATA,'loadedData'],[TOP_LOADED_METADATA,'loadedMetadata'],[TOP_LOAD_START,'loadStart'],[TOP_LOST_POINTER_CAPTURE,'lostPointerCapture'],[TOP_MOUSE_MOVE,'mouseMove'],[TOP_MOUSE_OUT,'mouseOut'],[TOP_MOUSE_OVER,'mouseOver'],[TOP_PLAYING,'playing'],[TOP_POINTER_MOVE,'pointerMove'],[TOP_POINTER_OUT,'pointerOut'],[TOP_POINTER_OVER,'pointerOver'],[TOP_PROGRESS,'progress'],[TOP_SCROLL,'scroll'],[TOP_SEEKING,'seeking'],[TOP_STALLED,'stalled'],[TOP_SUSPEND,'suspend'],[TOP_TIME_UPDATE,'timeUpdate'],[TOP_TOGGLE,'toggle'],[TOP_TOUCH_MOVE,'touchMove'],[TOP_TRANSITION_END,'transitionEnd'],[TOP_WAITING,'waiting'],[TOP_WHEEL,'wheel']];var eventTypes$4={};var topLevelEventsToDispatchConfig={};function addEventTypeNameToConfig(_ref,isInteractive){var topEvent=_ref[0],event=_ref[1];var capitalizedEvent=event[0].toUpperCase()+event.slice(1);var onEvent='on'+capitalizedEvent;var type={phasedRegistrationNames:{bubbled:onEvent,captured:onEvent+'Capture'},dependencies:[topEvent],isInteractive:isInteractive};eventTypes$4[event]=type;topLevelEventsToDispatchConfig[topEvent]=type;}interactiveEventTypeNames.forEach(function(eventTuple){addEventTypeNameToConfig(eventTuple,true);});nonInteractiveEventTypeNames.forEach(function(eventTuple){addEventTypeNameToConfig(eventTuple,false);});// Only used in DEV for exhaustiveness validation.\nvar knownHTMLTopLevelTypes=[TOP_ABORT,TOP_CANCEL,TOP_CAN_PLAY,TOP_CAN_PLAY_THROUGH,TOP_CLOSE,TOP_DURATION_CHANGE,TOP_EMPTIED,TOP_ENCRYPTED,TOP_ENDED,TOP_ERROR,TOP_INPUT,TOP_INVALID,TOP_LOAD,TOP_LOADED_DATA,TOP_LOADED_METADATA,TOP_LOAD_START,TOP_PAUSE,TOP_PLAY,TOP_PLAYING,TOP_PROGRESS,TOP_RATE_CHANGE,TOP_RESET,TOP_SEEKED,TOP_SEEKING,TOP_STALLED,TOP_SUBMIT,TOP_SUSPEND,TOP_TIME_UPDATE,TOP_TOGGLE,TOP_VOLUME_CHANGE,TOP_WAITING];var SimpleEventPlugin={eventTypes:eventTypes$4,isInteractiveTopLevelEventType:function isInteractiveTopLevelEventType(topLevelType){var config=topLevelEventsToDispatchConfig[topLevelType];return config!==undefined&&config.isInteractive===true;},extractEvents:function extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget){var dispatchConfig=topLevelEventsToDispatchConfig[topLevelType];if(!dispatchConfig){return null;}var EventConstructor=void 0;switch(topLevelType){case TOP_KEY_PRESS:// Firefox creates a keypress event for function keys too. This removes\n// the unwanted keypress events. Enter is however both printable and\n// non-printable. One would expect Tab to be as well (but it isn't).\nif(getEventCharCode(nativeEvent)===0){return null;}/* falls through */case TOP_KEY_DOWN:case TOP_KEY_UP:EventConstructor=SyntheticKeyboardEvent;break;case TOP_BLUR:case TOP_FOCUS:EventConstructor=SyntheticFocusEvent;break;case TOP_CLICK:// Firefox creates a click event on right mouse clicks. This removes the\n// unwanted click events.\nif(nativeEvent.button===2){return null;}/* falls through */case TOP_AUX_CLICK:case TOP_DOUBLE_CLICK:case TOP_MOUSE_DOWN:case TOP_MOUSE_MOVE:case TOP_MOUSE_UP:// TODO: Disabled elements should not respond to mouse events\n/* falls through */case TOP_MOUSE_OUT:case TOP_MOUSE_OVER:case TOP_CONTEXT_MENU:EventConstructor=SyntheticMouseEvent;break;case TOP_DRAG:case TOP_DRAG_END:case TOP_DRAG_ENTER:case TOP_DRAG_EXIT:case TOP_DRAG_LEAVE:case TOP_DRAG_OVER:case TOP_DRAG_START:case TOP_DROP:EventConstructor=SyntheticDragEvent;break;case TOP_TOUCH_CANCEL:case TOP_TOUCH_END:case TOP_TOUCH_MOVE:case TOP_TOUCH_START:EventConstructor=SyntheticTouchEvent;break;case TOP_ANIMATION_END:case TOP_ANIMATION_ITERATION:case TOP_ANIMATION_START:EventConstructor=SyntheticAnimationEvent;break;case TOP_TRANSITION_END:EventConstructor=SyntheticTransitionEvent;break;case TOP_SCROLL:EventConstructor=SyntheticUIEvent;break;case TOP_WHEEL:EventConstructor=SyntheticWheelEvent;break;case TOP_COPY:case TOP_CUT:case TOP_PASTE:EventConstructor=SyntheticClipboardEvent;break;case TOP_GOT_POINTER_CAPTURE:case TOP_LOST_POINTER_CAPTURE:case TOP_POINTER_CANCEL:case TOP_POINTER_DOWN:case TOP_POINTER_MOVE:case TOP_POINTER_OUT:case TOP_POINTER_OVER:case TOP_POINTER_UP:EventConstructor=SyntheticPointerEvent;break;default:{if(knownHTMLTopLevelTypes.indexOf(topLevelType)===-1){warningWithoutStack$1(false,'SimpleEventPlugin: Unhandled event type, `%s`. This warning '+'is likely caused by a bug in React. Please file an issue.',topLevelType);}}// HTML Events\n// @see http://www.w3.org/TR/html5/index.html#events-0\nEventConstructor=SyntheticEvent;break;}var event=EventConstructor.getPooled(dispatchConfig,targetInst,nativeEvent,nativeEventTarget);accumulateTwoPhaseDispatches(event);return event;}};var isInteractiveTopLevelEventType=SimpleEventPlugin.isInteractiveTopLevelEventType;var CALLBACK_BOOKKEEPING_POOL_SIZE=10;var callbackBookkeepingPool=[];/**\n * Find the deepest React component completely containing the root of the\n * passed-in instance (for use when entire React trees are nested within each\n * other). If React trees are not nested, returns null.\n */function findRootContainerNode(inst){// TODO: It may be a good idea to cache this to prevent unnecessary DOM\n// traversal, but caching is difficult to do correctly without using a\n// mutation observer to listen for all DOM changes.\nwhile(inst.return){inst=inst.return;}if(inst.tag!==HostRoot){// This can happen if we're in a detached tree.\nreturn null;}return inst.stateNode.containerInfo;}// Used to store ancestor hierarchy in top level callback\nfunction getTopLevelCallbackBookKeeping(topLevelType,nativeEvent,targetInst){if(callbackBookkeepingPool.length){var instance=callbackBookkeepingPool.pop();instance.topLevelType=topLevelType;instance.nativeEvent=nativeEvent;instance.targetInst=targetInst;return instance;}return{topLevelType:topLevelType,nativeEvent:nativeEvent,targetInst:targetInst,ancestors:[]};}function releaseTopLevelCallbackBookKeeping(instance){instance.topLevelType=null;instance.nativeEvent=null;instance.targetInst=null;instance.ancestors.length=0;if(callbackBookkeepingPool.length|EventPluginHub| | Event |\n * | | . | | +-----------+ | Propagators|\n * | ReactEvent | . | | |TapEvent | |------------|\n * | Emitter | . | |<---+|Plugin | |other plugin|\n * | | . | | +-----------+ | utilities |\n * | +-----------.--->| | +------------+\n * | | | . +--------------+\n * +-----|------+ . ^ +-----------+\n * | . | |Enter/Leave|\n * + . +-------+|Plugin |\n * +-------------+ . +-----------+\n * | application | .\n * |-------------| .\n * | | .\n * | | .\n * +-------------+ .\n * .\n * React Core . General Purpose Event Plugin System\n */var alreadyListeningTo={};var reactTopListenersCounter=0;/**\n * To ensure no conflicts with other potential React instances on the page\n */var topListenersIDKey='_reactListenersID'+(''+Math.random()).slice(2);function getListeningForDocument(mountAt){// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`\n// directly.\nif(!Object.prototype.hasOwnProperty.call(mountAt,topListenersIDKey)){mountAt[topListenersIDKey]=reactTopListenersCounter++;alreadyListeningTo[mountAt[topListenersIDKey]]={};}return alreadyListeningTo[mountAt[topListenersIDKey]];}/**\n * We listen for bubbled touch events on the document object.\n *\n * Firefox v8.01 (and possibly others) exhibited strange behavior when\n * mounting `onmousemove` events at some node that was not the document\n * element. The symptoms were that if your mouse is not moving over something\n * contained within that mount point (for example on the background) the\n * top-level listeners for `onmousemove` won't be called. However, if you\n * register the `mousemove` on the document object, then it will of course\n * catch all `mousemove`s. This along with iOS quirks, justifies restricting\n * top-level listeners to the document object only, at least for these\n * movement types of events and possibly all events.\n *\n * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html\n *\n * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but\n * they bubble to document.\n *\n * @param {string} registrationName Name of listener (e.g. `onClick`).\n * @param {object} mountAt Container where to mount the listener\n */function listenTo(registrationName,mountAt){var isListening=getListeningForDocument(mountAt);var dependencies=registrationNameDependencies[registrationName];for(var i=0;i=offset){return{node:node,offset:offset-nodeStart};}nodeStart=nodeEnd;}node=getLeafNode(getSiblingNode(node));}}/**\n * @param {DOMElement} outerNode\n * @return {?object}\n */function getOffsets(outerNode){var ownerDocument=outerNode.ownerDocument;var win=ownerDocument&&ownerDocument.defaultView||window;var selection=win.getSelection&&win.getSelection();if(!selection||selection.rangeCount===0){return null;}var anchorNode=selection.anchorNode,anchorOffset=selection.anchorOffset,focusNode=selection.focusNode,focusOffset=selection.focusOffset;// In Firefox, anchorNode and focusNode can be \"anonymous divs\", e.g. the\n// up/down buttons on an . Anonymous divs do not seem to\n// expose properties, triggering a \"Permission denied error\" if any of its\n// properties are accessed. The only seemingly possible way to avoid erroring\n// is to access a property that typically works for non-anonymous divs and\n// catch any error that may otherwise arise. See\n// https://bugzilla.mozilla.org/show_bug.cgi?id=208427\ntry{/* eslint-disable no-unused-expressions */anchorNode.nodeType;focusNode.nodeType;/* eslint-enable no-unused-expressions */}catch(e){return null;}return getModernOffsetsFromPoints(outerNode,anchorNode,anchorOffset,focusNode,focusOffset);}/**\n * Returns {start, end} where `start` is the character/codepoint index of\n * (anchorNode, anchorOffset) within the textContent of `outerNode`, and\n * `end` is the index of (focusNode, focusOffset).\n *\n * Returns null if you pass in garbage input but we should probably just crash.\n *\n * Exported only for testing.\n */function getModernOffsetsFromPoints(outerNode,anchorNode,anchorOffset,focusNode,focusOffset){var length=0;var start=-1;var end=-1;var indexWithinAnchor=0;var indexWithinFocus=0;var node=outerNode;var parentNode=null;outer:while(true){var next=null;while(true){if(node===anchorNode&&(anchorOffset===0||node.nodeType===TEXT_NODE)){start=length+anchorOffset;}if(node===focusNode&&(focusOffset===0||node.nodeType===TEXT_NODE)){end=length+focusOffset;}if(node.nodeType===TEXT_NODE){length+=node.nodeValue.length;}if((next=node.firstChild)===null){break;}// Moving from `node` to its first child `next`.\nparentNode=node;node=next;}while(true){if(node===outerNode){// If `outerNode` has children, this is always the second time visiting\n// it. If it has no children, this is still the first loop, and the only\n// valid selection is anchorNode and focusNode both equal to this node\n// and both offsets 0, in which case we will have handled above.\nbreak outer;}if(parentNode===anchorNode&&++indexWithinAnchor===anchorOffset){start=length;}if(parentNode===focusNode&&++indexWithinFocus===focusOffset){end=length;}if((next=node.nextSibling)!==null){break;}node=parentNode;parentNode=node.parentNode;}// Moving from `node` to its next sibling `next`.\nnode=next;}if(start===-1||end===-1){// This should never happen. (Would happen if the anchor/focus nodes aren't\n// actually inside the passed-in node.)\nreturn null;}return{start:start,end:end};}/**\n * In modern non-IE browsers, we can support both forward and backward\n * selections.\n *\n * Note: IE10+ supports the Selection object, but it does not support\n * the `extend` method, which means that even in modern IE, it's not possible\n * to programmatically create a backward selection. Thus, for all IE\n * versions, we use the old IE API to create our selections.\n *\n * @param {DOMElement|DOMTextNode} node\n * @param {object} offsets\n */function setOffsets(node,offsets){var doc=node.ownerDocument||document;var win=doc&&doc.defaultView||window;var selection=win.getSelection();var length=node.textContent.length;var start=Math.min(offsets.start,length);var end=offsets.end===undefined?start:Math.min(offsets.end,length);// IE 11 uses modern selection, but doesn't support the extend method.\n// Flip backward selections, so we can set with a single range.\nif(!selection.extend&&start>end){var temp=end;end=start;start=temp;}var startMarker=getNodeForCharacterOffset(node,start);var endMarker=getNodeForCharacterOffset(node,end);if(startMarker&&endMarker){if(selection.rangeCount===1&&selection.anchorNode===startMarker.node&&selection.anchorOffset===startMarker.offset&&selection.focusNode===endMarker.node&&selection.focusOffset===endMarker.offset){return;}var range=doc.createRange();range.setStart(startMarker.node,startMarker.offset);selection.removeAllRanges();if(start>end){selection.addRange(range);selection.extend(endMarker.node,endMarker.offset);}else{range.setEnd(endMarker.node,endMarker.offset);selection.addRange(range);}}}function isTextNode(node){return node&&node.nodeType===TEXT_NODE;}function containsNode(outerNode,innerNode){if(!outerNode||!innerNode){return false;}else if(outerNode===innerNode){return true;}else if(isTextNode(outerNode)){return false;}else if(isTextNode(innerNode)){return containsNode(outerNode,innerNode.parentNode);}else if('contains'in outerNode){return outerNode.contains(innerNode);}else if(outerNode.compareDocumentPosition){return!!(outerNode.compareDocumentPosition(innerNode)&16);}else{return false;}}function isInDocument(node){return node&&node.ownerDocument&&containsNode(node.ownerDocument.documentElement,node);}function getActiveElementDeep(){var win=window;var element=getActiveElement();while(element instanceof win.HTMLIFrameElement){// Accessing the contentDocument of a HTMLIframeElement can cause the browser\n// to throw, e.g. if it has a cross-origin src attribute\ntry{win=element.contentDocument.defaultView;}catch(e){return element;}element=getActiveElement(win.document);}return element;}/**\n * @ReactInputSelection: React input selection module. Based on Selection.js,\n * but modified to be suitable for react and has a couple of bug fixes (doesn't\n * assume buttons have range selections allowed).\n * Input selection module for React.\n */ /**\n * @hasSelectionCapabilities: we get the element types that support selection\n * from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart`\n * and `selectionEnd` rows.\n */function hasSelectionCapabilities(elem){var nodeName=elem&&elem.nodeName&&elem.nodeName.toLowerCase();return nodeName&&(nodeName==='input'&&(elem.type==='text'||elem.type==='search'||elem.type==='tel'||elem.type==='url'||elem.type==='password')||nodeName==='textarea'||elem.contentEditable==='true');}function getSelectionInformation(){var focusedElem=getActiveElementDeep();return{focusedElem:focusedElem,selectionRange:hasSelectionCapabilities(focusedElem)?getSelection$1(focusedElem):null};}/**\n * @restoreSelection: If any selection information was potentially lost,\n * restore it. This is useful when performing operations that could remove dom\n * nodes and place them back in, resulting in focus being lost.\n */function restoreSelection(priorSelectionInformation){var curFocusedElem=getActiveElementDeep();var priorFocusedElem=priorSelectionInformation.focusedElem;var priorSelectionRange=priorSelectionInformation.selectionRange;if(curFocusedElem!==priorFocusedElem&&isInDocument(priorFocusedElem)){if(priorSelectionRange!==null&&hasSelectionCapabilities(priorFocusedElem)){setSelection(priorFocusedElem,priorSelectionRange);}// Focusing a node can change the scroll position, which is undesirable\nvar ancestors=[];var ancestor=priorFocusedElem;while(ancestor=ancestor.parentNode){if(ancestor.nodeType===ELEMENT_NODE){ancestors.push({element:ancestor,left:ancestor.scrollLeft,top:ancestor.scrollTop});}}if(typeof priorFocusedElem.focus==='function'){priorFocusedElem.focus();}for(var i=0;i).\nReact.Children.forEach(children,function(child){if(child==null){return;}content+=child;// Note: we don't warn about invalid children here.\n// Instead, this is done separately below so that\n// it happens during the hydration codepath too.\n});return content;}/**\n * Implements an