Merge pull request #10102 from BerkeleyTrue/feature/add-redux-night-mode
Feature(theme): add nightmode react logicpull/10111/head
commit
8579c16ac6
|
@ -8,6 +8,7 @@ import codeStorageSaga from './code-storage-saga';
|
|||
import gitterSaga from './gitter-saga';
|
||||
import mouseTrapSaga from './mouse-trap-saga';
|
||||
import analyticsSaga from './analytics-saga';
|
||||
import nightModeSaga from './night-mode-saga';
|
||||
|
||||
export default [
|
||||
errSaga,
|
||||
|
@ -19,5 +20,6 @@ export default [
|
|||
codeStorageSaga,
|
||||
gitterSaga,
|
||||
mouseTrapSaga,
|
||||
analyticsSaga
|
||||
analyticsSaga,
|
||||
nightModeSaga
|
||||
];
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import { Observable } from 'rx';
|
||||
import { postJSON$ } from '../../common/utils/ajax-stream';
|
||||
import types from '../../common/app/redux/types';
|
||||
import {
|
||||
addThemeToBody,
|
||||
updateTheme,
|
||||
createErrorObservable
|
||||
} from '../../common/app/redux/actions';
|
||||
|
||||
export default function nightModeSaga(
|
||||
actions,
|
||||
getState,
|
||||
{ document: { body } }
|
||||
) {
|
||||
const toggleBodyClass = actions
|
||||
.filter(({ type }) => types.addThemeToBody === type)
|
||||
.doOnNext(({ payload: theme }) => {
|
||||
if (theme === 'night') {
|
||||
body.classList.add('night');
|
||||
} else {
|
||||
body.classList.remove('night');
|
||||
}
|
||||
})
|
||||
.filter(() => false);
|
||||
const toggle = actions
|
||||
.filter(({ type }) => types.toggleNightMode === type);
|
||||
|
||||
const optimistic = toggle
|
||||
.flatMap(() => {
|
||||
const { app: { theme } } = getState();
|
||||
const newTheme = !theme || theme === 'default' ? 'night' : 'default';
|
||||
return Observable.of(
|
||||
updateTheme(newTheme),
|
||||
addThemeToBody(newTheme)
|
||||
);
|
||||
});
|
||||
|
||||
const ajax = toggle
|
||||
.debounce(250)
|
||||
.flatMapLatest(() => {
|
||||
const { app: { theme, csrfToken: _csrf } } = getState();
|
||||
return postJSON$('/update-my-theme', { _csrf, theme })
|
||||
.catch(createErrorObservable);
|
||||
});
|
||||
|
||||
return Observable.merge(optimistic, toggleBodyClass, ajax);
|
||||
}
|
|
@ -188,4 +188,13 @@ export const closeHelpChat = createAction(
|
|||
})
|
||||
);
|
||||
|
||||
export const toggleNightMode = createAction(types.toggleNightMode);
|
||||
export const toggleNightMode = createAction(
|
||||
types.toggleNightMode,
|
||||
// we use this function to avoid hanging onto the eventObject
|
||||
// so that react can recycle it
|
||||
() => null
|
||||
);
|
||||
// updateTheme(theme: /night|default/) => Action
|
||||
export const updateTheme = createAction(types.updateTheme);
|
||||
// addThemeToBody(theme: /night|default/) => Action
|
||||
export const addThemeToBody = createAction(types.addThemeToBody);
|
||||
|
|
|
@ -5,11 +5,12 @@ import {
|
|||
updateThisUser,
|
||||
updateCompletedChallenges,
|
||||
createErrorObservable,
|
||||
showSignIn
|
||||
showSignIn,
|
||||
updateTheme,
|
||||
addThemeToBody
|
||||
} from './actions';
|
||||
|
||||
const { fetchUser } = types;
|
||||
|
||||
export default function getUserSaga(action$, getState, { services }) {
|
||||
return action$
|
||||
.filter(action => action.type === fetchUser)
|
||||
|
@ -19,10 +20,14 @@ export default function getUserSaga(action$, getState, { services }) {
|
|||
if (!entities || !result) {
|
||||
return Observable.just(showSignIn());
|
||||
}
|
||||
const user = entities.user[result];
|
||||
const isNightMode = user.theme === 'night';
|
||||
return Observable.of(
|
||||
addUser(entities),
|
||||
updateCompletedChallenges(result),
|
||||
updateThisUser(result),
|
||||
updateCompletedChallenges(result)
|
||||
isNightMode ? updateTheme(user.theme) : null,
|
||||
isNightMode ? addThemeToBody(user.theme) : null
|
||||
);
|
||||
})
|
||||
.catch(createErrorObservable);
|
||||
|
|
|
@ -10,7 +10,8 @@ const initialState = {
|
|||
windowHeight: 0,
|
||||
navHeight: 0,
|
||||
isMainChatOpen: false,
|
||||
isHelpChatOpen: false
|
||||
isHelpChatOpen: false,
|
||||
theme: 'default'
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
|
@ -29,6 +30,10 @@ export default handleActions(
|
|||
...state,
|
||||
lang: payload
|
||||
}),
|
||||
[types.updateTheme]: (state, { payload = 'default' }) => ({
|
||||
...state,
|
||||
theme: payload
|
||||
}),
|
||||
[types.showSignIn]: state => ({
|
||||
...state,
|
||||
shouldShowSignIn: true
|
||||
|
|
|
@ -18,7 +18,6 @@ export default createTypes([
|
|||
'updateMyCurrentChallenge',
|
||||
|
||||
'handleError',
|
||||
'toggleNightMode',
|
||||
// used to hit the server
|
||||
'hardGoTo',
|
||||
'delayedRedirect',
|
||||
|
@ -44,5 +43,10 @@ export default createTypes([
|
|||
|
||||
'openHelpChat',
|
||||
'closeHelpChat',
|
||||
'toggleHelpChat'
|
||||
'toggleHelpChat',
|
||||
|
||||
// night mode
|
||||
'toggleNightMode',
|
||||
'updateTheme',
|
||||
'addThemeToBody'
|
||||
], 'app');
|
||||
|
|
|
@ -599,6 +599,7 @@ module.exports = function(User) {
|
|||
.toPromise();
|
||||
};
|
||||
|
||||
// deprecated. remove once live
|
||||
User.remoteMethod(
|
||||
'updateTheme',
|
||||
{
|
||||
|
|
|
@ -64,8 +64,26 @@ export default function settingsController(app) {
|
|||
);
|
||||
}
|
||||
|
||||
function updateMyTheme(req, res, next) {
|
||||
req.checkBody('theme', 'Theme is invalid.').isLength({ min: 4 });
|
||||
const { body: { theme } } = req;
|
||||
const errors = req.validationErrors(true);
|
||||
if (errors) {
|
||||
return res.status(403).json({ errors });
|
||||
}
|
||||
if (req.user.theme === theme) {
|
||||
return res.json({ msg: 'Theme already set' });
|
||||
}
|
||||
return req.user.updateTheme('' + theme)
|
||||
.then(
|
||||
data => res.json(data),
|
||||
next
|
||||
);
|
||||
}
|
||||
|
||||
api.post(
|
||||
'/toggle-lockdown',
|
||||
ifNoUser401,
|
||||
toggleUserFlag('isLocked')
|
||||
);
|
||||
api.post(
|
||||
|
@ -99,5 +117,12 @@ export default function settingsController(app) {
|
|||
ifNoUser401,
|
||||
updateMyCurrentChallenge
|
||||
);
|
||||
|
||||
api.post(
|
||||
'/update-my-theme',
|
||||
ifNoUser401,
|
||||
updateMyTheme
|
||||
);
|
||||
|
||||
app.use(api);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue