diff --git a/packages/libs/coreui/src/components/icons/Plus.tsx b/packages/libs/coreui/src/components/icons/Plus.tsx new file mode 100644 index 0000000000..3e901ba980 --- /dev/null +++ b/packages/libs/coreui/src/components/icons/Plus.tsx @@ -0,0 +1,19 @@ +import { SVGProps } from 'react'; +import { Add } from '@material-ui/icons'; + +const Plus = (props: SVGProps) => { + const { height = '1em', width = '1em' } = props; + return ( + + + + ); +}; + +export default Plus; diff --git a/packages/libs/coreui/src/components/icons/index.tsx b/packages/libs/coreui/src/components/icons/index.tsx index 86a67ed044..0d9d9a45b7 100644 --- a/packages/libs/coreui/src/components/icons/index.tsx +++ b/packages/libs/coreui/src/components/icons/index.tsx @@ -14,6 +14,7 @@ export { default as Filter } from './Filter'; export { default as Loading } from './Loading'; export { default as NoEdit } from './NoEdit'; export { default as Pencil } from './Pencil'; +export { default as Plus } from './Plus'; export { default as SampleDetailsDark } from './SampleDetailsDark'; export { default as SampleDetailsLight } from './SampleDetailsLight'; export { default as Share } from './Share'; diff --git a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx index 8d3c5f3dfd..f52d07bb8b 100755 --- a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx +++ b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx @@ -13,7 +13,6 @@ import { NumberVariable, OverlayConfig, PromiseResult, - useAnalysis, useAnalysisClient, useDataClient, useDownloadClient, @@ -31,8 +30,10 @@ import { DocumentationContainer } from '../../core/components/docs/Documentation import { CheckIcon, Download, + Plus, FilledButton, Filter as FilterIcon, + FloatingButton, H5, Table, } from '@veupathdb/coreui'; @@ -73,9 +74,10 @@ import { useLoginCallbacks } from '../../workspace/sharing/hooks'; import NameAnalysis from '../../workspace/sharing/NameAnalysis'; import NotesTab from '../../workspace/NotesTab'; import ConfirmShareAnalysis from '../../workspace/sharing/ConfirmShareAnalysis'; -import { useHistory } from 'react-router'; +import { useHistory, useRouteMatch } from 'react-router'; import { uniq } from 'lodash'; +import Path from 'path'; import DownloadTab from '../../workspace/DownloadTab'; import { RecordController } from '@veupathdb/wdk-client/lib/Controllers'; import { @@ -120,6 +122,7 @@ import { DraggablePanelCoordinatePair } from '@veupathdb/coreui/lib/components/c import _ from 'lodash'; import EZTimeFilter from './EZTimeFilter'; +import AnalysisNameDialog from '../../workspace/AnalysisNameDialog'; enum MapSideNavItemLabels { Download = 'Download', @@ -213,8 +216,7 @@ interface Props { } export function MapAnalysis(props: Props) { - const analysisState = useAnalysis(props.analysisId, 'pass'); - const appStateAndSetters = useAppState('@@mapApp@@', analysisState); + const appStateAndSetters = useAppState('@@mapApp@@', props.analysisId); const geoConfigs = useGeoConfig(useStudyEntities()); if (geoConfigs == null || geoConfigs.length === 0) @@ -231,7 +233,6 @@ export function MapAnalysis(props: Props) { ); @@ -749,6 +750,18 @@ function MapAnalysisImpl(props: ImplProps) { const filteredEntities = uniq(filters?.map((f) => f.entityId)); + const [isAnalysisNameDialogOpen, setIsAnalysisNameDialogOpen] = + useState(false); + const { url: urlRouteMatch } = useRouteMatch(); + const redirectURL = studyId + ? urlRouteMatch.endsWith(studyId) + ? `/workspace/${urlRouteMatch}/new` + : Path.resolve(urlRouteMatch, '../new') + : null; + const redirectToNewAnalysis = useCallback(() => { + if (redirectURL) history.push(redirectURL); + }, [history, redirectURL]); + const sideNavigationButtonConfigurationObjects: SideNavigationItemConfigurationObject[] = [ { @@ -1104,6 +1117,32 @@ function MapAnalysisImpl(props: ImplProps) { maxWidth: '1500px', }} > + {analysisId && redirectToNewAnalysis ? ( +
+ setIsAnalysisNameDialogOpen(true) + : redirectToNewAnalysis + } + textTransform="none" + /> +
+ ) : ( + <> + )} + {analysisState.analysis && ( + + )} { + // flip bit when analysis id changes + setAppStateChecked(false); + }, [analysisId]); + const { analysis, setVariableUISettings } = analysisState; const appState = pipe( AppState.decode( @@ -177,11 +183,8 @@ export function useAppState(uiStateKey: string, analysisState: AnalysisState) { [defaultVariable, defaultTimeVariable] ); - // make some backwards compatability updates to the appstate retrieved from the back end - const appStateCheckedRef = useRef(false); - useEffect(() => { - if (appStateCheckedRef.current) return; + if (appStateChecked) return; if (analysis) { if (!appState) { setVariableUISettings((prev) => ({ @@ -216,9 +219,16 @@ export function useAppState(uiStateKey: string, analysisState: AnalysisState) { })); } } - appStateCheckedRef.current = true; + setAppStateChecked(true); } - }, [analysis, appState, setVariableUISettings, uiStateKey, defaultAppState]); + }, [ + analysis, + appState, + setVariableUISettings, + uiStateKey, + defaultAppState, + appStateChecked, + ]); function useSetter(key: T) { return useCallback( @@ -243,6 +253,7 @@ export function useAppState(uiStateKey: string, analysisState: AnalysisState) { return { appState, + analysisState, setActiveMarkerConfigurationType: useSetter( 'activeMarkerConfigurationType' ), diff --git a/packages/libs/eda/src/lib/workspace/AnalysisNameDialog.tsx b/packages/libs/eda/src/lib/workspace/AnalysisNameDialog.tsx index dc8cb5581a..98aa809a64 100644 --- a/packages/libs/eda/src/lib/workspace/AnalysisNameDialog.tsx +++ b/packages/libs/eda/src/lib/workspace/AnalysisNameDialog.tsx @@ -22,7 +22,7 @@ interface AnalysisNameDialogProps { redirectToNewAnalysis: () => void; } -export function AnalysisNameDialog({ +export default function AnalysisNameDialog({ isOpen, setIsOpen, initialAnalysisName, @@ -30,11 +30,17 @@ export function AnalysisNameDialog({ redirectToNewAnalysis, }: AnalysisNameDialogProps) { const [inputText, setInputText] = useState(initialAnalysisName); + const [continueText, setContinueText] = + useState<'Continue' | 'Rename and continue'>('Continue'); const [nameIsValid, setNameIsValid] = useState(true); + const [disableButtons, setDisableButtons] = useState(false); const handleTextChange = (event: React.ChangeEvent) => { const newText = event.target.value; setInputText(newText); + setContinueText( + newText === initialAnalysisName ? 'Continue' : 'Rename and continue' + ); // Currently the only requirement is no empty name newText.length > 0 ? setNameIsValid(true) : setNameIsValid(false); }; @@ -44,11 +50,12 @@ export function AnalysisNameDialog({ setInputText(initialAnalysisName); }; - const handleContinue = async () => { - // TypeScript says this `await` has no effect, but it seems to be required - // for this function to finish before the page redirect - await setAnalysisName(inputText); - redirectToNewAnalysis(); + const handleContinue = () => { + setDisableButtons(true); + setAnalysisName(inputText); + // The timeout for saving an analysis is 1 second, + // so wait a bit longer than that + setTimeout(redirectToNewAnalysis, 1200); }; return ( @@ -74,20 +81,23 @@ export function AnalysisNameDialog({ error={!nameIsValid} // Currently the only requirement is no empty name helperText={nameIsValid ? ' ' : 'Name must not be blank'} + disabled={disableButtons} /> - diff --git a/packages/libs/eda/src/lib/workspace/EDAWorkspaceHeading.tsx b/packages/libs/eda/src/lib/workspace/EDAWorkspaceHeading.tsx index 1b5e191a51..11daf56faa 100644 --- a/packages/libs/eda/src/lib/workspace/EDAWorkspaceHeading.tsx +++ b/packages/libs/eda/src/lib/workspace/EDAWorkspaceHeading.tsx @@ -6,7 +6,7 @@ import Path from 'path'; import { H3, Table, FloatingButton } from '@veupathdb/coreui'; import { safeHtml } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils'; -import { AnalysisNameDialog } from './AnalysisNameDialog'; +import AnalysisNameDialog from './AnalysisNameDialog'; import AddIcon from '@material-ui/icons/Add'; // Hooks