From 3e290dc9ef02f47c72bec0347387a43322b63fc9 Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Fri, 28 Jun 2024 17:44:40 -0400 Subject: [PATCH 1/4] feat: added tooltip text and fixed jumping issue --- .../MovetoExtractionDialog.helpers.ts | 5 ++- .../EditMetadataRow/AddMetadataRow.tsx | 2 +- .../EditAnalyses/EditAnalyses.tsx | 4 ++- .../EditAnnotationsHotTable.helpers.tsx | 6 ++-- .../EditAnnotationsHotTable.tsx | 32 +++++++++++++++++-- .../useEditAnnotationsHotTable.tsx | 2 +- .../useEditStudyAnnotationsHotTable.tsx | 2 +- .../components/HotTables/HotTables.module.css | 2 +- 8 files changed, 43 insertions(+), 12 deletions(-) diff --git a/compose/neurosynth-frontend/src/components/Dialogs/MoveToExtractionDialog/MovetoExtractionDialog.helpers.ts b/compose/neurosynth-frontend/src/components/Dialogs/MoveToExtractionDialog/MovetoExtractionDialog.helpers.ts index d45890844..5dba69fc8 100644 --- a/compose/neurosynth-frontend/src/components/Dialogs/MoveToExtractionDialog/MovetoExtractionDialog.helpers.ts +++ b/compose/neurosynth-frontend/src/components/Dialogs/MoveToExtractionDialog/MovetoExtractionDialog.helpers.ts @@ -32,7 +32,10 @@ export const selectBestBaseStudyVersion = (baseStudyVersions: Array export const selectBestVersionsForStudyset = (baseStudies: Array): string[] => { const selectedVersions = baseStudies.map((baseStudy) => { - return (selectBestBaseStudyVersion as StudyReturn)?.id as string; + const studyVersion = selectBestBaseStudyVersion( + (baseStudy?.versions || []) as StudyReturn[] + ); + return studyVersion.id as string; }); return selectedVersions; diff --git a/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.tsx b/compose/neurosynth-frontend/src/components/EditMetadata/EditMetadataRow/AddMetadataRow.tsx index 98aa45cff..e4718c060 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 af612ce08..66ae098c6 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 6f76d0a91..e3f5f1c10 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx @@ -17,8 +17,8 @@ export const hotSettings: HotTableProps = { viewportColumnRenderingOffset: 4, // we do not want column virtualization as it screws up the spreadsheet width: '100%', fixedColumnsStart: 2, - wordWrap: true, - autoRowSize: true, + wordWrap: false, + autoRowSize: false, afterGetRowHeaderRenderers: (headerRenderers) => { headerRenderers.push((row, TH) => { TH.className = styles['no-top-bottom-borders']; @@ -150,7 +150,7 @@ 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']} ${styles.truncate}`, readOnly: true, }, { diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx index f4444f957..1072b593b 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.tsx @@ -57,6 +57,7 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro '25px', // ADD_METADATA_INPUT_MARGIN_BOTTOM '75px', // BOTTOM_BUTTON_CONTAINER '1rem', // EXTRA SPACE + '30px', // TOP TOOLTIP ]; const sizeStr = sizes.reduce((acc, curr, index, list) => { if (index === 0) { @@ -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)" /> )} +
+ Hover over a Study to see the study name +
{hotData.length > 0 ? ( = React.memo((pro data={JSON.parse(JSON.stringify(hotData))} afterOnCellMouseUp={handleCellMouseUp} beforeOnCellMouseDown={handleCellMouseDown} + // afterOnCellMouseOver={(event, coords, TD) => { + afterOnCellMouseOver={(event, coords, TD) => { + if (coords.col === 0) { + const tooltip = document.querySelector( + '#tooltip' + ) as HTMLDivElement; + if (!tooltip) return; + tooltip.innerText = `${TD.innerText}`; + tooltip.style.color = 'gray'; + + TD.title = TD.innerText; + } + }} /> ) : ( diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx index 8c714b430..9bd62cff3 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx @@ -82,7 +82,7 @@ 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]); return { diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditStudyAnnotationsHotTable/useEditStudyAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditStudyAnnotationsHotTable/useEditStudyAnnotationsHotTable.tsx index de65c1a63..54770a983 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 37a9a005b..6dea76435 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 +} From 89e14504073fc1045a60ff87426f5cfb26b5b8a5 Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Mon, 1 Jul 2024 15:25:49 -0400 Subject: [PATCH 2/4] feat: removed tootlip and manually calculated row heights --- .../EditAnnotationsHotTable.helpers.tsx | 68 +++++++++++++++++-- .../EditAnnotationsHotTable.tsx | 28 +------- .../useEditAnnotationsHotTable.tsx | 6 ++ 3 files changed, 72 insertions(+), 30 deletions(-) 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 e3f5f1c10..31f07d984 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx @@ -17,7 +17,7 @@ export const hotSettings: HotTableProps = { viewportColumnRenderingOffset: 4, // we do not want column virtualization as it screws up the spreadsheet width: '100%', fixedColumnsStart: 2, - wordWrap: false, + wordWrap: true, autoRowSize: false, afterGetRowHeaderRenderers: (headerRenderers) => { headerRenderers.push((row, TH) => { @@ -150,12 +150,13 @@ export const createColumnHeader = ( export const createColumns = (noteKeys: NoteKeyType[], disable?: boolean) => [ { - className: `${styles['study-col']} ${styles['read-only-col']} ${styles.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,61 @@ 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) { + 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; + }); + console.log({ hotData, rowHeights }); + 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 1072b593b..753230078 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(() => { @@ -57,7 +58,6 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro '25px', // ADD_METADATA_INPUT_MARGIN_BOTTOM '75px', // BOTTOM_BUTTON_CONTAINER '1rem', // EXTRA SPACE - '30px', // TOP TOOLTIP ]; const sizeStr = sizes.reduce((acc, curr, index, list) => { if (index === 0) { @@ -241,18 +241,6 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro
)} -
- Hover over a Study to see the study name -
{hotData.length > 0 ? ( = React.memo((pro disableVisualSelection={!canEdit} colHeaders={hotColumnHeaders} colWidths={colWidths} + rowHeights={rowHeights} columns={hotColumns} data={JSON.parse(JSON.stringify(hotData))} afterOnCellMouseUp={handleCellMouseUp} beforeOnCellMouseDown={handleCellMouseDown} - // afterOnCellMouseOver={(event, coords, TD) => { - afterOnCellMouseOver={(event, coords, TD) => { - if (coords.col === 0) { - const tooltip = document.querySelector( - '#tooltip' - ) as HTMLDivElement; - if (!tooltip) return; - tooltip.innerText = `${TD.innerText}`; - tooltip.style.color = 'gray'; - - TD.title = TD.innerText; - } - }} /> ) : ( diff --git a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/useEditAnnotationsHotTable.tsx index 9bd62cff3..eb8212ed8 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) => { @@ -85,6 +86,10 @@ const useEditAnnotationsHotTable = (annotationId?: string, disableEdit?: boolean 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, }; }; From 5518e332bfc2aeb4db4149b0cfba1944bc1258c1 Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Mon, 1 Jul 2024 15:27:30 -0400 Subject: [PATCH 3/4] fix: add comments and remove console log --- .../EditAnnotationsHotTable.helpers.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 31f07d984..785cba7a5 100644 --- a/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx +++ b/compose/neurosynth-frontend/src/components/HotTables/EditAnnotationsHotTable/EditAnnotationsHotTable.helpers.tsx @@ -241,6 +241,8 @@ export const getRowHeights = ( 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++; @@ -263,6 +265,5 @@ export const getRowHeights = ( } currIndex = currIndex + rowspan; }); - console.log({ hotData, rowHeights }); return rowHeights; }; From 66cc416ea870f21014ae2b5b3c97de7f903ecd25 Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Tue, 2 Jul 2024 09:11:02 -0400 Subject: [PATCH 4/4] fix: failing test case --- .../EditMetadata/EditMetadataRow/AddMetadataRow.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bd3af73f9..d56cedabc 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(); });