mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-02 08:38:11 +08:00
Anno editing label/category error states (#1048)
* anno error states * label validation should not treat empty string as error * disable Submit if no label or category entered * edit category name error states * flex start * pr cleanup * label name validation fixes * PR comment responses
This commit is contained in:
@@ -31,19 +31,26 @@ class CategoryValue extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
editedLabelText: ""
|
||||
editedLabelText: String(
|
||||
props.categoricalSelection[props.metadataField].categoryValues[
|
||||
props.categoryIndex
|
||||
]
|
||||
).valueOf()
|
||||
};
|
||||
}
|
||||
|
||||
handleDeleteValue = () => {
|
||||
const {
|
||||
dispatch,
|
||||
metadataField,
|
||||
categoryIndex,
|
||||
categoricalSelection
|
||||
} = this.props;
|
||||
getLabel = () => {
|
||||
const { metadataField, categoryIndex, categoricalSelection } = this.props;
|
||||
const category = categoricalSelection[metadataField];
|
||||
const label = category.categoryValues[categoryIndex];
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
handleDeleteValue = () => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const label = this.getLabel();
|
||||
|
||||
dispatch({
|
||||
type: "annotation: delete label",
|
||||
metadataField,
|
||||
@@ -52,14 +59,8 @@ class CategoryValue extends React.Component {
|
||||
};
|
||||
|
||||
handleAddCurrentSelectionToThisLabel = () => {
|
||||
const {
|
||||
dispatch,
|
||||
metadataField,
|
||||
categoryIndex,
|
||||
categoricalSelection
|
||||
} = this.props;
|
||||
const category = categoricalSelection[metadataField];
|
||||
const label = category.categoryValues[categoryIndex];
|
||||
const { dispatch, metadataField, categoryIndex } = this.props;
|
||||
const label = this.getLabel();
|
||||
dispatch({
|
||||
type: "annotation: label current cell selection",
|
||||
metadataField,
|
||||
@@ -69,15 +70,9 @@ class CategoryValue extends React.Component {
|
||||
};
|
||||
|
||||
handleEditValue = () => {
|
||||
const {
|
||||
dispatch,
|
||||
metadataField,
|
||||
categoryIndex,
|
||||
categoricalSelection
|
||||
} = this.props;
|
||||
const { dispatch, metadataField, categoryIndex } = this.props;
|
||||
const { editedLabelText } = this.state;
|
||||
const category = categoricalSelection[metadataField];
|
||||
const label = category.categoryValues[categoryIndex];
|
||||
const label = this.getLabel();
|
||||
dispatch({
|
||||
type: "annotation: label edited",
|
||||
editedLabel: editedLabelText,
|
||||
@@ -85,7 +80,80 @@ class CategoryValue extends React.Component {
|
||||
categoryIndex,
|
||||
label
|
||||
});
|
||||
this.setState({ editedLabelText: "" });
|
||||
};
|
||||
|
||||
valueNameErrorMessage = () => {
|
||||
const { editedLabelText } = this.state;
|
||||
const err = this.valueNameError();
|
||||
if (!err) return null;
|
||||
|
||||
let markup = null;
|
||||
|
||||
if (err === "empty_string") {
|
||||
markup = (
|
||||
<span
|
||||
style={{
|
||||
fontStyle: "italic",
|
||||
fontSize: 12,
|
||||
marginTop: 5,
|
||||
color: Colors.ORANGE3
|
||||
}}
|
||||
>
|
||||
{"Label cannot be blank"}
|
||||
</span>
|
||||
);
|
||||
} else if (err === "duplicate") {
|
||||
markup = (
|
||||
<span
|
||||
style={{
|
||||
fontStyle: "italic",
|
||||
fontSize: 12,
|
||||
marginTop: 5,
|
||||
color: Colors.ORANGE3
|
||||
}}
|
||||
>
|
||||
{"Label must be unique"}
|
||||
</span>
|
||||
);
|
||||
} else if (err === "characters") {
|
||||
markup = (
|
||||
<span
|
||||
style={{
|
||||
fontStyle: "italic",
|
||||
fontSize: 12,
|
||||
marginTop: 5,
|
||||
color: Colors.ORANGE3
|
||||
}}
|
||||
>
|
||||
{"Only alphanumeric and underscore allowed"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return markup;
|
||||
};
|
||||
|
||||
valueNameError = () => {
|
||||
const { editedLabelText } = this.state;
|
||||
const { categoricalSelection, metadataField, categoryIndex } = this.props;
|
||||
|
||||
let err = null;
|
||||
|
||||
const category = categoricalSelection[metadataField];
|
||||
const displayString = String(
|
||||
category.categoryValues[categoryIndex]
|
||||
).valueOf();
|
||||
|
||||
if (editedLabelText === "") {
|
||||
err = "empty_string";
|
||||
} else if (
|
||||
category.categoryValues.indexOf(editedLabelText) > -1 &&
|
||||
editedLabelText !== displayString
|
||||
) {
|
||||
err = "duplicate";
|
||||
} else if (!AnnotationsHelpers.isLegalAnnotationName(editedLabelText)) {
|
||||
err = "characters";
|
||||
}
|
||||
return err;
|
||||
};
|
||||
|
||||
activateEditLabelMode = () => {
|
||||
@@ -115,7 +183,7 @@ class CategoryValue extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
shouldComponentUpdate = nextProps => {
|
||||
shouldComponentUpdate = (nextProps, nextState) => {
|
||||
/*
|
||||
Checks to see if at least one of the following changed:
|
||||
* world state
|
||||
@@ -125,7 +193,7 @@ class CategoryValue extends React.Component {
|
||||
|
||||
If and only if true, update the component
|
||||
*/
|
||||
const { props } = this;
|
||||
const { props, state } = this;
|
||||
const { metadataField, categoryIndex, categoricalSelection } = props;
|
||||
const { categoricalSelection: newCategoricalSelection } = nextProps;
|
||||
|
||||
@@ -141,13 +209,15 @@ class CategoryValue extends React.Component {
|
||||
const colorAccessorChange = props.colorAccessor !== nextProps.colorAccessor;
|
||||
const annotationsChange = props.annotations !== nextProps.annotations;
|
||||
const crossfilterChange = props.crossfilter !== nextProps.crossfilter;
|
||||
const editingLabel = state.editedLabelText !== nextState.editedLabelText;
|
||||
|
||||
return (
|
||||
valueSelectionChange ||
|
||||
worldChange ||
|
||||
colorAccessorChange ||
|
||||
annotationsChange ||
|
||||
crossfilterChange
|
||||
crossfilterChange ||
|
||||
editingLabel
|
||||
);
|
||||
};
|
||||
|
||||
@@ -222,6 +292,8 @@ class CategoryValue extends React.Component {
|
||||
flippedProps
|
||||
} = this.props;
|
||||
|
||||
const { editedLabelText } = this.state;
|
||||
|
||||
if (!categoricalSelection) return null;
|
||||
|
||||
const category = categoricalSelection[metadataField];
|
||||
@@ -347,6 +419,9 @@ class CategoryValue extends React.Component {
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
if (this.valueNameError()) {
|
||||
return;
|
||||
}
|
||||
this.handleEditValue();
|
||||
}}
|
||||
>
|
||||
@@ -357,6 +432,7 @@ class CategoryValue extends React.Component {
|
||||
}}
|
||||
small
|
||||
autoFocus
|
||||
intent={this.valueNameError() ? "warning" : "none"}
|
||||
onChange={e => {
|
||||
this.setState({ editedLabelText: e.target.value });
|
||||
}}
|
||||
@@ -365,6 +441,7 @@ class CategoryValue extends React.Component {
|
||||
<Button
|
||||
minimal
|
||||
style={{ position: "relative", top: -1 }}
|
||||
disabled={this.valueNameError()}
|
||||
type="button"
|
||||
icon="small-tick"
|
||||
data-testclass="submitEdit"
|
||||
@@ -373,6 +450,7 @@ class CategoryValue extends React.Component {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{this.valueNameErrorMessage()}
|
||||
</form>
|
||||
) : null}
|
||||
{/*
|
||||
|
||||
Reference in New Issue
Block a user