UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

321 lines (262 loc) 11.9 kB
Object.defineProperty(exports, '__esModule', { value: true }); var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ var _libFileTreeStore2; function _libFileTreeStore() { return _libFileTreeStore2 = require('../lib/FileTreeStore'); } var _reactForAtom2; function _reactForAtom() { return _reactForAtom2 = require('react-for-atom'); } var _FileTreeEntryComponent2; function _FileTreeEntryComponent() { return _FileTreeEntryComponent2 = require('./FileTreeEntryComponent'); } var _EmptyComponent2; function _EmptyComponent() { return _EmptyComponent2 = require('./EmptyComponent'); } var _nuclideAnalytics2; function _nuclideAnalytics() { return _nuclideAnalytics2 = require('../../nuclide-analytics'); } var _commonsNodeOnce2; function _commonsNodeOnce() { return _commonsNodeOnce2 = _interopRequireDefault(require('../../commons-node/once')); } var _classnames2; function _classnames() { return _classnames2 = _interopRequireDefault(require('classnames')); } var _atom2; function _atom() { return _atom2 = require('atom'); } var BUFFER_ELEMENTS = 15; var FileTree = (function (_React$Component) { _inherits(FileTree, _React$Component); _createClass(FileTree, null, [{ key: 'trackFirstRender', value: (0, (_commonsNodeOnce2 || _commonsNodeOnce()).default)(function () { var rootKeysLength = (_libFileTreeStore2 || _libFileTreeStore()).FileTreeStore.getInstance().roots.size; // Wait using `setTimeout` and not `process.nextTick` or `setImmediate` // because those queue tasks in the current and next turn of the event loop // respectively. Since `setTimeout` gets preempted by them, it works great // for a more realistic "first render". Note: The scheduler for promises // (`Promise.resolve().then`) runs on the same queue as `process.nextTick` // but with a higher priority. setTimeout(function () { (0, (_nuclideAnalytics2 || _nuclideAnalytics()).track)('filetree-first-render', { 'time-to-render': String(process.uptime() * 1000), 'root-keys': String(rootKeysLength) }); }); }), enumerable: true }]); function FileTree(props) { _classCallCheck(this, FileTree); _get(Object.getPrototypeOf(FileTree.prototype), 'constructor', this).call(this, props); this._store = (_libFileTreeStore2 || _libFileTreeStore()).FileTreeStore.getInstance(); this._disposables = new (_atom2 || _atom()).CompositeDisposable(); this.state = { elementHeight: 22 }; // The minimal observed height makes a good default this._initialHeightMeasured = false; this._afRequestId = null; this._measureHeights = this._measureHeights.bind(this); } _createClass(FileTree, [{ key: 'componentDidMount', value: function componentDidMount() { var _this = this; FileTree.trackFirstRender(this); this._scrollToTrackedNodeIfNeeded(); this._measureHeights(); window.addEventListener('resize', this._measureHeights); this._disposables.add(atom.themes.onDidChangeActiveThemes(function () { _this._initialHeightMeasured = false; _this._afRequestId = window.requestAnimationFrame(function () { _this._afRequestId = null; _this._measureHeights(); }); }), new (_atom2 || _atom()).Disposable(function () { window.removeEventListener('resize', _this._measureHeights); })); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { if (this._afRequestId != null) { window.cancelAnimationFrame(this._afRequestId); } this._disposables.dispose(); } }, { key: 'componentDidUpdate', value: function componentDidUpdate() { if (!this._initialHeightMeasured) { this._measureHeights(); } this._scrollToTrackedNodeIfNeeded(); } }, { key: '_scrollToTrackedNodeIfNeeded', value: function _scrollToTrackedNodeIfNeeded() { var trackedIndex = findIndexOfTheTrackedNode(this._store.roots); if (trackedIndex < 0) { return; } this.props.scrollToPosition(trackedIndex * this.state.elementHeight, this.state.elementHeight); } }, { key: '_measureHeights', value: function _measureHeights() { var measuredComponent = this.refs.measured; if (measuredComponent == null) { return; } this._initialHeightMeasured = true; var node = (_reactForAtom2 || _reactForAtom()).ReactDOM.findDOMNode(measuredComponent); var elementHeight = node.clientHeight; if (elementHeight !== this.state.elementHeight && elementHeight > 0) { this.setState({ elementHeight: elementHeight }); } } }, { key: 'render', value: function render() { var classes = { 'nuclide-file-tree': true, 'focusable-panel': true, 'tree-view': true, 'nuclide-file-tree-editing-working-set': this._store.isEditingWorkingSet() }; return (_reactForAtom2 || _reactForAtom()).React.createElement( 'div', { className: (0, (_classnames2 || _classnames()).default)(classes), tabIndex: 0 }, this._renderChildren() ); } }, { key: '_renderChildren', value: function _renderChildren() { var roots = this._store.roots; var childrenCount = countShownNodes(roots); if (childrenCount === 0) { return (_reactForAtom2 || _reactForAtom()).React.createElement((_EmptyComponent2 || _EmptyComponent()).EmptyComponent, null); } var scrollTop = this.props.containerScrollTop; var containerHeight = this.props.containerHeight; var elementHeight = this.state.elementHeight; var elementsInView = Math.ceil(containerHeight / elementHeight); var firstToRender = Math.floor(scrollTop / elementHeight) - BUFFER_ELEMENTS; // The container might have been scrolled too far for the current elements if (firstToRender > childrenCount - elementsInView) { firstToRender = childrenCount - elementsInView; } firstToRender = Math.max(firstToRender, 0); var amountToRender = elementsInView + 2 * BUFFER_ELEMENTS; var visibleChildren = []; var chosenMeasured = false; var node = findFirstNodeToRender(roots, firstToRender); // The chosen key is intentionally non-unique. This is to force React to reuse nodes // when scrolling is performed, rather then delete one and create another. // The selected key is a node's index modulo the amount of the rendered nodes. This way, // when a node is scrolled out of the view, another is added with just the same index. // Were React allowed to delete and creates nodes at its will it would have caused an // abrupt stop in the scrolling process. // See: https://github.com/facebook/react/issues/2295 var key = firstToRender % amountToRender; while (node != null && visibleChildren.length < amountToRender) { if (!node.isRoot && !chosenMeasured) { visibleChildren.push((_reactForAtom2 || _reactForAtom()).React.createElement((_FileTreeEntryComponent2 || _FileTreeEntryComponent()).FileTreeEntryComponent, { key: key, node: node, ref: 'measured' })); chosenMeasured = true; } else { visibleChildren.push((_reactForAtom2 || _reactForAtom()).React.createElement((_FileTreeEntryComponent2 || _FileTreeEntryComponent()).FileTreeEntryComponent, { key: key, node: node })); } node = node.findNext(); key = (key + 1) % amountToRender; } var topPlaceholderSize = firstToRender * elementHeight; var bottomPlaceholderCount = childrenCount - (firstToRender + visibleChildren.length); var bottomPlaceholderSize = bottomPlaceholderCount * elementHeight; return (_reactForAtom2 || _reactForAtom()).React.createElement( 'div', null, (_reactForAtom2 || _reactForAtom()).React.createElement('div', { style: { height: topPlaceholderSize + 'px' } }), (_reactForAtom2 || _reactForAtom()).React.createElement( 'ul', { className: 'list-tree has-collapsable-children' }, visibleChildren ), (_reactForAtom2 || _reactForAtom()).React.createElement('div', { style: { height: bottomPlaceholderSize + 'px' } }) ); } }]); return FileTree; })((_reactForAtom2 || _reactForAtom()).React.Component); exports.FileTree = FileTree; function findFirstNodeToRender(_x4, _x5) { var _again2 = true; _function2: while (_again2) { var roots = _x4, firstToRender = _x5; _again2 = false; var skipped = 0; var node = roots.find(function (r) { if (skipped + r.shownChildrenBelow > firstToRender) { return true; } skipped += r.shownChildrenBelow; return false; }); if (node == null) { return null; } if (skipped === firstToRender) { return node; } // The result is under this root, but not the root itself - skipping it and searching recursively _x4 = node.children; _x5 = firstToRender - skipped - 1; _again2 = true; skipped = node = undefined; continue _function2; } } function findIndexOfTheTrackedNode(nodes) { var skipped = 0; var trackedNodeRoot = nodes.find(function (node) { if (node.containsTrackedNode) { return true; } skipped += node.shownChildrenBelow; return false; }); if (trackedNodeRoot == null) { return -1; } if (trackedNodeRoot.isTracked) { return skipped; } return skipped + 1 + findIndexOfTheTrackedNode(trackedNodeRoot.children); } function countShownNodes(roots) { return roots.reduce(function (sum, root) { return sum + root.shownChildrenBelow; }, 0); }