diff --git a/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.spec.tsx b/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.spec.tsx index bd3af73f..d56cedab 100644 --- a/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.spec.tsx +++ b/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.spec.tsx @@ -170,7 +170,7 @@ describe('AddMetadataRow Component', () => { const addButton = screen.getByText('ADD').closest('button') as HTMLElement; userEvent.click(addButton); - const errorMessage = screen.getByText('All metadata keys must be unique'); + const errorMessage = screen.getByText('All keys must be unique'); expect(errorMessage).toBeInTheDocument(); }); diff --git a/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.tsx b/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.tsx index 98aa45cf..e4718c06 100644 --- a/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.tsx +++ b/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.tsx @@ -122,7 +122,7 @@ const AddMetadataRow: React.FC = (props) => { variant="outlined" placeholder={keyPlaceholderText || 'New metadata key'} fullWidth - helperText={!isValid ? errorMessage || 'All metadata keys must be unique' : ''} + helperText={!isValid ? errorMessage || 'All keys must be unique' : ''} error={!isValid} value={metadataRow.metadataKey} /> diff --git a/compose/neurosynth-frontend/src/components/EditStudyComponents/EditAnalyses/EditAnalyses.tsx b/compose/neurosynth-frontend/src/components/EditStudyComponents/EditAnalyses/EditAnalyses.tsx index af612ce0..66ae098c 100644 --- a/compose/neurosynth-frontend/src/components/EditStudyComponents/EditAnalyses/EditAnalyses.tsx +++ b/compose/neurosynth-frontend/src/components/EditStudyComponents/EditAnalyses/EditAnalyses.tsx @@ -50,7 +50,9 @@ const EditAnalyses: React.FC<{ disabled: boolean }> = React.memo(({ disabled }) }; useEffect(() => { - if (!selectedAnalysisId && analyses.length > 0) { + const exists = analyses.find((analysis) => analysis.id === selectedAnalysisId); + + if ((analyses.length > 0 && !selectedAnalysisId) || (!exists && analyses.length > 0)) { // select the first analysis on first render setSelectedAnalysisId(analyses[0].id); } diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx index 6f76d0a9..785cba7a 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx @@ -18,7 +18,7 @@ export const hotSettings: HotTableProps = { width: '100%', fixedColumnsStart: 2, wordWrap: true, - autoRowSize: true, + autoRowSize: false, afterGetRowHeaderRenderers: (headerRenderers) => { headerRenderers.push((row, TH) => { TH.className = styles['no-top-bottom-borders']; @@ -150,12 +150,13 @@ export const createColumnHeader = ( export const createColumns = (noteKeys: NoteKeyType[], disable?: boolean) => [ { - className: `${styles['study-col']} ${styles['read-only-col']} truncate`, + className: `${styles['study-col']} ${styles['read-only-col']}`, readOnly: true, }, { - className: styles['read-only-col'], + className: `${styles['read-only-col']} ${styles['truncate']}`, readOnly: true, + wordWrap: false, }, ...noteKeys.map((x) => { return { @@ -173,7 +174,8 @@ export const createColumns = (noteKeys: NoteKeyType[], disable?: boolean) => }), ] as ColumnSettings[]; -// we can assume that the input is already sorted +// we can assume that the hashmap maintains order and is sorted by key +// this function gets all merge cells and only merge cells. If a cell does not need to be merged, a mergeCellObj is not creatd export const getMergeCells = ( hotDataToStudyMapping: Map ) => { @@ -206,3 +208,62 @@ export const getMergeCells = ( return mergeCells; }; + +const getCalculatedRowHeight = (title: string, maxWidthInPx: number) => { + const container = document.createElement('td'); + container.style.maxWidth = `${maxWidthInPx - 10}px`; // account for padding and borders + container.style.width = `${maxWidthInPx - 10}px`; // account for padding and borders + + container.style.fontSize = '13px'; // handsontable default font size + container.style.fontFamily = + '-apple-system, system-ui, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Helvetica Neue", Arial, sans-serif'; + container.style.lineHeight = '21px'; // handsontable default line height + container.style.border = '1px solid black'; + container.style.padding = '0px 4px'; + container.style.fontWeight = '400'; + container.style.display = 'table-cell'; + container.style.textAlign = 'center'; + + container.innerText = title; + document.body.appendChild(container); + const height = container.offsetHeight; + container.parentNode?.removeChild(container); + return height; +}; + +export const getRowHeights = ( + hotData: AnnotationNoteValue[][], + mergeCells: MergeCellsSettings[], + maxWidthInPx: number +) => { + const rowHeights: number[] = []; + let currIndex = 0; + + mergeCells.forEach(({ row, col, rowspan, colspan }) => { + while (currIndex < row) { + // sometimes the merge cells skip a few rows as they do not need to be merged. + // we therefore need to account for that by calculting those row heights (which have rowspan = 1) + const currIndexTitle = hotData[currIndex][0] as string; + rowHeights.push(getCalculatedRowHeight(currIndexTitle, maxWidthInPx)); + currIndex++; + } + const title = hotData[row][0] as string; + const height = getCalculatedRowHeight(title, maxWidthInPx); + + const potentialRowHeight = Math.ceil(height / rowspan); + if (rowspan * 23 >= height) { + // the title is smaller than the space taken up by the analyses + for (let i = 0; i < rowspan; i++) { + rowHeights.push(potentialRowHeight < 23 ? 23 : potentialRowHeight); + } + } else { + // the title is bigger than the space taken up by the analyses + // we want to split that space evenly + for (let i = 0; i < rowspan; i++) { + rowHeights.push(potentialRowHeight); + } + } + currIndex = currIndex + rowspan; + }); + return rowHeights; +}; diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx index f4444f95..75323007 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx @@ -43,6 +43,7 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro noteKeys, hotDataToStudyMapping, isEdited, + rowHeights, } = useEditAnnotationsHotTable(props.annotationId, !canEdit); useEffect(() => { @@ -166,7 +167,7 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro TD: HTMLTableCellElement, controller: SelectionController ): void => { - const isRowHeader = coords.col === -1; + const isRowHeader = coords.col === -1 || coords.col === 0; if (isRowHeader) { event.stopImmediatePropagation(); return; @@ -224,7 +225,7 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro return ( - {theUserOwnsThisAnnotation && !canEdit && ( + {theUserOwnsThisAnnotation && canEdit && ( = React.memo((pro showMetadataValueInput={false} allowNumber={false} allowNone={false} - errorMessage="cannot add annotation - this key may already exist" + errorMessage="can't add column (key already exists)" /> )} @@ -249,6 +250,7 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro disableVisualSelection={!canEdit} colHeaders={hotColumnHeaders} colWidths={colWidths} + rowHeights={rowHeights} columns={hotColumns} data={JSON.parse(JSON.stringify(hotData))} afterOnCellMouseUp={handleCellMouseUp} diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx index 8c714b43..eb8212ed 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx @@ -11,6 +11,7 @@ import { createColumnHeader, getMergeCells, createColumns, + getRowHeights, } from 'components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers'; const useEditAnnotationsHotTable = (annotationId?: string, disableEdit?: boolean) => { @@ -82,9 +83,13 @@ const useEditAnnotationsHotTable = (annotationId?: string, disableEdit?: boolean }, [annotationsHotState.noteKeys]); const colWidths = useMemo(() => { - return createColWidths(annotationsHotState.noteKeys, 200, 150, 200); + return createColWidths(annotationsHotState.noteKeys, 300, 150, 200); }, [annotationsHotState.noteKeys]); + const rowHeights = useMemo(() => { + return getRowHeights(annotationsHotState.hotData, annotationsHotState.mergeCells, 300); + }, [annotationsHotState.hotData, annotationsHotState.mergeCells]); + return { theUserOwnsThisAnnotation, getAnnotationIsLoading, @@ -92,6 +97,7 @@ const useEditAnnotationsHotTable = (annotationId?: string, disableEdit?: boolean hotColumnHeaders, setAnnotationsHotState, colWidths, + rowHeights, ...annotationsHotState, }; }; diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditStudyAnnotationsHotTable/useEditStudyAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditStudyAnnotationsHotTable/useEditStudyAnnotationsHotTable.tsx index de65c1a6..54770a98 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditStudyAnnotationsHotTable/useEditStudyAnnotationsHotTable.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditStudyAnnotationsHotTable/useEditStudyAnnotationsHotTable.tsx @@ -77,7 +77,7 @@ const useEditStudyAnnotationsHotTable = (readonly?: boolean) => { const MIN_HEIGHT_PX = 100; const MAX_HEIGHT_PX = 500; const HEADER_HEIGHT_PX = 26; - const ROW_HEIGHT_PX = 24 + 24; // +24 to padd row height a little bit + const ROW_HEIGHT_PX = 24; // +24 to padd row height a little bit const visibleNotes = (notes || []).filter((x) => x.study === studyId); diff --git a/compose/neurosynth-frontend/src/components/HotTables/HotTables.module.css b/compose/neurosynth-frontend/src/components/HotTables/HotTables.module.css index 37a9a005..6dea7643 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/HotTables.module.css +++ b/compose/neurosynth-frontend/src/components/HotTables/HotTables.module.css @@ -43,4 +43,4 @@ .null { color: gray !important; text-align: center !important; -} \ No newline at end of file +}