From 8e89dcbcaa5b3b2f34f9ae55aad1e60282f04d20 Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Tue, 1 Oct 2024 18:27:30 -0400 Subject: [PATCH] feat: update edit study page --- .../src/helpers/BeforeUnload.helpers.ts | 38 +++++++ .../Extraction/components/ExtractionTable.tsx | 4 +- .../src/pages/Project/store/ProjectStore.ts | 11 +- .../src/pages/Study/EditStudyPage.tsx | 22 +++- .../Study/components/EditStudySaveButton.tsx | 16 ++- .../components/EditStudyToolbar.styles.ts | 3 +- .../Study/components/EditStudyToolbar.tsx | 102 ++++++++++++------ .../src/pages/Study/store/StudyStore.ts | 25 +++-- .../src/stores/AnnotationStore.ts | 5 + 9 files changed, 171 insertions(+), 55 deletions(-) create mode 100644 compose/neurosynth-frontend/src/helpers/BeforeUnload.helpers.ts diff --git a/compose/neurosynth-frontend/src/helpers/BeforeUnload.helpers.ts b/compose/neurosynth-frontend/src/helpers/BeforeUnload.helpers.ts new file mode 100644 index 00000000..22890077 --- /dev/null +++ b/compose/neurosynth-frontend/src/helpers/BeforeUnload.helpers.ts @@ -0,0 +1,38 @@ +enum EUnloadStatus { + STUDYSTORE = 'study-store-unsaved-changes', + PROJECTSTORE = 'project-store-unsaved-changes', + ANNOTATIONSTORE = 'annotation-store-unsaved-changes', +} + +const onUnloadHandler = (event: BeforeUnloadEvent) => { + return (event.returnValue = 'Are you sure you want to leave?'); +}; + +export const setUnloadHandler = (store: 'project' | 'study' | 'annotation') => { + if (store === 'project') { + window.sessionStorage.setItem(EUnloadStatus.PROJECTSTORE, 'true'); + } else if (store === 'study') { + window.sessionStorage.setItem(EUnloadStatus.STUDYSTORE, 'true'); + } else if (store === 'annotation') { + window.sessionStorage.setItem(EUnloadStatus.ANNOTATIONSTORE, 'true'); + } + if (!window.onbeforeunload) window.onbeforeunload = onUnloadHandler; +}; + +export const unsetUnloadHandler = (store: 'project' | 'study' | 'annotation') => { + if (store === 'project') { + window.sessionStorage.removeItem(EUnloadStatus.PROJECTSTORE); + } else if (store === 'study') { + window.sessionStorage.removeItem(EUnloadStatus.STUDYSTORE); + } else if (store === 'annotation') { + window.sessionStorage.removeItem(EUnloadStatus.ANNOTATIONSTORE); + } + + if ( + window.sessionStorage.getItem(EUnloadStatus.PROJECTSTORE) === null && + window.sessionStorage.getItem(EUnloadStatus.STUDYSTORE) === null && + window.sessionStorage.getItem(EUnloadStatus.ANNOTATIONSTORE) === null + ) { + window.onbeforeunload = null; + } +}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx index 1024be82..fd0f20f1 100644 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx +++ b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx @@ -419,7 +419,7 @@ const ExtractionTable: React.FC = () => { ))} - + {columnFilters.length > 0 ? ( Viewing {table.getFilteredRowModel().rows.length} /{' '} @@ -428,7 +428,7 @@ const ExtractionTable: React.FC = () => { ) : ( Total: {data.length} studies )} - + diff --git a/compose/neurosynth-frontend/src/pages/Project/store/ProjectStore.ts b/compose/neurosynth-frontend/src/pages/Project/store/ProjectStore.ts index 1c398bd8..c0abdf1e 100644 --- a/compose/neurosynth-frontend/src/pages/Project/store/ProjectStore.ts +++ b/compose/neurosynth-frontend/src/pages/Project/store/ProjectStore.ts @@ -29,10 +29,7 @@ import { useParams } from 'react-router-dom'; import API from 'utils/api'; import { create } from 'zustand'; import { TProjectStore } from './ProjectStore.types'; - -const onUnloadHandler = (event: BeforeUnloadEvent) => { - return (event.returnValue = 'Are you sure you want to leave?'); -}; +import { setUnloadHandler, unsetUnloadHandler } from 'helpers/BeforeUnload.helpers'; const useProjectStore = create()((set, get) => { return { @@ -148,7 +145,7 @@ const useProjectStore = create()((set, get) => { if (existingTimeout && oldDebouncedStoreData.id === prevId) clearTimeout(existingTimeout); - window.addEventListener('beforeunload', onUnloadHandler); + setUnloadHandler('project'); const newTimeout = setTimeout(async () => { const { data } = await API.NeurosynthServices.ProjectsService.projectsIdGet( @@ -177,7 +174,7 @@ const useProjectStore = create()((set, get) => { { variant: 'error', persist: true } ); } - window.removeEventListener('beforeunload', onUnloadHandler); + unsetUnloadHandler('project'); return; } @@ -248,7 +245,7 @@ const useProjectStore = create()((set, get) => { } }, onSettled: () => { - window.removeEventListener('beforeunload', onUnloadHandler); + unsetUnloadHandler('project'); }, } ); diff --git a/compose/neurosynth-frontend/src/pages/Study/EditStudyPage.tsx b/compose/neurosynth-frontend/src/pages/Study/EditStudyPage.tsx index 5bc3a522..93536921 100644 --- a/compose/neurosynth-frontend/src/pages/Study/EditStudyPage.tsx +++ b/compose/neurosynth-frontend/src/pages/Study/EditStudyPage.tsx @@ -73,7 +73,19 @@ const EditStudyPage: React.FC = (props) => { {/* */} - + + + + - - + {/* + + */} + + + ); diff --git a/compose/neurosynth-frontend/src/pages/Study/components/EditStudySaveButton.tsx b/compose/neurosynth-frontend/src/pages/Study/components/EditStudySaveButton.tsx index b0430818..18170eef 100644 --- a/compose/neurosynth-frontend/src/pages/Study/components/EditStudySaveButton.tsx +++ b/compose/neurosynth-frontend/src/pages/Study/components/EditStudySaveButton.tsx @@ -38,6 +38,7 @@ import { storeNotesToDBNotes } from 'stores/AnnotationStore.helpers'; import API from 'utils/api'; import { arrayToMetadata } from './EditStudyMetadata'; import { hasDuplicateStudyAnalysisNames, hasEmptyStudyPoints } from './EditStudySaveButton.helpers'; +import { unsetUnloadHandler } from 'helpers/BeforeUnload.helpers'; const EditStudySaveButton: React.FC = React.memo((props) => { const { user } = useAuth0(); @@ -91,6 +92,8 @@ const EditStudySaveButton: React.FC = React.memo((props) => { }); updateAnnotationNotes(updatedNotes); await updateAnnotationInDB(); + unsetUnloadHandler('study'); + unsetUnloadHandler('annotation'); queryClient.invalidateQueries('studies'); queryClient.invalidateQueries('annotations'); @@ -105,6 +108,8 @@ const EditStudySaveButton: React.FC = React.memo((props) => { const handleUpdateStudyInDB = async () => { try { await updateStudyInDB(annotationId as string); + unsetUnloadHandler('study'); + unsetUnloadHandler('annotation'); queryClient.invalidateQueries('studies'); queryClient.invalidateQueries('annotations'); @@ -118,6 +123,8 @@ const EditStudySaveButton: React.FC = React.memo((props) => { const handleUpdateAnnotationInDB = async () => { try { await updateAnnotationInDB(); + unsetUnloadHandler('study'); + unsetUnloadHandler('annotation'); queryClient.invalidateQueries('annotations'); enqueueSnackbar('Annotation saved', { variant: 'success' }); } catch (e) { @@ -227,6 +234,9 @@ const EditStudySaveButton: React.FC = React.memo((props) => { }, }); + unsetUnloadHandler('study'); + unsetUnloadHandler('annotation'); + navigate(`/projects/${projectId}/extraction/studies/${clonedStudyId}/edit`); enqueueSnackbar('Saved successfully. You are now the owner of this study', { variant: 'success', @@ -271,13 +281,13 @@ const EditStudySaveButton: React.FC = React.memo((props) => { return ( ); diff --git a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.styles.ts b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.styles.ts index e3e24155..6d4615da 100644 --- a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.styles.ts +++ b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.styles.ts @@ -7,7 +7,8 @@ const EditStudyToolbarStyles: Style = { }, toolbarContainer: { position: 'absolute', - right: 'calc(-10%)', + right: '-9%', + transform: 'translateX(-8px)', borderRadius: '4px', border: '1px solid', borderColor: 'primary.main', diff --git a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx index e623780d..3c7bf072 100644 --- a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx +++ b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx @@ -32,6 +32,12 @@ import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import EditStudyToolbarStyles from './EditStudyToolbar.styles'; import SaveIcon from '@mui/icons-material/Save'; +import { + ArrowBack, + ArrowForward, + KeyboardArrowLeft, + KeyboardArrowRight, +} from '@mui/icons-material'; const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = false }) => { const navigate = useNavigate(); @@ -197,48 +203,82 @@ const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = fal )} - - + + + + + + {/* + */} - + + {/* + */} + + + - - - - - {/* diff --git a/compose/neurosynth-frontend/src/pages/Study/store/StudyStore.ts b/compose/neurosynth-frontend/src/pages/Study/store/StudyStore.ts index d4a8a123..aebfbcf5 100644 --- a/compose/neurosynth-frontend/src/pages/Study/store/StudyStore.ts +++ b/compose/neurosynth-frontend/src/pages/Study/store/StudyStore.ts @@ -1,14 +1,9 @@ import { AxiosResponse } from 'axios'; import { IMetadataRowModel } from 'components/EditMetadata/EditMetadata.types'; -import { arrayToMetadata, metadataToArray } from 'pages/Study/components/EditStudyMetadata'; -import { AnalysisReturn, StudyReturn } from 'neurostore-typescript-sdk'; import { setAnalysesInAnnotationAsIncluded } from 'helpers/Annotation.helpers'; -import { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import API from 'utils/api'; -import { v4 as uuid } from 'uuid'; -import { create } from 'zustand'; -import { persist } from 'zustand/middleware'; +import { setUnloadHandler } from 'helpers/BeforeUnload.helpers'; +import { AnalysisReturn, StudyReturn } from 'neurostore-typescript-sdk'; +import { arrayToMetadata, metadataToArray } from 'pages/Study/components/EditStudyMetadata'; import { IStoreAnalysis, IStoreCondition, @@ -19,6 +14,12 @@ import { storeAnalysesToStudyAnalyses, studyAnalysesToStoreAnalyses, } from 'pages/Study/store/StudyStore.helpers'; +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import API from 'utils/api'; +import { v4 as uuid } from 'uuid'; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; export type StudyStoreActions = { initStudyStore: (studyId?: string) => void; @@ -138,6 +139,7 @@ const useStudyStore = create< })); }, updateStudy: (fieldName, value) => { + setUnloadHandler('study'); set((state) => ({ ...state, study: { @@ -222,6 +224,7 @@ const useStudyStore = create< } }, addOrUpdateStudyMetadataRow: (row) => { + setUnloadHandler('study'); set((state) => { const metadataUpdate = [...state.study.metadata]; const foundRowIndex = metadataUpdate.findIndex( @@ -252,6 +255,7 @@ const useStudyStore = create< }); }, deleteStudyMetadataRow: (id) => { + setUnloadHandler('study'); set((state) => { const metadataUpdate = [...state.study.metadata]; const foundRowIndex = metadataUpdate.findIndex((x) => x.metadataKey === id); @@ -273,6 +277,7 @@ const useStudyStore = create< }); }, addOrUpdateAnalysis: (analysis) => { + setUnloadHandler('study'); let createdOrUpdatedAnalysis: IStoreAnalysis; // we do this outside the set func here so that we can return the updated or created analysis @@ -330,6 +335,7 @@ const useStudyStore = create< return createdOrUpdatedAnalysis; }, deleteAnalysis: (analysisId) => { + setUnloadHandler('study'); set((state) => { const updatedAnalyses = [ ...state.study.analyses.filter((x) => x.id !== analysisId), @@ -464,6 +470,7 @@ const useStudyStore = create< }); }, createAnalysisPoints: (analysisId, points, index) => { + setUnloadHandler('study'); set((state) => { const updatedAnalyses = [...state.study.analyses]; const foundAnalysisIndex = updatedAnalyses.findIndex( @@ -505,6 +512,7 @@ const useStudyStore = create< }); }, deleteAnalysisPoints: (analysisId, ids) => { + setUnloadHandler('study'); set((state) => { const updatedAnalyses = [...state.study.analyses]; const foundAnalysisIndex = updatedAnalyses.findIndex( @@ -547,6 +555,7 @@ const useStudyStore = create< }); }, updateAnalysisPoints: (analysisId, pointsToUpdate) => { + setUnloadHandler('study'); set((state) => { const updatedAnalyses = [...state.study.analyses]; const foundAnalysisIndex = updatedAnalyses.findIndex( diff --git a/compose/neurosynth-frontend/src/stores/AnnotationStore.ts b/compose/neurosynth-frontend/src/stores/AnnotationStore.ts index 57000ef8..c9e4bbe6 100644 --- a/compose/neurosynth-frontend/src/stores/AnnotationStore.ts +++ b/compose/neurosynth-frontend/src/stores/AnnotationStore.ts @@ -13,6 +13,7 @@ import { IStoreAnnotation, IStoreNoteCollectionReturn, } from 'stores/AnnotationStore.types'; +import { setUnloadHandler } from 'helpers/BeforeUnload.helpers'; export const useAnnotationStore = create< { @@ -137,6 +138,7 @@ export const useAnnotationStore = create< })); }, updateNotes: (updatedNotes) => { + setUnloadHandler('annotation'); set((state) => ({ ...state, annotation: { @@ -150,6 +152,7 @@ export const useAnnotationStore = create< })); }, updateAnnotationNoteName: (note) => { + setUnloadHandler('annotation'); set((state) => ({ ...state, annotation: { @@ -159,6 +162,7 @@ export const useAnnotationStore = create< })); }, createAnnotationNote: (analysisId, studyId, analysisName) => { + setUnloadHandler('annotation'); set((state) => { if (!state.annotation.notes || !state.annotation.note_keys) return state; @@ -189,6 +193,7 @@ export const useAnnotationStore = create< }); }, deleteAnnotationNote: (analysisId) => { + setUnloadHandler('annotation'); set((state) => ({ ...state, annotation: {