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) => {
{/* */}
-
+
+
+
+
}
disableElevation
@@ -129,8 +141,12 @@ 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: {