Skip to content

Commit

Permalink
[ENH] ux extraction annoyances (#810)
Browse files Browse the repository at this point in the history
* feat: updated edit analysis page with toolbar

* feat: added toolbar and fixed bug with study annotations row orderingh

* fix: ignore unused vars

* chore: up[date comments
  • Loading branch information
nicoalee authored Sep 9, 2024
1 parent 58725b4 commit 12ed401
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ export const organizeSleuthStubsIntoHTTPRequests = (
(correspondingBaseStudy.versions as IStudyVersion[]) || []
).find((version) => version.user === currUserId);
if (loggedInUsersExistingStudyVersion && loggedInUsersExistingStudyVersion.id) {
// if there is a duplicate, then this analyses POST request will just return the existing analysis. If not, it will create the analysis and return that instead
allRequests.push(() =>
API.NeurostoreServices.AnalysesService.analysesPost({
...newAnalysis,
Expand Down Expand Up @@ -613,11 +614,6 @@ export const applyPubmedStudyDetailsToBaseStudiesAndRemoveDuplicates = (
if (pubmedStudy.DOI) idToPubmedStudyMap.set(pubmedStudy.DOI, pubmedStudy);
});

console.log({
baseStudySleuthStubs,
pubmedStudies,
});

const deduplicatedBaseStudiesWithDetails: BaseStudy[] = [];
baseStudySleuthStubs.forEach((baseStudy) => {
const associatedPubmedStudy =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const EditStudyPage: React.FC = (props) => {
const getAnnotationIsLoading = useGetAnnotationIsLoading();

useInitProjectStoreIfRequired();
// instead of the useInitStudyStoreIfRequired hook,
// instead of the useInitStudyStoreIfRequired hook we call these funcitons in a useEffect as
// we want to clear and init the study store every time in case the user wants to refresh the page and cancel their edits
useEffect(() => {
clearStudyStore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const EditStudyAnalyses: React.FC = React.memo(() => {
description: '',
isNew: true,
conditions: [],
order: analyses.length + 1, // not 0 indexed in the BE
});

if (!createdAnalysis.id) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useState } from 'react';

const InputNumberDialog: React.FC<
IDialog & {
onInputNumber: (value: number) => void;
onSubmit: (value: number) => void;
dialogTitle: string;
dialogDescription: string;
}
Expand All @@ -17,8 +17,9 @@ const InputNumberDialog: React.FC<
props.onCloseDialog();
} else {
if (!val) return;
props.onInputNumber(val);
props.onSubmit(val);
}
setVal(4);
};

return (
Expand All @@ -27,7 +28,10 @@ const InputNumberDialog: React.FC<
maxWidth="xs"
dialogTitle={props.dialogTitle}
isOpen={props.isOpen}
onCloseDialog={props.onCloseDialog}
onCloseDialog={() => {
setVal(4);
props.onCloseDialog();
}}
>
<Box>
<Typography>{props.dialogDescription}</Typography>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { HotTable } from '@handsontable/react';
import Add from '@mui/icons-material/Add';
import { Box, Button, Link, Typography } from '@mui/material';
import InputNumberDialog from 'pages/Study/components/EditStudyAnalysisInputNumberDialog';
import { Box } from '@mui/material';
import { CellRange } from 'handsontable';
import { CellChange, ChangeSource, RangeType } from 'handsontable/common';
import { registerAllModules } from 'handsontable/registry';
import InputNumberDialog from 'pages/Study/components/EditStudyAnalysisInputNumberDialog';
import {
useCreateAnalysisPoints,
useDeleteAnalysisPoints,
Expand All @@ -14,13 +13,14 @@ import {
import { IStorePoint } from 'pages/Study/store/StudyStore.helpers';
import React, { useMemo, useRef } from 'react';
import { sanitizePaste } from '../../../components/HotTables/HotTables.utils';
import useEditAnalysisPointsHotTable from '../hooks/useEditAnalysisPointsHotTable';
import {
EditStudyAnalysisPointsDefaultConfig,
getHotTableColumnSettings,
getHotTableInsertionIndices,
hotTableColHeaders,
getHotTableColumnSettings,
} from './EditStudyAnalysisPointsHotTable.helpers';
import useEditAnalysisPointsHotTable from '../hooks/useEditAnalysisPointsHotTable';
import EditStudyAnalysisPointsHotTableToolbar from './EditStudyAnalysisPointsHotTableToolbar';

registerAllModules();

Expand All @@ -35,10 +35,10 @@ const EditStudyAnalysisPointsHotTable: React.FC<{ analysisId?: string; readOnly?
insertRowsAbove: boolean;
insertedRowsViaPaste: any[][];
}>({
insertRowsAbove: true,
insertRowsAbove: false,
insertedRowsViaPaste: [],
});
const { height, insertRowsDialogIsOpen, closeInsertRowsDialog } =
const { height, insertRowsDialogIsOpen, openInsertRowsDialog, closeInsertRowsDialog } =
useEditAnalysisPointsHotTable(analysisId, hotTableRef, hotTableMetadata);

// handsontable binds and updates to the data references themselves which means the original data is being mutated.
Expand Down Expand Up @@ -151,8 +151,11 @@ const EditStudyAnalysisPointsHotTable: React.FC<{ analysisId?: string; readOnly?

const handleInsertRows = (numRows: number) => {
if (hotTableRef.current?.hotInstance && analysisId) {
const selectedCoords = hotTableRef.current.hotInstance.getSelected();
if (!selectedCoords) return;
let selectedCoords = hotTableRef.current.hotInstance.getSelected();

if (!selectedCoords) {
selectedCoords = [[0, 0, (points || []).length - 1, 2]];
}

const { insertAboveIndex, insertBelowIndex } =
getHotTableInsertionIndices(selectedCoords);
Expand All @@ -174,6 +177,41 @@ const EditStudyAnalysisPointsHotTable: React.FC<{ analysisId?: string; readOnly?
return getHotTableColumnSettings(readOnly);
}, [readOnly]);

const handleAddRow = () => {
if (!analysisId) return;
createPoint(
analysisId,
[
{
value: undefined,
isNew: true,
},
],
points?.length || 0
);
};

const handleAddRows = () => {
openInsertRowsDialog();
};

// we do not allow multiple selections, so we can assume that the selected rows will have 1 size
const handleDeleteRows = () => {
if (!hotTableRef.current?.hotInstance) return;
const selected = hotTableRef.current.hotInstance.getSelected() || [];
if (!selected) return;
if (selected.length !== 1) return;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [startRow, startCol, endRow, endCol] = selected[0];

const selectedRowIndices: number[] = [];
for (let i = startRow; i <= endRow; i++) {
selectedRowIndices.push(i);
}
handleBeforeRemoveRow(0, selectedRowIndices.length || 0, selectedRowIndices, undefined);
};

/**
* Hook Order:
* 1) handleBeforePaste
Expand All @@ -188,66 +226,39 @@ const EditStudyAnalysisPointsHotTable: React.FC<{ analysisId?: string; readOnly?
isOpen={insertRowsDialogIsOpen}
dialogTitle="Enter number of rows to insert"
onCloseDialog={() => closeInsertRowsDialog()}
onInputNumber={(val) => handleInsertRows(val)}
onSubmit={(val) => handleInsertRows(val)}
dialogDescription=""
/>
<HotTable
{...EditStudyAnalysisPointsDefaultConfig}
ref={hotTableRef}
afterChange={handleAfterChange} // beforeChange results in weird update issues so we use afterChange
beforePaste={handleBeforePaste}
beforeCreateRow={handleBeforeCreateRow}
beforeRemoveRow={handleBeforeRemoveRow}
afterAutofill={handleAfterAutofill}
height={height}
columns={hotTableColumnSettings}
colHeaders={hotTableColHeaders}
data={[...(points || [])]}
/>
{(points?.length || 0) === 0 && !readOnly ? (
<Typography sx={{ color: 'warning.dark', marginTop: '0.5rem' }}>
No coordinate data.{' '}
<Link
onClick={() => {
if (!analysisId) return;
createPoint(
analysisId,
[
{
value: undefined,
isNew: true,
},
],
0
);
}}
underline="hover"
sx={{ cursor: 'pointer' }}
>
Click here to get started
</Link>
</Typography>
) : (
<Button
endIcon={<Add />}
disabled={readOnly}
onClick={() => {
if (!analysisId) return;
createPoint(
analysisId,
[
{
value: undefined,
isNew: true,
},
],
points?.length || 0
);
<Box sx={{ display: 'flex' }}>
<Box
sx={{
width: '40px',
height: '120px',
marginRight: '0.5rem',
}}
>
Add Row
</Button>
)}
<EditStudyAnalysisPointsHotTableToolbar
onAddRow={handleAddRow}
onAddRows={handleAddRows}
onDeleteRows={handleDeleteRows}
/>
</Box>
<Box sx={{ height: height, width: '100%' }}>
<HotTable
{...EditStudyAnalysisPointsDefaultConfig}
ref={hotTableRef}
afterChange={handleAfterChange} // beforeChange results in weird update issues so we use afterChange
beforePaste={handleBeforePaste}
beforeCreateRow={handleBeforeCreateRow}
beforeRemoveRow={handleBeforeRemoveRow}
afterAutofill={handleAfterAutofill}
height={height}
columns={hotTableColumnSettings}
colHeaders={hotTableColHeaders}
data={[...(points || [])]}
/>
</Box>
</Box>
</Box>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Add, Delete, TableRows } from '@mui/icons-material';
import { Box, Button, ButtonGroup, Tooltip } from '@mui/material';
import React from 'react';

const EditStudyAnalysisPointsHotTableToolbar: React.FC<{
onAddRow: () => void;
onAddRows: () => void;
onDeleteRows: () => void;
}> = React.memo(({ onAddRow, onAddRows, onDeleteRows }) => {
return (
<Box sx={{ borderRadius: '8px', width: '100%' }}>
<ButtonGroup orientation="vertical" sx={{ width: '40px' }}>
<Tooltip title="Add row" placement="right">
<Button onClick={onAddRow} color="primary" sx={{ height: '40px' }}>
<Add fontSize="small" />
</Button>
</Tooltip>
<Tooltip title="Add rows" placement="right">
<Button onClick={onAddRows} sx={{ height: '40px' }} color="primary">
<Add sx={{ fontSize: '18px' }} />
<TableRows sx={{ fontSize: '12px' }} />
</Button>
</Tooltip>
<Tooltip title="Delete row(s)" placement="right">
<Button onClick={onDeleteRows} sx={{ height: '40px' }} color="error">
<Delete sx={{ fontSize: '20px' }} />
</Button>
</Tooltip>
</ButtonGroup>
</Box>
);
});

export default EditStudyAnalysisPointsHotTableToolbar;
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,22 @@ export const hasEmptyStudyPoints = (
): { errorMessage: string; isError: boolean } => {
for (let i = 0; i < analyses.length; i++) {
const analysis = analyses[i];
const isDefaultSinglePoint =
analysis.points.length === 1 &&
analysis.points.every(
({ x, y, z, subpeak, value, cluster_size }) =>
x === undefined &&
y === undefined &&
z === undefined &&
subpeak === undefined &&
cluster_size === undefined
);

const hasEmptyPoint = analysis.points.some(
(xyz) => xyz.x === undefined || xyz.y === undefined || xyz.z === undefined
);

if (hasEmptyPoint)
if (!isDefaultSinglePoint && hasEmptyPoint)
return {
errorMessage: `Analysis ${analysis.name} has empty coordinates. Please add coordinatesa and try again.`,
isError: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,14 @@ const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = fal
backgroundColor:
extractionStatus?.status ===
EExtractionStatus.COMPLETED
? 'lightgray'
? '#ebebeb'
: '',
}}
onClick={() =>
handleClickStudyListStatus(EExtractionStatus.COMPLETED)
}
>
<CheckIcon />
<CheckIcon color="success" />
</IconButton>
</Tooltip>
</Box>
Expand All @@ -270,7 +270,7 @@ const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = fal
backgroundColor:
extractionStatus?.status ===
EExtractionStatus.SAVEDFORLATER
? 'lightgray'
? '#ebebeb'
: '',
}}
onClick={() =>
Expand All @@ -279,7 +279,7 @@ const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = fal
)
}
>
<BookmarkIcon />
<BookmarkIcon color="info" />
</IconButton>
</Tooltip>
</Box>
Expand Down
Loading

0 comments on commit 12ed401

Please sign in to comment.