diff --git a/client/src/components/categorical/annoDialogAddLabel.js b/client/src/components/categorical/category/annoDialogAddLabel.js similarity index 81% rename from client/src/components/categorical/annoDialogAddLabel.js rename to client/src/components/categorical/category/annoDialogAddLabel.js index d0ad62e6..7fea9fcc 100644 --- a/client/src/components/categorical/annoDialogAddLabel.js +++ b/client/src/components/categorical/category/annoDialogAddLabel.js @@ -1,37 +1,37 @@ import React from "react"; import { connect } from "react-redux"; -import AnnoDialog from "./annoDialog"; -import LabelInput from "./labelInput"; -import { labelPrompt, isLabelErroneous } from "./labelUtil"; +import AnnoDialog from "../annoDialog"; +import LabelInput from "../labelInput"; +import { labelPrompt, isLabelErroneous } from "../labelUtil"; -@connect(state => ({ +@connect((state) => ({ colorAccessor: state.colors.colorAccessor, categoricalSelection: state.categoricalSelection, annotations: state.annotations, universe: state.universe, ontology: state.ontology, - crossfilter: state.crossfilter + crossfilter: state.crossfilter, })) class Category extends React.PureComponent { constructor(props) { super(props); this.state = { - newLabelText: "" + newLabelText: "", }; } - disableAddNewLabelMode = e => { + disableAddNewLabelMode = (e) => { const { dispatch } = this.props; this.setState({ - newLabelText: "" + newLabelText: "", }); dispatch({ - type: "annotation: disable add new label mode" + type: "annotation: disable add new label mode", }); if (e) e.preventDefault(); }; - handleAddNewLabelToCategory = e => { + handleAddNewLabelToCategory = (e) => { const { dispatch, metadataField } = this.props; const { newLabelText } = this.state; @@ -40,12 +40,12 @@ class Category extends React.PureComponent { type: "annotation: add new label to category", metadataField, newLabelText, - assignSelectedCells: false + assignSelectedCells: false, }); e.preventDefault(); }; - addLabelAndAssignCells = e => { + addLabelAndAssignCells = (e) => { const { dispatch, metadataField } = this.props; const { newLabelText } = this.state; @@ -54,21 +54,21 @@ class Category extends React.PureComponent { type: "annotation: add new label to category", metadataField, newLabelText, - assignSelectedCells: true + assignSelectedCells: true, }); e.preventDefault(); }; - labelNameError = name => { + labelNameError = (name) => { const { metadataField, ontology, universe } = this.props; return isLabelErroneous(name, metadataField, ontology, universe.schema); }; - instruction = label => { + instruction = (label) => { return labelPrompt(this.labelNameError(label), "New, unique label", ":"); }; - handleChangeOrSelect = label => { + handleChangeOrSelect = (label) => { this.setState({ newLabelText: label }); }; @@ -86,7 +86,7 @@ class Category extends React.PureComponent { } inputProps={{ "data-testid": `${metadataField}:create-label-dialog` }} primaryButtonProps={{ - "data-testid": `${metadataField}:submit-label` + "data-testid": `${metadataField}:submit-label`, }} title="Add new label to category" instruction={this.instruction(newLabelText)} @@ -107,7 +107,7 @@ class Category extends React.PureComponent { "data-testid": `${metadataField}:new-label-name`, leftIcon: "tag", intent: "none", - autoFocus: true + autoFocus: true, }} /> } diff --git a/client/src/components/categorical/annoDialogEditCategoryName.js b/client/src/components/categorical/category/annoDialogEditCategoryName.js similarity index 85% rename from client/src/components/categorical/annoDialogEditCategoryName.js rename to client/src/components/categorical/category/annoDialogEditCategoryName.js index 28e80c2e..55845e79 100644 --- a/client/src/components/categorical/annoDialogEditCategoryName.js +++ b/client/src/components/categorical/category/annoDialogEditCategoryName.js @@ -1,41 +1,41 @@ import React from "react"; import _ from "lodash"; import { connect } from "react-redux"; -import AnnoDialog from "./annoDialog"; -import LabelInput from "./labelInput"; -import { labelPrompt } from "./labelUtil"; +import AnnoDialog from "../annoDialog"; +import LabelInput from "../labelInput"; +import { labelPrompt } from "../labelUtil"; -import { AnnotationsHelpers } from "../../util/stateManager"; +import { AnnotationsHelpers } from "../../../util/stateManager"; -@connect(state => ({ +@connect((state) => ({ categoricalSelection: state.categoricalSelection, annotations: state.annotations, universe: state.universe, - ontology: state.ontology + ontology: state.ontology, })) class AnnoDialogEditCategoryName extends React.PureComponent { constructor(props) { super(props); this.state = { - newCategoryText: props.metadataField + newCategoryText: props.metadataField, }; } - handleChangeOrSelect = name => { + handleChangeOrSelect = (name) => { this.setState({ - newCategoryText: name + newCategoryText: name, }); }; disableEditCategoryMode = () => { const { dispatch, metadataField } = this.props; dispatch({ - type: "annotation: disable category edit mode" + type: "annotation: disable category edit mode", }); this.setState({ newCategoryText: metadataField }); }; - handleEditCategory = e => { + handleEditCategory = (e) => { const { dispatch, metadataField, categoricalSelection } = this.props; const { newCategoryText } = this.state; @@ -54,12 +54,12 @@ class AnnoDialogEditCategoryName extends React.PureComponent { type: "annotation: category edited", metadataField, newCategoryText, - data: newCategoryText + data: newCategoryText, }); e.preventDefault(); }; - editedCategoryNameError = name => { + editedCategoryNameError = (name) => { const { metadataField, categoricalSelection } = this.props; /* check for syntax errors in category name */ @@ -80,7 +80,7 @@ class AnnoDialogEditCategoryName extends React.PureComponent { return false; }; - instruction = name => { + instruction = (name) => { return labelPrompt( this.editedCategoryNameError(name), "New, unique category name", @@ -101,10 +101,10 @@ class AnnoDialogEditCategoryName extends React.PureComponent { annotations.categoryBeingEdited === metadataField } inputProps={{ - "data-testid": `${metadataField}:edit-category-name-dialog` + "data-testid": `${metadataField}:edit-category-name-dialog`, }} primaryButtonProps={{ - "data-testid": `${metadataField}:submit-category-edit` + "data-testid": `${metadataField}:submit-category-edit`, }} title="Edit category name" instruction={this.instruction(newCategoryText)} @@ -124,7 +124,7 @@ class AnnoDialogEditCategoryName extends React.PureComponent { "data-testid": `${metadataField}:edit-category-name-text`, leftIcon: "tag", intent: "none", - autoFocus: true + autoFocus: true, }} newLabelMessage="New category" /> diff --git a/client/src/components/categorical/annoMenuCategory.js b/client/src/components/categorical/category/annoMenuCategory.js similarity index 100% rename from client/src/components/categorical/annoMenuCategory.js rename to client/src/components/categorical/category/annoMenuCategory.js diff --git a/client/src/components/categorical/categoryFlipperLayout.js b/client/src/components/categorical/category/categoryFlipperLayout.js similarity index 83% rename from client/src/components/categorical/categoryFlipperLayout.js rename to client/src/components/categorical/category/categoryFlipperLayout.js index 87fcfd62..0569b168 100644 --- a/client/src/components/categorical/categoryFlipperLayout.js +++ b/client/src/components/categorical/category/categoryFlipperLayout.js @@ -3,11 +3,11 @@ import _ from "lodash"; import { connect } from "react-redux"; import { Flipper, Flipped } from "react-flip-toolkit"; -import * as globals from "../../globals"; -import Value from "./value"; +import * as globals from "../../../globals"; +import Value from "../value"; -@connect(state => ({ - categoricalSelection: state.categoricalSelection +@connect((state) => ({ + categoricalSelection: state.categoricalSelection, })) class Category extends React.Component { constructor(props) { @@ -21,7 +21,7 @@ class Category extends React.Component { return _.map(optTuples, (tuple, i) => { return ( - {flippedProps => ( + {(flippedProps) => ( t[0]).join(""); // animation + const optTuplesAsKey = _.map(optTuples, (t) => t[0]).join(""); // animation return (
{children} diff --git a/client/src/components/categorical/category.js b/client/src/components/categorical/category/index.js similarity index 83% rename from client/src/components/categorical/category.js rename to client/src/components/categorical/category/index.js index 8086f51c..7265a4fd 100644 --- a/client/src/components/categorical/category.js +++ b/client/src/components/categorical/category/index.js @@ -7,27 +7,31 @@ import { Button, Tooltip, Icon, - Position + Position, } from "@blueprintjs/core"; import CategoryFlipperLayout from "./categoryFlipperLayout"; import AnnoMenu from "./annoMenuCategory"; import AnnoDialogEditCategoryName from "./annoDialogEditCategoryName"; import AnnoDialogAddLabel from "./annoDialogAddLabel"; -import * as globals from "../../globals"; -import maybeTruncateString from "../../util/maybeTruncateString"; +import * as globals from "../../../globals"; +import maybeTruncateString from "../../../util/maybeTruncateString"; -@connect(state => ({ - colorAccessor: state.colors.colorAccessor, - categoricalSelection: state.categoricalSelection, - annotations: state.annotations, - universe: state.universe -})) +@connect((state, ownProps) => { + const { metadataField } = ownProps; + return { + isColorAccessor: state.colors.colorAccessor === metadataField, + categoricalSelection: state.categoricalSelection, + annotations: state.annotations, + universe: state.universe, + schema: state.world?.schema, + }; +}) class Category extends React.Component { constructor(props) { super(props); this.state = { - isChecked: true + isChecked: true, }; } @@ -47,7 +51,7 @@ class Category extends React.Component { cat.categoryValueSelected, (res, cond) => (cond ? res + 1 : res), 0 - ) + ), }; if (categoryCount.selectedCatCount === categoryCount.totalCatCount) { /* everything is on, so not indeterminate */ @@ -69,7 +73,7 @@ class Category extends React.Component { const { dispatch, metadataField } = this.props; dispatch({ type: "color by categorical metadata", - colorAccessor: metadataField + colorAccessor: metadataField, }); }; @@ -77,7 +81,7 @@ class Category extends React.Component { const { dispatch, metadataField } = this.props; dispatch({ type: "categorical metadata filter all of these", - metadataField + metadataField, }); this.setState({ isChecked: true }); } @@ -86,7 +90,7 @@ class Category extends React.Component { const { dispatch, metadataField } = this.props; dispatch({ type: "categorical metadata filter none of these", - metadataField + metadataField, }); this.setState({ isChecked: false }); } @@ -114,25 +118,25 @@ class Category extends React.Component { return (
{truncatedString || metadataField} @@ -169,11 +173,11 @@ class Category extends React.Component { const { metadataField, categoricalSelection, - colorAccessor, - isUserAnno, + isColorAccessor, annotations, isExpanded, - onExpansionChange + onExpansionChange, + schema, } = this.props; const isStillLoading = !(categoricalSelection?.[metadataField] ?? false); @@ -181,6 +185,8 @@ class Category extends React.Component { return this.renderIsStillLoading(); } + const isUserAnno = + schema?.annotations?.obsByName[metadataField]?.writable ?? false; const isTruncated = _.get( categoricalSelection, [metadataField, "isTruncated"], @@ -192,6 +198,20 @@ class Category extends React.Component { globals.categoryDisplayStringMaxLength ); + if ( + !isUserAnno && + schema?.annotations?.obsByName[metadataField]?.categories?.length === 1 + ) { + return ( +
+ + {truncatedString ? truncatedString : metadataField} + + : {schema.annotations.obsByName[metadataField].categories[0]} +
+ ); + } + return (