From 74d803cd5085c440d9754435ed806a2e7e96287e Mon Sep 17 00:00:00 2001 From: Tim Jennison Date: Mon, 13 Jan 2025 20:53:58 +0000 Subject: [PATCH] Fix several small issues on the cohort overiew * Swap cohort criteria to left side. Make the visualizations secondary until they're interactive enough to be the primary editing surface. * Make the cohort criteria wider. Almost all criteria were too long to be effectively displayed. * Remove interactivity from the background of criteria so only the title selects/unselects. It was causing a number of corner cases where criteria controls behaved unpredictably and some were difficult to solve due to the way JS handles mouse events. This removes the need to squash mouse events throughout the code. Show title hover underlining on selected criteria as well to make it clear it's still clickable. * Refactor uncontained select style to a shared file. * Fix rare issue were criteria buttons could spill onto a second row. * Add a drop down to select which visualizations are displayed. Add optional config to limit the default set displayed. --- ui/src/components/hintDataSelect.tsx | 20 +---- ui/src/components/rangeSlider.tsx | 4 - ui/src/components/select.tsx | 17 ++++ ui/src/criteria/textSearch.tsx | 21 +---- ui/src/criteria/unhintedValue.tsx | 12 +-- ui/src/criteria/valueData.tsx | 20 +---- ui/src/demographicCharts.tsx | 101 ++++++++++++++++++++-- ui/src/overview.tsx | 121 +++++++++++---------------- ui/src/plugins/vumc/biovu.tsx | 30 +------ ui/src/underlaysSlice.ts | 1 + 10 files changed, 170 insertions(+), 177 deletions(-) create mode 100644 ui/src/components/select.tsx diff --git a/ui/src/components/hintDataSelect.tsx b/ui/src/components/hintDataSelect.tsx index 2d18198fb..2f8cbaf89 100644 --- a/ui/src/components/hintDataSelect.tsx +++ b/ui/src/components/hintDataSelect.tsx @@ -58,15 +58,7 @@ export function HintDataSelect(props: HintDataSelectProps) { }; return ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - > + theme.palette.primary.main, + "& .MuiOutlinedInput-input": { + px: 0, + py: "2px", + }, + "& .MuiSelect-select": (theme: Theme) => ({ + ...theme.typography.body2, + }), + "& .MuiOutlinedInput-notchedOutline": { + borderStyle: "none", + }, + }; +} diff --git a/ui/src/criteria/textSearch.tsx b/ui/src/criteria/textSearch.tsx index 9a266844c..9ea01c5d4 100644 --- a/ui/src/criteria/textSearch.tsx +++ b/ui/src/criteria/textSearch.tsx @@ -193,16 +193,7 @@ function TextSearchInline(props: TextSearchInlineProps) { return ( {!!hintDataState.data?.hintData?.enumHintOptions ? ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - > + { - e.stopPropagation(); - e.preventDefault(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - onDelete={(e) => { - e.stopPropagation(); + onDelete={() => { onDelete(c); }} /> diff --git a/ui/src/criteria/unhintedValue.tsx b/ui/src/criteria/unhintedValue.tsx index a54bb0417..47fbf1136 100644 --- a/ui/src/criteria/unhintedValue.tsx +++ b/ui/src/criteria/unhintedValue.tsx @@ -168,15 +168,7 @@ function UnhintedValueInline(props: UnhintedValueInlineProps) { return ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - > + } diff --git a/ui/src/demographicCharts.tsx b/ui/src/demographicCharts.tsx index 7bad974be..e429e5073 100644 --- a/ui/src/demographicCharts.tsx +++ b/ui/src/demographicCharts.tsx @@ -1,11 +1,19 @@ +import MenuItem from "@mui/material/MenuItem"; +import OutlinedInput from "@mui/material/OutlinedInput"; +import Select, { SelectChangeEvent } from "@mui/material/Select"; +import FormControl from "@mui/material/FormControl"; import Paper from "@mui/material/Paper"; import Typography from "@mui/material/Typography"; +import { uncontainedSelectSx } from "components/select"; import { Cohort } from "data/source"; import { useUnderlay } from "hooks"; import { GridBox } from "layout/gridBox"; import GridLayout from "layout/gridLayout"; import { ReactNode } from "react"; import { VizContainer } from "viz/vizContainer"; +import { useState } from "react"; +import Empty from "components/empty"; +import { Underlay } from "underlaysSlice"; export type DemographicChartsProps = { cohort?: Cohort; @@ -15,6 +23,23 @@ export type DemographicChartsProps = { export function DemographicCharts(props: DemographicChartsProps) { const underlay = useUnderlay(); + const [selectedVisualizations, setSelectedVisualizations] = useState( + getInitialVisualizations(underlay) + ); + + const onSelect = (event: SelectChangeEvent) => { + const { + target: { value: sel }, + } = event; + if (typeof sel === "string") { + // This case is only for selects with text input. + return; + } + + setSelectedVisualizations(sel); + storeSelectedVisualizations(underlay.name, sel); + }; + return ( - + Cohort visualizations - + + + {props.extraControls} theme.spacing(3), }} > - {underlay.visualizations.map((v) => - props.cohort ? ( - - ) : null + {selectedVisualizations.length > 0 ? ( + underlay.visualizations.map((v) => + selectedVisualizations.find((sv) => sv === v.name) && + props.cohort ? ( + + ) : null + ) + ) : ( + )} ); } + +// TODO(tjennison): Store the selected visualizations in local storage per +// underlay for now. Longer term, these should be stored on the backend but +// there a few options about whether they should be stored attached to the +// cohort, study, user, etc. as part of the visualiation changes. +function storageKey(underlay: string) { + return `tanagra-selected-visualizations-${underlay}`; +} + +function loadSelectedVisualizations(underlay: string): string[] | undefined { + const stored = localStorage.getItem(storageKey(underlay)); + if (!stored) { + return undefined; + } + return JSON.parse(stored); +} + +function storeSelectedVisualizations(underlay: string, sel: string[]) { + localStorage.setItem(storageKey(underlay), JSON.stringify(sel)); +} + +function getInitialVisualizations(underlay: Underlay) { + const stored = loadSelectedVisualizations(underlay.name); + if (!stored) { + return ( + underlay.uiConfiguration.defaultVisualizations ?? + underlay.visualizations.map((viz) => viz.name) + ); + } + + return stored.filter((v) => + underlay.visualizations.find((viz) => viz.name === v) + ); +} diff --git a/ui/src/overview.tsx b/ui/src/overview.tsx index a880f7783..694c9cb93 100644 --- a/ui/src/overview.tsx +++ b/ui/src/overview.tsx @@ -1,3 +1,4 @@ +import { uncontainedSelectSx } from "components/select"; import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; @@ -135,10 +136,19 @@ export function Overview() { /> + + + + + - - - - - ); @@ -441,6 +442,7 @@ function ParticipantsGroupSection(props: { ) : ( e.stopPropagation()} inputProps={{ type: "number", min: 1, @@ -697,19 +699,7 @@ function ReducingOperatorSelect(props: { : undefined, }); }} - sx={{ - color: (theme) => theme.palette.primary.main, - "& .MuiOutlinedInput-input": { - px: 0, - py: "2px", - }, - "& .MuiSelect-select": (theme) => ({ - ...theme.typography.body2, - }), - "& .MuiOutlinedInput-notchedOutline": { - borderStyle: "none", - }, - }} + sx={uncontainedSelectSx()} > Any mention of @@ -851,16 +841,6 @@ function ParticipantsGroup(props: { return ( { - navigate( - "../" + - cohortURL( - cohort.id, - props.groupSection.id, - !selected ? props.group.id : undefined - ) - ); - }} sx={{ p: 2, height: "auto", @@ -869,15 +849,7 @@ function ParticipantsGroup(props: { backgroundColor: "#F1F2FA", boxShadow: "inset 0 -1px 0 #BEC2E9, inset 0 1px 0 #BEC2E9", } - : { - "&:hover": { - textDecoration: "underline", - cursor: "pointer", - color: (theme) => - (theme.typography as { link: { color: string } }).link - .color, - }, - }), + : undefined), }} > @@ -894,10 +866,29 @@ function ParticipantsGroup(props: { )} { + navigate( + "../" + + cohortURL( + cohort.id, + props.groupSection.id, + !selected ? props.group.id : undefined + ) + ); + }} sx={{ textOverflow: "ellipsis", whiteSpace: "nowrap", overflow: "hidden", + "&:hover": { + textDecoration: "underline", + cursor: "pointer", + color: (theme) => + !selected + ? (theme.typography as { link: { color: string } }).link + .color + : undefined, + }, }} > - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - > + {selected && !!plugin.renderEdit ? ( ) : null} - + {modifierCriteria[i].config.displayName} - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} + + deleteCohortCriteriaModifier( + context, + props.groupSection.id, + props.group.id, + p.id + ) + } > - - deleteCohortCriteriaModifier( - context, - props.groupSection.id, - props.group.id, - p.id - ) - } - > - - - + + diff --git a/ui/src/plugins/vumc/biovu.tsx b/ui/src/plugins/vumc/biovu.tsx index 4f6292b09..06716d78b 100644 --- a/ui/src/plugins/vumc/biovu.tsx +++ b/ui/src/plugins/vumc/biovu.tsx @@ -146,15 +146,7 @@ function BioVUInline(props: BioVUInlineProps) { return !props.config.plasmaFilter ? ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - > + - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - > + @@ -194,15 +178,7 @@ function BioVUInline(props: BioVUInlineProps) { - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseUp={(e) => { - e.stopPropagation(); - }} - > + diff --git a/ui/src/underlaysSlice.ts b/ui/src/underlaysSlice.ts index 580398277..08222408c 100644 --- a/ui/src/underlaysSlice.ts +++ b/ui/src/underlaysSlice.ts @@ -26,6 +26,7 @@ export type UIConfiguration = { demographicChartConfigs: DemographicChartConfig; criteriaSearchConfig: CriteriaSearchConfig; cohortReviewConfig: CohortReviewConfig; + defaultVisualizations: string[]; }; export type FeatureConfig = {