diff --git a/debug.log b/debug.log new file mode 100644 index 000000000..85a48b558 --- /dev/null +++ b/debug.log @@ -0,0 +1,17 @@ +[1125/154339.498:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1126/072635.367:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1126/101414.775:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1127/094210.853:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1130/073934.972:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1130/095727.044:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1130/113242.179:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1130/121144.917:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1130/122645.010:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1130/164350.825:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1201/093245.416:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1201/094839.161:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1201/100204.943:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1202/122926.150:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1202/131956.775:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1203/082340.317:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1208/082200.400:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) diff --git a/js/components/projects/projectDetailDrawer/index.js b/js/components/projects/projectDetailDrawer/index.js index 7408b9bbd..6a07bb64d 100644 --- a/js/components/projects/projectDetailDrawer/index.js +++ b/js/components/projects/projectDetailDrawer/index.js @@ -1,15 +1,14 @@ -import React, { memo, useContext } from 'react'; -import { IconButton, makeStyles, Drawer, Typography, Grid } from '@material-ui/core'; +import React, { memo } from 'react'; +import { IconButton, makeStyles, Drawer, Typography, Grid, Box } from '@material-ui/core'; import { Share, Close } from '@material-ui/icons'; import { Gitgraph, templateExtend, TemplateName } from '@gitgraph/react'; import { base_url, URLS } from '../../routes/constants'; import moment from 'moment'; import Modal from '../../common/Modal'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory, useRouteMatch } from 'react-router-dom'; import palette from '../../../theme/palette'; import { setIsOpenModalBeforeExit, setSelectedSnapshotToSwitch, setSharedSnapshot } from '../../snapshot/redux/actions'; -import { NglContext } from '../../nglView/nglProvider'; +import Gallery from 'react-grid-gallery'; const useStyles = makeStyles(theme => ({ drawer: { @@ -72,46 +71,67 @@ const options = { export const ProjectDetailDrawer = memo(({ showHistory, setShowHistory }) => { const [open, setOpen] = React.useState(false); const classes = useStyles(); - let history = useHistory(); - let match = useRouteMatch(); - const { nglViewList } = useContext(NglContext); const dispatch = useDispatch(); - const projectID = match && match.params && match.params.projectId; const currentProjectID = useSelector(state => state.projectReducers.currentProject.projectID); const currentSnapshotID = useSelector(state => state.projectReducers.currentSnapshot.id); const currentSnapshotList = useSelector(state => state.projectReducers.currentSnapshotList); const currentSnapshotTree = useSelector(state => state.projectReducers.currentSnapshotTree); const isLoadingTree = useSelector(state => state.projectReducers.isLoadingTree); + const currentSnapshotImageList = useSelector(state => state.trackingReducers.snapshotActionImageList); const handleClickOnCommit = commit => { dispatch(setSelectedSnapshotToSwitch(commit.hash)); dispatch(setIsOpenModalBeforeExit(true)); }; - const commitFunction = ({ title, description, photo, author, email, hash, isSelected, created }) => ({ + const commitFunction = ({ title, description, photo, author, email, hash, isSelected, created, images }) => ({ hash: `${hash}`, subject: `${title}`, body: ( <> - {/* setOpen(true)} />*/} - {/**/} - {/* */} - {/**/} - {/*
*/} - - {`${moment(created).format('LLL')}, ${email}: `} - {description} - - { - dispatch( - setSharedSnapshot({ title, description, url: `${base_url}${URLS.projects}${currentProjectID}/${hash}` }) - ); - }} - > - - + + { + + {/* setOpen(true)} />*/} + {/**/} + {/* */} + {/**/} + {/*
*/} + + {`${moment(created).format('LLL')}, ${email}: `} + {description} + +
+ } + { + { + dispatch( + setSharedSnapshot({ + title, + description, + url: `${base_url}${URLS.projects}${currentProjectID}/${hash}` + }) + ); + }} + > + + + } + { + + + + } +
), onMessageClick: handleClickOnCommit, @@ -128,6 +148,20 @@ export const ProjectDetailDrawer = memo(({ showHistory, setShowHistory }) => { name: node.title }); + let currentSnapshotImage = currentSnapshotImageList.find(i => i.id === node.id); + const nodeImages = + currentSnapshotImage != null + ? [ + { + src: currentSnapshotImage.image, + thumbnail: currentSnapshotImage.image, + thumbnailWidth: 0, + thumbnailHeight: 0, + caption: currentSnapshotImage.title + } + ] + : []; + newBranch.commit( commitFunction({ title: node.title || '', @@ -136,7 +170,8 @@ export const ProjectDetailDrawer = memo(({ showHistory, setShowHistory }) => { email: (node.author && node.author.email) || '', hash: node.id, isSelected: currentSnapshotID === node.id, - created: node.created + created: node.created, + images: nodeImages }) ); @@ -150,6 +185,20 @@ export const ProjectDetailDrawer = memo(({ showHistory, setShowHistory }) => { setShowHistory(false); }; + let image = currentSnapshotTree != null ? currentSnapshotImageList.find(i => i.id === currentSnapshotTree.id) : null; + const images = + image != null + ? [ + { + src: image.image, + thumbnail: image.image, + thumbnailWidth: 0, + thumbnailHeight: 0, + caption: image.title + } + ] + : []; + return ( <> @@ -187,7 +236,8 @@ export const ProjectDetailDrawer = memo(({ showHistory, setShowHistory }) => { email: (currentSnapshotTree.author && currentSnapshotTree.author.email) || '', hash: currentSnapshotTree.id, isSelected: currentSnapshotID === currentSnapshotTree.id, - created: currentSnapshotTree.created + created: currentSnapshotTree.created, + images }) ); diff --git a/js/components/projects/redux/dispatchActions.js b/js/components/projects/redux/dispatchActions.js index bb1eb5133..34ed6b19d 100644 --- a/js/components/projects/redux/dispatchActions.js +++ b/js/components/projects/redux/dispatchActions.js @@ -393,10 +393,9 @@ export const createProjectFromScratch = ({ title, description, target, author, t }); }; -export const createProjectWithoutStateModification = data => dispatch => { - return api({ url: `${base_url}/api/session-projects/`, method: METHOD.POST, data }).then(response => { - return response.data.id; - }); +export const createProjectWithoutStateModification = data => async () => { + const response = await api({ url: `${base_url}/api/session-projects/`, method: METHOD.POST, data }); + return response.data.id; }; export const createInitSnapshotToProjectWitActions = (session_project, author, parent, target) => ( diff --git a/js/components/snapshot/redux/dispatchActions.js b/js/components/snapshot/redux/dispatchActions.js index e0fe26056..cc1641922 100644 --- a/js/components/snapshot/redux/dispatchActions.js +++ b/js/components/snapshot/redux/dispatchActions.js @@ -27,6 +27,7 @@ import { selectFirstMolGroup } from '../../preview/moleculeGroups/redux/dispatch import { reloadDatasetsReducer } from '../../datasets/redux/actions'; import { saveCurrentActionsList } from '../../../reducers/tracking/dispatchActions'; import { sendTrackingActionsByProjectId, manageSendTrackingActions } from '../../../reducers/tracking/dispatchActions'; +import { captureScreenOfSnapshot } from '../../userFeedback/browserApi'; export const getListOfSnapshots = () => (dispatch, getState) => { const userID = DJANGO_CONTEXT['pk'] || null; @@ -215,7 +216,10 @@ export const createNewSnapshot = ({ title, description, type, author, parent, se }).then(res => { // redirect to project with newest created snapshot /:projectID/:snapshotID if (res.data.id && session_project) { - Promise.resolve(dispatch(saveCurrentActionsList(res.data.id, session_project, nglViewList))).then(() => { + 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 ! @@ -251,6 +255,7 @@ export const activateSnapshotDialog = (loggedInUserID = undefined, finallyShareS const projectID = state.projectReducers.currentProject.projectID; const currentSnapshotAuthor = state.projectReducers.currentSnapshot.author; + dispatch(captureScreenOfSnapshot()); dispatch(manageSendTrackingActions()); dispatch(setDisableRedirect(finallyShareSnapshot)); @@ -321,13 +326,16 @@ export const createNewSnapshotWithoutStateModification = ({ disableRedirect: true }) ); - dispatch(saveCurrentActionsList(res.data.id, session_project, nglViewList)); + + let snapshot = { id: res.data.id, title: title }; + let project = { projectID: session_project, authorID: author }; + dispatch(saveCurrentActionsList(snapshot, project, nglViewList)); } }); }); }; -export const saveAndShareSnapshot = nglViewList => (dispatch, getState) => { +export const saveAndShareSnapshot = nglViewList => async (dispatch, getState) => { const state = getState(); const targetId = state.apiReducers.target_on; const loggedInUserID = DJANGO_CONTEXT['pk']; @@ -335,6 +343,7 @@ export const saveAndShareSnapshot = nglViewList => (dispatch, getState) => { dispatch(setDisableRedirect(true)); if (targetId) { + dispatch(captureScreenOfSnapshot()); dispatch(setIsLoadingSnapshotDialog(true)); const data = { title: ProjectCreationType.READ_ONLY, @@ -344,36 +353,34 @@ export const saveAndShareSnapshot = nglViewList => (dispatch, getState) => { tags: '[]' }; - dispatch(createProjectWithoutStateModification(data)) - .then(projectID => { - const username = DJANGO_CONTEXT['username']; - const title = moment().format('-- YYYY-MM-DD -- HH:mm:ss'); - const description = - loggedInUserID === undefined ? 'Snapshot generated by anonymous user' : `snapshot generated by ${username}`; - const type = SnapshotType.MANUAL; - const author = loggedInUserID || null; - const parent = null; - const session_project = projectID; - - dispatch(sendTrackingActionsByProjectId(projectID, author)); - - return dispatch( - createNewSnapshotWithoutStateModification({ - title, - description, - type, - author, - parent, - session_project, - nglViewList - }) - ); - }) - .catch(error => { - throw new Error(error); - }) - .finally(() => { - dispatch(setIsLoadingSnapshotDialog(false)); - }); + try { + let projectID = await dispatch(createProjectWithoutStateModification(data)); + const username = DJANGO_CONTEXT['username']; + const title = moment().format('-- YYYY-MM-DD -- HH:mm:ss'); + const description = + loggedInUserID === undefined ? 'Snapshot generated by anonymous user' : `snapshot generated by ${username}`; + const type = SnapshotType.MANUAL; + const author = loggedInUserID || null; + const parent = null; + const session_project = projectID; + + await dispatch(sendTrackingActionsByProjectId(projectID, author)); + + await dispatch( + createNewSnapshotWithoutStateModification({ + title, + description, + type, + author, + parent, + session_project, + nglViewList + }) + ); + + dispatch(setIsLoadingSnapshotDialog(false)); + } catch (error) { + throw new Error(error); + } } }; diff --git a/js/components/tracking/EditableText.js b/js/components/tracking/EditableText.js new file mode 100644 index 000000000..91549d9de --- /dev/null +++ b/js/components/tracking/EditableText.js @@ -0,0 +1,68 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { makeStyles, TextField, IconButton, Tooltip, Grid } from '@material-ui/core'; +import { Edit } from '@material-ui/icons'; + +const useStyles = makeStyles(theme => ({ + search: { + width: '100%' + }, + fontSizeSmall: { + fontSize: '0.82rem' + } +})); + +const EditableInput = ({ dataText, index, updateText }) => { + const inputRef = useRef(null); + const [inputVisible, setInputVisible] = useState(false); + const [text, setText] = useState(dataText); + const classes = useStyles(); + + const onClickOutSide = e => { + if (inputRef.current && !inputRef.current.contains(e.target)) { + setInputVisible(false); + if (updateText && text !== dataText) { + updateText(text); + } + } + }; + + useEffect(() => { + // Handle outside clicks on mounted state + if (inputVisible) { + document.addEventListener('mousedown', onClickOutSide); + } + + // This is a necessary step to "dismount" unnecessary events when we destroy the component + return () => { + document.removeEventListener('mousedown', onClickOutSide); + }; + }); + + return ( + + {inputVisible ? ( + { + setText(e.target.value); + }} + /> + ) : ( + + { setInputVisible(true)}>{text}} + { + setInputVisible(true)}> + + + + + } + + )} + + ); +}; + +export default EditableInput; diff --git a/js/components/tracking/timelineView.js b/js/components/tracking/timelineView.js new file mode 100644 index 000000000..18036eed2 --- /dev/null +++ b/js/components/tracking/timelineView.js @@ -0,0 +1,228 @@ +import React, { useState, useRef, memo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { makeStyles, IconButton, Tooltip, Grid, Box, Chip } from '@material-ui/core'; +import { Check, Clear, Warning, Favorite, Star } from '@material-ui/icons'; +import { actionAnnotation } from '../../reducers/tracking/constants'; +import { TimelineEvent } from 'react-event-timeline'; +import EditableText from './editableText'; +import palette from '../../theme/palette'; +import { updateTrackingActions } from '../../reducers/tracking/dispatchActions'; +import { actionType } from '../../reducers/tracking/constants'; +import Gallery from 'react-grid-gallery'; + +const useStyles = makeStyles(theme => ({ + headerGrid: { + height: '15px' + }, + grid: { + height: 'inherit' + }, + iconButton: { + padding: '6px', + paddingTop: '0px' + }, + timelineEvent: { + borderBottom: '1px dashed ' + palette.divider, + paddingBottom: '10px' + }, + title: { + position: 'relative', + paddingLeft: '45px', + textAlign: 'left', + fontWeight: 'bold' + }, + titleMargin: { + marginRight: '5px' + } +})); + +const TimelineView = memo(({ data, index }) => { + const dispatch = useDispatch(); + const classes = useStyles(); + + const currentSnapshotID = useSelector(state => state.projectReducers.currentSnapshot.id); + let isSelected = currentSnapshotID === data.object_id; + + const ref = useRef(null); + const [isHovering, setIsHovering] = useState(false); + const [updatedIcon, setUpdatedIcon] = useState(null); + + const getActionIcon = annotation => { + if (annotation) { + switch (annotation) { + case actionAnnotation.CHECK: + return ; + case actionAnnotation.CLEAR: + return ; + case actionAnnotation.WARNING: + return ; + case actionAnnotation.FAVORITE: + return ; + case actionAnnotation.STAR: + return ; + default: + return ; + } + } else { + return ; + } + }; + + const annotationActions = [ + { + setUpdatedIcon(actionAnnotation.CHECK); + updateDataAnnotation(actionAnnotation.CHECK); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.CLEAR); + updateDataAnnotation(actionAnnotation.CLEAR); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.WARNING); + updateDataAnnotation(actionAnnotation.WARNING); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.FAVORITE); + updateDataAnnotation(actionAnnotation.FAVORITE); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.STAR); + updateDataAnnotation(actionAnnotation.STAR); + }} + > + + + + + ]; + + const updateDataText = text => { + data.text = text; + dispatch(updateTrackingActions(data)); + }; + + const updateDataAnnotation = annotation => { + data.annotation = annotation; + dispatch(updateTrackingActions(data)); + }; + + const images = [ + { + src: data.image, + thumbnail: data.image, + thumbnailWidth: 0, + thumbnailHeight: 0, + caption: data.object_name + } + ]; + + return ( +
setIsHovering(true)} onMouseLeave={() => setIsHovering(false)}> + {data.type && data.type === actionType.SNAPSHOT ? ( + <> + + {isSelected && ( + + + + )} + { + + {`${data.text}`} + + } + { + + + + } + + + ) : ( + <> + + + { + + + + } + {isHovering && ( + + {annotationActions && + annotationActions.map((action, index) => ( + + {action} + + ))} + + )} + +
+ } + createdAt={new Date(data.timestamp).toLocaleString()} + icon={updatedIcon && updatedIcon != null ? getActionIcon(updatedIcon) : getActionIcon(data.annotation)} + iconColor={palette.primary.main} + className={classes.timelineEvent} + > + + )} + + ); +}); + +TimelineView.displayName = 'TimelineView'; +export default TimelineView; diff --git a/js/components/tracking/trackingModal.js b/js/components/tracking/trackingModal.js index d47761e84..09cb10118 100644 --- a/js/components/tracking/trackingModal.js +++ b/js/components/tracking/trackingModal.js @@ -2,10 +2,10 @@ import React, { memo, useCallback, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Modal from '../common/Modal'; import { Grid, makeStyles, IconButton, Tooltip } from '@material-ui/core'; -import { Timeline, TimelineEvent } from 'react-event-timeline'; -import { Check, Clear, Close } from '@material-ui/icons'; -import palette from '../../theme/palette'; +import { Timeline } from 'react-event-timeline'; +import { Close } from '@material-ui/icons'; import { Panel } from '../common'; +import TimelineView from './timelineView'; import { setProjectTrackingActions } from '../../reducers/tracking/dispatchActions'; const useStyles = makeStyles(theme => ({ @@ -16,10 +16,7 @@ const useStyles = makeStyles(theme => ({ customContentModal: { height: '100%' }, - timelineEvent: { - borderBottom: '1px dashed ' + palette.divider, - paddingBottom: '10px' - }, + divContainer: { height: '100%', width: '100%', @@ -78,24 +75,7 @@ export const TrackingModal = memo(({ openModal, onModalClose }) => { {orderedActionList && orderedActionList.map((data, index) => { if (data && data != null) { - return ( - - ) : ( - - ) - } - iconColor={palette.primary.main} - className={classes.timelineEvent} - > - ); + return ; } })} diff --git a/js/components/userFeedback/browserApi.js b/js/components/userFeedback/browserApi.js index 3a40b14ea..f14b006eb 100644 --- a/js/components/userFeedback/browserApi.js +++ b/js/components/userFeedback/browserApi.js @@ -1,5 +1,6 @@ import { setImageSource, setIsOpenForm } from './redux/actions'; - +import { setTrackingImageSource } from '../../reducers/tracking/actions'; +import html2canvas from 'html2canvas'; /* Getting image from screen capture or */ // https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser @@ -81,3 +82,9 @@ export const captureScreen = () => async dispatch => { dispatch(setImageSource(image)); dispatch(setIsOpenForm(true)); }; + +export const captureScreenOfSnapshot = () => async dispatch => { + html2canvas(document.body).then(canvas => { + dispatch(setTrackingImageSource(canvas.toDataURL())); + }); +}; diff --git a/js/reducers/tracking/actions.js b/js/reducers/tracking/actions.js index d3efec7a2..6dc0fd75a 100644 --- a/js/reducers/tracking/actions.js +++ b/js/reducers/tracking/actions.js @@ -84,6 +84,13 @@ export const setProjectActionList = function(project_actions_list) { }; }; +export const setSnapshotImageActionList = function(snapshotActionImageList) { + return { + type: constants.SET_SNAPSOT_IMAGE_ACTIONS_LIST, + snapshotActionImageList: snapshotActionImageList + }; +}; + export const setIsActionsSaving = function(isActionSaving) { return { type: constants.SET_IS_ACTIONS_SAVING, @@ -104,3 +111,8 @@ export const resetTrackingState = function() { type: constants.RESET_TRACKING_STATE }; }; + +export const setTrackingImageSource = imageSource => ({ + type: constants.SET_TRACKING_IMAGE_SOURCE, + payload: imageSource +}); diff --git a/js/reducers/tracking/constants.js b/js/reducers/tracking/constants.js index b1576102b..b95b41327 100644 --- a/js/reducers/tracking/constants.js +++ b/js/reducers/tracking/constants.js @@ -15,7 +15,9 @@ export const constants = { SET_PROJECT_ACTIONS_LIST: prefix + 'SET_PROJECT_ACTIONS_LIST', SET_IS_ACTIONS_SAVING: prefix + 'SET_IS_ACTIONS_SAVING', SET_IS_ACTIONS_RESTORING: prefix + 'SET_IS_ACTIONS_RESTORING', - RESET_TRACKING_STATE: prefix + 'RESET_TRACKING_STATE' + RESET_TRACKING_STATE: prefix + 'RESET_TRACKING_STATE', + SET_TRACKING_IMAGE_SOURCE: prefix + 'SET_TRACKING_IMAGE_SOURCE', + SET_SNAPSOT_IMAGE_ACTIONS_LIST: prefix + 'SET_SNAPSOT_IMAGE_ACTIONS_LIST' }; export const actionType = { @@ -44,6 +46,7 @@ export const actionType = { NGL_STATE: 'NGL_STATE', UNDO: 'UNDO', REDO: 'REDO', + SNAPSHOT: 'SNAPSHOT', ALL_HIDE: 'ALL_HIDE', ALL_TURNED_ON: 'ALL_TURNED_ON', ALL_TURNED_OFF: 'ALL_TURNED_OFF', @@ -86,3 +89,11 @@ export const actionObjectType = { CROSS_REFERENCE: 'CROSS_REFERENCE', REPRESENTATION: 'REPRESENTATION' }; + +export const actionAnnotation = { + CHECK: 'CHECK', + CLEAR: 'CLEAR', + WARNING: 'WARNING', + FAVORITE: 'FAVORITE', + STAR: 'STAR' +}; diff --git a/js/reducers/tracking/dispatchActions.js b/js/reducers/tracking/dispatchActions.js index fc8aee47c..2aa1362e5 100644 --- a/js/reducers/tracking/dispatchActions.js +++ b/js/reducers/tracking/dispatchActions.js @@ -61,7 +61,13 @@ import { import * as listType from '../../constants/listTypes'; import { assignRepresentationToComp } from '../../components/nglView/generatingObjects'; import { deleteObject, setOrientation } from '../../../js/reducers/ngl/dispatchActions'; -import { setSendActionsList, setIsActionsSending, setIsActionsLoading, setActionsList } from './actions'; +import { + setSendActionsList, + setIsActionsSending, + setIsActionsLoading, + setActionsList, + setSnapshotImageActionList +} from './actions'; import { api, METHOD } from '../../../js/utils/api'; import { base_url } from '../../components/routes/constants'; import { CONSTANTS } from '../../../js/constants/constants'; @@ -88,12 +94,15 @@ import { setDeselectedAllByType as setDeselectedAllByTypeOfDataset } from '../../components/datasets/redux/actions'; -export const saveCurrentActionsList = (snapshotID, projectID, nglViewList) => async (dispatch, getState) => { +export const saveCurrentActionsList = (snapshot, project, nglViewList) => async (dispatch, getState) => { + let projectID = project && project.projectID; let actionList = await dispatch(getTrackingActions(projectID)); - dispatch(saveActionsList(snapshotID, actionList, nglViewList)); + + dispatch(setSnapshotToActions(actionList, snapshot, projectID)); + await dispatch(saveActionsList(project, snapshot, actionList, nglViewList)); }; -export const saveActionsList = (snapshotID, actionList, nglViewList) => (dispatch, getState) => { +const saveActionsList = (project, snapshot, actionList, nglViewList) => async (dispatch, getState) => { const state = getState(); const currentTargetOn = state.apiReducers.target_on; @@ -101,23 +110,14 @@ export const saveActionsList = (snapshotID, actionList, nglViewList) => (dispatc const currentLigands = state.selectionReducers.fragmentDisplayList; const currentProteins = state.selectionReducers.proteinList; const currentComplexes = state.selectionReducers.complexList; - const currentSurfaces = state.selectionReducers.surfaceList; - const currentVectors = state.selectionReducers.vectorOnList; - const currentBuyList = state.selectionReducers.to_buy_list; - const currentVector = state.selectionReducers.currentVector; const currentSelectionAll = state.selectionReducers.moleculeAllSelection; const currentDatasetLigands = state.datasetsReducers.ligandLists; const currentDatasetProteins = state.datasetsReducers.proteinLists; const currentDatasetComplexes = state.datasetsReducers.complexLists; - const currentDatasetSurfaces = state.datasetsReducers.surfaceLists; const currentDatasetSelectionAll = state.datasetsReducers.moleculeAllSelection; - const currentDatasetBuyList = state.datasetsReducers.compoundsToBuyDatasetMap; - const currentobjectsInView = state.nglReducers.objectsInView; - const currentTargets = (currentTargetOn && [currentTargetOn]) || []; - const currentVectorSmiles = (currentVector && [currentVector]) || []; let orderedActionList = actionList.reverse((a, b) => a.timestamp - b.timestamp); @@ -201,97 +201,188 @@ export const saveActionsList = (snapshotID, actionList, nglViewList) => (dispatc getCollection(currentProteins), currentActions ); + const snapshotID = snapshot && snapshot.id; + if (snapshotID) { + const currentTargetOn = state.apiReducers.target_on; + const currentSites = state.selectionReducers.mol_group_selection; + const currentLigands = state.selectionReducers.fragmentDisplayList; + const currentProteins = state.selectionReducers.proteinList; + const currentComplexes = state.selectionReducers.complexList; + const currentSurfaces = state.selectionReducers.surfaceList; + const currentVectors = state.selectionReducers.vectorOnList; + const currentBuyList = state.selectionReducers.to_buy_list; + const currentVector = state.selectionReducers.currentVector; + const currentSelectionAll = state.selectionReducers.moleculeAllSelection; + + const currentDatasetLigands = state.datasetsReducers.ligandLists; + const currentDatasetProteins = state.datasetsReducers.proteinLists; + const currentDatasetComplexes = state.datasetsReducers.complexLists; + const currentDatasetSurfaces = state.datasetsReducers.surfaceLists; + const currentDatasetSelectionAll = state.datasetsReducers.moleculeAllSelection; + + const currentDatasetBuyList = state.datasetsReducers.compoundsToBuyDatasetMap; + const currentobjectsInView = state.nglReducers.objectsInView; + + const currentTargets = (currentTargetOn && [currentTargetOn]) || []; + const currentVectorSmiles = (currentVector && [currentVector]) || []; + + let orderedActionList = actionList.reverse((a, b) => a.timestamp - b.timestamp); + + let currentActions = []; + + getCurrentActionList(orderedActionList, actionType.TARGET_LOADED, getCollection(currentTargets), currentActions); + getCurrentActionList(orderedActionList, actionType.SITE_TURNED_ON, getCollection(currentSites), currentActions); + getCurrentActionList(orderedActionList, actionType.LIGAND_TURNED_ON, getCollection(currentLigands), currentActions); + + getCurrentActionList( + orderedActionList, + actionType.ALL_TURNED_ON, + getCollection(currentSelectionAll), + currentActions + ); + getCurrentActionList( + orderedActionList, + actionType.ALL_TURNED_ON, + getCollectionOfDataset(currentDatasetSelectionAll), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.INTERACTIONS_TURNED_ON, - getCollection(currentComplexes), - currentActions - ); - getCurrentActionList(orderedActionList, actionType.SURFACE_TURNED_ON, getCollection(currentSurfaces), currentActions); - getCurrentActionList(orderedActionList, actionType.VECTORS_TURNED_ON, getCollection(currentVectors), currentActions); - getCurrentActionList( - orderedActionList, - actionType.VECTOR_SELECTED, - getCollection(currentVectorSmiles), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.SIDECHAINS_TURNED_ON, + getCollection(currentProteins), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.MOLECULE_ADDED_TO_SHOPPING_CART, - getCollectionOfShoppingCart(currentBuyList), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.INTERACTIONS_TURNED_ON, + getCollection(currentComplexes), + currentActions + ); + getCurrentActionList( + orderedActionList, + actionType.SURFACE_TURNED_ON, + getCollection(currentSurfaces), + currentActions + ); + getCurrentActionList( + orderedActionList, + actionType.VECTORS_TURNED_ON, + getCollection(currentVectors), + currentActions + ); + getCurrentActionList( + orderedActionList, + actionType.VECTOR_SELECTED, + getCollection(currentVectorSmiles), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.LIGAND_TURNED_ON, - getCollectionOfDataset(currentDatasetLigands), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.MOLECULE_ADDED_TO_SHOPPING_CART, + getCollectionOfShoppingCart(currentBuyList), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.SIDECHAINS_TURNED_ON, - getCollectionOfDataset(currentDatasetProteins), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.LIGAND_TURNED_ON, + getCollectionOfDataset(currentDatasetLigands), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.INTERACTIONS_TURNED_ON, - getCollectionOfDataset(currentDatasetComplexes), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.SIDECHAINS_TURNED_ON, + getCollectionOfDataset(currentDatasetProteins), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.SURFACE_TURNED_ON, - getCollectionOfDataset(currentDatasetSurfaces), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.INTERACTIONS_TURNED_ON, + getCollectionOfDataset(currentDatasetComplexes), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.COMPOUND_SELECTED, - getCollectionOfDataset(currentDatasetBuyList), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.SURFACE_TURNED_ON, + getCollectionOfDataset(currentDatasetSurfaces), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.REPRESENTATION_ADDED, - getCollectionOfDatasetOfRepresentation(currentobjectsInView), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.COMPOUND_SELECTED, + getCollectionOfDataset(currentDatasetBuyList), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.REPRESENTATION_CHANGED, - getCollectionOfDatasetOfRepresentation(currentobjectsInView), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.REPRESENTATION_ADDED, + getCollectionOfDatasetOfRepresentation(currentobjectsInView), + currentActions + ); - if (nglViewList) { - let nglStateList = nglViewList.map(nglView => { - return { id: nglView.id, orientation: nglView.stage.viewerControls.getOrientation() }; - }); + getCurrentActionList( + orderedActionList, + actionType.REPRESENTATION_CHANGED, + getCollectionOfDatasetOfRepresentation(currentobjectsInView), + currentActions + ); - let trackAction = { - type: actionType.NGL_STATE, - timestamp: Date.now(), - nglStateList: nglStateList - }; + if (nglViewList) { + let nglStateList = nglViewList.map(nglView => { + return { id: nglView.id, orientation: nglView.stage.viewerControls.getOrientation() }; + }); + + let trackAction = { + type: actionType.NGL_STATE, + timestamp: Date.now(), + nglStateList: nglStateList + }; - currentActions.push(Object.assign({ ...trackAction })); + currentActions.push(Object.assign({ ...trackAction })); + } + + await dispatch(saveSnapshotAction(snapshot, project, currentActions)); + await dispatch(saveTrackingActions(currentActions, snapshotID)); + dispatch(setCurrentActionsList(currentActions)); } +}; - dispatch(setCurrentActionsList(currentActions)); - dispatch(saveTrackingActions(currentActions, snapshotID)); +const saveSnapshotAction = (snapshot, project, currentActions) => async (dispatch, getState) => { + const state = getState(); + const trackingImageSource = state.trackingReducers.trackingImageSource; + + let sendActions = []; + let snapshotAction = { + type: actionType.SNAPSHOT, + timestamp: Date.now(), + object_name: snapshot.title, + object_id: snapshot.id, + snapshotId: snapshot.id, + text: `Snapshot: ${snapshot.id} - ${snapshot.title}`, + image: trackingImageSource + }; + sendActions.push(snapshotAction); + currentActions.push(snapshotAction); + await dispatch(sendTrackingActions(sendActions, project)); }; -export const saveTrackingActions = (currentActions, snapshotID) => (dispatch, getState) => { +const setSnapshotToActions = (actionList, snapshot, projectID) => (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)); + } +}; + +export const saveTrackingActions = (currentActions, snapshotID) => async (dispatch, getState) => { const state = getState(); const project = state.projectReducers.currentProject; const projectID = project && project.projectID; @@ -578,7 +669,8 @@ export const restoreAfterTargetActions = (stages, projectId) => async (dispatch, await dispatch(restoreActions(orderedActionList, majorView.stage)); await dispatch(restoreRepresentationActions(orderedActionList, stages)); await dispatch(restoreProject(projectId)); - await dispatch(restoreNglStateAction(orderedActionList, stages)); + dispatch(restoreSnapshotImageActions(projectId)); + dispatch(restoreNglStateAction(orderedActionList, stages)); dispatch(setIsActionsRestoring(false, true)); } }; @@ -742,7 +834,6 @@ const restoreAllSelectionActions = (moleculesAction, stage, isSelection) => (dis }; const restoreAllSelectionByTypeActions = (moleculesAction, stage, isSelection) => (dispatch, getState) => { - let state = getState(); let actions = isSelection === true @@ -825,6 +916,18 @@ const restoreRepresentationActions = (moleculesAction, stages) => (dispatch, get } }; +const restoreSnapshotImageActions = projectID => async (dispatch, getState) => { + let actionList = await dispatch(getTrackingActions(projectID)); + + 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 }; + }); + dispatch(setSnapshotImageActionList(actions)); + } +}; + const restoreProject = projectId => (dispatch, getState) => { if (projectId !== undefined) { return api({ url: `${base_url}/api/session-projects/${projectId}/` }).then(response => { @@ -1641,7 +1744,7 @@ export const checkSendTrackingActions = (save = false) => (dispatch, getState) = } }; -const sendTrackingActions = (sendActions, project, clear = true) => (dispatch, getState) => { +const sendTrackingActions = (sendActions, project, clear = true) => async (dispatch, getState) => { if (project) { const projectID = project && project.projectID; @@ -1682,11 +1785,11 @@ export const setProjectTrackingActions = () => (dispatch, getState) => { const state = getState(); const currentProject = state.projectReducers.currentProject; const projectID = currentProject && currentProject.projectID; - - dispatch(getTrackingActions(projectID)); + dispatch(setProjectActionList([])); + dispatch(getTrackingActions(projectID, true)); }; -const getTrackingActions = projectID => (dispatch, getState) => { +const getTrackingActions = (projectID, withTreeSeparation) => (dispatch, getState) => { const state = getState(); const sendActions = state.trackingReducers.send_actions_list; @@ -1700,9 +1803,14 @@ const getTrackingActions = projectID => (dispatch, getState) => { let listToSet = []; results.forEach(r => { let resultActions = JSON.parse(r.actions); - listToSet.push(...resultActions); + let actions = resultActions.map(obj => ({ ...obj, actionId: r.id })); + listToSet.push(...actions); }); + if (withTreeSeparation === true) { + listToSet = dispatch(separateTrackkingActionBySnapshotTree(listToSet)); + } + let projectActions = [...listToSet, ...sendActions]; dispatch(setProjectActionList(projectActions)); return Promise.resolve(projectActions); @@ -1720,17 +1828,52 @@ const getTrackingActions = projectID => (dispatch, getState) => { } }; -const checkActionsProject = projectID => (dispatch, getState) => { +const separateTrackkingActionBySnapshotTree = actionList => (dispatch, getState) => { + const state = getState(); + const snapshotID = state.projectReducers.currentSnapshot && state.projectReducers.currentSnapshot.id; + const currentSnapshotTree = state.projectReducers.currentSnapshotTree; + const currentSnapshotList = state.projectReducers.currentSnapshotList; + + if (snapshotID && currentSnapshotTree != null) { + let treeActionList = []; + let snapshotIdList = []; + snapshotIdList.push(currentSnapshotTree.id); + + if (currentSnapshotList != null) { + for (const id in currentSnapshotList) { + let snapshot = currentSnapshotList[id]; + let snapshotChildren = snapshot.children; + + if ( + (snapshotChildren && snapshotChildren !== null && snapshotChildren.includes(snapshotID)) || + snapshot.id === snapshotID + ) { + snapshotIdList.push(snapshot.id); + } + } + } + + treeActionList = actionList.filter( + a => snapshotIdList.includes(a.snapshotId) || a.snapshotId === null || a.snapshotId === undefined + ); + return treeActionList; + } else { + return actionList; + } +}; + +const checkActionsProject = projectID => async (dispatch, getState) => { const state = getState(); const currentProject = state.projectReducers.currentProject; const currentProjectID = currentProject && currentProject.projectID; - Promise.resolve(dispatch(getTrackingActions(projectID))).then(() => { - dispatch(copyActionsToProject(currentProject, true, currentProjectID && currentProjectID != null ? true : false)); - }); + await dispatch(getTrackingActions(projectID)); + await dispatch( + copyActionsToProject(currentProject, true, currentProjectID && currentProjectID != null ? true : false) + ); }; -const copyActionsToProject = (toProject, setActionList = true, clearSendList = true) => (dispatch, getState) => { +const copyActionsToProject = (toProject, setActionList = true, clearSendList = true) => async (dispatch, getState) => { const state = getState(); const actionList = state.trackingReducers.project_actions_list; @@ -1744,20 +1887,19 @@ const copyActionsToProject = (toProject, setActionList = true, clearSendList = t if (setActionList === true) { dispatch(setActionsList(newActionsList)); } - dispatch(sendTrackingActions(newActionsList, toProject, clearSendList)); + await dispatch(sendTrackingActions(newActionsList, toProject, clearSendList)); } }; -export const sendTrackingActionsByProjectId = (projectID, authorID) => (dispatch, getState) => { +export const sendTrackingActionsByProjectId = (projectID, authorID) => async (dispatch, getState) => { const state = getState(); const currentProject = state.projectReducers.currentProject; const currentProjectID = currentProject && currentProject.projectID; const project = { projectID, authorID }; - Promise.resolve(dispatch(getTrackingActions(currentProjectID))).then(() => { - dispatch(copyActionsToProject(project, false, currentProjectID && currentProjectID != null ? true : false)); - }); + await dispatch(getTrackingActions(currentProjectID)); + await dispatch(copyActionsToProject(project, false, currentProjectID && currentProjectID != null ? true : false)); }; export const sendInitTrackingActionByProjectId = target_on => (dispatch, getState) => { @@ -1773,3 +1915,79 @@ export const sendInitTrackingActionByProjectId = target_on => (dispatch, getStat dispatch(saveTrackingActions(actions, snapshotID)); } }; + +export const updateTrackingActions = action => (dispatch, getState) => { + const state = getState(); + const project = state.projectReducers.currentProject; + const projectActions = state.trackingReducers.project_actions_list; + const projectID = project && project.projectID; + let actionID = action && action.actionId; + + if (projectID && actionID && projectActions) { + let actions = projectActions.filter(a => a.actionId === actionID); + + if (actions && actions.length > 0) { + const dataToSend = { + session_action_id: actionID, + session_project: projectID, + author: project.authorID, + last_update_date: moment().format(), + actions: JSON.stringify(actions) + }; + return api({ + url: `${base_url}/api/session-actions/${actionID}`, + method: METHOD.PUT, + data: JSON.stringify(dataToSend) + }) + .then(() => {}) + .catch(error => { + throw new Error(error); + }) + .finally(() => {}); + } else { + return Promise.resolve(); + } + } else { + return Promise.resolve(); + } +}; + +function groupArrayOfObjects(list, key) { + return list.reduce(function(rv, x) { + (rv[x[key]] = rv[x[key]] || []).push(x); + return rv; + }, {}); +} + +export const setAndUpdateTrackingActions = (actionList, projectID) => (dispatch, getState) => { + if (projectID) { + const groupBy = groupArrayOfObjects(actionList, 'actionId'); + + for (const group in groupBy) { + let actionID = group; + let actions = groupBy[group]; + if (actionID && actions && actions.length > 0) { + const dataToSend = { + session_action_id: actionID, + session_project: projectID, + last_update_date: moment().format(), + actions: JSON.stringify(actions) + }; + return api({ + url: `${base_url}/api/session-actions/${actionID}`, + method: METHOD.PUT, + data: JSON.stringify(dataToSend) + }) + .then(() => {}) + .catch(error => { + throw new Error(error); + }) + .finally(() => {}); + } else { + return Promise.resolve(); + } + } + } else { + return Promise.resolve(); + } +}; diff --git a/js/reducers/tracking/trackingActions.js b/js/reducers/tracking/trackingActions.js index 5d359a6b5..637cf689e 100644 --- a/js/reducers/tracking/trackingActions.js +++ b/js/reducers/tracking/trackingActions.js @@ -1,4 +1,4 @@ -import { actionType, actionObjectType, actionDescription } from './constants'; +import { actionType, actionObjectType, actionDescription, actionAnnotation } from './constants'; import { constants as apiConstants } from '../api/constants'; import { CONSTANTS as nglConstants } from '../ngl/constants'; import { constants as selectionConstants } from '../selection/constants'; @@ -17,6 +17,7 @@ export const findTrackAction = (action, state) => { let targetName = getTargetName(action.target_on, state); trackAction = { type: actionType.TARGET_LOADED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.TARGET, @@ -33,6 +34,7 @@ export const findTrackAction = (action, state) => { let molGroupName = getMolGroupName(action.mol_group_on, state); trackAction = { type: actionType.SITE_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.SITE, @@ -55,6 +57,21 @@ export const findTrackAction = (action, state) => { selectionGroups, text: `${actionDescription.SITE} ${molGroupName} ${actionDescription.TURNED_OFF}` }; + } else if (action.type.includes(selectionConstants.SET_OBJECT_SELECTION)) { + let objectId = action.payload && action.payload[0]; + if (objectId) { + let molGroupName = getMolGroupName(objectId, state); + trackAction = { + type: actionType.SITE_TURNED_OFF, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: actionObjectType.SITE, + object_name: molGroupName, + object_id: objectId, + text: `${actionDescription.SITE} ${molGroupName} ${actionDescription.TURNED_OFF}` + }; + } } else if (action.type === selectionConstants.SET_HIDE_ALL) { if (action.data) { let objectType = actionObjectType.MOLECULE; @@ -62,6 +79,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_HIDE, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -76,6 +94,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -98,6 +117,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -121,6 +141,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON_BY_TYPE, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -137,6 +158,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF_BY_TYPE, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -152,6 +174,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -170,6 +193,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -188,6 +212,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -206,6 +231,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -224,6 +250,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -242,6 +269,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -260,6 +288,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -278,6 +307,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -296,6 +326,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.VECTORS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -314,6 +345,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.VECTORS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -332,6 +364,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.MOLECULE_ADDED_TO_SHOPPING_CART, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.MOLECULE, @@ -348,6 +381,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -364,6 +398,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.VECTOR_SELECTED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -379,6 +414,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.COMPOUND_SELECTED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -395,6 +431,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.COMPOUND_DESELECTED, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -412,6 +449,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -433,6 +471,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -456,6 +495,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON_BY_TYPE, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -474,6 +514,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF_BY_TYPE, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -490,6 +531,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -507,6 +549,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -524,6 +567,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -541,6 +585,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -558,6 +603,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -575,6 +621,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -592,6 +639,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -609,6 +657,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -623,6 +672,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.REPRESENTATION_CHANGED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.REPRESENTATION, @@ -639,6 +689,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.REPRESENTATION_ADDED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.REPRESENTATION, @@ -653,6 +704,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.REPRESENTATION_REMOVED, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, diff --git a/js/reducers/tracking/trackingReducers.js b/js/reducers/tracking/trackingReducers.js index bf8f5c569..1724a8e91 100644 --- a/js/reducers/tracking/trackingReducers.js +++ b/js/reducers/tracking/trackingReducers.js @@ -13,8 +13,10 @@ export const INITIAL_STATE = { isActionSaving: false, send_actions_list: [], project_actions_list: [], + snapshotActionImageList: [], isActionRestoring: false, - isActionRestored: false + isActionRestored: false, + trackingImageSource: '' }; export function trackingReducers(state = INITIAL_STATE, action = {}) { @@ -79,6 +81,11 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { project_actions_list: action.project_actions_list }); + case constants.SET_SNAPSOT_IMAGE_ACTIONS_LIST: + return Object.assign({}, state, { + snapshotActionImageList: action.snapshotActionImageList + }); + case constants.SET_IS_ACTIONS_SAVING: return Object.assign({}, state, { isActionSaving: action.isActionSaving @@ -90,6 +97,11 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { isActionRestored: action.isActionRestored }); + case constants.SET_TRACKING_IMAGE_SOURCE: + return Object.assign({}, state, { + trackingImageSource: action.payload + }); + case constants.RESET_TRACKING_STATE: return INITIAL_STATE; diff --git a/package.json b/package.json index 53677783f..b2a3a3ea2 100755 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "formik": "^2.1.4", "formik-material-ui": "^2.0.0-beta.1", "formik-material-ui-pickers": "^0.0.8", + "html2canvas": "^1.0.0-rc.7", "js-base64": "^2.5.2", "jszip": "^3.2.2", "lodash": "^4.17.15", @@ -61,6 +62,7 @@ "react-color": "^2.17.3", "react-dom": "^16.12.0", "react-event-timeline": "^1.6.3", + "react-grid-gallery": "^0.5.5", "react-hot-loader": "^4.12.18", "react-infinite-scroller": "^1.2.4", "react-redux": "^7.1.3",