alm
Version:
The best IDE for TypeScript
251 lines (250 loc) • 11.1 kB
JavaScript
;
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;