Files
cellxgene/client/src/components/labelInput.js
T
eaae6df5e3 TS Revert (1) (#2402)
* revert all commits to before Typescript migration

* update compat workflow to match latest deps (#2335)

* update compat workflow to match latest deps

* attempt to debug

* attempt to debug

* remove debugging code

* typo

* update deps to match desktop (#2340)

* fix: don't run lint with `--fix` on push tests (#2273)

* fix: don't run lint with `--fix` on push tests

* npx

Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>

* rename X_approx_distribution to X_approximate_distribution (#2337)

* Correctly handle non-finite numbers in heuristic determination of X distribution (#2342)

* handle non-finites explicitly

* improve and test edge case handling for distribution estimation

* revert debugging changes

* code readability

* clean up type inferencing (#2332)

* unit tests for 64 bit conversion

* clean up type handling

* type inference tests

* more type inference fixes

* use schema to determine user intent for data typing

* stop using deprecated API

* fbs type encoding test

* add missing test

* add more tests

* correctly infer X type for CXG adaptor

* lint

* fix typo

* ts migration

* cleanup from PR review

* lint

* PR review changes

* remove unused packages from client (#2359)

* remove unused packages from client

* add missing peer dep

* fix: disable FE auth testing on compatibility tests (#2377)

* update: release process (#2277)

Co-authored-by: maniarathi <mani.arathi@gmail.com>

* fix: remove spaces in param setup (#2380)

* delete deploy workflow (#2396)

* undo reformatting which now does not pass lint

* fix snapshots which changed due to npm dep changes

* add missing quoting to snapshot

* another snapshot typo fix

* TS Revert (2) - replay PR #2347 and #2354 (#2403)

* replay edits from PR 2347

* TS Revert (3) - replay edits in PR #2327 (#2404)

* replay edits in PR 2327

* TS Revert (4) - replay PR #2355 (#2405)

* replay edits in PR 2355

* add additional babel config

* reformat with new prettier config

Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
2021-08-23 15:01:36 -07:00

179 lines
4.9 KiB
JavaScript

import React from "react";
import { InputGroup, MenuItem, Keys } from "@blueprintjs/core";
import { Suggest } from "@blueprintjs/select";
import fuzzysort from "fuzzysort";
export default class LabelInput extends React.PureComponent {
/*
Input widget for text labels, which acts like an InputGroup, but will also
accept a suggestion list (of labels), with sublime-like suggest search.
Properties:
* labelSuggestions -- array of suggested lables. May be array of string or
other objects. If array of objects, specify `labelKey`. If null, suggestion
mode is disabled.
* onSelect -- optional, callback upon selection of item from labelSuggestions.
(label) => void
* onChange -- optional, callback upon change in text input. (label) => void
* label -- component value, for controlled use
* newLabelMessage -- text to display when user enters a label not in labelSuggestions
(not used if suggestion mode disabled)
* inputProps -- will be passed to InputGroup
* popoverProps -- will be passed to <Suggest>
*/
/* maxinum number of suggestions */
static QueryResultLimit = 100;
constructor(props) {
super(props);
const { label } = props;
const query = label || "";
const queryResults = this.filterLabels(query);
this.state = {
query,
queryResults,
};
}
handleQueryChange = (query, event) => {
// https://github.com/palantir/blueprint/issues/2983
if (!event) return;
const queryResults = this.filterLabels(query);
this.setState({
query,
queryResults,
});
const { onChange } = this.props;
if (onChange) onChange(query, event);
};
handleItemSelect = (item, event) => {
/* only report the select if not already reported via onChange() */
const { target } = item;
const { query } = this.state;
const { onSelect } = this.props;
if (target !== query && onSelect) onSelect(target, event);
};
handleKeyDown = (e) => {
/*
prevent these events from propagating to containing form/dialog
and causing further side effects (eg, closing dialog, submitting
form, etc).
*/
const { keyCode } = e;
if (keyCode === Keys.ENTER || keyCode === Keys.ESCAPE) {
e.preventDefault();
}
if (keyCode === Keys.ESCAPE) {
e.stopPropagation();
}
};
handleChange = (e) => {
const { onChange } = this.props;
if (onChange) onChange(e.target.value);
};
renderLabelSuggestion = (queryResult, { handleClick, modifiers }) => {
if (queryResult.newLabel) {
const { newLabelMessage } = this.props;
return (
<MenuItem
icon="flag"
active={modifiers.active}
disabled={modifiers.disabled}
key={queryResult.target}
onClick={handleClick}
text={<em>{queryResult.target}</em>}
label={newLabelMessage || "New label"}
/>
);
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
key={queryResult.target}
onClick={handleClick}
text={queryResult.target}
/>
);
};
filterLabels(query) {
const { labelSuggestions } = this.props;
if (!labelSuggestions) return [];
/* empty query is wildcard */
if (query === "") {
return labelSuggestions
.slice(0, LabelInput.QueryResultLimit)
.map((l) => ({
target: l,
score: -10000,
}));
}
/* else, do a fuzzy query */
const options = {
limit: LabelInput.QueryResultLimit,
threshold: -10000, // don't return bad results
};
let queryResults = fuzzysort.go(query, labelSuggestions, options);
/* exact match will always be first in list */
if (query !== "" && queryResults[0]?.target !== query)
queryResults = [{ target: query, newLabel: true }, ...queryResults];
return queryResults;
}
render() {
const { props } = this;
const { labelSuggestions, label, autoFocus = true } = props;
const suggestEnabled = !!labelSuggestions && labelSuggestions.length > 0;
if (!suggestEnabled) {
return (
<InputGroup
autoFocus={autoFocus}
{...props.inputProps} // eslint-disable-line react/jsx-props-no-spreading --- Allows for modularity
value={label}
onChange={this.handleChange}
/>
);
}
const popoverProps = {
minimal: true,
...props.popoverProps,
};
const inputProps = {
...props.inputProps,
autoFocus: false,
};
const { queryResults } = this.state;
return (
<>
<Suggest
fill
inputValueRenderer={(i) => i.target}
items={queryResults}
itemRenderer={this.renderLabelSuggestion}
onItemSelect={this.handleItemSelect}
query={label}
onQueryChange={this.handleQueryChange}
popoverProps={popoverProps}
inputProps={inputProps}
onKeyDown={this.handleKeyDown}
/>
</>
);
}
}