diff --git a/js/components/preview/viewerControls/displayControls/index.js b/js/components/preview/viewerControls/displayControls/index.js index ef9653586..a16125bd7 100644 --- a/js/components/preview/viewerControls/displayControls/index.js +++ b/js/components/preview/viewerControls/displayControls/index.js @@ -9,7 +9,8 @@ import { NglContext } from '../../../nglView/nglProvider'; import { addComponentRepresentation, removeComponentRepresentation, - updateComponentRepresentation + updateComponentRepresentation, + changeComponentRepresentation } from '../../../../reducers/ngl/actions'; import { deleteObject } from '../../../../reducers/ngl/dispatchActions'; import { MOL_REPRESENTATION, OBJECT_TYPE, SELECTION_TYPE } from '../../../nglView/constants'; @@ -71,10 +72,12 @@ export default memo(({ open, onClose }) => { oldRepresentation.lastKnownID ); // add new representation to redux - dispatch(addComponentRepresentation(parentKey, newRepresentation)); + dispatch(addComponentRepresentation(parentKey, newRepresentation, true)); // remove previous representation from NGL - removeRepresentation(representation, parentKey); + removeRepresentation(representation, parentKey, true); + + dispatch(changeComponentRepresentation(parentKey, oldRepresentation, newRepresentation)); }; const addMolecularRepresentation = (parentKey, e) => { @@ -88,7 +91,7 @@ export default memo(({ open, onClose }) => { dispatch(addComponentRepresentation(parentKey, newRepresentation)); }; - const removeRepresentation = (representation, parentKey) => { + const removeRepresentation = (representation, parentKey, skipTracking) => { const nglView = getNglView(objectsInView[parentKey].display_div); const comp = nglView.stage.getComponentsByName(parentKey).first; let foundedRepresentation = undefined; @@ -107,7 +110,7 @@ export default memo(({ open, onClose }) => { // remove from nglReducer and selectionReducer dispatch(deleteObject(targetObject, nglView.stage, true)); } else { - dispatch(removeComponentRepresentation(parentKey, representation)); + dispatch(removeComponentRepresentation(parentKey, representation, skipTracking)); } } }; diff --git a/js/components/snapshot/modals/newSnapshotForm.js b/js/components/snapshot/modals/newSnapshotForm.js index eb9d5309a..b0c9c43ba 100644 --- a/js/components/snapshot/modals/newSnapshotForm.js +++ b/js/components/snapshot/modals/newSnapshotForm.js @@ -1,5 +1,5 @@ import React, { memo, useState, useContext } from 'react'; -import { Grid, makeStyles, Typography } from '@material-ui/core'; +import { Grid, makeStyles, Typography, Checkbox, FormControlLabel } from '@material-ui/core'; import { useDispatch, useSelector } from 'react-redux'; import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; import { Form, Formik, Field } from 'formik'; @@ -28,6 +28,9 @@ const useStyles = makeStyles(theme => ({ formControl: { margin: theme.spacing(1), width: 400 + }, + checkbox: { + margin: theme.spacing(0) } })); @@ -36,15 +39,25 @@ export const NewSnapshotForm = memo(({ handleCloseModal }) => { const [state, setState] = useState(); const dispatch = useDispatch(); const { nglViewList } = useContext(NglContext); + const [overwriteSnapshot, setoverwriteSnapshot] = useState(false); const currentSnapshot = useSelector(state => state.projectReducers.currentSnapshot); const currentProject = useSelector(state => state.projectReducers.currentProject); const isLoadingSnapshotDialog = useSelector(state => state.snapshotReducers.isLoadingSnapshotDialog); const isForceProjectCreated = useSelector(state => state.projectReducers.isForceProjectCreated); + const currentSnapshotId = currentSnapshot && currentSnapshot.id; const loggedInUserID = DJANGO_CONTEXT['pk']; const username = DJANGO_CONTEXT['username']; + const toggleoverwriteSnapshot = () => { + if (overwriteSnapshot === true) { + setoverwriteSnapshot(false); + } else { + setoverwriteSnapshot(true); + } + }; + return ( <> Snapshot details @@ -73,13 +86,22 @@ export const NewSnapshotForm = memo(({ handleCloseModal }) => { const parent = isForceProjectCreated === false ? currentSnapshot.id : null; const session_project = currentProject.projectID; - dispatch(createNewSnapshot({ title, description, type, author, parent, session_project, nglViewList })).catch( - error => { - setState(() => { - throw error; - }); - } - ); + dispatch( + createNewSnapshot({ + title, + description, + type, + author, + parent, + session_project, + nglViewList, + overwriteSnapshot + }) + ).catch(error => { + setState(() => { + throw error; + }); + }); }} > {({ submitForm, isSubmitting }) => ( @@ -115,6 +137,25 @@ export const NewSnapshotForm = memo(({ handleCloseModal }) => { } /> + {currentSnapshotId && ( + + { + toggleoverwriteSnapshot(); + }} + /> + } + label="Overwrite current snapshot" + labelPlacement="end" + className={classes.checkbox} + disabled={isLoadingSnapshotDialog || isSubmitting} + /> + + )} diff --git a/js/components/snapshot/redux/dispatchActions.js b/js/components/snapshot/redux/dispatchActions.js index cc1641922..6b50a8886 100644 --- a/js/components/snapshot/redux/dispatchActions.js +++ b/js/components/snapshot/redux/dispatchActions.js @@ -25,8 +25,12 @@ import { base_url, URLS } from '../../routes/constants'; import { resetCurrentSnapshot, setCurrentSnapshot, setForceCreateProject } from '../../projects/redux/actions'; import { selectFirstMolGroup } from '../../preview/moleculeGroups/redux/dispatchActions'; import { reloadDatasetsReducer } from '../../datasets/redux/actions'; -import { saveCurrentActionsList } from '../../../reducers/tracking/dispatchActions'; -import { sendTrackingActionsByProjectId, manageSendTrackingActions } from '../../../reducers/tracking/dispatchActions'; +import { + saveCurrentActionsList, + addCurrentActionsListToSnapshot, + sendTrackingActionsByProjectId, + manageSendTrackingActions +} from '../../../reducers/tracking/dispatchActions'; import { captureScreenOfSnapshot } from '../../userFeedback/browserApi'; export const getListOfSnapshots = () => (dispatch, getState) => { @@ -179,71 +183,95 @@ export const createInitSnapshotFromCopy = ({ return Promise.reject('ProjectID is missing'); }; -export const createNewSnapshot = ({ title, description, type, author, parent, session_project, nglViewList }) => ( - dispatch, - getState -) => { +export const createNewSnapshot = ({ + title, + description, + type, + author, + parent, + session_project, + nglViewList, + overwriteSnapshot +}) => (dispatch, getState) => { const state = getState(); const selectedSnapshotToSwitch = state.snapshotReducers.selectedSnapshotToSwitch; const disableRedirect = state.snapshotReducers.disableRedirect; + const currentSnapshot = state.projectReducers.currentSnapshot; + const currentSnapshotId = currentSnapshot && currentSnapshot.id; if (!session_project) { return Promise.reject('Project ID is missing!'); } - let newType = type; - - return Promise.all([ - dispatch(setIsLoadingSnapshotDialog(true)), - api({ url: `${base_url}/api/snapshots/?session_project=${session_project}&type=INIT` }).then(response => { - if (response.data.count === 0) { - newType = SnapshotType.INIT; + if (overwriteSnapshot === true && currentSnapshotId) { + dispatch(setIsLoadingSnapshotDialog(true)); + let project = { projectID: session_project, authorID: author }; + + return Promise.resolve(dispatch(addCurrentActionsListToSnapshot(currentSnapshot, project, nglViewList))).then( + () => { + if (disableRedirect === false && selectedSnapshotToSwitch != null) { + window.location.replace(`${URLS.projects}${session_project}/${selectedSnapshotToSwitch}`); + } else { + dispatch(setIsLoadingSnapshotDialog(false)); + dispatch(setOpenSnapshotSavingDialog(false)); + } } + ); + } else { + let newType = type; - return api({ - url: `${base_url}/api/snapshots/`, - data: { - title, - description, - type: newType, - author, - parent, - session_project, - data: '[]', - children: [] - }, - method: METHOD.POST - }).then(res => { - // redirect to project with newest created snapshot /:projectID/:snapshotID - if (res.data.id && session_project) { - let snapshot = { id: res.data.id, title: title }; - let project = { projectID: session_project, authorID: author }; - - Promise.resolve(dispatch(saveCurrentActionsList(snapshot, project, nglViewList))).then(() => { - if (disableRedirect === false) { - // 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 ? res.data.id : selectedSnapshotToSwitch - }` - ); - } else { - dispatch(setOpenSnapshotSavingDialog(false)); - dispatch(setIsLoadingSnapshotDialog(false)); - dispatch( - setSharedSnapshot({ - title, - description, - url: `${base_url}${URLS.projects}${session_project}/${res.data.id}` - }) - ); - } - }); + return Promise.all([ + dispatch(setIsLoadingSnapshotDialog(true)), + api({ url: `${base_url}/api/snapshots/?session_project=${session_project}&type=INIT` }).then(response => { + if (response.data.count === 0) { + newType = SnapshotType.INIT; } - }); - }) - ]); + + return api({ + url: `${base_url}/api/snapshots/`, + data: { + title, + description, + type: newType, + author, + parent, + session_project, + data: '[]', + children: [] + }, + method: METHOD.POST + }).then(res => { + // redirect to project with newest created snapshot /:projectID/:snapshotID + if (res.data.id && session_project) { + let snapshot = { id: res.data.id, title: title }; + let project = { projectID: session_project, authorID: author }; + + Promise.resolve(dispatch(saveCurrentActionsList(snapshot, project, nglViewList))).then(() => { + if (disableRedirect === false) { + // 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 ? res.data.id : selectedSnapshotToSwitch + }` + ); + } else { + dispatch(setOpenSnapshotSavingDialog(false)); + dispatch(setIsLoadingSnapshotDialog(false)); + dispatch( + setSharedSnapshot({ + title, + description, + url: `${base_url}${URLS.projects}${session_project}/${res.data.id}` + }) + ); + } + }); + } + }); + }) + ]); + } }; export const activateSnapshotDialog = (loggedInUserID = undefined, finallyShareSnapshot = false) => ( @@ -329,7 +357,7 @@ export const createNewSnapshotWithoutStateModification = ({ let snapshot = { id: res.data.id, title: title }; let project = { projectID: session_project, authorID: author }; - dispatch(saveCurrentActionsList(snapshot, project, nglViewList)); + dispatch(saveCurrentActionsList(snapshot, project, nglViewList, true)); } }); }); diff --git a/js/index.js b/js/index.js index fb8ddd49e..809d28816 100644 --- a/js/index.js +++ b/js/index.js @@ -5,7 +5,6 @@ import { DJANGO_CONTEXT } from './utils/djangoContext'; // Sentry logging import { init, configureScope } from '@sentry/browser'; // Setup log rocket logging -import LogRocket from 'logrocket'; import { Provider } from 'react-redux'; import { applyMiddleware, createStore } from 'redux'; import { rootReducer } from './reducers/rootReducer'; @@ -16,27 +15,6 @@ import { composeWithDevTools } from 'redux-devtools-extension'; require('react-hot-loader/patch'); -if (process.env.NODE_ENV === 'production') { - LogRocket.init('eoalzb/fragalysis'); - // This is the log rocket setup - - LogRocket.identify(DJANGO_CONTEXT['username'], { - pk: DJANGO_CONTEXT['pk'], - name: DJANGO_CONTEXT['name'], - email: DJANGO_CONTEXT['email'] - }); - - init({ - dsn: 'https://27fa0675f555431aa02ca552e93d8cfb@sentry.io/1298290' - }); - - LogRocket.getSessionURL(sessionURL => { - configureScope(scope => { - scope.setExtra('logRocketURL', sessionURL); - }); - }); -} - const middlewareEnhancer = applyMiddleware( //loggerMiddleware, thunkMiddleware, diff --git a/js/reducers/ngl/actions.js b/js/reducers/ngl/actions.js index b9ac62da3..87feabbc8 100644 --- a/js/reducers/ngl/actions.js +++ b/js/reducers/ngl/actions.js @@ -20,15 +20,24 @@ export const updateComponentRepresentation = (objectInViewID, representationID, change }); -export const addComponentRepresentation = (objectInViewID, newRepresentation) => ({ +export const addComponentRepresentation = (objectInViewID, newRepresentation, skipTracking = false) => ({ type: CONSTANTS.ADD_COMPONENT_REPRESENTATION, newRepresentation, - objectInViewID + objectInViewID, + skipTracking }); -export const removeComponentRepresentation = (objectInViewID, representation) => ({ +export const removeComponentRepresentation = (objectInViewID, representation, skipTracking = false) => ({ type: CONSTANTS.REMOVE_COMPONENT_REPRESENTATION, representation, + objectInViewID, + skipTracking +}); + +export const changeComponentRepresentation = (objectInViewID, oldRepresentation, newRepresentation) => ({ + type: CONSTANTS.CHANGE_COMPONENT_REPRESENTATION, + oldRepresentation, + newRepresentation, objectInViewID }); @@ -91,5 +100,5 @@ export const removeMoleculeOrientation = moleculeGroupID => ({ export const addToPdbCache = (name, cacheItem) => ({ type: CONSTANTS.ADD_TO_PDB_CACHE, - payload: {name: name, cacheItem: cacheItem} + payload: { name: name, cacheItem: cacheItem } }); diff --git a/js/reducers/ngl/constants.js b/js/reducers/ngl/constants.js index 86a4de479..3f019d6db 100644 --- a/js/reducers/ngl/constants.js +++ b/js/reducers/ngl/constants.js @@ -7,6 +7,7 @@ export const CONSTANTS = { UPDATE_COMPONENT_REPRESENTATION: prefix + 'UPDATE_COMPONENT_REPRESENTATION', REMOVE_COMPONENT_REPRESENTATION: prefix + 'REMOVE_COMPONENT_REPRESENTATION', ADD_COMPONENT_REPRESENTATION: prefix + 'ADD_COMPONENT_REPRESENTATION', + CHANGE_COMPONENT_REPRESENTATION: prefix + 'CHANGE_COMPONENT_REPRESENTATION', SET_NGL_VIEW_PARAMS: prefix + 'SET_NGL_VIEW_PARAMS', SET_ORIENTATION: prefix + 'SET_ORIENTATION', diff --git a/js/reducers/tracking/constants.js b/js/reducers/tracking/constants.js index 760e4ca4b..22703292a 100644 --- a/js/reducers/tracking/constants.js +++ b/js/reducers/tracking/constants.js @@ -41,9 +41,10 @@ export const actionType = { MOLECULE_REMOVED_FROM_SHOPPING_CART: 'MOLECULE_REMOVED_FROM_SHOPPING_CART', COMPOUND_SELECTED: 'COMPOUND_SELECTED', COMPOUND_DESELECTED: 'COMPOUND_DESELECTED', - REPRESENTATION_CHANGED: 'REPRESENTATION_CHANGED', + REPRESENTATION_UPDATED: 'REPRESENTATION_UPDATED', REPRESENTATION_ADDED: 'REPRESENTATION_ADDED', REPRESENTATION_REMOVED: 'REPRESENTATION_REMOVED', + REPRESENTATION_CHANGED: 'REPRESENTATION_CHANGED', NGL_STATE: 'NGL_STATE', UNDO: 'UNDO', REDO: 'REDO', @@ -66,6 +67,7 @@ export const actionDescription = { ADDED: 'was added', REMOVED: 'was removed', CHANGED: 'was changed', + UPDATED: 'was updated', TO_SHOPPING_CART: 'to shopping cart', FROM_SHOPPING_CART: 'from shopping cart', LIGAND: 'Ligand', diff --git a/js/reducers/tracking/dispatchActions.js b/js/reducers/tracking/dispatchActions.js index ac9d59f43..48944e793 100644 --- a/js/reducers/tracking/dispatchActions.js +++ b/js/reducers/tracking/dispatchActions.js @@ -56,7 +56,8 @@ import { getUrl, loadAllMolsFromMolGroup } from '../../../js/utils/genericList'; import { removeComponentRepresentation, addComponentRepresentation, - updateComponentRepresentation + updateComponentRepresentation, + changeComponentRepresentation } from '../../../js/reducers/ngl/actions'; import * as listType from '../../constants/listTypes'; import { assignRepresentationToComp } from '../../components/nglView/generatingObjects'; @@ -95,11 +96,22 @@ import { setDeselectedAllByType as setDeselectedAllByTypeOfDataset } from '../../components/datasets/redux/actions'; -export const saveCurrentActionsList = (snapshot, project, nglViewList) => async (dispatch, getState) => { +export const addCurrentActionsListToSnapshot = (snapshot, project, nglViewList) => async (dispatch, getState) => { let projectID = project && project.projectID; let actionList = await dispatch(getTrackingActions(projectID)); - dispatch(setSnapshotToActions(actionList, snapshot, projectID)); + await dispatch(setSnapshotToActions(actionList, snapshot, projectID, project, nglViewList, true)); +}; + +export const saveCurrentActionsList = (snapshot, project, nglViewList, all = false) => async (dispatch, getState) => { + let projectID = project && project.projectID; + let actionList = await dispatch(getTrackingActions(projectID)); + + if (all === false) { + dispatch(setSnapshotToActions(actionList, snapshot, projectID, project, nglViewList, false)); + } else { + dispatch(setSnapshotToAllActions(actionList, snapshot, projectID)); + } await dispatch(saveActionsList(project, snapshot, actionList, nglViewList)); }; @@ -331,7 +343,7 @@ const saveActionsList = (project, snapshot, actionList, nglViewList) => async (d getCurrentActionList( orderedActionList, - actionType.REPRESENTATION_CHANGED, + actionType.REPRESENTATION_UPDATED, getCollectionOfDatasetOfRepresentation(currentobjectsInView), currentActions ); @@ -375,11 +387,25 @@ const saveSnapshotAction = (snapshot, project, currentActions) => async (dispatc await dispatch(sendTrackingActions(sendActions, project)); }; -const setSnapshotToActions = (actionList, snapshot, projectID) => (dispatch, getState) => { +const setSnapshotToActions = (actionList, snapshot, projectID, project, nglViewList, addToSnapshot) => async ( + dispatch, + getState +) => { if (actionList && snapshot) { let actionsWithoutSnapshot = actionList.filter(a => a.snapshotId === null || a.snapshotId === undefined); let updatedActions = actionsWithoutSnapshot.map(obj => ({ ...obj, snapshotId: snapshot.id })); dispatch(setAndUpdateTrackingActions(updatedActions, projectID)); + + if (addToSnapshot === true) { + await dispatch(saveActionsList(project, snapshot, updatedActions, nglViewList)); + } + } +}; + +const setSnapshotToAllActions = (actionList, snapshot, projectID) => async (dispatch, getState) => { + if (actionList && snapshot) { + let updatedActions = actionList.map(obj => ({ ...obj, snapshotId: snapshot.id })); + dispatch(setAndUpdateTrackingActions(updatedActions, projectID)); } }; @@ -677,7 +703,8 @@ export const restoreAfterTargetActions = (stages, projectId) => async (dispatch, }; const restoreNglStateAction = (orderedActionList, stages) => (dispatch, getState) => { - let action = orderedActionList.find(action => action.type === actionType.NGL_STATE); + let actions = orderedActionList.filter(action => action.type === actionType.NGL_STATE); + let action = [...actions].pop(); if (action && action.nglStateList) { action.nglStateList.forEach(nglView => { dispatch(setOrientation(nglView.id, nglView.orientation)); @@ -907,11 +934,11 @@ const restoreRepresentationActions = (moleculesAction, stages) => (dispatch, get } let representationsChangesActions = moleculesAction.filter( - action => action.type === actionType.REPRESENTATION_CHANGED + action => action.type === actionType.REPRESENTATION_UPDATED ); if (representationsChangesActions) { representationsChangesActions.forEach(action => { - dispatch(changeRepresentation(true, action.change, action.object_id, action.representation, nglView)); + dispatch(updateRepresentation(true, action.change, action.object_id, action.representation, nglView)); }); } }; @@ -922,9 +949,11 @@ const restoreSnapshotImageActions = projectID => async (dispatch, getState) => { let snapshotActions = actionList.filter(action => action.type === actionType.SNAPSHOT); if (snapshotActions) { let actions = snapshotActions.map(s => { - return { id: s.object_id, image: s.image, title: s.object_name }; + return { id: s.object_id, image: s.image, title: s.object_name, timestamp: s.timestamp }; }); - dispatch(setSnapshotImageActionList(actions)); + const key = 'object_id'; + const arrayUniqueByKey = [...new Map(actions.map(item => [item[key], item])).values()]; + dispatch(setSnapshotImageActionList(arrayUniqueByKey)); } }; @@ -1255,8 +1284,8 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => { case actionType.COMPOUND_DESELECTED: dispatch(handleCompoundAction(action, true)); break; - case actionType.REPRESENTATION_CHANGED: - dispatch(handleChangeRepresentationAction(action, false, majorView)); + case actionType.REPRESENTATION_UPDATED: + dispatch(handleUpdateRepresentationAction(action, false, majorView)); break; case actionType.REPRESENTATION_ADDED: dispatch(handleRepresentationAction(action, false, majorView)); @@ -1264,6 +1293,9 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => { case actionType.REPRESENTATION_REMOVED: dispatch(handleRepresentationAction(action, true, majorView)); break; + case actionType.REPRESENTATION_CHANGED: + dispatch(handleChangeRepresentationAction(action, false, majorView)); + break; default: break; } @@ -1353,8 +1385,8 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => { case actionType.COMPOUND_DESELECTED: dispatch(handleCompoundAction(action, false)); break; - case actionType.REPRESENTATION_CHANGED: - dispatch(handleChangeRepresentationAction(action, true, majorView)); + case actionType.REPRESENTATION_UPDATED: + dispatch(handleUpdateRepresentationAction(action, true, majorView)); break; case actionType.REPRESENTATION_ADDED: dispatch(handleRepresentationAction(action, true, majorView)); @@ -1362,6 +1394,9 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => { case actionType.REPRESENTATION_REMOVED: dispatch(handleRepresentationAction(action, false, majorView)); break; + case actionType.REPRESENTATION_CHANGED: + dispatch(handleChangeRepresentationAction(action, true, majorView)); + break; default: break; } @@ -1598,14 +1633,17 @@ const handleShoppingCartAction = (action, isAdd) => (dispatch, getState) => { const handleRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { if (action) { if (isAdd === true) { - dispatch(addRepresentation(action.object_id, action.representation, nglView)); + dispatch(addRepresentation(action, action.object_id, action.representation, nglView)); } else { - dispatch(removeRepresentation(action.object_id, action.representation, nglView)); + dispatch(removeRepresentation(action, action.object_id, action.representation, nglView)); } } }; -const addRepresentation = (parentKey, representation, nglView) => (dispatch, getState) => { +const addRepresentation = (action, parentKey, representation, nglView, update, skipTracking = false) => ( + dispatch, + getState +) => { const oldRepresentation = representation; const newRepresentationType = oldRepresentation.type; const comp = nglView.stage.getComponentsByName(parentKey).first; @@ -1615,16 +1653,22 @@ const addRepresentation = (parentKey, representation, nglView) => (dispatch, get comp, oldRepresentation.lastKnownID ); - dispatch(addComponentRepresentation(parentKey, newRepresentation)); + action.representation = newRepresentation; + if (update === true) { + action.newRepresentation = newRepresentation; + } else { + action.oldRepresentation = newRepresentation; + } + dispatch(addComponentRepresentation(parentKey, newRepresentation, skipTracking)); }; -const handleChangeRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { +const handleUpdateRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { if (action) { - dispatch(changeRepresentation(isAdd, action.change, action.object_id, action.representation, nglView)); + dispatch(updateRepresentation(isAdd, action.change, action.object_id, action.representation, nglView)); } }; -const changeRepresentation = (isAdd, change, parentKey, representation, nglView) => (dispatch, getState) => { +const updateRepresentation = (isAdd, change, parentKey, representation, nglView) => (dispatch, getState) => { const comp = nglView.stage.getComponentsByName(parentKey).first; const r = comp.reprList.find(rep => rep.uuid === representation.uuid || rep.uuid === representation.lastKnownID); if (r && change) { @@ -1638,7 +1682,10 @@ const changeRepresentation = (isAdd, change, parentKey, representation, nglView) } }; -const removeRepresentation = (parentKey, representation, nglView) => (dispatch, getState) => { +const removeRepresentation = (action, parentKey, representation, nglView, skipTracking = false) => ( + dispatch, + getState +) => { const comp = nglView.stage.getComponentsByName(parentKey).first; let foundedRepresentation = undefined; comp.eachRepresentation(r => { @@ -1646,17 +1693,39 @@ const removeRepresentation = (parentKey, representation, nglView) => (dispatch, foundedRepresentation = r; } }); + if (foundedRepresentation) { comp.removeRepresentation(foundedRepresentation); if (comp.reprList.length === 0) { dispatch(deleteObject(nglView, nglView.stage, true)); } else { - dispatch(removeComponentRepresentation(parentKey, representation)); + dispatch(removeComponentRepresentation(parentKey, representation, skipTracking)); } } }; +const handleChangeRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { + if (action) { + dispatch(changeRepresentation(isAdd, action, nglView)); + } +}; + +const changeRepresentation = (isAdd, action, nglView) => (dispatch, getState) => { + let oldRepresentation = action.oldRepresentation; + let newRepresentation = action.newRepresentation; + + if (isAdd === true) { + dispatch(changeComponentRepresentation(action.object_id, oldRepresentation, newRepresentation)); + dispatch(addRepresentation(action, action.object_id, newRepresentation, nglView, isAdd, true)); + dispatch(removeRepresentation(action, action.object_id, oldRepresentation, nglView, true)); + } else { + dispatch(changeComponentRepresentation(action.object_id, newRepresentation, oldRepresentation)); + dispatch(addRepresentation(action, action.object_id, oldRepresentation, nglView, isAdd, true)); + dispatch(removeRepresentation(action, action.object_id, newRepresentation, nglView, true)); + } +}; + const handleMoleculeGroupAction = (action, isSelected, stageSummaryView, majorViewStage) => (dispatch, getState) => { const state = getState(); if (action) { @@ -1864,6 +1933,15 @@ const getTrackingActions = (projectID, withTreeSeparation) => (dispatch, getStat if (withTreeSeparation === true) { listToSet = dispatch(separateTrackkingActionBySnapshotTree(listToSet)); + + let actionsWithoutSnapshot = listToSet.filter(action => action.type !== actionType.SNAPSHOT); + let snapshotActions = listToSet.filter(action => action.type === actionType.SNAPSHOT); + if (snapshotActions) { + const key = 'object_id'; + const arrayUniqueByKey = [...new Map(snapshotActions.map(item => [item[key], item])).values()]; + actionsWithoutSnapshot.push(...arrayUniqueByKey); + listToSet = actionsWithoutSnapshot; + } } let projectActions = [...listToSet, ...sendActions]; diff --git a/js/reducers/tracking/trackingActions.js b/js/reducers/tracking/trackingActions.js index 637cf689e..1a513c81d 100644 --- a/js/reducers/tracking/trackingActions.js +++ b/js/reducers/tracking/trackingActions.js @@ -671,7 +671,7 @@ export const findTrackAction = (action, state) => { let objectType = actionObjectType.REPRESENTATION; trackAction = { - type: actionType.REPRESENTATION_CHANGED, + type: actionType.REPRESENTATION_UPDATED, annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, @@ -681,7 +681,7 @@ export const findTrackAction = (action, state) => { representation_id: action.representationID, representation: action.newRepresentation, change: action.change, - text: `${objectType} '${action.change?.key}' of ${action.objectInViewID} ${actionDescription.CHANGED} from value: ${action.change?.oldValue} to value: ${action.change?.value}` + text: `${objectType} '${action.change?.key}' of ${action.objectInViewID} ${actionDescription.UPDATED} from value: ${action.change?.oldValue} to value: ${action.change?.value}` }; } else if (action.type.includes(nglConstants.ADD_COMPONENT_REPRESENTATION)) { let objectType = actionObjectType.REPRESENTATION; @@ -713,6 +713,23 @@ export const findTrackAction = (action, state) => { representation: action.representation, text: `${objectType} '${representationName}' of ${action.objectInViewID} ${actionDescription.REMOVED}` }; + } else if (action.type.includes(nglConstants.CHANGE_COMPONENT_REPRESENTATION)) { + let objectType = actionObjectType.REPRESENTATION; + let oldRepresentationName = action.oldRepresentation && action.oldRepresentation.type; + let newRepresentationName = action.newRepresentation && action.newRepresentation.type; + + trackAction = { + type: actionType.REPRESENTATION_CHANGED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: objectType, + object_name: action.objectInViewID, + object_id: action.objectInViewID, + oldRepresentation: action.oldRepresentation, + newRepresentation: action.newRepresentation, + text: `${objectType} of ${action.objectInViewID} ${actionDescription.CHANGED} from value: ${oldRepresentationName} to value: ${newRepresentationName}` + }; } } return trackAction; diff --git a/package.json b/package.json index a7c112f0b..60f1ed549 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fragalysis-frontend", - "version": "0.9.41", + "version": "0.9.42", "description": "Frontend for fragalysis", "main": "webpack.config.js", "scripts": { @@ -54,7 +54,6 @@ "js-base64": "^2.5.2", "jszip": "^3.2.2", "lodash": "^4.17.15", - "logrocket": "^1.0.3", "moment": "^2.24.0", "ngl": "2.0.0-dev.37", "react": "^16.11.0",