Skip to content

Commit

Permalink
Updating userSettings to be injected via new context API.
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Krulis committed Jul 18, 2019
1 parent 1766d20 commit 0e66b99
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 95 deletions.
69 changes: 38 additions & 31 deletions src/components/forms/Fields/SourceCodeField.js
Original file line number Diff line number Diff line change
@@ -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
}) => (
<FormGroup controlId={input.name} validationState={error ? 'error' : warning ? 'warning' : undefined}>
{Boolean(label) && <ControlLabel>{label}</ControlLabel>}
<ClientOnly>
<div className={readOnly ? 'noselection' : ''}>
<AceEditor
{...props}
{...input}
mode={getAceModeFromExtension(mode)}
theme={darkTheme ? 'monokai' : 'github'}
name={input.name}
tabIndex={tabIndex}
keyboardHandler={vimMode ? 'vim' : undefined}
width="100%"
height="100%"
minLines={5}
maxLines={20}
readOnly={readOnly}
onBlur={
() => input.onBlur() // this is a hack that will ensure blur call witout distorting the contents
}
editorProps={{
$blockScrolling: Infinity,
$autoScrollEditorIntoView: true,
}}
/>
<UserSettingsContext.Consumer>
{({ vimMode = false, darkTheme = false }) => (
<AceEditor
{...props}
{...input}
mode={getAceModeFromExtension(mode)}
theme={darkTheme ? 'monokai' : 'github'}
name={input.name}
tabIndex={tabIndex}
keyboardHandler={vimMode ? 'vim' : undefined}
width="100%"
height="100%"
minLines={5}
maxLines={20}
readOnly={readOnly}
onBlur={
() => input.onBlur() // this is a hack that will ensure blur call witout distorting the contents
}
editorProps={{
$blockScrolling: Infinity,
$autoScrollEditorIntoView: true,
}}
/>
)}
</UserSettingsContext.Consumer>
</div>
</ClientOnly>
{error && <HelpBlock> {error} </HelpBlock>}
Expand Down Expand Up @@ -68,8 +79,4 @@ SourceCodeField.propTypes = {
onBlur: PropTypes.func,
};

SourceCodeField.contextTypes = {
userSettings: PropTypes.object,
};

export default SourceCodeField;
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<Well>
<Alert bsStyle="info">
<WarningIcon gapRight />
Expand All @@ -36,9 +36,9 @@ const LocalizedAssigmentFormField = ({ prefix, data: enabled }) => (
</Well>
);

LocalizedAssigmentFormField.propTypes = {
LocalizedAssignmentFormField.propTypes = {
prefix: PropTypes.string.isRequired,
data: PropTypes.bool.isRequired,
};

export default LocalizedAssigmentFormField;
export default LocalizedAssignmentFormField;
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<Well>
<SharedLocalizedFields prefix={prefix} enabled={enabled} />
<SharedExerciseAssignmentLocalizedFields prefix={prefix} enabled={enabled} />
</Well>
);

LocalizedShadowAssigmentFormField.propTypes = {
LocalizedShadowAssignmentFormField.propTypes = {
prefix: PropTypes.string.isRequired,
data: PropTypes.bool.isRequired,
};

export default LocalizedShadowAssigmentFormField;
export default LocalizedShadowAssignmentFormField;
34 changes: 16 additions & 18 deletions src/components/helpers/SourceCodeViewer/SourceCodeViewer.js
Original file line number Diff line number Diff line change
@@ -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 } }
) => (
<AceEditor
value={content}
mode={getAceModeFromExtension(name.split('.').pop())}
keyboardHandler={vimMode ? 'vim' : undefined}
theme={darkTheme ? 'monokai' : 'github'}
name="source-code-viewer"
width="100%"
height="100%"
editorProps={{ $blockScrolling: true, $autoScrollEditorIntoView: true }}
/>
const SourceCodeViewer = ({ name, content = '', lineNumbers = true }) => (
<UserSettingsContext.Consumer>
{({ vimMode = false, darkTheme = false }) => (
<AceEditor
value={content}
mode={getAceModeFromExtension(name.split('.').pop())}
keyboardHandler={vimMode ? 'vim' : undefined}
theme={darkTheme ? 'monokai' : 'github'}
name="source-code-viewer"
width="100%"
height="100%"
editorProps={{ $blockScrolling: true, $autoScrollEditorIntoView: true }}
/>
)}
</UserSettingsContext.Consumer>
);

SourceCodeViewer.propTypes = {
Expand All @@ -26,8 +28,4 @@ SourceCodeViewer.propTypes = {
lineNumbers: PropTypes.bool,
};

SourceCodeViewer.contextTypes = {
userSettings: PropTypes.object,
};

export default SourceCodeViewer;
12 changes: 1 addition & 11 deletions src/containers/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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),
Expand Down
60 changes: 32 additions & 28 deletions src/containers/LayoutContainer/LayoutContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -102,7 +102,7 @@ class LayoutContainer extends Component {
*/

getDefaultLang = () => {
const { userSettings } = this.context;
const { userSettings } = this.props;
return userSettings && userSettings.defaultLanguage ? userSettings.defaultLanguage : 'en';
};

Expand All @@ -119,6 +119,7 @@ class LayoutContainer extends Component {
toggleSize,
toggleVisibility,
pendingFetchOperations,
userSettings,
} = this.props;

const { lang } = this.state;
Expand All @@ -127,19 +128,21 @@ class LayoutContainer extends Component {

return (
<IntlProvider locale={lang} messages={this.getMessages(lang)} formats={ADDITIONAL_INTL_FORMATS}>
<Layout
isLoggedIn={isLoggedIn}
sidebarIsCollapsed={sidebarIsCollapsed}
sidebarIsOpen={sidebarIsOpen}
toggleSize={toggleSize}
toggleVisibility={toggleVisibility}
onCloseSidebar={this.maybeHideSidebar}
lang={lang}
availableLangs={Object.keys(messages)}
currentUrl={pathname}
pendingFetchOperations={pendingFetchOperations}>
{children}
</Layout>
<UserSettingsContext.Provider value={userSettings}>
<Layout
isLoggedIn={isLoggedIn}
sidebarIsCollapsed={sidebarIsCollapsed}
sidebarIsOpen={sidebarIsOpen}
toggleSize={toggleSize}
toggleVisibility={toggleVisibility}
onCloseSidebar={this.maybeHideSidebar}
lang={lang}
availableLangs={Object.keys(messages)}
currentUrl={pathname}
pendingFetchOperations={pendingFetchOperations}>
{children}
</Layout>
</UserSettingsContext.Provider>
</IntlProvider>
);
}
Expand All @@ -153,7 +156,6 @@ LayoutContainer.childContextTypes = {

LayoutContainer.contextTypes = {
router: PropTypes.object,
userSettings: PropTypes.object,
};

LayoutContainer.propTypes = {
Expand All @@ -172,13 +174,15 @@ LayoutContainer.propTypes = {
pathname: PropTypes.string.isRequired,
}).isRequired,
children: PropTypes.element,
userSettings: PropTypes.object,
};

const mapStateToProps = (state, props) => ({
isLoggedIn: isLoggedIn(state),
sidebarIsCollapsed: isCollapsed(state),
sidebarIsOpen: isVisible(state),
pendingFetchOperations: anyPendingFetchOperations(state),
userSettings: getLoggedInUserSettings(state),
});

const mapDispatchToProps = { toggleVisibility, toggleSize, collapse, unroll };
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/contexts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react';

export const UserSettingsContext = React.createContext({});
10 changes: 9 additions & 1 deletion src/redux/selectors/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,25 @@ 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(
[usersSelector, loggedInUserIdSelector],
(users, id) => (id && users ? users.get(id) : null)
);

export const getLoggedInUserSettings = createSelector(
loggedInUserSelector,
userSettingsSelector
);

export const isLoggedAsSuperAdmin = createSelector(
[loggedInUserSelector],
loggedInUser =>
Expand Down

0 comments on commit 0e66b99

Please sign in to comment.