Skip to content

Commit

Permalink
feat: added annotation study table
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoalee committed Oct 17, 2023
1 parent 6954ca7 commit c72f7e9
Show file tree
Hide file tree
Showing 25 changed files with 603 additions and 196 deletions.
2 changes: 1 addition & 1 deletion compose/neurosynth-frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import BaseNavigation from './pages/BaseNavigation/BaseNavigation';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5000, // set default staleTime to 10 seconds https://tkdodo.eu/blog/practical-react-query#the-defaults-explained
// staleTime: 5000, // https://tkdodo.eu/blog/practical-react-query#the-defaults-explained
},
},
queryCache: new QueryCache({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,76 +1,43 @@
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { Chip } from '@mui/material';
import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent';
import {
useStudyAnalyses,
useStudyAnalysisName,
useStudyAnalysisPoints,
useStudyIsLoading,
} from 'pages/Studies/StudyStore';
import { IStorePoint } from 'pages/Studies/StudyStore.helpers';

export const isCoordinateMNI = (x: number, y: number, z: number) => {
const dims = {
xMax: 90,
xMin: -90,
yMax: 90,
yMin: -126,
zMax: 108,
zMin: -72,
};

return (
x <= dims.xMax &&
x >= dims.xMin &&
y <= dims.yMax &&
y >= dims.yMin &&
z <= dims.zMax &&
z >= dims.zMin
);
};
import useDisplayWarnings from './useDisplayWarnings';
import { useStudyIsLoading } from 'pages/Studies/StudyStore';

const DisplayAnalysisWarnings: React.FC<{ analysisId: string }> = (props) => {
const points = useStudyAnalysisPoints(props.analysisId) as IStorePoint[] | null;
const name = useStudyAnalysisName(props.analysisId);
const analyses = useStudyAnalyses();
const studyIsLoading = useStudyIsLoading();

const noPoints = (points?.length || 0) === 0;
const noName = (name || '').length === 0;
const duplicateName = analyses
.filter((x) => x.id !== props.analysisId)
.some((x) => x.name === name);
const allCoordinatesAreMNI = (points || []).every((x) => {
return isCoordinateMNI(x.x || 0, x.y || 0, x.z || 0);
});
const { hasNoPoints, hasNoName, hasDuplicateName, hasNonMNICoordinates } = useDisplayWarnings(
props.analysisId
);

return (
<StateHandlerComponent isLoading={studyIsLoading} isError={false} loaderSize={20}>
{noPoints && (
{hasNoPoints && (
<Chip
sx={{ margin: '2px', marginBottom: '1rem' }}
icon={<ErrorOutlineIcon />}
label="This analysis has no coordinates"
color="warning"
/>
)}
{noName && (
{hasNoName && (
<Chip
sx={{ margin: '2px', marginBottom: '1rem' }}
icon={<ErrorOutlineIcon />}
label="No analysis name"
color="warning"
/>
)}
{duplicateName && (
{hasDuplicateName && (
<Chip
sx={{ margin: '2px', marginBottom: '1rem' }}
icon={<ErrorOutlineIcon />}
label="Duplicate analysis name"
color="warning"
/>
)}
{!allCoordinatesAreMNI && (
{hasNonMNICoordinates && (
<Chip
sx={{ margin: '2px', marginBottom: '1rem' }}
icon={<ErrorOutlineIcon />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
useStudyAnalyses,
useStudyAnalysisName,
useStudyAnalysisPoints,
} from 'pages/Studies/StudyStore';
import { IStorePoint } from 'pages/Studies/StudyStore.helpers';
import { useMemo } from 'react';

export const isCoordinateMNI = (x: number, y: number, z: number) => {
const dims = {
xMax: 90,
xMin: -90,
yMax: 90,
yMin: -126,
zMax: 108,
zMin: -72,
};

return (
x <= dims.xMax &&
x >= dims.xMin &&
y <= dims.yMax &&
y >= dims.yMin &&
z <= dims.zMax &&
z >= dims.zMin
);
};

const useDisplayWarnings = (analysisId?: string) => {
const points = useStudyAnalysisPoints(analysisId) as IStorePoint[] | null;
const name = useStudyAnalysisName(analysisId);
const analyses = useStudyAnalyses();

const hasNoPoints = useMemo(() => (points?.length || 0) === 0, [points]);
const hasNoName = useMemo(() => (name || '').length === 0, [name]);
const hasDuplicateName = useMemo(
() => analyses.filter((x) => x.id !== analysisId).some((x) => x.name === name),
[analyses, analysisId, name]
);

const hasNonMNICoordinates = useMemo(
() =>
(points || []).some((x) => {
return !isCoordinateMNI(x.x || 0, x.y || 0, x.z || 0);
}),
[points]
);

return {
hasNoPoints,
hasNoName,
hasDuplicateName,
hasNonMNICoordinates,
};
};

export default useDisplayWarnings;
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ const EditAnalyses: React.FC = (props) => {
width: '150px',
marginLeft: 'auto',
}}
variant="outlined"
variant="contained"
disableElevation
startIcon={<Add />}
>
analysis
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { ListItem, ListItemButton, ListItemIcon, ListItemText, Tooltip } from '@mui/material';
import { isCoordinateMNI } from 'components/DisplayStudy/DisplayAnalyses/DisplayAnalysisWarnings/DisplayAnalysisWarnings';
import { AnalysisReturn, PointReturn } from 'neurostore-typescript-sdk';
import { IStoreAnalysis, IStorePoint } from 'pages/Studies/StudyStore.helpers';
import React from 'react';
import useDisplayWarnings from 'components/DisplayStudy/DisplayAnalyses/DisplayAnalysisWarnings/useDisplayWarnings';
import { AnalysisReturn } from 'neurostore-typescript-sdk';
import { IStoreAnalysis } from 'pages/Studies/StudyStore.helpers';
import React, { useMemo } from 'react';

const EditAnalysesListItem: React.FC<{
analysis: AnalysisReturn | IStoreAnalysis;
Expand All @@ -12,19 +12,18 @@ const EditAnalysesListItem: React.FC<{
onSelectAnalysis: (analysisId: string, index: number) => void;
}> = React.memo((props) => {
const { analysis, selected, onSelectAnalysis } = props;
const { hasDuplicateName, hasNoName, hasNoPoints, hasNonMNICoordinates } = useDisplayWarnings(
analysis.id
);

const handleSelectAnalysis = () => {
if (!analysis.id) return;
onSelectAnalysis(analysis.id, props.index);
};

const coordinatesAreMNI = (
(props.analysis.points || []) as Array<IStorePoint | PointReturn>
).every((x) => {
return isCoordinateMNI(x.x || 0, x.y || 0, x.z || 0);
});
const hasPoints = (analysis?.points?.length || 0) > 0;
const showWarningIcon = !hasPoints || !coordinatesAreMNI;
const showWarningIcon = useMemo(() => {
return hasDuplicateName || hasNoName || hasNoPoints || hasNonMNICoordinates;
}, [hasDuplicateName, hasNoName, hasNoPoints, hasNonMNICoordinates]);

return (
<ListItem disablePadding divider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { useState } from 'react';
import EditAnalysisDetails from 'components/EditStudyComponents/EditAnalyses/EditAnalysisDetails/EditAnalysisDetails';
import EditAnalysisPoints from 'components/HotTables/EditAnalysisPointsHotTable/EditAnalysisPoints';
import EditAnalysisPointSpaceAndStatistic from 'components/EditStudyComponents/EditAnalyses/EditAnalysisPoints/EditAnalysisPointSpaceAndStatistic/EditAnalysisPointSpaceAndStatistic';
import { useDeleteAnnotationNote } from 'stores/AnnotationStore.actions';

const EditAnalysis: React.FC<{ analysisId?: string; onDeleteAnalysis: () => void }> = (props) => {
const deleteAnalysis = useDeleteAnalysis();
const deleteAnnotationNote = useDeleteAnnotationNote();

const [dialogIsOpen, setDialogIsOpen] = useState(false);

Expand All @@ -20,6 +22,7 @@ const EditAnalysis: React.FC<{ analysisId?: string; onDeleteAnalysis: () => void
const handleCloseDialog = (confirm?: boolean) => {
if (confirm && props.analysisId) {
deleteAnalysis(props.analysisId);
deleteAnnotationNote(props.analysisId);
props.onDeleteAnalysis();
}
setDialogIsOpen(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,30 +57,30 @@ const EditStudyAnnotations: React.FC = (props) => {
// CURRTODO: i need to refactor this - give edit study annotations its own HotTable and make it robust to many rerenders
// this means using a setState and storing tabularData in the store

useEffect(() => {
if (annotation) {
const annotationNotesForStudy = ((notes as NoteCollectionReturn[]) || []).filter(
(x) => x.study === studyId
);
const { hotData, hotDataToStudyMapping } = annotationNotesToHotData(
noteKeys,
annotationNotesForStudy,
(annotationNote) => {
const analysis = analyses.find((x) => x.id === annotationNote.analysis);
return [analysis?.name || '', analysis?.description || ''];
}
);
// useEffect(() => {
// if (annotation) {
// const annotationNotesForStudy = ((notes as NoteCollectionReturn[]) || []).filter(
// (x) => x.study === studyId
// );
// const { hotData, hotDataToStudyMapping } = annotationNotesToHotData(
// noteKeys,
// annotationNotesForStudy,
// (annotationNote) => {
// const analysis = analyses.find((x) => x.id === annotationNote.analysis);
// return [analysis?.name || '', analysis?.description || ''];
// }
// );

setInitialAnnotationHotState({
hotDataToStudyMapping,
noteKeys,
hotColumns: createColumns(noteKeys),
hotData: hotData,
mergeCells: [],
size: `${(hotData.length + 1) * 35 > 400 ? 400 : (hotData.length + 1) * 35}px`,
});
}
}, [studyId, analyses, annotation, notes, noteKeys]);
// setInitialAnnotationHotState({
// hotDataToStudyMapping,
// noteKeys,
// hotColumns: createColumns(noteKeys),
// hotData: hotData,
// mergeCells: [],
// size: `${(hotData.length + 1) * 35 > 400 ? 400 : (hotData.length + 1) * 35}px`,
// });
// }
// }, [studyId, analyses, annotation, notes, noteKeys]);

const handleChange = useCallback(
(hotData: AnnotationNoteValue[][], noteKeys: NoteKeyType[]) => {
Expand Down Expand Up @@ -129,17 +129,17 @@ const EditStudyAnnotations: React.FC = (props) => {
</Typography>
}
>
<Box sx={{ height: initialAnnotationHotState.size, padding: '1rem 0' }}>
<Box sx={{ padding: '1rem 0' }}>
<StateHandlerComponent isLoading={isLoading} isError={isError}>
<EditStudyAnnotationsHotTable />
<AnnotationsHotTable
{/* <AnnotationsHotTable
{...initialAnnotationHotState}
hardCodedReadOnlyCols={hardCodedColumns}
allowAddColumn={false}
wordWrap={false}
allowRemoveColumns={false}
onChange={handleChange}
/>
/> */}
</StateHandlerComponent>
</Box>
</NeurosynthAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Style } from 'index';

const EditStudyComponentsStyles: Style = {
accordion: {
border: '1px solid',
border: '2px solid',
borderTop: 'none',
borderColor: 'secondary.main',
borderRadius: '0 !important',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const EditStudyDetails: React.FC = React.memo((props) => {
expandIconColor="secondary.main"
sx={[
EditStudyComponentsStyles.accordion,
{ borderTop: '1px solid', borderColor: 'secondary.main' },
{ borderTop: '2px solid', borderColor: 'secondary.main' },
]}
accordionSummarySx={EditStudyComponentsStyles.accordionSummary}
TitleElement={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@

.boolean {
color: green !important;
text-align: center !important;
}

.string {
color: orange !important;
text-align: center !important;
}

.number {
color: blue !important;
text-align: center !important;
}

.null {
color: gray !important;
text-align: center !important;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const nonEmptyNumericValidator = (value: CellValue, callback: (isValid: boolean)
value !== 'e' &&
value !== true &&
value !== false &&
value !== null &&
value !== '' // all these things are considered numbers
) {
callback(true);
Expand Down Expand Up @@ -75,19 +74,6 @@ export const EditAnalysisPointsDefaultConfig: HotTableProps = {
colWidths: [50, 50, 50, 150, 150, 100],
};

export const replaceString = (val: string) => {
// replace = ['֊', '‐', '‑', '⁃', '﹣', '-', '‒', '–', '—', '﹘', '−', '-']

return val.replaceAll(new RegExp('֊|‐|‑|⁃|﹣|-|‒|–|—|﹘|−|-', 'g'), '-');
};

export const stripTags = (stringWhichMayHaveHTML: any) => {
if (typeof stringWhichMayHaveHTML !== 'string') return '';

let doc = new DOMParser().parseFromString(stringWhichMayHaveHTML, 'text/html');
return doc.body.textContent || '';
};

export const getHotTableInsertionIndices = (selectedCoords: [number, number, number, number][]) => {
if (selectedCoords.length === 0)
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import {
getHotTableInsertionIndices,
hotTableColHeaders,
hotTableColumnSettings,
replaceString,
stripTags,
} from './EditAnalysisPoints.helpers';
import { sanitizePaste } from '../helpers/utils';

export const ROW_HEIGHT = 56;

Expand Down Expand Up @@ -111,20 +110,7 @@ const EditAnalysisPoints: React.FC<{ analysisId?: string }> = React.memo((props)
const handleBeforePaste = (data: any[][], coords: RangeType[]) => {
if (!points) return false;

data.forEach((dataRow, rowIndex) => {
dataRow.forEach((value, valueIndex) => {
if (typeof value === 'number') return;

let newVal = value;

newVal = stripTags(newVal); // strip all HTML tags that were copied over if they exist
newVal = replaceString(newVal); // replace minus operator with javascript character code

if (newVal === 'true') newVal = true;
else if (newVal === 'false') newVal = false;
data[rowIndex][valueIndex] = newVal;
});
});
sanitizePaste(data);

// if a paste leads to rows being created, we store those rows in a ref for the handleBeforeCreateRow hook to
// know how many rows to create
Expand Down
Loading

0 comments on commit c72f7e9

Please sign in to comment.