diff --git a/public/app/config/channels.js b/public/app/config/channels.js index d0ca0199..ad3dbe28 100644 --- a/public/app/config/channels.js +++ b/public/app/config/channels.js @@ -52,6 +52,8 @@ module.exports = { SET_USER_MODE_CHANNEL: 'user:mode:set', SET_SPACE_AS_FAVORITE_CHANNEL: 'user:set-space-favorite:set', SET_SPACE_AS_RECENT_CHANNEL: 'user:set-space-recent:set', + SET_ACTION_ACCESSIBILITY_CHANNEL: 'user:action:accessibility:set', + SET_ACTION_ENABLED_CHANNEL: 'user:action:enabled:set', GET_CLASSROOMS_CHANNEL: 'classrooms:get', ADD_CLASSROOM_CHANNEL: 'classroom:add', DELETE_CLASSROOM_CHANNEL: 'classroom:delete', diff --git a/public/app/config/config.js b/public/app/config/config.js index 4330fdc8..b94be8fd 100644 --- a/public/app/config/config.js +++ b/public/app/config/config.js @@ -52,6 +52,8 @@ const DEFAULT_PROTOCOL = 'https'; const DEFAULT_LOGGING_LEVEL = 'info'; const AUTHENTICATED = 'authenticated'; const DEFAULT_AUTHENTICATION = false; +const DEFAULT_ACTION_ACCESSIBILITY = false; +const DEFAULT_ACTION_ENABLED = true; const DEFAULT_USER = { geolocation: null, @@ -61,6 +63,8 @@ const DEFAULT_USER = { geolocationEnabled: DEFAULT_GEOLOCATION_ENABLED, syncMode: DEFAULT_SYNC_MODE, userMode: DEFAULT_USER_MODE, + actionAccessibility: DEFAULT_ACTION_ACCESSIBILITY, + actionEnabled: DEFAULT_ACTION_ENABLED, }, favoriteSpaces: [], recentSpaces: [], diff --git a/public/app/config/messages.js b/public/app/config/messages.js index a1337f0d..7fd7d52d 100644 --- a/public/app/config/messages.js +++ b/public/app/config/messages.js @@ -99,6 +99,10 @@ const ERROR_INVALID_USERNAME_MESSAGE = 'This username is invalid'; const ERROR_NO_USER_TO_DELETE_MESSAGE = 'There is no user to delete'; const ERROR_GETTING_SPACE_IN_CLASSROOM_MESSAGE = 'There was an error getting the space in this classroom'; +const ERROR_SETTING_ACTION_ACCESSIBILITY = + 'There was an error setting the action accessibility'; +const ERROR_SETTING_ACTION_ENABLED = + 'There was an error setting the action enabled'; module.exports = { ERROR_GETTING_DEVELOPER_MODE, @@ -167,4 +171,6 @@ module.exports = { ERROR_NO_USER_TO_DELETE_MESSAGE, ERROR_GETTING_SPACE_IN_CLASSROOM_MESSAGE, ERROR_INVALID_USERNAME_MESSAGE, + ERROR_SETTING_ACTION_ACCESSIBILITY, + ERROR_SETTING_ACTION_ENABLED, }; diff --git a/public/app/listeners/index.js b/public/app/listeners/index.js index 063ac27c..8ebacf68 100644 --- a/public/app/listeners/index.js +++ b/public/app/listeners/index.js @@ -47,6 +47,8 @@ const deleteUsersInClassroom = require('./deleteUsersInClassroom'); const editUserInClassroom = require('./editUserInClassroom'); const getSpaceInClassroom = require('./getSpaceInClassroom'); const loadSpaceInClassroom = require('./loadSpaceInClassroom'); +const setActionAccessibility = require('./setActionAccessibility'); +const setActionEnabled = require('./setActionEnabled'); module.exports = { loadSpace, @@ -97,4 +99,6 @@ module.exports = { editUserInClassroom, getSpaceInClassroom, loadSpaceInClassroom, + setActionAccessibility, + setActionEnabled, }; diff --git a/public/app/listeners/setActionAccessibility.js b/public/app/listeners/setActionAccessibility.js new file mode 100644 index 00000000..0d022b84 --- /dev/null +++ b/public/app/listeners/setActionAccessibility.js @@ -0,0 +1,24 @@ +const { SET_ACTION_ACCESSIBILITY_CHANNEL } = require('../config/channels'); +const { ERROR_GENERAL } = require('../config/errors'); +const logger = require('../logger'); + +const setActionAccessibility = (mainWindow, db) => async ( + event, + actionAccessibility +) => { + try { + db.set('user.settings.actionAccessibility', actionAccessibility).write(); + mainWindow.webContents.send( + SET_ACTION_ACCESSIBILITY_CHANNEL, + actionAccessibility + ); + } catch (e) { + logger.error(e); + mainWindow.webContents.send( + SET_ACTION_ACCESSIBILITY_CHANNEL, + ERROR_GENERAL + ); + } +}; + +module.exports = setActionAccessibility; diff --git a/public/app/listeners/setActionEnabled.js b/public/app/listeners/setActionEnabled.js new file mode 100644 index 00000000..9b9fcdd2 --- /dev/null +++ b/public/app/listeners/setActionEnabled.js @@ -0,0 +1,19 @@ +const { SET_ACTION_ENABLED_CHANNEL } = require('../config/channels'); +const { ERROR_GENERAL } = require('../config/errors'); +const { DEFAULT_ACTION_ENABLED } = require('../config/config'); +const logger = require('../logger'); + +const setActionEnabled = (mainWindow, db) => async (event, actionEnabled) => { + try { + db.set('user.settings.actionEnabled', actionEnabled).write(); + mainWindow.webContents.send( + SET_ACTION_ENABLED_CHANNEL, + actionEnabled || DEFAULT_ACTION_ENABLED + ); + } catch (e) { + logger.error(e); + mainWindow.webContents.send(SET_ACTION_ENABLED_CHANNEL, ERROR_GENERAL); + } +}; + +module.exports = setActionEnabled; diff --git a/public/electron.js b/public/electron.js index 11b4b58f..3a6377bc 100644 --- a/public/electron.js +++ b/public/electron.js @@ -72,6 +72,8 @@ const { EDIT_USER_IN_CLASSROOM_CHANNEL, GET_SPACE_IN_CLASSROOM_CHANNEL, LOAD_SPACE_IN_CLASSROOM_CHANNEL, + SET_ACTION_ACCESSIBILITY_CHANNEL, + SET_ACTION_ENABLED_CHANNEL, } = require('./app/config/channels'); const env = require('./env.json'); const { @@ -122,6 +124,8 @@ const { editUserInClassroom, getSpaceInClassroom, loadSpaceInClassroom, + setActionAccessibility, + setActionEnabled, } = require('./app/listeners'); const isMac = require('./app/utils/isMac'); @@ -463,6 +467,15 @@ app.on('ready', async () => { setGeolocationEnabled(mainWindow, db) ); + // called when setting action accessibility + ipcMain.on( + SET_ACTION_ACCESSIBILITY_CHANNEL, + setActionAccessibility(mainWindow, db) + ); + + // called when setting action enabled + ipcMain.on(SET_ACTION_ENABLED_CHANNEL, setActionEnabled(mainWindow, db)); + // called when getting student mode ipcMain.on(GET_USER_MODE_CHANNEL, getUserMode(mainWindow, db)); diff --git a/src/actions/user.js b/src/actions/user.js index 507b05d0..62b2961f 100644 --- a/src/actions/user.js +++ b/src/actions/user.js @@ -28,6 +28,10 @@ import { SET_SPACE_AS_FAVORITE_SUCCEEDED, FLAG_SETTING_SPACE_AS_RECENT, SET_SPACE_AS_RECENT_SPACES_SUCCEEDED, + FLAG_SETTING_ACTION_ACCESSIBILITY, + SET_ACTION_ACCESSIBILITY_SUCCEEDED, + FLAG_SETTING_ACTION_ENABLED, + SET_ACTION_ENABLED_SUCCEEDED, } from '../types'; import { ERROR_GETTING_GEOLOCATION, @@ -45,6 +49,8 @@ import { ERROR_SETTING_USER_MODE, ERROR_SETTING_SPACE_AS_FAVORITE, ERROR_SETTING_SPACE_AS_RECENT, + ERROR_SETTING_ACTION_ACCESSIBILITY, + ERROR_SETTING_ACTION_ENABLED, } from '../config/messages'; import { GET_USER_FOLDER_CHANNEL, @@ -60,6 +66,8 @@ import { SET_USER_MODE_CHANNEL, SET_SPACE_AS_FAVORITE_CHANNEL, SET_SPACE_AS_RECENT_CHANNEL, + SET_ACTION_ACCESSIBILITY_CHANNEL, + SET_ACTION_ENABLED_CHANNEL, } from '../config/channels'; import { createFlag } from './common'; import { ERROR_GENERAL } from '../config/errors'; @@ -81,6 +89,10 @@ const flagGettingUserMode = createFlag(FLAG_GETTING_USER_MODE); const flagSettingUserMode = createFlag(FLAG_SETTING_USER_MODE); const flagSettingSpaceAsFavorite = createFlag(FLAG_SETTING_SPACE_AS_FAVORITE); const flagSettingSpaceAsRecent = createFlag(FLAG_SETTING_SPACE_AS_RECENT); +const flagSettingActionAccessibility = createFlag( + FLAG_SETTING_ACTION_ACCESSIBILITY +); +const flagSettingActionEnabled = createFlag(FLAG_SETTING_ACTION_ENABLED); const getGeolocation = async () => async dispatch => { // only fetch location if online @@ -399,6 +411,54 @@ const setSpaceAsRecent = payload => dispatch => { } }; +const setActionAccessibility = payload => dispatch => { + try { + dispatch(flagSettingActionAccessibility(true)); + window.ipcRenderer.send(SET_ACTION_ACCESSIBILITY_CHANNEL, payload); + window.ipcRenderer.once( + SET_ACTION_ACCESSIBILITY_CHANNEL, + (event, response) => { + if (response === ERROR_GENERAL) { + toastr.error( + ERROR_MESSAGE_HEADER, + ERROR_SETTING_ACTION_ACCESSIBILITY + ); + } else { + dispatch({ + type: SET_ACTION_ACCESSIBILITY_SUCCEEDED, + payload, + }); + } + dispatch(flagSettingActionAccessibility(false)); + } + ); + } catch (e) { + console.error(e); + toastr.error(ERROR_MESSAGE_HEADER, ERROR_SETTING_ACTION_ACCESSIBILITY); + } +}; + +const setActionEnabled = payload => dispatch => { + try { + dispatch(flagSettingActionEnabled(true)); + window.ipcRenderer.send(SET_ACTION_ENABLED_CHANNEL, payload); + window.ipcRenderer.once(SET_ACTION_ENABLED_CHANNEL, (event, response) => { + if (response === ERROR_GENERAL) { + toastr.error(ERROR_MESSAGE_HEADER, ERROR_SETTING_ACTION_ENABLED); + } else { + dispatch({ + type: SET_ACTION_ENABLED_SUCCEEDED, + payload, + }); + } + dispatch(flagSettingActionEnabled(false)); + }); + } catch (e) { + console.error(e); + toastr.error(ERROR_MESSAGE_HEADER, ERROR_SETTING_ACTION_ENABLED); + } +}; + export { getUserFolder, getGeolocation, @@ -414,4 +474,6 @@ export { setUserMode, setSpaceAsFavorite, setSpaceAsRecent, + setActionAccessibility, + setActionEnabled, }; diff --git a/src/components/Settings.js b/src/components/Settings.js index f7cf08fe..444fe6bc 100644 --- a/src/components/Settings.js +++ b/src/components/Settings.js @@ -6,6 +6,7 @@ import Typography from '@material-ui/core/Typography'; import { connect } from 'react-redux'; import { FormGroup } from '@material-ui/core'; import { withTranslation } from 'react-i18next'; +import Divider from '@material-ui/core/Divider'; import Styles from '../Styles'; import LanguageSelect from './common/LanguageSelect'; import DeveloperSwitch from './common/DeveloperSwitch'; @@ -14,8 +15,17 @@ import Main from './common/Main'; import { SETTINGS_MAIN_ID } from '../config/selectors'; import SyncAdvancedSwitch from './space/sync/SyncAdvancedSwitch'; import StudentModeSwitch from './common/StudentModeSwitch'; +import ActionEnabledSwitch from './common/ActionEnabledSwitch'; +import ActionAccessibilitySwitch from './common/ActionAccessibilitySwitch'; import { USER_MODES } from '../config/constants'; +const styles = theme => ({ + ...Styles(theme), + divider: { + margin: theme.spacing(2, 0), + }, +}); + // eslint-disable-next-line react/prefer-stateless-function export class Settings extends Component { static propTypes = { @@ -27,6 +37,7 @@ export class Settings extends Component { content: PropTypes.string.isRequired, contentShift: PropTypes.string.isRequired, settings: PropTypes.string.isRequired, + divider: PropTypes.string.isRequired, }).isRequired, i18n: PropTypes.shape({ changeLanguage: PropTypes.func.isRequired, @@ -40,7 +51,7 @@ export class Settings extends Component { return (
- + {t('Settings')} @@ -50,6 +61,14 @@ export class Settings extends Component { {userMode === USER_MODES.TEACHER ? : null} + + + {t('Actions')} + + + + +
); @@ -62,7 +81,7 @@ const mapStateToProps = ({ authentication }) => ({ const ConnectedComponent = connect(mapStateToProps, null)(Settings); -const StyledComponent = withStyles(Styles, { withTheme: true })( +const StyledComponent = withStyles(styles, { withTheme: true })( ConnectedComponent ); diff --git a/src/components/Settings.test.js b/src/components/Settings.test.js index c280a3ee..486b18ef 100644 --- a/src/components/Settings.test.js +++ b/src/components/Settings.test.js @@ -50,7 +50,7 @@ describe('', () => { }); it('renders one component with text Settings', () => { - const typography = wrapper.find(Typography); + const typography = wrapper.find(Typography).find({ variant: 'h4' }); expect(typography).toHaveLength(1); expect(typography.contains('Settings')).toBeTruthy(); }); diff --git a/src/components/__snapshots__/Settings.test.js.snap b/src/components/__snapshots__/Settings.test.js.snap index 4ce88813..2d0bb7ca 100644 --- a/src/components/__snapshots__/Settings.test.js.snap +++ b/src/components/__snapshots__/Settings.test.js.snap @@ -9,7 +9,7 @@ exports[` renders correctly 1`] = ` > Settings @@ -20,6 +20,25 @@ exports[` renders correctly 1`] = ` + + + Actions + + + + + `; diff --git a/src/components/common/ActionAccessibilitySwitch.js b/src/components/common/ActionAccessibilitySwitch.js new file mode 100644 index 00000000..1a901a6e --- /dev/null +++ b/src/components/common/ActionAccessibilitySwitch.js @@ -0,0 +1,87 @@ +import React, { Component } from 'react'; +import FormControl from '@material-ui/core/FormControl'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import { withStyles } from '@material-ui/core/styles'; +import { withTranslation } from 'react-i18next'; +import Switch from '@material-ui/core/Switch'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { setActionAccessibility } from '../../actions'; +import Loader from './Loader'; +import { + DEFAULT_ACTION_ACCESSIBILITY, + FORM_CONTROL_MIN_WIDTH, +} from '../../config/constants'; + +const styles = theme => ({ + formControl: { + margin: theme.spacing(), + minWidth: FORM_CONTROL_MIN_WIDTH, + }, +}); + +export class ActionAccessibilitySwitch extends Component { + static propTypes = { + activity: PropTypes.bool.isRequired, + actionAccessibility: PropTypes.bool.isRequired, + t: PropTypes.func.isRequired, + dispatchSetActionAccessibility: PropTypes.func.isRequired, + classes: PropTypes.shape({ + formControl: PropTypes.string.isRequired, + }).isRequired, + }; + + handleChange = async ({ target }) => { + const { dispatchSetActionAccessibility } = this.props; + const { checked } = target; + dispatchSetActionAccessibility(checked); + }; + + render() { + const { t, activity, actionAccessibility, classes } = this.props; + + if (activity) { + return ; + } + + const control = ( + + ); + + return ( + + + + ); + } +} + +const mapStateToProps = ({ authentication }) => ({ + activity: Boolean(authentication.getIn(['current', 'activity']).size), + actionAccessibility: + authentication.getIn(['user', 'settings', 'actionAccessibility']) || + DEFAULT_ACTION_ACCESSIBILITY, +}); + +const mapDispatchToProps = { + dispatchSetActionAccessibility: setActionAccessibility, +}; + +const ConnectedComponent = connect( + mapStateToProps, + mapDispatchToProps +)(ActionAccessibilitySwitch); + +const StyledComponent = withStyles(styles)(ConnectedComponent); + +const TranslatedComponent = withTranslation()(StyledComponent); + +export default TranslatedComponent; diff --git a/src/components/common/ActionEnabledSwitch.js b/src/components/common/ActionEnabledSwitch.js new file mode 100644 index 00000000..7516c6f0 --- /dev/null +++ b/src/components/common/ActionEnabledSwitch.js @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import FormControl from '@material-ui/core/FormControl'; +import { withStyles } from '@material-ui/core/styles'; +import { withTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import Switch from '@material-ui/core/Switch'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import { setActionEnabled } from '../../actions'; +import Loader from './Loader'; +import { + FORM_CONTROL_MIN_WIDTH, + DEFAULT_ACTION_ENABLED, +} from '../../config/constants'; + +const styles = theme => ({ + formControl: { + margin: theme.spacing(), + minWidth: FORM_CONTROL_MIN_WIDTH, + }, +}); + +export class ActionEnabledSwitch extends Component { + static propTypes = { + actionEnabled: PropTypes.bool.isRequired, + activity: PropTypes.bool.isRequired, + t: PropTypes.func.isRequired, + dispatchSetActionEnabled: PropTypes.func.isRequired, + classes: PropTypes.shape({ + formControl: PropTypes.string.isRequired, + }).isRequired, + }; + + handleChange = async ({ target }) => { + const { dispatchSetActionEnabled } = this.props; + const { checked } = target; + dispatchSetActionEnabled(checked); + }; + + render() { + const { classes, t, actionEnabled, activity } = this.props; + + if (activity) { + return ; + } + + const control = ( + + ); + + return ( + + + + ); + } +} + +const mapStateToProps = ({ authentication }) => ({ + actionEnabled: + authentication.getIn(['user', 'settings', 'actionEnabled']) || + DEFAULT_ACTION_ENABLED, + activity: Boolean(authentication.getIn(['current', 'activity']).size), +}); + +const mapDispatchToProps = { + dispatchSetActionEnabled: setActionEnabled, +}; + +const ConnectedComponent = connect( + mapStateToProps, + mapDispatchToProps +)(ActionEnabledSwitch); + +const StyledComponent = withStyles(styles)(ConnectedComponent); + +const TranslatedComponent = withTranslation()(StyledComponent); + +export default TranslatedComponent; diff --git a/src/components/dashboard/ActionEditor.js b/src/components/dashboard/ActionEditor.js index 59c977cf..c0705696 100644 --- a/src/components/dashboard/ActionEditor.js +++ b/src/components/dashboard/ActionEditor.js @@ -1,65 +1,38 @@ import React, { Component } from 'react'; import _ from 'lodash'; -import { connect } from 'react-redux'; import ReactJson from 'react-json-view'; import PropTypes from 'prop-types'; import Typography from '@material-ui/core/Typography'; import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core'; -import { getDatabase } from '../../actions'; import Loader from '../common/Loader'; import Styles from '../../Styles'; -import { - SELECT_ALL_SPACES_ID, - SELECT_ALL_USERS_ID, -} from '../../config/constants'; +// eslint-disable-next-line react/prefer-stateless-function export class ActionEditor extends Component { static propTypes = { t: PropTypes.func.isRequired, classes: PropTypes.shape({ button: PropTypes.string.isRequired, }).isRequired, - dispatchGetDatabase: PropTypes.func.isRequired, - database: PropTypes.shape({ - user: PropTypes.object, - spaces: PropTypes.arrayOf(PropTypes.object), - actions: PropTypes.arrayOf(PropTypes.object), - }), - spaceId: PropTypes.string, - userId: PropTypes.string, + actions: PropTypes.shape(PropTypes.object), }; static defaultProps = { - database: {}, - spaceId: SELECT_ALL_SPACES_ID, - userId: SELECT_ALL_USERS_ID, + actions: [], }; - componentDidMount() { - const { dispatchGetDatabase } = this.props; - dispatchGetDatabase(); - } - render() { - const { database, t, spaceId, userId } = this.props; + const { actions, t } = this.props; - if (!database) { + if (!actions) { return ; } - if (_.isEmpty(database)) { + if (_.isEmpty(actions)) { return

{t('The database is empty.')}

; } - let { actions } = database; - if (userId !== SELECT_ALL_USERS_ID) { - actions = actions.filter(({ user }) => user === userId); - } - if (spaceId !== SELECT_ALL_SPACES_ID) { - actions = actions.filter(({ spaceId: id }) => id === spaceId); - } - return (
{t('Action Database')} @@ -69,19 +42,7 @@ export class ActionEditor extends Component { } } -const mapStateToProps = ({ Developer }) => ({ - database: Developer.get('database'), -}); - -const mapDispatchToProps = { - dispatchGetDatabase: getDatabase, -}; - const StyledComponent = withStyles(Styles, { withTheme: true })(ActionEditor); const TranslatedComponent = withTranslation()(StyledComponent); -const ConnectedComponent = connect( - mapStateToProps, - mapDispatchToProps -)(TranslatedComponent); -export default ConnectedComponent; +export default TranslatedComponent; diff --git a/src/components/dashboard/Dashboard.js b/src/components/dashboard/Dashboard.js index 21678797..1ef79509 100644 --- a/src/components/dashboard/Dashboard.js +++ b/src/components/dashboard/Dashboard.js @@ -157,31 +157,8 @@ export class Dashboard extends Component { ); }; - renderActionWidgets = () => { - const { database, t, classes, userMode, userId } = this.props; - const { spaceId, filteredUserId } = this.state; - - let filteredActions = database.actions; - - // filter action per user if userMode is student or with filter - if (userMode === USER_MODES.STUDENT) { - filteredActions = filteredActions.filter(({ user }) => user === userId); - } else if (filteredUserId !== SELECT_ALL_USERS_ID) { - filteredActions = filteredActions.filter( - ({ user }) => user === filteredUserId - ); - } - - // filter action per space - if (spaceId !== SELECT_ALL_SPACES_ID) { - filteredActions = filteredActions.filter( - ({ spaceId: id }) => id === spaceId - ); - } - - if (_.isEmpty(filteredActions)) { - return

{t('No action has been recorded.')}

; - } + renderActionWidgets = filteredActions => { + const { database, classes } = this.props; return ( { + const { t, database, userMode, userId } = this.props; + const { spaceId, filteredUserId } = this.state; + + let filteredActions = database.actions; + const { users } = database; + const isStudent = userMode === USER_MODES.STUDENT; + + filteredActions = filteredActions.filter(({ user }) => { + const isOwnAction = user === userId; + const actionUser = users.find(({ id }) => id === user); + const isAccessible = + actionUser && actionUser.settings.actionAccessibility; + const filteredUserSelected = filteredUserId !== SELECT_ALL_USERS_ID; + + // filter action per user if userMode is student + return ( + (isStudent && isOwnAction) || + // filter actions per selected user if selected + // only teachers have access to user filter + // actions are displayed either if the user set action as accessible or + // actions are own by the current user + (!isStudent && + (!filteredUserSelected || user === filteredUserId) && + (isAccessible || isOwnAction)) + ); + }); + + // filter action per space + if (spaceId !== SELECT_ALL_SPACES_ID) { + filteredActions = filteredActions.filter( + ({ spaceId: id }) => id === spaceId + ); + } + + return filteredActions.length ? ( + <> + {this.renderActionWidgets(filteredActions)} + + + ) : ( + {t('No action has been recorded.')} + ); + }; + render() { const { classes, t, database } = this.props; - const { spaceId, filteredUserId } = this.state; if (!database) { return ; @@ -242,9 +263,7 @@ export class Dashboard extends Component { - {this.renderActionWidgets()} - - + {this.renderContent()}
); diff --git a/src/components/phase/PhaseApp.js b/src/components/phase/PhaseApp.js index c18e0788..d58bd56d 100644 --- a/src/components/phase/PhaseApp.js +++ b/src/components/phase/PhaseApp.js @@ -25,6 +25,7 @@ import { import { DEFAULT_LANGUAGE, SMART_GATEWAY_QUERY_STRING_DIVIDER, + DEFAULT_ACTION_ENABLED, } from '../../config/constants'; import { isSmartGatewayUrl } from '../../utils/url'; import { getHeight, setHeight } from '../../actions/layout'; @@ -60,6 +61,7 @@ class PhaseApp extends Component { }), geolocation: PropTypes.instanceOf(Map), geolocationEnabled: PropTypes.bool.isRequired, + actionEnabled: PropTypes.bool.isRequired, }; static defaultProps = { @@ -117,6 +119,7 @@ class PhaseApp extends Component { dispatchPostAction, geolocation, geolocationEnabled, + actionEnabled, } = this.props; // get app instance id in message @@ -139,13 +142,16 @@ class PhaseApp extends Component { case GET_APP_INSTANCE: return dispatchGetAppInstance(payload, this.postMessage); case POST_ACTION: { - // add geolocation to action if enabled - payload.geolocation = null; - if (geolocationEnabled) { - const { latitude, longitude } = geolocation.getIn(['coords']); - payload.geolocation = { ll: [latitude, longitude] }; + if (actionEnabled) { + // add geolocation to action if enabled + payload.geolocation = null; + if (geolocationEnabled) { + const { latitude, longitude } = geolocation.getIn(['coords']); + payload.geolocation = { ll: [latitude, longitude] }; + } + return dispatchPostAction(payload, this.postMessage); } - return dispatchPostAction(payload, this.postMessage); + break; } default: return false; @@ -197,6 +203,7 @@ class PhaseApp extends Component { phaseId, appInstance, userId, + actionEnabled, } = this.props; let uri = url; if (asset) { @@ -231,6 +238,7 @@ class PhaseApp extends Component { itemId: id, offline: true, subSpaceId: phaseId, + analytics: actionEnabled, }; const queryString = Qs.stringify(params); @@ -308,6 +316,9 @@ const mapStateToProps = ({ authentication, Space }) => ({ 'geolocationEnabled', ]), userId: authentication.getIn(['user', 'id']), + actionEnabled: + authentication.getIn(['user', 'settings', 'actionEnabled']) || + DEFAULT_ACTION_ENABLED, }); const mapDispatchToProps = { diff --git a/src/config/channels.js b/src/config/channels.js index d0ca0199..ad3dbe28 100644 --- a/src/config/channels.js +++ b/src/config/channels.js @@ -52,6 +52,8 @@ module.exports = { SET_USER_MODE_CHANNEL: 'user:mode:set', SET_SPACE_AS_FAVORITE_CHANNEL: 'user:set-space-favorite:set', SET_SPACE_AS_RECENT_CHANNEL: 'user:set-space-recent:set', + SET_ACTION_ACCESSIBILITY_CHANNEL: 'user:action:accessibility:set', + SET_ACTION_ENABLED_CHANNEL: 'user:action:enabled:set', GET_CLASSROOMS_CHANNEL: 'classrooms:get', ADD_CLASSROOM_CHANNEL: 'classroom:add', DELETE_CLASSROOM_CHANNEL: 'classroom:delete', diff --git a/src/config/constants.js b/src/config/constants.js index 5cd3f8c2..c59daa22 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -30,6 +30,9 @@ export const CONTROL_TYPES = { export const MIN_CARD_WIDTH = 345; export const DEFAULT_PROTOCOL = 'https'; +export const DEFAULT_ACTION_ACCESSIBILITY = false; +export const DEFAULT_ACTION_ENABLED = true; + // math export const BLOCK_MATH_DIV = 'p'; export const INLINE_MATH_DIV = 'span'; diff --git a/src/config/messages.js b/src/config/messages.js index 20be0ff1..34c74b9b 100644 --- a/src/config/messages.js +++ b/src/config/messages.js @@ -99,6 +99,10 @@ const ERROR_NO_USER_TO_DELETE_MESSAGE = 'There is no user to delete'; const ERROR_GETTING_SPACE_IN_CLASSROOM_MESSAGE = 'There was an error getting the space in this classroom'; const ERROR_INVALID_USERNAME_MESSAGE = 'This username is invalid'; +const ERROR_SETTING_ACTION_ACCESSIBILITY = + 'There was an error setting the action accessibility'; +const ERROR_SETTING_ACTION_ENABLED = + 'There was an error setting the action enabled'; module.exports = { ERROR_GETTING_DEVELOPER_MODE, @@ -167,4 +171,6 @@ module.exports = { ERROR_NO_USER_TO_DELETE_MESSAGE, ERROR_GETTING_SPACE_IN_CLASSROOM_MESSAGE, ERROR_INVALID_USERNAME_MESSAGE, + ERROR_SETTING_ACTION_ACCESSIBILITY, + ERROR_SETTING_ACTION_ENABLED, }; diff --git a/src/data/sample.js b/src/data/sample.js index 5cd4427d..138c2874 100644 --- a/src/data/sample.js +++ b/src/data/sample.js @@ -1,4 +1,9 @@ -import { SYNC_MODES, USER_MODES } from '../config/constants'; +import { + SYNC_MODES, + USER_MODES, + DEFAULT_ACTION_ACCESSIBILITY, + DEFAULT_ACTION_ENABLED, +} from '../config/constants'; const SampleDatabase = { spaces: [ @@ -168,7 +173,7 @@ const SampleDatabase = { format: 'v1', type: 'input', visibility: 'private', - user: '5ce422795fe28eeca1001e0a', + user: '1', id: '5ce430152f6f6672b16fca57', verb: 'saved', spaceId: '6ccb068bbb18b80359966631', @@ -176,7 +181,7 @@ const SampleDatabase = { ], users: [ { - id: 1, + id: '1', username: 'graasp', createdAt: '2019-05-21T17:06:29.683Z', geolocation: null, @@ -186,6 +191,8 @@ const SampleDatabase = { geolocationEnabled: false, syncMode: SYNC_MODES.ADVANCED, userMode: USER_MODES.TEACHER, + actionEnabled: DEFAULT_ACTION_ENABLED, + actionAccessibility: DEFAULT_ACTION_ACCESSIBILITY, }, }, ], diff --git a/src/reducers/authenticationReducer.js b/src/reducers/authenticationReducer.js index 779549a4..96c470c1 100644 --- a/src/reducers/authenticationReducer.js +++ b/src/reducers/authenticationReducer.js @@ -31,6 +31,10 @@ import { FLAG_SETTING_SPACE_AS_FAVORITE, SET_SPACE_AS_FAVORITE_SUCCEEDED, SET_SPACE_AS_RECENT_SPACES_SUCCEEDED, + FLAG_SETTING_ACTION_ACCESSIBILITY, + SET_ACTION_ACCESSIBILITY_SUCCEEDED, + SET_ACTION_ENABLED_SUCCEEDED, + FLAG_SETTING_ACTION_ENABLED, } from '../types'; import { updateActivityList } from './common'; import { @@ -41,6 +45,8 @@ import { DEFAULT_SYNC_MODE, DEFAULT_USER_MODE, MAX_RECENT_SPACES, + DEFAULT_ACTION_ACCESSIBILITY, + DEFAULT_ACTION_ENABLED, } from '../config/constants'; const updateFavoriteSpaces = ({ favorite, spaceId }) => favoriteSpaces => { @@ -85,6 +91,8 @@ export const DEFAULT_USER_SETTINGS = { geolocationEnabled: DEFAULT_GEOLOCATION_ENABLED, syncMode: DEFAULT_SYNC_MODE, userMode: DEFAULT_USER_MODE, + actionAccessibility: DEFAULT_ACTION_ACCESSIBILITY, + actionEnabled: DEFAULT_ACTION_ENABLED, }; export const DEFAULT_USER = { @@ -120,6 +128,8 @@ export default (state = INITIAL_STATE, { type, payload }) => { case FLAG_SETTING_SPACE_AS_FAVORITE: case FLAG_SIGNING_IN: case FLAG_SIGNING_OUT: + case FLAG_SETTING_ACTION_ACCESSIBILITY: + case FLAG_SETTING_ACTION_ENABLED: return state.updateIn( ['current', 'activity'], updateActivityList(payload) @@ -166,6 +176,10 @@ export default (state = INITIAL_STATE, { type, payload }) => { ['user', 'recentSpaces'], updateRecentSpaces(payload) ); + case SET_ACTION_ENABLED_SUCCEEDED: + return state.setIn(['user', 'settings', 'actionEnabled'], payload); + case SET_ACTION_ACCESSIBILITY_SUCCEEDED: + return state.setIn(['user', 'settings', 'actionAccessibility'], payload); default: return state; } diff --git a/src/types/user.js b/src/types/user.js index c483111a..cc65b45f 100644 --- a/src/types/user.js +++ b/src/types/user.js @@ -34,3 +34,9 @@ export const FLAG_SETTING_SPACE_AS_RECENT = 'FLAG_SETTING_SPACE_AS_RECENT'; export const SET_SPACE_AS_RECENT = 'SET_SPACE_AS_RECENT'; export const SET_SPACE_AS_RECENT_SPACES_SUCCEEDED = 'SET_SPACE_AS_RECENT_SPACES_SUCCEEDED'; +export const FLAG_SETTING_ACTION_ACCESSIBILITY = + 'FLAG_SETTING_ACTION_ACCESSIBILITY'; +export const SET_ACTION_ACCESSIBILITY_SUCCEEDED = + 'SET_ACTION_ACCESSIBILITY_SUCCEEDED'; +export const FLAG_SETTING_ACTION_ENABLED = 'FLAG_SETTING_ACTION_ENABLED'; +export const SET_ACTION_ENABLED_SUCCEEDED = 'SET_ACTION_ENABLED_SUCCEEDED';