Feature(mousetrap): Add mousetrap features to redux

This commit is contained in:
Berkeley Martinez
2016-06-17 22:20:20 -07:00
parent d1b78eba9b
commit d9e9af0a0f
11 changed files with 82 additions and 36 deletions

View File

@ -45,7 +45,8 @@ const sagaOptions = {
isDev, isDev,
window, window,
document: window.document, document: window.document,
location: window.location location: window.location,
history: window.history
}; };

View File

@ -1,16 +1,10 @@
import { hardGoTo } from '../../common/app/redux/types'; import { hardGoTo } from '../../common/app/redux/types';
export default function hardGoToSaga(action$, getState, { location }) { export default function hardGoToSaga(action$, getState, { history }) {
return action$ return action$
.filter(({ type }) => type === hardGoTo) .filter(({ type }) => type === hardGoTo)
.map(({ payload = '/map' }) => { .map(({ payload = '/settings' }) => {
if (!location.pathname) { history.push(history.state, null, payload);
return {
type: 'app.error',
error: new Error('no location object found')
};
}
location.pathname = payload;
return null; return null;
}); });
} }

View File

@ -6,6 +6,7 @@ import executeChallengeSaga from './execute-challenge-saga';
import frameSaga from './frame-saga'; import frameSaga from './frame-saga';
import codeStorageSaga from './code-storage-saga'; import codeStorageSaga from './code-storage-saga';
import gitterSaga from './gitter-saga'; import gitterSaga from './gitter-saga';
import mouseTrapSaga from './mouse-trap-saga';
export default [ export default [
errSaga, errSaga,
@ -15,5 +16,6 @@ export default [
executeChallengeSaga, executeChallengeSaga,
frameSaga, frameSaga,
codeStorageSaga, codeStorageSaga,
gitterSaga gitterSaga,
mouseTrapSaga
]; ];

View File

@ -0,0 +1,41 @@
import { Observable } from 'rx';
import MouseTrap from 'mousetrap';
import { push } from 'react-router-redux';
import {
toggleNightMode,
toggleMapDrawer,
toggleMainChat,
hardGoTo
} from '../../common/app/redux/actions';
function bindKey$(key, actionCreator) {
return Observable.fromEventPattern(
h => MouseTrap.bind(key, h),
h => MouseTrap.unbind(key, h)
)
.map(actionCreator);
}
const softRedirects = {
'g n n': '/challenges/next-challenge',
'g n a': '/about',
'g n m': '/map',
'g n w': '/wiki',
'g n s': '/shop',
'g n o': '/settings'
};
export default function mouseTrapSaga(actions$) {
const traps$ = [
...Object.keys(softRedirects)
.map(key => bindKey$(key, () => push(softRedirects[key]))),
bindKey$(
'g n r',
() => hardGoTo('https://github.com/freecodecamp/freecodecamp')
),
bindKey$('g m', toggleMapDrawer),
bindKey$('g t n', toggleNightMode),
bindKey$('g c', toggleMainChat)
];
return Observable.merge(traps$).takeUntil(actions$.last());
}

View File

@ -1,26 +1,22 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { hardGoTo } from '../../redux/actions';
const win = typeof window !== 'undefined' ? window : {}; export class NotFound extends React.Component {
function goToServer(path) {
win.location = '/' + path;
}
export default class extends React.Component {
static displayName = 'NotFound'; static displayName = 'NotFound';
static propTypes = { static propTypes = {
params: PropTypes.object location: PropTypes.object,
hardGoTo: PropTypes.func
}; };
componentWillMount() { componentWillMount() {
goToServer(this.props.params.splat); this.props.hardGoTo(this.props.location.pathname);
}
componentDidMount() {
} }
render() { render() {
return <span></span>; return <span></span>;
} }
} }
export default connect(null, { hardGoTo })(NotFound);

View File

@ -56,3 +56,4 @@ export const toggleWikiDrawer = createAction(types.toggleWikiDrawer);
// chat // chat
export const toggleMainChat = createAction(types.toggleMainChat); export const toggleMainChat = createAction(types.toggleMainChat);
export const toggleNightMode = createAction(types.toggleNightMode);

View File

@ -9,6 +9,7 @@ export default createTypes([
'makeToast', 'makeToast',
'updatePoints', 'updatePoints',
'handleError', 'handleError',
'toggleNightMode',
// used to hit the server // used to hit the server
'hardGoTo', 'hardGoTo',
'delayedRedirect', 'delayedRedirect',

View File

@ -1,8 +1,16 @@
import { modernChallenges, map, challenges } from './challenges'; import { modernChallenges, map, challenges } from './challenges';
import NotFound from '../components/NotFound/index.jsx'; import NotFound from '../components/NotFound/index.jsx';
import { addLang } from '../utils/add-lang';
export default { export default {
path: '/:lang', path: '/:lang',
indexRoute: {
onEnter(nextState, replace) {
const { lang } = nextState.params;
const { pathname } = nextState.location;
replace(addLang(pathname, lang));
}
},
childRoutes: [ childRoutes: [
challenges, challenges,
modernChallenges, modernChallenges,

View File

@ -1,19 +1,7 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router'; import { Link } from 'react-router';
import supportedLanguages from '../../utils/supported-languages'; import { addLang } from './add-lang';
const toLowerCase = String.prototype.toLowerCase;
function addLang(url, lang) {
const maybeLang = toLowerCase.call(url.split('/')[1]);
if (supportedLanguages[maybeLang]) {
return url;
}
if (supportedLanguages[lang]) {
return `/${lang}${url}`;
}
return `/en${url}`;
}
const mapStateToProps = state => ({ lang: state.app.lang }); const mapStateToProps = state => ({ lang: state.app.lang });

View File

@ -0,0 +1,13 @@
import supportedLanguages from '../../utils/supported-languages';
const toLowerCase = String.prototype.toLowerCase;
export function addLang(url, lang) {
const maybeLang = toLowerCase.call(url.split('/')[1]);
if (supportedLanguages[maybeLang]) {
return url;
}
if (supportedLanguages[lang]) {
return `/${lang}${url}`;
}
return `/en${url}`;
}

View File

@ -66,6 +66,7 @@
"moment-timezone": "^0.5.0", "moment-timezone": "^0.5.0",
"mongodb": "^2.0.33", "mongodb": "^2.0.33",
"morgan": "^1.6.1", "morgan": "^1.6.1",
"mousetrap": "^1.6.0",
"node-uuid": "^1.4.3", "node-uuid": "^1.4.3",
"nodemailer": "^2.1.0", "nodemailer": "^2.1.0",
"nodemailer-ses-transport": "^1.3.0", "nodemailer-ses-transport": "^1.3.0",