Handle loading state when rendering categories with one label (#1393)

* refactor & loading state

* truncate

* fix minor PR review issues

* lint

Co-authored-by: bkmartinjr <bruce@chanzuckerberg.com>
This commit is contained in:
Colin Megill
2020-04-15 14:38:44 -04:00
committed by GitHub
co-authored by bkmartinjr
parent 7d4d360e52
commit ea434fc46f
9 changed files with 164 additions and 196 deletions
@@ -1,37 +1,37 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import AnnoDialog from "./annoDialog"; import AnnoDialog from "../annoDialog";
import LabelInput from "./labelInput"; import LabelInput from "../labelInput";
import { labelPrompt, isLabelErroneous } from "./labelUtil"; import { labelPrompt, isLabelErroneous } from "../labelUtil";
@connect(state => ({ @connect((state) => ({
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
categoricalSelection: state.categoricalSelection, categoricalSelection: state.categoricalSelection,
annotations: state.annotations, annotations: state.annotations,
universe: state.universe, universe: state.universe,
ontology: state.ontology, ontology: state.ontology,
crossfilter: state.crossfilter crossfilter: state.crossfilter,
})) }))
class Category extends React.PureComponent { class Category extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
newLabelText: "" newLabelText: "",
}; };
} }
disableAddNewLabelMode = e => { disableAddNewLabelMode = (e) => {
const { dispatch } = this.props; const { dispatch } = this.props;
this.setState({ this.setState({
newLabelText: "" newLabelText: "",
}); });
dispatch({ dispatch({
type: "annotation: disable add new label mode" type: "annotation: disable add new label mode",
}); });
if (e) e.preventDefault(); if (e) e.preventDefault();
}; };
handleAddNewLabelToCategory = e => { handleAddNewLabelToCategory = (e) => {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
const { newLabelText } = this.state; const { newLabelText } = this.state;
@@ -40,12 +40,12 @@ class Category extends React.PureComponent {
type: "annotation: add new label to category", type: "annotation: add new label to category",
metadataField, metadataField,
newLabelText, newLabelText,
assignSelectedCells: false assignSelectedCells: false,
}); });
e.preventDefault(); e.preventDefault();
}; };
addLabelAndAssignCells = e => { addLabelAndAssignCells = (e) => {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
const { newLabelText } = this.state; const { newLabelText } = this.state;
@@ -54,21 +54,21 @@ class Category extends React.PureComponent {
type: "annotation: add new label to category", type: "annotation: add new label to category",
metadataField, metadataField,
newLabelText, newLabelText,
assignSelectedCells: true assignSelectedCells: true,
}); });
e.preventDefault(); e.preventDefault();
}; };
labelNameError = name => { labelNameError = (name) => {
const { metadataField, ontology, universe } = this.props; const { metadataField, ontology, universe } = this.props;
return isLabelErroneous(name, metadataField, ontology, universe.schema); return isLabelErroneous(name, metadataField, ontology, universe.schema);
}; };
instruction = label => { instruction = (label) => {
return labelPrompt(this.labelNameError(label), "New, unique label", ":"); return labelPrompt(this.labelNameError(label), "New, unique label", ":");
}; };
handleChangeOrSelect = label => { handleChangeOrSelect = (label) => {
this.setState({ newLabelText: label }); this.setState({ newLabelText: label });
}; };
@@ -86,7 +86,7 @@ class Category extends React.PureComponent {
} }
inputProps={{ "data-testid": `${metadataField}:create-label-dialog` }} inputProps={{ "data-testid": `${metadataField}:create-label-dialog` }}
primaryButtonProps={{ primaryButtonProps={{
"data-testid": `${metadataField}:submit-label` "data-testid": `${metadataField}:submit-label`,
}} }}
title="Add new label to category" title="Add new label to category"
instruction={this.instruction(newLabelText)} instruction={this.instruction(newLabelText)}
@@ -107,7 +107,7 @@ class Category extends React.PureComponent {
"data-testid": `${metadataField}:new-label-name`, "data-testid": `${metadataField}:new-label-name`,
leftIcon: "tag", leftIcon: "tag",
intent: "none", intent: "none",
autoFocus: true autoFocus: true,
}} }}
/> />
} }
@@ -1,41 +1,41 @@
import React from "react"; import React from "react";
import _ from "lodash"; import _ from "lodash";
import { connect } from "react-redux"; import { connect } from "react-redux";
import AnnoDialog from "./annoDialog"; import AnnoDialog from "../annoDialog";
import LabelInput from "./labelInput"; import LabelInput from "../labelInput";
import { labelPrompt } from "./labelUtil"; import { labelPrompt } from "../labelUtil";
import { AnnotationsHelpers } from "../../util/stateManager"; import { AnnotationsHelpers } from "../../../util/stateManager";
@connect(state => ({ @connect((state) => ({
categoricalSelection: state.categoricalSelection, categoricalSelection: state.categoricalSelection,
annotations: state.annotations, annotations: state.annotations,
universe: state.universe, universe: state.universe,
ontology: state.ontology ontology: state.ontology,
})) }))
class AnnoDialogEditCategoryName extends React.PureComponent { class AnnoDialogEditCategoryName extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
newCategoryText: props.metadataField newCategoryText: props.metadataField,
}; };
} }
handleChangeOrSelect = name => { handleChangeOrSelect = (name) => {
this.setState({ this.setState({
newCategoryText: name newCategoryText: name,
}); });
}; };
disableEditCategoryMode = () => { disableEditCategoryMode = () => {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
type: "annotation: disable category edit mode" type: "annotation: disable category edit mode",
}); });
this.setState({ newCategoryText: metadataField }); this.setState({ newCategoryText: metadataField });
}; };
handleEditCategory = e => { handleEditCategory = (e) => {
const { dispatch, metadataField, categoricalSelection } = this.props; const { dispatch, metadataField, categoricalSelection } = this.props;
const { newCategoryText } = this.state; const { newCategoryText } = this.state;
@@ -54,12 +54,12 @@ class AnnoDialogEditCategoryName extends React.PureComponent {
type: "annotation: category edited", type: "annotation: category edited",
metadataField, metadataField,
newCategoryText, newCategoryText,
data: newCategoryText data: newCategoryText,
}); });
e.preventDefault(); e.preventDefault();
}; };
editedCategoryNameError = name => { editedCategoryNameError = (name) => {
const { metadataField, categoricalSelection } = this.props; const { metadataField, categoricalSelection } = this.props;
/* check for syntax errors in category name */ /* check for syntax errors in category name */
@@ -80,7 +80,7 @@ class AnnoDialogEditCategoryName extends React.PureComponent {
return false; return false;
}; };
instruction = name => { instruction = (name) => {
return labelPrompt( return labelPrompt(
this.editedCategoryNameError(name), this.editedCategoryNameError(name),
"New, unique category name", "New, unique category name",
@@ -101,10 +101,10 @@ class AnnoDialogEditCategoryName extends React.PureComponent {
annotations.categoryBeingEdited === metadataField annotations.categoryBeingEdited === metadataField
} }
inputProps={{ inputProps={{
"data-testid": `${metadataField}:edit-category-name-dialog` "data-testid": `${metadataField}:edit-category-name-dialog`,
}} }}
primaryButtonProps={{ primaryButtonProps={{
"data-testid": `${metadataField}:submit-category-edit` "data-testid": `${metadataField}:submit-category-edit`,
}} }}
title="Edit category name" title="Edit category name"
instruction={this.instruction(newCategoryText)} instruction={this.instruction(newCategoryText)}
@@ -124,7 +124,7 @@ class AnnoDialogEditCategoryName extends React.PureComponent {
"data-testid": `${metadataField}:edit-category-name-text`, "data-testid": `${metadataField}:edit-category-name-text`,
leftIcon: "tag", leftIcon: "tag",
intent: "none", intent: "none",
autoFocus: true autoFocus: true,
}} }}
newLabelMessage="New category" newLabelMessage="New category"
/> />
@@ -3,11 +3,11 @@ import _ from "lodash";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Flipper, Flipped } from "react-flip-toolkit"; import { Flipper, Flipped } from "react-flip-toolkit";
import * as globals from "../../globals"; import * as globals from "../../../globals";
import Value from "./value"; import Value from "../value";
@connect(state => ({ @connect((state) => ({
categoricalSelection: state.categoricalSelection categoricalSelection: state.categoricalSelection,
})) }))
class Category extends React.Component { class Category extends React.Component {
constructor(props) { constructor(props) {
@@ -21,7 +21,7 @@ class Category extends React.Component {
return _.map(optTuples, (tuple, i) => { return _.map(optTuples, (tuple, i) => {
return ( return (
<Flipped key={tuple[1]} flipId={tuple[1]}> <Flipped key={tuple[1]} flipId={tuple[1]}>
{flippedProps => ( {(flippedProps) => (
<Value <Value
isUserAnno={isUserAnno} isUserAnno={isUserAnno}
optTuples={optTuples} optTuples={optTuples}
@@ -42,17 +42,17 @@ class Category extends React.Component {
metadataField, metadataField,
categoricalSelection, categoricalSelection,
children, children,
isExpanded isExpanded,
} = this.props; } = this.props;
const { isTruncated } = categoricalSelection[metadataField]; const { isTruncated } = categoricalSelection[metadataField];
const cat = categoricalSelection[metadataField]; const cat = categoricalSelection[metadataField];
const optTuples = [...cat.categoryValueIndices]; const optTuples = [...cat.categoryValueIndices];
const optTuplesAsKey = _.map(optTuples, t => t[0]).join(""); // animation const optTuplesAsKey = _.map(optTuples, (t) => t[0]).join(""); // animation
return ( return (
<div <div
style={{ style={{
maxWidth: globals.maxControlsWidth maxWidth: globals.maxControlsWidth,
}} }}
data-testclass="category" data-testclass="category"
data-testid={`category-${metadataField}`} data-testid={`category-${metadataField}`}
@@ -61,7 +61,7 @@ class Category extends React.Component {
style={{ style={{
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "baseline" alignItems: "baseline",
}} }}
> >
{children} {children}
@@ -7,27 +7,31 @@ import {
Button, Button,
Tooltip, Tooltip,
Icon, Icon,
Position Position,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import CategoryFlipperLayout from "./categoryFlipperLayout"; import CategoryFlipperLayout from "./categoryFlipperLayout";
import AnnoMenu from "./annoMenuCategory"; import AnnoMenu from "./annoMenuCategory";
import AnnoDialogEditCategoryName from "./annoDialogEditCategoryName"; import AnnoDialogEditCategoryName from "./annoDialogEditCategoryName";
import AnnoDialogAddLabel from "./annoDialogAddLabel"; import AnnoDialogAddLabel from "./annoDialogAddLabel";
import * as globals from "../../globals"; import * as globals from "../../../globals";
import maybeTruncateString from "../../util/maybeTruncateString"; import maybeTruncateString from "../../../util/maybeTruncateString";
@connect(state => ({ @connect((state, ownProps) => {
colorAccessor: state.colors.colorAccessor, const { metadataField } = ownProps;
categoricalSelection: state.categoricalSelection, return {
annotations: state.annotations, isColorAccessor: state.colors.colorAccessor === metadataField,
universe: state.universe categoricalSelection: state.categoricalSelection,
})) annotations: state.annotations,
universe: state.universe,
schema: state.world?.schema,
};
})
class Category extends React.Component { class Category extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
isChecked: true isChecked: true,
}; };
} }
@@ -47,7 +51,7 @@ class Category extends React.Component {
cat.categoryValueSelected, cat.categoryValueSelected,
(res, cond) => (cond ? res + 1 : res), (res, cond) => (cond ? res + 1 : res),
0 0
) ),
}; };
if (categoryCount.selectedCatCount === categoryCount.totalCatCount) { if (categoryCount.selectedCatCount === categoryCount.totalCatCount) {
/* everything is on, so not indeterminate */ /* everything is on, so not indeterminate */
@@ -69,7 +73,7 @@ class Category extends React.Component {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
type: "color by categorical metadata", type: "color by categorical metadata",
colorAccessor: metadataField colorAccessor: metadataField,
}); });
}; };
@@ -77,7 +81,7 @@ class Category extends React.Component {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
type: "categorical metadata filter all of these", type: "categorical metadata filter all of these",
metadataField metadataField,
}); });
this.setState({ isChecked: true }); this.setState({ isChecked: true });
} }
@@ -86,7 +90,7 @@ class Category extends React.Component {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
type: "categorical metadata filter none of these", type: "categorical metadata filter none of these",
metadataField metadataField,
}); });
this.setState({ isChecked: false }); this.setState({ isChecked: false });
} }
@@ -114,25 +118,25 @@ class Category extends React.Component {
return ( return (
<div <div
style={{ style={{
maxWidth: globals.maxControlsWidth maxWidth: globals.maxControlsWidth,
}} }}
> >
<div <div
style={{ style={{
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "baseline" alignItems: "baseline",
}} }}
> >
<div <div
style={{ style={{
display: "flex", display: "flex",
justifyContent: "flex-start", justifyContent: "flex-start",
alignItems: "flex-start" alignItems: "flex-start",
}} }}
> >
<label className="bp3-control bp3-checkbox"> <label className="bp3-control bp3-checkbox">
<input disabled checked={true} type="checkbox" /> <input disabled checked type="checkbox" />
<span className="bp3-control-indicator" /> <span className="bp3-control-indicator" />
</label> </label>
<Tooltip <Tooltip
@@ -143,13 +147,13 @@ class Category extends React.Component {
usePortal usePortal
modifiers={{ modifiers={{
preventOverflow: { enabled: false }, preventOverflow: { enabled: false },
hide: { enabled: false } hide: { enabled: false },
}} }}
> >
<span <span
style={{ style={{
cursor: "pointer", cursor: "pointer",
display: "inline-block" display: "inline-block",
}} }}
> >
{truncatedString || metadataField} {truncatedString || metadataField}
@@ -169,11 +173,11 @@ class Category extends React.Component {
const { const {
metadataField, metadataField,
categoricalSelection, categoricalSelection,
colorAccessor, isColorAccessor,
isUserAnno,
annotations, annotations,
isExpanded, isExpanded,
onExpansionChange onExpansionChange,
schema,
} = this.props; } = this.props;
const isStillLoading = !(categoricalSelection?.[metadataField] ?? false); const isStillLoading = !(categoricalSelection?.[metadataField] ?? false);
@@ -181,6 +185,8 @@ class Category extends React.Component {
return this.renderIsStillLoading(); return this.renderIsStillLoading();
} }
const isUserAnno =
schema?.annotations?.obsByName[metadataField]?.writable ?? false;
const isTruncated = _.get( const isTruncated = _.get(
categoricalSelection, categoricalSelection,
[metadataField, "isTruncated"], [metadataField, "isTruncated"],
@@ -192,6 +198,20 @@ class Category extends React.Component {
globals.categoryDisplayStringMaxLength globals.categoryDisplayStringMaxLength
); );
if (
!isUserAnno &&
schema?.annotations?.obsByName[metadataField]?.categories?.length === 1
) {
return (
<div style={{ marginBottom: 10, marginTop: 4 }}>
<span style={{ fontWeight: 700 }}>
{truncatedString ? truncatedString : metadataField}
</span>
: {schema.annotations.obsByName[metadataField].categories[0]}
</div>
);
}
return ( return (
<CategoryFlipperLayout <CategoryFlipperLayout
metadataField={metadataField} metadataField={metadataField}
@@ -202,7 +222,7 @@ class Category extends React.Component {
style={{ style={{
display: "flex", display: "flex",
justifyContent: "flex-start", justifyContent: "flex-start",
alignItems: "flex-start" alignItems: "flex-start",
}} }}
> >
<label className="bp3-control bp3-checkbox"> <label className="bp3-control bp3-checkbox">
@@ -210,7 +230,7 @@ class Category extends React.Component {
data-testclass="category-select" data-testclass="category-select"
data-testid={`${metadataField}:category-select`} data-testid={`${metadataField}:category-select`}
onChange={this.handleToggleAllClick.bind(this)} onChange={this.handleToggleAllClick.bind(this)}
ref={el => { ref={(el) => {
this.checkbox = el; this.checkbox = el;
return el; return el;
}} }}
@@ -227,14 +247,14 @@ class Category extends React.Component {
usePortal usePortal
modifiers={{ modifiers={{
preventOverflow: { enabled: false }, preventOverflow: { enabled: false },
hide: { enabled: false } hide: { enabled: false },
}} }}
> >
<span <span
data-testid={`${metadataField}:category-expand`} data-testid={`${metadataField}:category-expand`}
style={{ style={{
cursor: "pointer", cursor: "pointer",
display: "inline-block" display: "inline-block",
}} }}
onClick={() => { onClick={() => {
const editingCategory = const editingCategory =
@@ -288,8 +308,8 @@ class Category extends React.Component {
data-testclass="colorby" data-testclass="colorby"
data-testid={`colorby-${metadataField}`} data-testid={`colorby-${metadataField}`}
onClick={this.handleColorChange} onClick={this.handleColorChange}
active={colorAccessor === metadataField} active={isColorAccessor}
intent={colorAccessor === metadataField ? "primary" : "none"} intent={isColorAccessor ? "primary" : "none"}
disabled={isTruncated} disabled={isTruncated}
icon="tint" icon="tint"
/> />
@@ -119,57 +119,6 @@ class Categories extends React.Component {
} }
}; };
maybeRenderSingleLabel = (allCategoryNames, schema) => {
return allCategoryNames.map((catName) =>
!schema.annotations.obsByName[catName].writable &&
schema.annotations.obsByName[catName].categories.length === 1 ? (
<div style={{ marginBottom: 10 }}>
<span style={{ fontWeight: 700 }}>{catName}</span>:{" "}
{schema.annotations.obsByName[catName].categories[0]}
</div>
) : null
);
};
maybeRenderReadOnly = (
allCategoryNames,
schema,
expandedCats,
createAnnoModeActive
) => {
return allCategoryNames.map((catName) =>
!schema.annotations.obsByName[catName].writable &&
schema.annotations.obsByName[catName].categories.length > 1 ? (
<Category
key={catName}
metadataField={catName}
onExpansionChange={this.onExpansionChange}
isExpanded={expandedCats.has(catName)}
createAnnoModeActive={createAnnoModeActive}
isUserAnno={false}
/>
) : null
);
};
maybeRenderAnnotations = (
allCategoryNames,
schema,
expandedCats,
createAnnoModeActive
) => {
return allCategoryNames.map((catName) =>
schema.annotations.obsByName[catName].writable ? (
<Category
key={catName}
metadataField={catName}
onExpansionChange={this.onExpansionChange}
isExpanded={expandedCats.has(catName)}
createAnnoModeActive={createAnnoModeActive}
isUserAnno
/>
) : null
);
};
render() { render() {
const { const {
createAnnoModeActive, createAnnoModeActive,
@@ -227,32 +176,31 @@ class Categories extends React.Component {
} }
/> />
<div style={{ marginLeft: 0 }}> {/* READ ONLY CATEGORICAL FIELDS */}
{ {/* this is duplicative but flat, could be abstracted */}
this.maybeRenderSingleLabel( {allCategoryNames.map((catName) =>
allCategoryNames, !schema.annotations.obsByName[catName].writable ? (
schema, <Category
expandedCats, key={catName}
createAnnoModeActive metadataField={catName}
) /* Categories with only one label */ onExpansionChange={this.onExpansionChange}
} isExpanded={expandedCats.has(catName)}
</div> createAnnoModeActive={createAnnoModeActive}
{ />
this.maybeRenderReadOnly( ) : null
allCategoryNames, )}
schema, {/* WRITEABLE FIELDS */}
expandedCats, {allCategoryNames.map((catName) =>
createAnnoModeActive schema.annotations.obsByName[catName].writable ? (
) /* Read only categorical fields, ostensibly 'normal' mode */ <Category
} key={catName}
{ metadataField={catName}
this.maybeRenderAnnotations( onExpansionChange={this.onExpansionChange}
allCategoryNames, isExpanded={expandedCats.has(catName)}
schema, createAnnoModeActive={createAnnoModeActive}
expandedCats, />
createAnnoModeActive ) : null
) /* Writable fields, ie., anno */ )}
}
{writableCategoriesEnabled ? ( {writableCategoriesEnabled ? (
<div> <div>
@@ -8,16 +8,16 @@ import {
Popover, Popover,
Position, Position,
PopoverInteractionKind, PopoverInteractionKind,
Tooltip Tooltip,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import Occupancy from "./occupancy"; import Occupancy from "./occupancy";
import * as globals from "../../globals"; import * as globals from "../../../globals";
import styles from "./categorical.css"; import styles from "../categorical.css";
import AnnoDialog from "./annoDialog"; import AnnoDialog from "../annoDialog";
import LabelInput from "./labelInput"; import LabelInput from "../labelInput";
import { AnnotationsHelpers } from "../../util/stateManager"; import { AnnotationsHelpers } from "../../../util/stateManager";
import { labelPrompt, isLabelErroneous } from "./labelUtil"; import { labelPrompt, isLabelErroneous } from "../labelUtil";
/* this is defined outside of the class so we can use it in connect() */ /* this is defined outside of the class so we can use it in connect() */
function _currentLabel(ownProps, categoricalSelection) { function _currentLabel(ownProps, categoricalSelection) {
@@ -29,7 +29,7 @@ function _currentLabel(ownProps, categoricalSelection) {
@connect((state, ownProps) => { @connect((state, ownProps) => {
const { pointDilation, categoricalSelection } = state; const { pointDilation, categoricalSelection } = state;
const { metadataField, categoryField } = ownProps; const { metadataField } = ownProps;
const isDilated = const isDilated =
pointDilation.metadataField === metadataField && pointDilation.metadataField === metadataField &&
pointDilation.categoryField === pointDilation.categoryField ===
@@ -43,14 +43,14 @@ function _currentLabel(ownProps, categoricalSelection) {
world: state.world, world: state.world,
crossfilter: state.crossfilter, crossfilter: state.crossfilter,
ontology: state.ontology, ontology: state.ontology,
isDilated isDilated,
}; };
}) })
class CategoryValue extends React.Component { class CategoryValue extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
editedLabelText: this.currentLabel() editedLabelText: this.currentLabel(),
}; };
} }
@@ -62,7 +62,7 @@ class CategoryValue extends React.Component {
prevProps.categoryIndex !== categoryIndex prevProps.categoryIndex !== categoryIndex
) { ) {
this.setState({ this.setState({
editedLabelText: this.currentLabel() editedLabelText: this.currentLabel(),
}); });
} }
} }
@@ -74,7 +74,7 @@ class CategoryValue extends React.Component {
dispatch({ dispatch({
type: "annotation: delete label", type: "annotation: delete label",
metadataField, metadataField,
label label,
}); });
}; };
@@ -85,11 +85,11 @@ class CategoryValue extends React.Component {
type: "annotation: label current cell selection", type: "annotation: label current cell selection",
metadataField, metadataField,
categoryIndex, categoryIndex,
label label,
}); });
}; };
handleEditValue = e => { handleEditValue = (e) => {
const { dispatch, metadataField, categoryIndex } = this.props; const { dispatch, metadataField, categoryIndex } = this.props;
const { editedLabelText } = this.state; const { editedLabelText } = this.state;
const label = this.getLabel(); const label = this.getLabel();
@@ -99,12 +99,12 @@ class CategoryValue extends React.Component {
editedLabel: editedLabelText, editedLabel: editedLabelText,
metadataField, metadataField,
categoryIndex, categoryIndex,
label label,
}); });
e.preventDefault(); e.preventDefault();
}; };
handleCreateArbitraryLabel = txt => { handleCreateArbitraryLabel = (txt) => {
const { dispatch, metadataField, categoryIndex } = this.props; const { dispatch, metadataField, categoryIndex } = this.props;
const label = this.getLabel(); const label = this.getLabel();
this.cancelEditMode(); this.cancelEditMode();
@@ -113,17 +113,17 @@ class CategoryValue extends React.Component {
metadataField, metadataField,
editedLabel: txt, editedLabel: txt,
categoryIndex, categoryIndex,
label label,
}); });
}; };
labelNameError = name => { labelNameError = (name) => {
const { metadataField, ontology, schema } = this.props; const { metadataField, ontology, schema } = this.props;
if (name === this.currentLabel()) return false; if (name === this.currentLabel()) return false;
return isLabelErroneous(name, metadataField, ontology, schema); return isLabelErroneous(name, metadataField, ontology, schema);
}; };
instruction = label => { instruction = (label) => {
return labelPrompt(this.labelNameError(label), "New, unique label", ":"); return labelPrompt(this.labelNameError(label), "New, unique label", ":");
}; };
@@ -132,19 +132,19 @@ class CategoryValue extends React.Component {
dispatch({ dispatch({
type: "annotation: activate edit label mode", type: "annotation: activate edit label mode",
metadataField, metadataField,
categoryIndex categoryIndex,
}); });
}; };
cancelEditMode = () => { cancelEditMode = () => {
const { dispatch, metadataField, categoryIndex } = this.props; const { dispatch, metadataField, categoryIndex } = this.props;
this.setState({ this.setState({
editedLabelText: this.currentLabel() editedLabelText: this.currentLabel(),
}); });
dispatch({ dispatch({
type: "annotation: cancel edit label mode", type: "annotation: cancel edit label mode",
metadataField, metadataField,
categoryIndex categoryIndex,
}); });
}; };
@@ -153,7 +153,7 @@ class CategoryValue extends React.Component {
dispatch({ dispatch({
type: "categorical metadata filter deselect", type: "categorical metadata filter deselect",
metadataField, metadataField,
categoryIndex categoryIndex,
}); });
}; };
@@ -203,7 +203,7 @@ class CategoryValue extends React.Component {
dispatch({ dispatch({
type: "categorical metadata filter select", type: "categorical metadata filter select",
metadataField, metadataField,
categoryIndex categoryIndex,
}); });
}; };
@@ -212,7 +212,7 @@ class CategoryValue extends React.Component {
dispatch({ dispatch({
type: "category value mouse hover start", type: "category value mouse hover start",
metadataField, metadataField,
categoryIndex categoryIndex,
}); });
}; };
@@ -221,15 +221,15 @@ class CategoryValue extends React.Component {
dispatch({ dispatch({
type: "category value mouse hover end", type: "category value mouse hover end",
metadataField, metadataField,
categoryIndex categoryIndex,
}); });
}; };
handleTextChange = text => { handleTextChange = (text) => {
this.setState({ editedLabelText: text }); this.setState({ editedLabelText: text });
}; };
handleChoice = e => { handleChoice = (e) => {
/* Blueprint Suggest format */ /* Blueprint Suggest format */
this.setState({ editedLabelText: e.target }); this.setState({ editedLabelText: e.target });
}; };
@@ -290,7 +290,7 @@ class CategoryValue extends React.Component {
// flippedProps is potentially brittle, their docs want {...flippedProps} on our div, // flippedProps is potentially brittle, their docs want {...flippedProps} on our div,
// our lint doesn't like jsx spread, we are version pinned to prevent api change on their part // our lint doesn't like jsx spread, we are version pinned to prevent api change on their part
flippedProps, flippedProps,
isDilated isDilated,
} = this.props; } = this.props;
const ontologyEnabled = ontology?.enabled ?? false; const ontologyEnabled = ontology?.enabled ?? false;
@@ -359,7 +359,7 @@ class CategoryValue extends React.Component {
alignItems: "baseline", alignItems: "baseline",
justifyContent: "space-between", justifyContent: "space-between",
marginBottom: "2px", marginBottom: "2px",
borderRadius: "2px" borderRadius: "2px",
}} }}
onMouseEnter={this.handleMouseEnter} onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseExit} onMouseLeave={this.handleMouseExit}
@@ -371,7 +371,7 @@ class CategoryValue extends React.Component {
userSelect: "none", userSelect: "none",
width: globals.leftSidebarWidth - 145, width: globals.leftSidebarWidth - 145,
display: "flex", display: "flex",
justifyContent: "space-between" justifyContent: "space-between",
}} }}
> >
<div style={{ display: "flex", alignItems: "baseline" }}> <div style={{ display: "flex", alignItems: "baseline" }}>
@@ -397,7 +397,7 @@ class CategoryValue extends React.Component {
usePortal usePortal
modifiers={{ modifiers={{
preventOverflow: { enabled: false }, preventOverflow: { enabled: false },
hide: { enabled: false } hide: { enabled: false },
}} }}
> >
<span <span
@@ -417,7 +417,7 @@ class CategoryValue extends React.Component {
lineHeight: "1.1em", lineHeight: "1.1em",
height: "1.1em", height: "1.1em",
wordBreak: "break-all", wordBreak: "break-all",
verticalAlign: "middle" verticalAlign: "middle",
}} }}
> >
{truncatedString || displayString} {truncatedString || displayString}
@@ -428,10 +428,10 @@ class CategoryValue extends React.Component {
<AnnoDialog <AnnoDialog
isActive={editModeActive} isActive={editModeActive}
inputProps={{ inputProps={{
"data-testid": `${metadataField}:edit-label-name-dialog` "data-testid": `${metadataField}:edit-label-name-dialog`,
}} }}
primaryButtonProps={{ primaryButtonProps={{
"data-testid": `${metadataField}:${displayString}:submit-label-edit` "data-testid": `${metadataField}:${displayString}:submit-label-edit`,
}} }}
title="Edit label" title="Edit label"
instruction={this.instruction(editedLabelText)} instruction={this.instruction(editedLabelText)}
@@ -452,7 +452,7 @@ class CategoryValue extends React.Component {
"data-testid": `${metadataField}:${displayString}:edit-label-name`, "data-testid": `${metadataField}:${displayString}:edit-label-name`,
leftIcon: "tag", leftIcon: "tag",
intent: "none", intent: "none",
autoFocus: true autoFocus: true,
}} }}
/> />
} }
@@ -483,7 +483,7 @@ class CategoryValue extends React.Component {
fontStyle: fontStyle:
displayString === globals.unassignedCategoryLabel displayString === globals.unassignedCategoryLabel
? "italic" ? "italic"
: "auto" : "auto",
}} }}
> >
{count} {count}
@@ -498,7 +498,7 @@ class CategoryValue extends React.Component {
backgroundColor: backgroundColor:
isColorBy && categories isColorBy && categories
? colorScale(categories.indexOf(value)) ? colorScale(categories.indexOf(value))
: "inherit" : "inherit",
}} }}
/> />
{isUserAnno ? ( {isUserAnno ? (
@@ -526,7 +526,7 @@ class CategoryValue extends React.Component {
displayString === displayString ===
globals.unassignedCategoryLabel globals.unassignedCategoryLabel
? "italic" ? "italic"
: "auto" : "auto",
}} }}
> >
{` ${displayString}`} {` ${displayString}`}
@@ -566,7 +566,7 @@ class CategoryValue extends React.Component {
marginLeft: 0, marginLeft: 0,
position: "relative", position: "relative",
top: -1, top: -1,
minHeight: 16 minHeight: 16,
}} }}
data-testclass="seeActions" data-testclass="seeActions"
data-testid={`${metadataField}:${displayString}:see-actions`} data-testid={`${metadataField}:${displayString}:see-actions`}
+6 -6
View File
@@ -1,22 +1,22 @@
// jshint esversion: 6 // jshint esversion: 6
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import Categorical from "../categorical/categorical"; import Categorical from "../categorical";
import * as globals from "../../globals"; import * as globals from "../../globals";
import DynamicScatterplot from "../scatterplot/scatterplot"; import DynamicScatterplot from "../scatterplot/scatterplot";
import TopLeftLogoAndTitle from "./topLeftLogoAndTitle"; import TopLeftLogoAndTitle from "./topLeftLogoAndTitle";
@connect(state => ({ @connect((state) => ({
responsive: state.responsive, responsive: state.responsive,
scatterplotXXaccessor: state.controls.scatterplotXXaccessor, scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
scatterplotYYaccessor: state.controls.scatterplotYYaccessor scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
})) }))
class LeftSideBar extends React.Component { class LeftSideBar extends React.Component {
render() { render() {
const { const {
responsive, responsive,
scatterplotXXaccessor, scatterplotXXaccessor,
scatterplotYYaccessor scatterplotYYaccessor,
} = this.props; } = this.props;
/* /*
@@ -31,7 +31,7 @@ class LeftSideBar extends React.Component {
position: "fixed", position: "fixed",
backgroundColor: "white", backgroundColor: "white",
/* x y blur spread color */ /* x y blur spread color */
borderRight: `1px solid ${globals.lightGrey}` borderRight: `1px solid ${globals.lightGrey}`,
}} }}
> >
<TopLeftLogoAndTitle /> <TopLeftLogoAndTitle />
@@ -41,7 +41,7 @@ class LeftSideBar extends React.Component {
marginTop: logoRelatedPadding, marginTop: logoRelatedPadding,
width: globals.leftSidebarWidth, width: globals.leftSidebarWidth,
overflowY: "auto", overflowY: "auto",
overflowX: "hidden" overflowX: "hidden",
}} }}
> >
<Categorical /> <Categorical />