Skip to content

Commit

Permalink
feat: allow user to delete all user input in a space
Browse files Browse the repository at this point in the history
closes #184
  • Loading branch information
juancarlosfarah committed Sep 7, 2019
1 parent ceb5983 commit f5ac61d
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 12 deletions.
4 changes: 4 additions & 0 deletions public/app/config/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ module.exports = {
RESPOND_SYNC_SPACE_PROMPT_CHANNEL: 'prompt:space:sync:respond',
SYNC_SPACE_CHANNEL: 'space:sync',
SYNCED_SPACE_CHANNEL: 'space:synced',
CLEAR_USER_INPUT_CHANNEL: 'space:clear',
CLEARED_USER_INPUT_CHANNEL: 'space:cleared',
SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL: 'prompt:space:clear:show',
RESPOND_CLEAR_USER_INPUT_PROMPT_CHANNEL: 'prompt:space:clear:respond',
};
56 changes: 56 additions & 0 deletions public/app/listeners/clearUserInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const _ = require('lodash');
const { CLEARED_USER_INPUT_CHANNEL } = require('../config/channels');
const { ERROR_GENERAL } = require('../config/errors');
const { SPACES_COLLECTION } = require('../db');
const logger = require('../logger');

const clearUserInput = (mainWindow, db) => async (event, { id }) => {
try {
logger.debug(`clearing user input for space ${id}`);

// get handle to the space
const spaceHandle = db.get(SPACES_COLLECTION).find({ id });

// get handle to regular items
const regularItems = spaceHandle
.get('phases')
// we only care about phases with items
.filter(phase => phase.items)
// ensure all items are in the same level of the array
.flatMap(phase => phase.items);

// get handle to tools
const tools = spaceHandle.get('items');

// remove user input in items within phases then
// remove user input in tools
[regularItems, tools].forEach(handle => {
// we only care about items with app instances
handle
.filter(item => item.appInstance)
// ensure all app instances are in the same level
.flatMap(item => item.appInstance)
// user input is saved inside resources
.filter(appInstance => appInstance.resources)
// iterate through app instances to be able to delete
// reference to the original resource array and thus
// mutate the db object
.forEach(appInstance => {
const resources = _.get(appInstance, 'resources');
// we should not remove resources marked as public
_.remove(resources, resource => resource.visibility !== 'public');
})
.write();
});

// we need to return the value of the mutated
// space object to the frontend
const space = spaceHandle.value();

mainWindow.webContents.send(CLEARED_USER_INPUT_CHANNEL, space);
} catch (err) {
mainWindow.webContents.send(CLEARED_USER_INPUT_CHANNEL, ERROR_GENERAL);
}
};

module.exports = clearUserInput;
6 changes: 5 additions & 1 deletion public/app/listeners/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const setLanguage = require('./setLanguage');
const getLanguage = require('./getLanguage');
const getDeveloperMode = require('./getDeveloperMode');
const setDeveloperMode = require('./setDeveloperMode');
const clearUserInput = require('./clearUserInput');
const showClearUserInputPrompt = require('./showClearUserInputPrompt');

module.exports = {
loadSpace,
Expand All @@ -35,5 +37,7 @@ module.exports = {
setLanguage,
getLanguage,
getDeveloperMode,
setDeveloperMode
setDeveloperMode,
clearUserInput,
showClearUserInputPrompt,
};
24 changes: 24 additions & 0 deletions public/app/listeners/showClearUserInputPrompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { dialog } = require('electron');
const {
RESPOND_CLEAR_USER_INPUT_PROMPT_CHANNEL,
} = require('../config/channels');

const showClearUserInputChannel = mainWindow => () => {
const options = {
type: 'warning',
buttons: ['Cancel', 'Clear'],
defaultId: 0,
cancelId: 0,
message:
'Are you sure you want to clear all of the user input in this space?',
};
dialog.showMessageBox(mainWindow, options, respond => {
mainWindow.webContents.send(
RESPOND_CLEAR_USER_INPUT_PROMPT_CHANNEL,
respond
);
});
};

module.exports = showClearUserInputChannel;
17 changes: 14 additions & 3 deletions public/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ const {
SET_DATABASE_CHANNEL,
SHOW_SYNC_SPACE_PROMPT_CHANNEL,
SYNC_SPACE_CHANNEL,
CLEAR_USER_INPUT_CHANNEL,
SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL,
} = require('./app/config/channels');
const { ERROR_GENERAL } = require('./app/config/errors');
const env = require('./env.json');
const {
loadSpace,
Expand All @@ -66,9 +67,11 @@ const {
setGeolocationEnabled,
getUserFolder,
setLanguage,
getLanguage
getLanguage,
getDeveloperMode,
setDeveloperMode
setDeveloperMode,
clearUserInput,
showClearUserInputPrompt,
} = require('./app/listeners');
const isMac = require('./app/utils/isMac');

Expand Down Expand Up @@ -323,6 +326,14 @@ app.on('ready', async () => {
showDeleteSpacePrompt(mainWindow)
);

// prompt when clearing the user input in a space
ipcMain.on(
SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL,
showClearUserInputPrompt(mainWindow)
);

ipcMain.on(CLEAR_USER_INPUT_CHANNEL, clearUserInput(mainWindow, db));

// called when getting user folder
ipcMain.on(GET_USER_FOLDER_CHANNEL, getUserFolder(mainWindow));

Expand Down
46 changes: 46 additions & 0 deletions src/actions/space.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
GET_SPACES_NEARBY_SUCCEEDED,
FLAG_SYNCING_SPACE,
SYNC_SPACE_SUCCEEDED,
FLAG_CLEARING_USER_INPUT,
} from '../types';
import {
ERROR_ZIP_CORRUPTED,
Expand Down Expand Up @@ -42,6 +43,10 @@ import {
RESPOND_SYNC_SPACE_PROMPT_CHANNEL,
SYNC_SPACE_CHANNEL,
SYNCED_SPACE_CHANNEL,
CLEAR_USER_INPUT_CHANNEL,
CLEARED_USER_INPUT_CHANNEL,
RESPOND_CLEAR_USER_INPUT_PROMPT_CHANNEL,
SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL,
} from '../config/channels';
import {
// ERROR_DOWNLOADING_MESSAGE,
Expand All @@ -63,6 +68,8 @@ import {
SUCCESS_SPACE_LOADED_MESSAGE,
SUCCESS_SYNCING_MESSAGE,
ERROR_SYNCING_MESSAGE,
ERROR_CLEARING_USER_INPUT_MESSAGE,
SUCCESS_CLEARING_USER_INPUT_MESSAGE,
} from '../config/messages';
import { createFlag, isErrorResponse } from './common';
import {
Expand All @@ -80,6 +87,7 @@ const flagExportingSpace = createFlag(FLAG_EXPORTING_SPACE);
const flagSavingSpace = createFlag(FLAG_SAVING_SPACE);
const flagGettingSpacesNearby = createFlag(FLAG_GETTING_SPACES_NEARBY);
const flagSyncingSpace = createFlag(FLAG_SYNCING_SPACE);
const flagClearingUserInput = createFlag(FLAG_CLEARING_USER_INPUT);

/**
* helper function to wrap a listener to the get space channel around a promise
Expand Down Expand Up @@ -285,6 +293,43 @@ const deleteSpace = ({ id }) => dispatch => {
});
};

const clearUserInput = async ({ id }) => async dispatch => {
try {
// show confirmation prompt
window.ipcRenderer.send(SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL);

// listen for response from prompt
window.ipcRenderer.once(
RESPOND_CLEAR_USER_INPUT_PROMPT_CHANNEL,
(event, response) => {
if (response === 1) {
dispatch(flagClearingUserInput(true));
window.ipcRenderer.send(CLEAR_USER_INPUT_CHANNEL, { id });
}
}
);

// listen for response from backend
window.ipcRenderer.once(CLEARED_USER_INPUT_CHANNEL, (event, response) => {
if (response === ERROR_GENERAL) {
toastr.error(ERROR_MESSAGE_HEADER, ERROR_CLEARING_USER_INPUT_MESSAGE);
} else {
toastr.success(
SUCCESS_MESSAGE_HEADER,
SUCCESS_CLEARING_USER_INPUT_MESSAGE
);
dispatch({
type: GET_SPACE_SUCCEEDED,
payload: response,
});
}
dispatch(flagClearingUserInput(false));
});
} catch (err) {
dispatch(flagClearingUserInput(false));
}
};

const syncSpace = async ({ id }) => async dispatch => {
try {
const url = generateGetSpaceEndpoint(id);
Expand Down Expand Up @@ -402,4 +447,5 @@ export {
saveSpace,
getSpacesNearby,
syncSpace,
clearUserInput,
};
11 changes: 3 additions & 8 deletions src/components/phase/PhaseItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,10 @@ const PhaseItems = ({ items, phaseId, spaceId }) => {
return items.map(item => {
const { description } = item;
return (
<>
<div key={item.id}>
<PhaseItemDescription description={description} />
<PhaseItem
key={item.id}
phaseId={phaseId}
spaceId={spaceId}
item={item}
/>
</>
<PhaseItem phaseId={phaseId} spaceId={spaceId} item={item} />
</div>
);
});
};
Expand Down
56 changes: 56 additions & 0 deletions src/components/space/ClearButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import Tooltip from '@material-ui/core/Tooltip';
import { connect } from 'react-redux';
import IconButton from '@material-ui/core/IconButton/IconButton';
import { withStyles } from '@material-ui/core';
import ClearAllIcon from '@material-ui/icons/ClearAll';
import Styles from '../../Styles';
import { clearUserInput } from '../../actions';

class ClearButton extends Component {
static propTypes = {
classes: PropTypes.shape({
button: PropTypes.string,
}).isRequired,
t: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
dispatchClearUserInput: PropTypes.func.isRequired,
};

handleClearUserInput = () => {
const { id, dispatchClearUserInput } = this.props;
dispatchClearUserInput({ id });
};

render() {
const { classes, t } = this.props;
return (
<Tooltip title={t('Clear all of the user input in this space.')}>
<IconButton
color="inherit"
className={classes.button}
onClick={this.handleClearUserInput}
>
<ClearAllIcon />
</IconButton>
</Tooltip>
);
}
}

const mapDispatchToProps = {
dispatchClearUserInput: clearUserInput,
};

const StyledComponent = withStyles(Styles)(ClearButton);

const TranslatedComponent = withTranslation()(StyledComponent);

const ConnectedComponent = connect(
null,
mapDispatchToProps
)(TranslatedComponent);

export default ConnectedComponent;
11 changes: 11 additions & 0 deletions src/components/space/SpaceHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { saveSpace } from '../../actions/space';
import DeleteButton from './DeleteButton';
import ExportButton from './ExportButton';
import SyncButton from './SyncButton';
import ClearButton from './ClearButton';

class SpaceHeader extends Component {
static propTypes = {
Expand Down Expand Up @@ -111,6 +112,15 @@ class SpaceHeader extends Component {
return null;
}

renderClearButton() {
const { space } = this.props;
const { saved, id } = space;
if (saved) {
return <ClearButton id={id} />;
}
return null;
}

render() {
const {
openDrawer,
Expand Down Expand Up @@ -139,6 +149,7 @@ class SpaceHeader extends Component {
</IconButton>
{name}
<span style={{ position: 'absolute', right: 20 }}>
{this.renderClearButton()}
{this.renderSyncButton()}
{this.renderPreviewIcon()}
{this.renderDeleteButton()}
Expand Down
4 changes: 4 additions & 0 deletions src/config/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ module.exports = {
RESPOND_SYNC_SPACE_PROMPT_CHANNEL: 'prompt:space:sync:respond',
SYNC_SPACE_CHANNEL: 'space:sync',
SYNCED_SPACE_CHANNEL: 'space:synced',
CLEAR_USER_INPUT_CHANNEL: 'space:clear',
CLEARED_USER_INPUT_CHANNEL: 'space:cleared',
SHOW_CLEAR_USER_INPUT_PROMPT_CHANNEL: 'prompt:space:clear:show',
RESPOND_CLEAR_USER_INPUT_PROMPT_CHANNEL: 'prompt:space:clear:respond',
};
6 changes: 6 additions & 0 deletions src/config/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const ERROR_GETTING_DATABASE = 'There was an error getting the database.';
const ERROR_SETTING_DATABASE = 'There was an error updating the database.';
const SUCCESS_SYNCING_MESSAGE = 'Space was successfully synced';
const ERROR_SYNCING_MESSAGE = 'There was an error syncing the space.';
const ERROR_CLEARING_USER_INPUT_MESSAGE =
'There was an error clearing the user input.';
const SUCCESS_CLEARING_USER_INPUT_MESSAGE =
'User input was successfully cleared.';

module.exports = {
ERROR_GETTING_DEVELOPER_MODE,
Expand Down Expand Up @@ -75,4 +79,6 @@ module.exports = {
ERROR_SETTING_DATABASE,
SUCCESS_SYNCING_MESSAGE,
ERROR_SYNCING_MESSAGE,
ERROR_CLEARING_USER_INPUT_MESSAGE,
SUCCESS_CLEARING_USER_INPUT_MESSAGE,
};
4 changes: 4 additions & 0 deletions src/reducers/SpaceReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
GET_SPACES_NEARBY_SUCCEEDED,
FLAG_SYNCING_SPACE,
SYNC_SPACE_SUCCEEDED,
FLAG_CLEARING_USER_INPUT,
CLEAR_USER_INPUT_SUCCEEDED,
} from '../types';
import { updateActivityList } from './common';

Expand Down Expand Up @@ -50,6 +52,7 @@ export default (state = INITIAL_STATE, { type, payload }) => {
case FLAG_EXPORTING_SPACE:
case FLAG_DELETING_SPACE:
case FLAG_SYNCING_SPACE:
case FLAG_CLEARING_USER_INPUT:
return state.updateIn(
['current', 'activity'],
updateActivityList(payload)
Expand All @@ -63,6 +66,7 @@ export default (state = INITIAL_STATE, { type, payload }) => {
case GET_SPACE_SUCCEEDED:
case SAVE_SPACE_SUCCEEDED:
case SYNC_SPACE_SUCCEEDED:
case CLEAR_USER_INPUT_SUCCEEDED:
return state.setIn(['current', 'content'], Map(payload));
case FLAG_GETTING_SPACES_NEARBY:
return state.setIn(['nearby', 'activity'], payload);
Expand Down
Loading

0 comments on commit f5ac61d

Please sign in to comment.