diff --git a/js/components/landing/Landing.js b/js/components/landing/Landing.js index 6f4b69f53..4c3bb2752 100644 --- a/js/components/landing/Landing.js +++ b/js/components/landing/Landing.js @@ -23,7 +23,7 @@ const Landing = memo( if (DJANGO_CONTEXT['authenticated'] !== true) { setLoginText( <> - {'To view own targets login here: '} + {'To view own projects login here: '} FedID Login diff --git a/js/components/preview/Preview.js b/js/components/preview/Preview.js index 3a2430d10..27d8a3a23 100644 --- a/js/components/preview/Preview.js +++ b/js/components/preview/Preview.js @@ -23,6 +23,7 @@ import { HeaderContext } from '../header/headerContext'; import { unmountPreviewComponent } from './redux/dispatchActions'; import { NglContext } from '../nglView/nglProvider'; import { SaveSnapshotBeforeExit } from '../snapshot/modals/saveSnapshotBeforeExit'; +import { ModalShareSnapshot } from '../snapshot/modals/modalShareSnapshot'; //import HotspotList from '../hotspot/hotspotList'; const hitNavigatorWidth = 504; @@ -158,6 +159,7 @@ const Preview = memo(({ isStateLoaded, hideProjects }) => { */} + {!hideProjects && } diff --git a/js/components/preview/molecule/moleculeList.js b/js/components/preview/molecule/moleculeList.js index 24e69ff48..f6f5525e4 100644 --- a/js/components/preview/molecule/moleculeList.js +++ b/js/components/preview/molecule/moleculeList.js @@ -52,6 +52,7 @@ import { addLigand, removeLigand } from './redux/dispatchActions'; +import { useRouteMatch } from 'react-router-dom'; const useStyles = makeStyles(theme => ({ container: { @@ -214,6 +215,8 @@ const MoleculeList = memo( const [currentPage, setCurrentPage] = useState(0); const imgHeight = 34; const imgWidth = 150; + let match = useRouteMatch(); + const target = match && match.params && match.params.target; const [predefinedFilter, setPredefinedFilter] = useState(filter !== undefined ? filter.predefined : DEFAULT_FILTER); @@ -269,7 +272,8 @@ const MoleculeList = memo( cached_mol_lists[mol_group_on] && firstLoadRef && firstLoadRef.current && - hideProjects + hideProjects && + target !== undefined ) { console.log('initializing molecules'); firstLoadRef.current = false; @@ -289,7 +293,9 @@ const MoleculeList = memo( target_on, setCachedMolLists, cached_mol_lists, - dispatch + dispatch, + hideProjects, + target ]); const listItemOffset = (currentPage + 1) * moleculesPerPage; diff --git a/js/components/preview/projectHistory/index.js b/js/components/preview/projectHistory/index.js index 199458e87..8cfd0ea51 100644 --- a/js/components/preview/projectHistory/index.js +++ b/js/components/preview/projectHistory/index.js @@ -1,27 +1,15 @@ import React, { memo, useContext, useEffect, useRef } from 'react'; import { Panel } from '../../common/Surfaces/Panel'; -import { - Grid, - IconButton, - Typography, - Table, - TableBody, - TableRow, - TableCell, - TableHead, - makeStyles -} from '@material-ui/core'; import { templateExtend, TemplateName, Orientation, Gitgraph } from '@gitgraph/react'; -import { MergeType, Share } from '@material-ui/icons'; +import { MergeType } from '@material-ui/icons'; +import { makeStyles } from '@material-ui/core'; import { Button } from '../../common/Inputs/Button'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory, useRouteMatch } from 'react-router-dom'; -import { base_url, URLS } from '../../routes/constants'; import { loadSnapshotTree } from '../../projects/redux/dispatchActions'; import palette from '../../../theme/palette'; import { ModalShareSnapshot } from '../../snapshot/modals/modalShareSnapshot'; -import { setIsOpenModalBeforeExit, setSelectedSnapshotToSwitch, setSharedSnapshot } from '../../snapshot/redux/actions'; -import { resetReducersBetweenSnapshots, switchBetweenSnapshots } from '../redux/dispatchActions'; +import { setIsOpenModalBeforeExit, setSelectedSnapshotToSwitch } from '../../snapshot/redux/actions'; import { NglContext } from '../../nglView/nglProvider'; export const heightOfProjectHistory = '164px'; @@ -86,11 +74,7 @@ export const ProjectHistory = memo(({ setHeight, showFullHistory }) => { let match = useRouteMatch(); const projectID = match && match.params && match.params.projectId; const snapshotId = match && match.params && match.params.snapshotId; - - const currentProjectID = useSelector(state => state.projectReducers.currentProject.projectID); const currentSnapshotID = useSelector(state => state.projectReducers.currentSnapshot.id); - const currentSnapshotTitle = useSelector(state => state.projectReducers.currentSnapshot.title); - const currentSnapshotDescription = useSelector(state => state.projectReducers.currentSnapshot.description); const currentSnapshotList = useSelector(state => state.projectReducers.currentSnapshotList); const currentSnapshotTree = useSelector(state => state.projectReducers.currentSnapshotTree); const isLoadingTree = useSelector(state => state.projectReducers.isLoadingTree); @@ -146,23 +130,6 @@ export const ProjectHistory = memo(({ setHeight, showFullHistory }) => { hasHeader title="Project History" headerActions={[ - , @@ -202,33 +169,7 @@ export const ProjectHistory = memo(({ setHeight, showFullHistory }) => { )} - {/**/} - {/* */} - {/* */} - {/* */} - {/* Title*/} - {/* Author*/} - {/* Created*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* {snapshotDetail.name}*/} - {/* */} - {/* */} - {/* {snapshotDetail.author && snapshotDetail.author.username},*/} - {/* {snapshotDetail.author && snapshotDetail.author.email}*/} - {/* */} - {/* */} - {/* {snapshotDetail.created && moment(snapshotDetail.created).format('LLL')}*/} - {/* */} - {/* */} - {/* */} - {/*
*/} - {/*
*/} - ); }); diff --git a/js/components/projects/index.js b/js/components/projects/index.js index 01e0e51dc..61d5c7c32 100644 --- a/js/components/projects/index.js +++ b/js/components/projects/index.js @@ -24,7 +24,7 @@ import moment from 'moment'; import { setProjectModalOpen } from './redux/actions'; import { useDispatch, useSelector } from 'react-redux'; import { ProjectModal } from './projectModal'; -import { loadListOfProjects, removeProject, searchInProjects } from './redux/dispatchActions'; +import { loadListOfAllProjects, removeProject, searchInProjects } from './redux/dispatchActions'; import { DJANGO_CONTEXT } from '../../utils/djangoContext'; const useStyles = makeStyles(theme => ({ @@ -58,7 +58,7 @@ export const Projects = memo(({}) => { }); useEffect(() => { - dispatch(loadListOfProjects()).catch(error => { + dispatch(loadListOfAllProjects()).catch(error => { throw new Error(error); }); }, [dispatch]); diff --git a/js/components/projects/projectPreview/index.js b/js/components/projects/projectPreview/index.js index ca25e818b..03f07c8a6 100644 --- a/js/components/projects/projectPreview/index.js +++ b/js/components/projects/projectPreview/index.js @@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { useRouteMatch } from 'react-router-dom'; import { loadCurrentSnapshotByID, loadSnapshotByProjectID } from '../redux/dispatchActions'; import { HeaderContext } from '../../header/headerContext'; +import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; export const ProjectPreview = memo(({}) => { const { setSnackBarTitle } = useContext(HeaderContext); @@ -14,6 +15,7 @@ export const ProjectPreview = memo(({}) => { const projectId = match && match.params && match.params.projectId; const snapshotId = match && match.params && match.params.snapshotId; const currentSnapshotID = useSelector(state => state.projectReducers.currentSnapshot.id); + const currentProject = useSelector(state => state.projectReducers.currentProject); useEffect(() => { if (!snapshotId && currentSnapshotID === null) { @@ -57,6 +59,12 @@ export const ProjectPreview = memo(({}) => { } return canShow === true && isSnapshotLoaded.current !== undefined ? ( - + ) : null; }); diff --git a/js/components/projects/redux/constants.js b/js/components/projects/redux/constants.js index 770fad20a..1a27850a5 100644 --- a/js/components/projects/redux/constants.js +++ b/js/components/projects/redux/constants.js @@ -21,7 +21,8 @@ export const constants = { export const ProjectCreationType = { NEW: 'NEW', - FROM_SNAPSHOT: 'FROM_SNAPSHOT' + FROM_SNAPSHOT: 'FROM_SNAPSHOT', + READ_ONLY: 'READ_ONLY' }; export const SnapshotType = { diff --git a/js/components/projects/redux/dispatchActions.js b/js/components/projects/redux/dispatchActions.js index caeb6f2a1..13cf4efbb 100644 --- a/js/components/projects/redux/dispatchActions.js +++ b/js/components/projects/redux/dispatchActions.js @@ -15,6 +15,7 @@ import { base_url, URLS } from '../../routes/constants'; import { setDialogCurrentStep } from '../../snapshot/redux/actions'; import { createInitSnapshotFromCopy, getListOfSnapshots } from '../../snapshot/redux/dispatchActions'; import { SnapshotType } from './constants'; +import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; export const assignSnapshotToProject = ({ projectID, snapshotID, ...rest }) => (dispatch, getState) => { dispatch(resetCurrentSnapshot()); @@ -47,16 +48,26 @@ export const assignSnapshotToProject = ({ projectID, snapshotID, ...rest }) => ( }); }; -export const loadListOfProjects = () => (dispatch, getState) => { - return api({ url: `${base_url}/api/session-projects/` }).then(response => - dispatch(setListOfProjects((response && response.data && response.data.results) || [])) - ); +export const loadListOfAllProjects = () => (dispatch, getState) => { + const userID = DJANGO_CONTEXT['pk'] || null; + if (userID !== null) { + return api({ url: `${base_url}/api/session-projects/?author=${userID}` }).then(response => + dispatch(setListOfProjects((response && response.data && response.data.results) || [])) + ); + } else { + return Promise.resolve(); + } }; export const searchInProjects = title => (dispatch, getState) => { - return api({ url: `${base_url}/api/session-projects/?title=${title}` }).then(response => - dispatch(setListOfProjects((response && response.data && response.data.results) || [])) - ); + const userID = DJANGO_CONTEXT['pk'] || null; + if (userID !== null) { + return api({ url: `${base_url}/api/session-projects/?author=${userID}&title=${title}` }).then(response => + dispatch(setListOfProjects((response && response.data && response.data.results) || [])) + ); + } else { + return Promise.resolve(); + } }; export const removeSnapshotByID = snapshotID => dispatch => { @@ -97,7 +108,7 @@ export const removeProject = projectID => dispatch => { .then(() => dispatch(removeSnapshotTree(projectID))) .then(() => api({ url: `${base_url}/api/session-projects/${projectID}/`, method: METHOD.DELETE }).then(() => - dispatch(loadListOfProjects()) + dispatch(loadListOfAllProjects()) ) ) .finally(() => { diff --git a/js/components/routes/Routes.js b/js/components/routes/Routes.js index c5be1a3bc..f6173a5c4 100644 --- a/js/components/routes/Routes.js +++ b/js/components/routes/Routes.js @@ -15,6 +15,7 @@ import { HeaderContext } from '../header/headerContext'; import { Close } from '@material-ui/icons'; import { Projects } from '../projects'; import { ProjectDetailSessionList } from '../projects/projectDetailSessionList'; +import { SessionRedirect } from '../snapshot/sessionRedirect'; const useStyles = makeStyles(theme => ({ content: { @@ -49,6 +50,7 @@ const Routes = memo(() => { + { const [downloading, setDownloading] = useState(false); + const disableUserInteraction = useDisableUserInteraction(); const handlePdbDownload = async () => { setDownloading(true); @@ -67,7 +69,13 @@ const DownloadPdb = memo(({ targetOn, targetOnName, key }) => { ); } else { return ( - ); diff --git a/js/components/snapshot/modals/newSnapshotModal.js b/js/components/snapshot/modals/newSnapshotModal.js index 1dc74a5ba..26486a141 100644 --- a/js/components/snapshot/modals/newSnapshotModal.js +++ b/js/components/snapshot/modals/newSnapshotModal.js @@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { setOpenSnapshotSavingDialog } from '../redux/actions'; import { NewSnapshotForm } from './newSnapshotForm'; import { AddProjectDetail } from '../../projects/addProjectDetail'; +import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; export const NewSnapshotModal = memo(({}) => { const dispatch = useDispatch(); @@ -17,7 +18,9 @@ export const NewSnapshotModal = memo(({}) => { return ( - {!projectID && dialogCurrentStep === 0 && } + {!projectID && dialogCurrentStep === 0 && DJANGO_CONTEXT['pk'] && ( + + )} {projectID && } ); diff --git a/js/components/snapshot/redux/dispatchActions.js b/js/components/snapshot/redux/dispatchActions.js index 1ee92a032..e9b4d128c 100644 --- a/js/components/snapshot/redux/dispatchActions.js +++ b/js/components/snapshot/redux/dispatchActions.js @@ -2,21 +2,38 @@ import { reloadApiState, setSessionTitle } from '../../../reducers/api/actions'; import { reloadSelectionReducer } from '../../../reducers/selection/actions'; import { api, METHOD } from '../../../utils/api'; import { - setDialogCurrentStep, setIsLoadingListOfSnapshots, setIsLoadingSnapshotDialog, setListOfSnapshots, setOpenSnapshotSavingDialog } from './actions'; import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; -import { assignSnapshotToProject, loadSnapshotTree } from '../../projects/redux/dispatchActions'; +import { + assignSnapshotToProject, + createProjectFromSnapshotDialog, + loadSnapshotTree +} from '../../projects/redux/dispatchActions'; import { reloadPreviewReducer } from '../../preview/redux/dispatchActions'; -import { SnapshotType } from '../../projects/redux/constants'; +import { ProjectCreationType, SnapshotType } from '../../projects/redux/constants'; import moment from 'moment'; import { setProteinLoadingState } from '../../../reducers/ngl/actions'; import { reloadNglViewFromSnapshot } from '../../../reducers/ngl/dispatchActions'; import { base_url, URLS } from '../../routes/constants'; import { resetCurrentSnapshot, setCurrentSnapshot } from '../../projects/redux/actions'; +import { useSelector } from 'react-redux'; + +export const getListOfSnapshots = () => (dispatch, getState) => { + dispatch(setIsLoadingListOfSnapshots(true)); + return api({ url: `${base_url}/api/snapshots/?session_project__isnull=False&author=${DJANGO_CONTEXT['pk']}` }) + .then(response => { + if (response && response.data && response.data.results) { + dispatch(setListOfSnapshots(response.data.results)); + } + }) + .finally(() => { + dispatch(setIsLoadingListOfSnapshots(false)); + }); +}; export const reloadSession = (snapshotData, nglViewList) => (dispatch, getState) => { const state = getState(); @@ -169,6 +186,7 @@ export const createNewSnapshot = ({ title, description, type, author, parent, se if (response.data.count === 0) { newType = SnapshotType.INIT; } + return api({ url: `${base_url}/api/snapshots/`, data: { @@ -182,33 +200,14 @@ export const createNewSnapshot = ({ title, description, type, author, parent, se children: [] }, method: METHOD.POST - }).then(response => { - Promise.all([ - dispatch(setDialogCurrentStep(0)), - dispatch(setIsLoadingSnapshotDialog(false)), - dispatch(setOpenSnapshotSavingDialog(false)), - dispatch( - setCurrentSnapshot({ - id: response.data.id, - type: response.data.type, - title: response.data.title, - author: response.data.author, - description: response.data.description, - created: response.data.created, - children: response.data.children, - parent: response.data.parent, - data: JSON.parse(response.data.data) - }) - ), - dispatch(getListOfSnapshots()) - ]); + }).then(res => { // redirect to project with newest created snapshot /:projectID/:snapshotID - if (response.data.id && session_project) { + if (res.data.id && session_project) { // Really bad usage or redirection. Hint for everybody in this line ignore it, but in other parts of code // use react-router ! window.location.replace( `${URLS.projects}${session_project}/${ - selectedSnapshotToSwitch === null ? response.data.id : selectedSnapshotToSwitch + selectedSnapshotToSwitch === null ? res.data.id : selectedSnapshotToSwitch }` ); } @@ -217,64 +216,24 @@ export const createNewSnapshot = ({ title, description, type, author, parent, se ]); }; -export const createSnapshotFromOld = (snapshot, history) => (dispatch, getState) => { - if (!snapshot) { - return Promise.reject('Snapshot is missing!'); - } - const { title, description, data, author, session_project, children } = snapshot; - if (!session_project) { - return Promise.reject('Project ID is missing!'); +export const activateSnapshotDialog = (isUserLoggedIn = undefined) => (dispatch, getState) => { + const targetId = getState().apiReducers.target_on; + if (!isUserLoggedIn && targetId) { + const data = { + title: ProjectCreationType.READ_ONLY, + description: ProjectCreationType.READ_ONLY, + target: targetId, + author: null, + tags: '[]' + }; + dispatch(createProjectFromSnapshotDialog(data)) + .then(() => { + dispatch(setOpenSnapshotSavingDialog(true)); + }) + .catch(error => { + throw new Error(error); + }); + } else { + dispatch(setOpenSnapshotSavingDialog(true)); } - - return Promise.all([ - dispatch(setIsLoadingSnapshotDialog(true)), - api({ - url: `${base_url}/api/snapshots/`, - data: { - title, - description, - type: SnapshotType.INIT, - author, - parent: null, - session_project, - data: JSON.stringify(data), - children - }, - method: METHOD.POST - }).then(response => { - Promise.all([ - dispatch( - setCurrentSnapshot({ - id: response.data.id, - type: response.data.type, - title: response.data.title, - author: response.data.author, - description: response.data.description, - created: response.data.created, - children: response.data.children, - parent: response.data.parent, - data: JSON.parse(response.data.data) - }) - ), - dispatch(getListOfSnapshots()) - ]); - // redirect to project with newest created snapshot /:projectID/:snapshotID - if (response.data.id && session_project) { - history.push(`${URLS.projects}${session_project}/${response.data.id}`); - } - }) - ]); -}; - -export const getListOfSnapshots = () => (dispatch, getState) => { - dispatch(setIsLoadingListOfSnapshots(true)); - return api({ url: `${base_url}/api/snapshots/?session_project!=null` }) - .then(response => { - if (response && response.data && response.data.results) { - dispatch(setListOfSnapshots(response.data.results)); - } - }) - .finally(() => { - dispatch(setIsLoadingListOfSnapshots(false)); - }); }; diff --git a/js/components/snapshot/sessionRedirect.js b/js/components/snapshot/sessionRedirect.js new file mode 100644 index 000000000..f5e00e4ca --- /dev/null +++ b/js/components/snapshot/sessionRedirect.js @@ -0,0 +1,46 @@ +import React, { memo, useContext, useEffect, useState } from 'react'; +import { api } from '../../utils/api'; +import { base_url, URLS } from '../routes/constants'; +import { useHistory, useRouteMatch } from 'react-router-dom'; +import { HeaderContext } from '../header/headerContext'; +import { setIsLoadingCurrentSnapshot } from '../projects/redux/actions'; +import { useDispatch } from 'react-redux'; + +export const SessionRedirect = memo(() => { + const dispatch = useDispatch(); + let history = useHistory(); + let match = useRouteMatch(); + const sessionUUID = match && match.params && match.params.sessionUUID; + const { setSnackBarTitle } = useContext(HeaderContext); + + useEffect(() => { + dispatch(setIsLoadingCurrentSnapshot(true)); + api({ url: `${base_url}/api/viewscene/?uuid=${sessionUUID}` }) + .then(response => { + if (response.data && response.data.results && response.data.results.length === 1) { + const session = response.data.results[0]; + if (session.snapshot) { + return api({ url: `${base_url}/api/snapshots/${session.snapshot}` }).then(res => { + if (res.data && res.data.session_project && res.data.session_project.id) { + history.push(`${URLS.projects}${res.data.session_project.id}/${session.snapshot}`); + } else { + return Promise.reject('Project is not found!'); + } + }); + } else { + return Promise.reject('Snapshot is not found!'); + } + } else { + return Promise.reject('Session is not found!'); + } + }) + .catch(error => { + setSnackBarTitle(error); + }) + .finally(() => { + dispatch(setIsLoadingCurrentSnapshot(false)); + }); + }, [dispatch, history, sessionUUID, setSnackBarTitle]); + + return null; +}); diff --git a/js/components/snapshot/withSnapshotManagement.js b/js/components/snapshot/withSnapshotManagement.js index 3834ce6a5..60e0e1dcf 100644 --- a/js/components/snapshot/withSnapshotManagement.js +++ b/js/components/snapshot/withSnapshotManagement.js @@ -1,12 +1,15 @@ import React, { memo, useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Button } from '@material-ui/core'; -import { Save } from '@material-ui/icons'; +import { Save, Share } from '@material-ui/icons'; import DownloadPdb from './downloadPdb'; -import { savingStateConst } from './constants'; import { HeaderContext } from '../header/headerContext'; -import { setOpenSnapshotSavingDialog } from './redux/actions'; +import { setSharedSnapshot } from './redux/actions'; import { useRouteMatch } from 'react-router-dom'; +import { DJANGO_CONTEXT } from '../../utils/djangoContext'; +import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; +import { base_url, URLS } from '../routes/constants'; +import { activateSnapshotDialog } from './redux/dispatchActions'; /** * Created by ricgillams on 13/06/2018. @@ -17,19 +20,22 @@ export const withSnapshotManagement = WrappedComponent => { let match = useRouteMatch(); const { setHeaderNavbarTitle, setHeaderButtons, setSnackBarTitle, setSnackBarColor } = useContext(HeaderContext); const dispatch = useDispatch(); - const savingState = useSelector(state => state.apiReducers.savingState); const sessionTitle = useSelector(state => state.apiReducers.sessionTitle); + const currentSnapshotID = useSelector(state => state.projectReducers.currentSnapshot.id); + const currentSnapshotTitle = useSelector(state => state.projectReducers.currentSnapshot.title); + const currentSnapshotDescription = useSelector(state => state.projectReducers.currentSnapshot.description); + const targetIdList = useSelector(state => state.apiReducers.target_id_list); const targetName = useSelector(state => state.apiReducers.target_on_name); + const currentProject = useSelector(state => state.projectReducers.currentProject); const projectId = match && match.params && match.params.projectId; + const target = match && match.params && match.params.target; + const disableUserInteraction = useDisableUserInteraction(); - const disableButtons = - (savingState && - (savingState.startsWith(savingStateConst.saving) || savingState.startsWith(savingStateConst.overwriting))) || - !projectId || - !targetName || - false; + const enableButton = + (projectId && currentProject.projectID !== null && currentProject.authorID !== null && DJANGO_CONTEXT['pk']) || + target !== undefined; // Function for set Header buttons, target title and snackBar information about session useEffect(() => { @@ -40,12 +46,34 @@ export const withSnapshotManagement = WrappedComponent => { , + , ]); // setSnackBarTitle('Currently no active session.'); @@ -57,7 +85,7 @@ export const withSnapshotManagement = WrappedComponent => { setHeaderNavbarTitle(''); }; }, [ - disableButtons, + enableButton, dispatch, sessionTitle, setHeaderNavbarTitle, @@ -66,7 +94,8 @@ export const withSnapshotManagement = WrappedComponent => { targetIdList, targetName, setSnackBarColor, - projectId + projectId, + disableUserInteraction ]); return ; diff --git a/js/components/target/redux/dispatchActions.js b/js/components/target/redux/dispatchActions.js index a34e99140..12530c1c9 100644 --- a/js/components/target/redux/dispatchActions.js +++ b/js/components/target/redux/dispatchActions.js @@ -64,7 +64,7 @@ export const updateTarget = ({ target, setIsLoading, targetIdList, projectId }) dispatch( setCurrentProject({ projectID: response.data.id, - authorID: response.data.author.id, + authorID: (response.data.author && response.data.author.id) || null, title: response.data.title, description: response.data.description, targetID: response.data.target.id,