UNPKG

alm

Version:

The best IDE for TypeScript

251 lines (250 loc) 11.1 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); /** * Provides a simple Select list view style API * similar to atom space pen views */ var React = require("react"); var ReactDOM = require("react-dom"); var csx = require("./base/csx"); var ui_1 = require("./ui"); var Modal = require("react-modal"); var styles = require("./styles/styles"); var utils_1 = require("../common/utils"); var commands = require("./commands/commands"); var fuzzaldrin_1 = require("fuzzaldrin"); var utils = require("../common/utils"); var typestyle = require("typestyle"); var inputClassName = typestyle.style(styles.modal.inputStyleBase); var SelectListView = /** @class */ (function (_super) { __extends(SelectListView, _super); function SelectListView(props) { var _this = _super.call(this, props) || this; _this.maxShowCount = 15; _this.closeOmniSearch = function () { _this.setState({ isOpen: false, filterValue: '' }); }; _this.onChangeFilter = utils_1.debounce(function (e) { var filterValue = ReactDOM.findDOMNode(_this.refs.omniSearchInput).value; _this.getNewData().then(function () { _this.filteredResults = getFilteredItems({ items: _this.state.data, textify: _this.state.textify, filterValue: filterValue }); _this.filteredResults = _this.filteredResults.slice(0, _this.maxShowCount); _this.setState({ filterValue: filterValue, selectedIndex: 0 }); }); }, 50); _this.incrementSelected = utils_1.debounce(function () { _this.setState({ selectedIndex: utils_1.rangeLimited({ num: _this.state.selectedIndex + 1, min: 0, max: Math.min(_this.maxShowCount - 1, _this.filteredResults.length - 1), loopAround: true }) }); }, 0, true); _this.decrementSelected = utils_1.debounce(function () { _this.setState({ selectedIndex: utils_1.rangeLimited({ num: _this.state.selectedIndex - 1, min: 0, max: Math.min(_this.maxShowCount - 1, _this.filteredResults.length - 1), loopAround: true }) }); }, 0, true); _this.onChangeSelected = function (e) { if (e.key == 'ArrowUp') { e.preventDefault(); _this.decrementSelected(); } if (e.key == 'ArrowDown') { e.preventDefault(); _this.incrementSelected(); } if (e.key == 'Enter') { e.preventDefault(); _this.selectIndex(_this.state.selectedIndex); } }; _this.selectIndex = function (index) { var result = _this.filteredResults[index]; _this.state.onSelect(result); _this.closeOmniSearch(); }; _this.getNewData = utils.onlyLastCall(function () { var filterValue = ReactDOM.findDOMNode(_this.refs.omniSearchInput).value; return _this.state.getNewData(filterValue).then(function (data) { _this.setState({ data: data }); }); }); _this.filteredResults = []; _this.state = { isOpen: false, selectedIndex: 0, header: '', data: [], }; return _this; } /* API sample usage this.refs.selectListView.show<ActiveProjectConfigDetails>({ header: 'Select the active project', data: availableProjects, render: (d,highlitedText) => <div>{highlitedText}</div>, textify: (d) => d.name, onSelect: (d) => { server.setActiveProjectName({ name: d.name }); state.setActiveProject(d.name); state.setInActiveProject(types.TriState.Unknown); } }); */ /** * The main interaction API */ SelectListView.prototype.show = function (args) { this.filteredResults = args.data.concat([]); this.setState({ isOpen: true, filterValue: '', selectedIndex: 0, header: args.header, data: args.data, render: args.render, textify: args.textify, onSelect: args.onSelect, getNewData: args.getNewData || (function () { return Promise.resolve(args.data); }), }); ReactDOM.findDOMNode(this.refs.omniSearchInput).focus(); }; SelectListView.prototype.componentDidMount = function () { var _this = this; exports.selectListView = this; commands.esc.on(function () { _this.closeOmniSearch(); }); }; SelectListView.prototype.componentDidUpdate = function () { var _this = this; // get the dom node that is selected // make sure its parent scrolls to make this visible setTimeout(function () { if (_this.refs.selected) { var selected = _this.refs.selected; selected.scrollIntoViewIfNeeded(false); } }); }; SelectListView.prototype.render = function () { var _this = this; var fileList = this.filteredResults; var selectedIndex = this.state.selectedIndex; var fileListRendered = fileList.map(function (item, i) { // key = i var selected = selectedIndex === i; var selectedStyle = selected ? { background: '#545454', color: 'white' } : {}; var ref = selected && "selected"; return (React.createElement("div", { key: i, style: csx.extend(selectedStyle, styles.padded2, styles.hand, csx.content), onClick: function () { return _this.selectIndex(i); }, ref: ref }, _this.state.render(item, renderMatchedSegments(_this.state.textify(item), _this.state.filterValue)))); }); return React.createElement(Modal, { isOpen: this.state.isOpen, onRequestClose: this.closeOmniSearch }, React.createElement("div", { style: csx.extend(csx.vertical, csx.flex) }, React.createElement("div", { style: csx.extend(csx.horizontal, csx.content) }, React.createElement("h4", null, this.state.header), React.createElement("div", { style: csx.flex }), React.createElement("div", { style: { fontSize: '0.9rem', color: 'grey' } }, React.createElement("code", { style: styles.modal.keyStrokeStyle }, "Esc"), " to exit ", React.createElement("code", { style: styles.modal.keyStrokeStyle }, "Enter"), " to select")), React.createElement("div", { style: csx.extend(styles.padded1TopBottom, csx.vertical, csx.content) }, React.createElement("input", { type: "text", ref: "omniSearchInput", placeholder: "Filter", className: inputClassName, onChange: this.onChangeFilter, onKeyDown: this.onChangeSelected })), React.createElement("div", { style: csx.extend(csx.vertical, csx.flex, { overflow: 'auto' }) }, React.createElement("div", { style: csx.vertical }, fileListRendered)))); }; return SelectListView; }(ui_1.BaseComponent)); exports.SelectListView = SelectListView; /** * Applies fuzzy filter to the text version of each item returning the matched items */ function getFilteredItems(args) { // Store the items for each text value var textValueToItems = Object.create(null); args.items.forEach(function (item) { var text = args.textify(item); if (!textValueToItems[text]) textValueToItems[text] = []; textValueToItems[text].push(item); }); // Get the unique text values var textValues = Object.keys(utils.createMap(args.items.map(args.textify))); // filter them var filteredTextValues = fuzzaldrin_1.filter(textValues, args.filterValue); return utils.selectMany(filteredTextValues.map(function (textvalue) { return textValueToItems[textvalue]; })); } exports.getFilteredItems = getFilteredItems; /** * Based on https://github.com/atom/fuzzy-finder/blob/51f1f2415ecbfab785596825a011c1d2fa2658d3/lib/fuzzy-finder-view.coffee#L56-L74 */ function renderMatchedSegments(result, query) { // local function that creates the *matched segment* data structure function getMatchedSegments(result, query) { var matches = fuzzaldrin_1.match(result, query); var matchMap = utils_1.createMap(matches); // collapse contiguous sections into a single `<strong>` var currentUnmatchedCharacters = []; var currentMatchedCharacters = []; var combined = []; function closeOffUnmatched() { if (currentUnmatchedCharacters.length) { combined.push({ str: currentUnmatchedCharacters.join(''), matched: false }); currentUnmatchedCharacters = []; } } function closeOffMatched() { if (currentMatchedCharacters.length) { combined.push({ str: currentMatchedCharacters.join(''), matched: true }); currentMatchedCharacters = []; } } result.split('').forEach(function (c, i) { var isMatched = matchMap[i]; if (isMatched) { if (currentMatchedCharacters.length) { currentMatchedCharacters.push(c); } else { currentMatchedCharacters = [c]; // close off any unmatched characters closeOffUnmatched(); } } else { if (currentUnmatchedCharacters.length) { currentUnmatchedCharacters.push(c); } else { currentUnmatchedCharacters = [c]; // close off any matched characters closeOffMatched(); } } }); closeOffMatched(); closeOffUnmatched(); return combined; } /** * Rendering the matched segment data structure is trivial */ var matched = getMatchedSegments(result, query); var matchedStyle = { fontWeight: 'bold', color: '#66d9ef' }; return matched.map(function (item, i) { return React.createElement("span", { key: i, style: item.matched ? matchedStyle : {} }, item.str); }); } exports.renderMatchedSegments = renderMatchedSegments;