Merge pull request #10102 from BerkeleyTrue/feature/add-redux-night-mode

Feature(theme): add nightmode react logic
pull/10111/head
Mrugesh Mohapatra 2016-08-06 22:51:01 +05:30 committed by GitHub
commit 8579c16ac6
8 changed files with 106 additions and 8 deletions

View File

@ -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
];

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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');

View File

@ -599,6 +599,7 @@ module.exports = function(User) {
.toPromise();
};
// deprecated. remove once live
User.remoteMethod(
'updateTheme',
{

View File

@ -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);
}