diff --git a/src/components/forms/Fields/SourceCodeField.js b/src/components/forms/Fields/SourceCodeField.js index 6d9e7d0d2..5e6cd3206 100644 --- a/src/components/forms/Fields/SourceCodeField.js +++ b/src/components/forms/Fields/SourceCodeField.js @@ -1,44 +1,55 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import { FormGroup, ControlLabel, HelpBlock } from 'react-bootstrap'; import ClientOnly from '../../helpers/ClientOnly'; - -import { FormGroup, ControlLabel, HelpBlock } from 'react-bootstrap'; +import { UserSettingsContext } from '../../../helpers/contexts'; // load the ACE editor only when rendering in the browser import { loadAceEditor, getAceModeFromExtension } from '../../helpers/AceEditorLoader'; let AceEditor = loadAceEditor(); -const SourceCodeField = ( - { input, mode, meta: { error, warning }, label = null, children, tabIndex, onBlur, readOnly = false, ...props }, - { userSettings: { vimMode = false, darkTheme = false } } -) => ( +const SourceCodeField = ({ + input, + mode, + meta: { error, warning }, + label = null, + children, + tabIndex, + onBlur, + readOnly = false, + ...props +}) => ( {Boolean(label) && {label}}
- input.onBlur() // this is a hack that will ensure blur call witout distorting the contents - } - editorProps={{ - $blockScrolling: Infinity, - $autoScrollEditorIntoView: true, - }} - /> + + {({ vimMode = false, darkTheme = false }) => ( + input.onBlur() // this is a hack that will ensure blur call witout distorting the contents + } + editorProps={{ + $blockScrolling: Infinity, + $autoScrollEditorIntoView: true, + }} + /> + )} +
{error && {error} } @@ -68,8 +79,4 @@ SourceCodeField.propTypes = { onBlur: PropTypes.func, }; -SourceCodeField.contextTypes = { - userSettings: PropTypes.object, -}; - export default SourceCodeField; diff --git a/src/components/forms/LocalizedTextsFormField/LocalizedAssignmentFormField.js b/src/components/forms/LocalizedTextsFormField/LocalizedAssignmentFormField.js index d57408ef0..4dd729418 100644 --- a/src/components/forms/LocalizedTextsFormField/LocalizedAssignmentFormField.js +++ b/src/components/forms/LocalizedTextsFormField/LocalizedAssignmentFormField.js @@ -9,7 +9,7 @@ import SharedExerciseAssignmentLocalizedFields from './SharedExerciseAssignmentL import { MarkdownTextAreaField } from '../Fields'; import { WarningIcon } from '../../icons'; -const LocalizedAssigmentFormField = ({ prefix, data: enabled }) => ( +const LocalizedAssignmentFormField = ({ prefix, data: enabled }) => ( @@ -36,9 +36,9 @@ const LocalizedAssigmentFormField = ({ prefix, data: enabled }) => ( ); -LocalizedAssigmentFormField.propTypes = { +LocalizedAssignmentFormField.propTypes = { prefix: PropTypes.string.isRequired, data: PropTypes.bool.isRequired, }; -export default LocalizedAssigmentFormField; +export default LocalizedAssignmentFormField; diff --git a/src/components/forms/LocalizedTextsFormField/LocalizedShadowAssignmentFormField.js b/src/components/forms/LocalizedTextsFormField/LocalizedShadowAssignmentFormField.js index c66bc0f68..b65c8df53 100644 --- a/src/components/forms/LocalizedTextsFormField/LocalizedShadowAssignmentFormField.js +++ b/src/components/forms/LocalizedTextsFormField/LocalizedShadowAssignmentFormField.js @@ -5,16 +5,16 @@ import { Well } from 'react-bootstrap'; import SharedLocalizedFields from './SharedLocalizedFields'; import SharedExerciseAssignmentLocalizedFields from './SharedExerciseAssignmentLocalizedFields'; -const LocalizedShadowAssigmentFormField = ({ prefix, data: enabled }) => ( +const LocalizedShadowAssignmentFormField = ({ prefix, data: enabled }) => ( ); -LocalizedShadowAssigmentFormField.propTypes = { +LocalizedShadowAssignmentFormField.propTypes = { prefix: PropTypes.string.isRequired, data: PropTypes.bool.isRequired, }; -export default LocalizedShadowAssigmentFormField; +export default LocalizedShadowAssignmentFormField; diff --git a/src/components/helpers/SourceCodeViewer/SourceCodeViewer.js b/src/components/helpers/SourceCodeViewer/SourceCodeViewer.js index 86f570295..663034611 100644 --- a/src/components/helpers/SourceCodeViewer/SourceCodeViewer.js +++ b/src/components/helpers/SourceCodeViewer/SourceCodeViewer.js @@ -1,23 +1,25 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { UserSettingsContext } from '../../../helpers/contexts'; import { loadAceEditor, getAceModeFromExtension } from '../../helpers/AceEditorLoader'; let AceEditor = loadAceEditor(); -const SourceCodeViewer = ( - { name, content = '', lineNumbers = true }, - { userSettings: { vimMode = false, darkTheme = false } } -) => ( - +const SourceCodeViewer = ({ name, content = '', lineNumbers = true }) => ( + + {({ vimMode = false, darkTheme = false }) => ( + + )} + ); SourceCodeViewer.propTypes = { @@ -26,8 +28,4 @@ SourceCodeViewer.propTypes = { lineNumbers: PropTypes.bool, }; -SourceCodeViewer.contextTypes = { - userSettings: PropTypes.object, -}; - export default SourceCodeViewer; diff --git a/src/containers/App/App.js b/src/containers/App/App.js index bcab35f19..94a89ff32 100644 --- a/src/containers/App/App.js +++ b/src/containers/App/App.js @@ -4,7 +4,7 @@ import { connect } from 'react-redux'; import { loggedInUserIdSelector, selectedInstanceId, accessTokenSelector } from '../../redux/selectors/auth'; import { fetchUserIfNeeded } from '../../redux/modules/users'; -import { getUser, fetchUserStatus, getUserSettings } from '../../redux/selectors/users'; +import { getUser, fetchUserStatus } from '../../redux/selectors/users'; import { isTokenValid, isTokenInNeedOfRefreshment } from '../../redux/helpers/token'; import { fetchUsersInstancesIfNeeded } from '../../redux/modules/instances'; import { fetchManyUserInstancesStatus } from '../../redux/selectors/instances'; @@ -67,10 +67,6 @@ class App extends Component { } } - getChildContext = () => ({ - userSettings: this.props.userSettings, - }); - /** * The validation in react-router does not cover all cases - validity of the token * must be checked more often. @@ -119,16 +115,11 @@ App.propTypes = { children: PropTypes.element, routes: PropTypes.array, loadAsync: PropTypes.func, - userSettings: PropTypes.object, fetchUserStatus: PropTypes.string, fetchManyGroupsStatus: PropTypes.string, fetchManyUserInstancesStatus: PropTypes.string, }; -App.childContextTypes = { - userSettings: PropTypes.object, -}; - export default connect( state => { const userId = loggedInUserIdSelector(state); @@ -137,7 +128,6 @@ export default connect( userId, instanceId: selectedInstanceId(state), isLoggedIn: Boolean(userId), - userSettings: getUserSettings(userId)(state), fetchUserStatus: fetchUserStatus(state, userId), fetchManyGroupsStatus: fetchManyGroupsStatus(state), fetchManyUserInstancesStatus: fetchManyUserInstancesStatus(state, userId), diff --git a/src/containers/LayoutContainer/LayoutContainer.js b/src/containers/LayoutContainer/LayoutContainer.js index edd022c7d..40cf63489 100644 --- a/src/containers/LayoutContainer/LayoutContainer.js +++ b/src/containers/LayoutContainer/LayoutContainer.js @@ -9,8 +9,10 @@ import { anyPendingFetchOperations } from '../../redux/selectors/app'; import { toggleSize, toggleVisibility, collapse, unroll } from '../../redux/modules/sidebar'; import { isVisible, isCollapsed } from '../../redux/selectors/sidebar'; import { isLoggedIn } from '../../redux/selectors/auth'; +import { getLoggedInUserSettings } from '../../redux/selectors/users'; import { messages, localeData, defaultLanguage } from '../../locales'; import { linksFactory, isAbsolute } from '../../links'; +import { UserSettingsContext } from '../../helpers/contexts'; import 'admin-lte/dist/css/AdminLTE.min.css'; import 'admin-lte/dist/css/skins/_all-skins.min.css'; @@ -45,33 +47,31 @@ class LayoutContainer extends Component { componentDidMount() { this.changeLang(this.props); - this.resizeSidebarToDefault(this.props, this.context); + this.resizeSidebarToDefault(this.props); } - UNSAFE_componentWillReceiveProps(newProps, newContext) { + componentDidUpdate(prevProps) { // TODO this needs to be rewritten along with the new context handling... if ( - (typeof this.context.userSettings.openedSidebar === 'undefined' && - typeof newContext.userSettings.openedSidebar !== 'undefined') || - (typeof this.context.userSettings.openedSidebar !== 'undefined' && - this.context.userSettings.openedSidebar !== newContext.userSettings.openedSidebar) + (prevProps.userSettings.openedSidebar === undefined && this.props.userSettings.openedSidebar !== undefined) || + (prevProps.userSettings.openedSidebar !== undefined && + prevProps.userSettings.openedSidebar !== this.props.userSettings.openedSidebar) ) { - this.resizeSidebarToDefault(newProps, newContext); + this.resizeSidebarToDefault(this.props); } - if (this.props.params.lang !== newProps.params.lang) { - this.changeLang(newProps); + if (this.props.params.lang !== prevProps.params.lang) { + this.changeLang(this.props); } } - resizeSidebarToDefault(props, context) { + resizeSidebarToDefault({ collapse, unroll, userSettings }) { // open or hide the sidebar based on user's settings - const { collapse, unroll } = props; - const shouldBeOpen = this.getDefaultOpenedSidebar(context); + const shouldBeOpen = this.getDefaultOpenedSidebar(userSettings); shouldBeOpen ? unroll() : collapse(); } - getDefaultOpenedSidebar = ({ userSettings }) => + getDefaultOpenedSidebar = userSettings => userSettings && typeof userSettings.openedSidebar !== 'undefined' ? userSettings.openedSidebar : true; changeLang = props => { @@ -102,7 +102,7 @@ class LayoutContainer extends Component { */ getDefaultLang = () => { - const { userSettings } = this.context; + const { userSettings } = this.props; return userSettings && userSettings.defaultLanguage ? userSettings.defaultLanguage : 'en'; }; @@ -119,6 +119,7 @@ class LayoutContainer extends Component { toggleSize, toggleVisibility, pendingFetchOperations, + userSettings, } = this.props; const { lang } = this.state; @@ -127,19 +128,21 @@ class LayoutContainer extends Component { return ( - - {children} - + + + {children} + + ); } @@ -153,7 +156,6 @@ LayoutContainer.childContextTypes = { LayoutContainer.contextTypes = { router: PropTypes.object, - userSettings: PropTypes.object, }; LayoutContainer.propTypes = { @@ -172,6 +174,7 @@ LayoutContainer.propTypes = { pathname: PropTypes.string.isRequired, }).isRequired, children: PropTypes.element, + userSettings: PropTypes.object, }; const mapStateToProps = (state, props) => ({ @@ -179,6 +182,7 @@ const mapStateToProps = (state, props) => ({ sidebarIsCollapsed: isCollapsed(state), sidebarIsOpen: isVisible(state), pendingFetchOperations: anyPendingFetchOperations(state), + userSettings: getLoggedInUserSettings(state), }); const mapDispatchToProps = { toggleVisibility, toggleSize, collapse, unroll }; diff --git a/src/helpers/contexts.js b/src/helpers/contexts.js new file mode 100644 index 000000000..613d7cf4e --- /dev/null +++ b/src/helpers/contexts.js @@ -0,0 +1,3 @@ +import React from 'react'; + +export const UserSettingsContext = React.createContext({}); diff --git a/src/redux/selectors/users.js b/src/redux/selectors/users.js index c757d9173..8313b7eba 100644 --- a/src/redux/selectors/users.js +++ b/src/redux/selectors/users.js @@ -100,10 +100,13 @@ export const isSupervisor = userId => role => isSupervisorRole(role) ); +const userSettingsSelector = user => + isReady(user) ? user.getIn(['data', 'privateData', 'settings']).toJS() : EMPTY_OBJ; + export const getUserSettings = userId => createSelector( getUser(userId), - user => (isReady(user) ? user.getIn(['data', 'privateData', 'settings']).toJS() : EMPTY_OBJ) + userSettingsSelector ); export const loggedInUserSelector = createSelector( @@ -111,6 +114,11 @@ export const loggedInUserSelector = createSelector( (users, id) => (id && users ? users.get(id) : null) ); +export const getLoggedInUserSettings = createSelector( + loggedInUserSelector, + userSettingsSelector +); + export const isLoggedAsSuperAdmin = createSelector( [loggedInUserSelector], loggedInUser =>