├── .gitignore
├── README.md
├── scripts
└── buildAll.sh
├── apps
├── react
│ ├── components
│ │ ├── NotFound.js
│ │ ├── InboxList.js
│ │ ├── Message.js
│ │ ├── App.js
│ │ ├── Main.js
│ │ ├── Nav.js
│ │ ├── Inbox.js
│ │ ├── InboxItem.js
│ │ └── Compose.js
│ ├── main.js
│ └── api.js
├── react-redux
│ ├── components
│ │ ├── NotFound.js
│ │ ├── InboxItem.js
│ │ ├── App.js
│ │ ├── Inbox.js
│ │ ├── Message.js
│ │ ├── Main.js
│ │ ├── InboxList.js
│ │ ├── Link.js
│ │ ├── Nav.js
│ │ └── Compose.js
│ ├── actions
│ │ └── draft.js
│ ├── reducers
│ │ ├── draft.js
│ │ └── emails.js
│ ├── main.js
│ └── store.js
├── routes.js
├── deku
│ ├── components
│ │ ├── NotFound.js
│ │ ├── App.js
│ │ ├── InboxList.js
│ │ ├── Message.js
│ │ ├── Nav.js
│ │ ├── InboxItem.js
│ │ ├── Main.js
│ │ ├── Inbox.js
│ │ └── Compose.js
│ ├── main.js
│ └── api.js
├── deku-redux
│ ├── components
│ │ ├── NotFound.js
│ │ ├── App.js
│ │ ├── InboxList.js
│ │ ├── Message.js
│ │ ├── InboxItem.js
│ │ ├── Main.js
│ │ ├── Inbox.js
│ │ ├── Nav.js
│ │ ├── Link.js
│ │ └── Compose.js
│ ├── actions
│ │ └── draft.js
│ ├── reducers
│ │ ├── draft.js
│ │ └── emails.js
│ ├── main.js
│ ├── store.js
│ └── api.js
├── cycle
│ ├── components
│ │ ├── Message.js
│ │ ├── InboxList.js
│ │ ├── Nav.js
│ │ ├── Inbox.js
│ │ ├── Main.js
│ │ └── Compose.js
│ ├── README.md
│ ├── routes.js
│ ├── create-router.js
│ ├── router5
│ │ ├── link-interceptor-plugin.js
│ │ ├── router-to-observable.js
│ │ ├── link-on-click.js
│ │ └── driver.js
│ ├── data
│ │ └── emails.js
│ └── main.js
├── cycle2
│ ├── routes.js
│ ├── create-router.js
│ ├── components
│ │ ├── Route.js
│ │ └── Nav.js
│ └── main.js
└── create-router.js
├── .editorconfig
├── index.html
├── example2.css
├── styles2.css
├── webpack.config.js
├── LICENSE
├── package.json
└── styles.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Moved to router5 repository](https://github.com/router5/router5/tree/master/packages/examples)
2 |
--------------------------------------------------------------------------------
/scripts/buildAll.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | npm run build:prod -- --app=react
3 | npm run build:prod -- --app=react-redux
4 | npm run build:prod -- --app=deku
5 | npm run build:prod -- --app=deku-redux
6 |
--------------------------------------------------------------------------------
/apps/react/components/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function NotFound(props) {
4 | return
Purposely Not found (not a bug)
;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/react-redux/components/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function NotFound(props) {
4 | return Purposely Not found (not a bug)
;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/routes.js:
--------------------------------------------------------------------------------
1 | export default [
2 | { name: 'inbox', path: '/inbox' },
3 | { name: 'inbox.message', path: '/message/:id' },
4 | { name: 'compose', path: '/compose' },
5 | { name: 'contacts', path: '/contacts' }
6 | ];
7 |
--------------------------------------------------------------------------------
/apps/deku/components/NotFound.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 |
3 | const NotFound = {
4 | render() {
5 | return element('div', { class: 'not-found' }, 'Purposely not found (not a bug)');
6 | }
7 | };
8 |
9 | export default NotFound;
10 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/NotFound.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 |
3 | const NotFound = {
4 | render() {
5 | return element('div', { class: 'not-found' }, 'Purposely not found (not a bug)');
6 | }
7 | };
8 |
9 | export default NotFound;
10 |
--------------------------------------------------------------------------------
/apps/cycle/components/Message.js:
--------------------------------------------------------------------------------
1 | import { h, h4, p } from '@cycle/dom';
2 |
3 | function Message({ email }) {
4 | return h('section', { className: 'mail' }, [
5 | h4(email.mailTitle),
6 | p(email.mailMessage)
7 | ]);
8 | }
9 |
10 | export default Message;
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 |
9 | charset = utf-8
10 | end_of_line = lf
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/apps/deku-redux/actions/draft.js:
--------------------------------------------------------------------------------
1 | export function updateTitle(title) {
2 | return {
3 | type: 'UPDATE_TITLE',
4 | title
5 | };
6 | }
7 |
8 | export function updateMessage(message) {
9 | return {
10 | type: 'UPDATE_MESSAGE',
11 | message
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/apps/cycle/README.md:
--------------------------------------------------------------------------------
1 | # Cycle and router5
2 |
3 | This is development work for a new router5 cycle driver.
4 |
5 | ## Build
6 |
7 | From the root directory this repo:
8 |
9 | ```sh
10 | npm run build -- --app cycle
11 | http-server -p 8080
12 | ```
13 |
14 | Navigate to http://localhost:8080.
15 |
16 |
--------------------------------------------------------------------------------
/apps/react-redux/actions/draft.js:
--------------------------------------------------------------------------------
1 | export function updateTitle(title) {
2 | return {
3 | type: 'UPDATE_TITLE',
4 | title
5 | };
6 | }
7 |
8 | export function updateMessage(message) {
9 | return {
10 | type: 'UPDATE_MESSAGE',
11 | message
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/apps/cycle2/routes.js:
--------------------------------------------------------------------------------
1 | const a = {
2 | name: 'a',
3 | path: '/a'
4 | };
5 |
6 | const b = {
7 | name: 'b',
8 | path: '/b'
9 | };
10 |
11 | const c = {
12 | name: 'c',
13 | path: '/c'
14 | };
15 |
16 | export default [
17 | { ...a, children: [a, b, c] },
18 | { ...b, children: [a, b, c] }
19 | ];
20 |
--------------------------------------------------------------------------------
/apps/react/components/InboxList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import InboxItem from './InboxItem';
3 |
4 | export default function InboxList(props) {
5 | return (
6 |
7 | { props.emails.map(mail => ) }
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/apps/cycle/routes.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | name: 'inbox',
4 | path: '/inbox'
5 | },
6 | {
7 | name: 'inbox.message',
8 | path: '/message/:id'
9 | },
10 | {
11 | name: 'compose',
12 | path: '/compose'
13 | },
14 | {
15 | name: 'contacts',
16 | path: '/contacts'
17 | }
18 | ];
19 |
--------------------------------------------------------------------------------
/apps/react-redux/components/InboxItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function InboxItem(props) {
4 | const { mailTitle, mailMessage, onClick } = props;
5 |
6 | return (
7 |
8 | { mailTitle }
9 | { mailMessage }
10 |
11 | );
12 | }
13 |
14 | export default InboxItem;
15 |
--------------------------------------------------------------------------------
/apps/react/components/Message.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { getEmail } from '../api';
3 |
4 | export default function Message(props) {
5 | const { mailTitle, mailMessage } = getEmail(props.messageId);
6 |
7 | return (
8 |
9 | { mailTitle }
10 | { mailMessage }
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/apps/deku/components/App.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import Nav from './Nav';
3 | import Main from './Main';
4 |
5 | const App = {
6 | render({ props }) {
7 | return element('div', {class: 'mail-client'}, [
8 | element('aside', {}, element(Nav)),
9 | element('main', {}, element(Main))
10 | ]);
11 | }
12 | }
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/App.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import Nav from './Nav';
3 | import Main from './Main';
4 |
5 | const App = {
6 | render({ props }) {
7 | return element('div', {class: 'mail-client'}, [
8 | element('aside', {}, element(Nav)),
9 | element('main', {}, element(Main))
10 | ]);
11 | }
12 | }
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/apps/deku/components/InboxList.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import InboxItem from './InboxItem';
3 |
4 | const InboxList = {
5 | render({ props }) {
6 | return element(
7 | 'ul',
8 | { class: 'mail-list' },
9 | props.emails.map(mail => element(InboxItem, { ...mail, key: mail.id }))
10 | );
11 | }
12 | };
13 |
14 | export default InboxList;
15 |
--------------------------------------------------------------------------------
/apps/react/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Nav from './Nav';
3 | import Main from './Main';
4 |
5 | export default function App(props) {
6 | return (
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/InboxList.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import InboxItem from './InboxItem';
3 |
4 | const InboxList = {
5 | render({ props }) {
6 | return element(
7 | 'ul',
8 | { class: 'mail-list' },
9 | props.emails.map(mail => element(InboxItem, { ...mail, key: mail.id }))
10 | );
11 | }
12 | };
13 |
14 | export default InboxList;
15 |
--------------------------------------------------------------------------------
/apps/react-redux/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Nav from './Nav';
3 | import Main from './Main';
4 |
5 | export default function App(props) {
6 | return (
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/apps/cycle/create-router.js:
--------------------------------------------------------------------------------
1 | import createRouter, { loggerPlugin } from 'router5';
2 | import browserPlugin from 'router5/plugins/browser';
3 |
4 | const configureRouter = (routes) => {
5 | return createRouter(routes, {
6 | defaultRoute: 'inbox'
7 | })
8 | .usePlugin(loggerPlugin)
9 | .usePlugin(browserPlugin({
10 | useHash: true
11 | }));
12 | };
13 |
14 | export default configureRouter;
15 |
--------------------------------------------------------------------------------
/apps/deku/components/Message.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import { getEmail } from '../api';
3 |
4 | const Message = {
5 | render({ props }) {
6 | const { mailTitle, mailMessage } = getEmail(props.messageId);
7 |
8 | return element('section', { class: 'mail' }, [
9 | element('h4', {}, mailTitle),
10 | element('p', {}, mailMessage)
11 | ]);
12 | }
13 | };
14 |
15 | export default Message;
16 |
--------------------------------------------------------------------------------
/apps/cycle/components/InboxList.js:
--------------------------------------------------------------------------------
1 | import { ul, li, a, h4, p } from '@cycle/dom';
2 |
3 | function InboxList({ emails, buildUrl }) {
4 | return ul({ className: 'mail-list' }, [
5 | emails.map(({ id, mailTitle, mailMessage }) => li(
6 | a({ href: buildUrl('inbox.message', { id }) }, [
7 | h4(mailTitle),
8 | p(mailMessage)
9 | ])
10 | ))
11 | ]);
12 | }
13 |
14 | export default InboxList;
15 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/Message.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import { getEmail } from '../api';
3 |
4 | const Message = {
5 | render({ props }) {
6 | const { mailTitle, mailMessage } = getEmail(props.messageId);
7 |
8 | return element('section', { class: 'mail' }, [
9 | element('h4', {}, mailTitle),
10 | element('p', {}, mailMessage)
11 | ]);
12 | }
13 | };
14 |
15 | export default Message;
16 |
--------------------------------------------------------------------------------
/apps/react/main.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import React from 'react';
3 | import App from './components/App';
4 | import { RouterProvider } from 'react-router5';
5 | import createRouter from '../create-router';
6 |
7 | const router = createRouter(true);
8 | const app = ;
9 |
10 | router.start(() => {
11 | ReactDOM.render(
12 | app,
13 | document.getElementById('app')
14 | );
15 | });
16 |
--------------------------------------------------------------------------------
/apps/deku/main.js:
--------------------------------------------------------------------------------
1 | import { tree, render } from 'deku';
2 | import element from 'virtual-element';
3 | import { routerPlugin } from 'deku-router5';
4 | import App from './components/App';
5 | import createRouter from '../create-router';
6 |
7 | const router = createRouter(true);
8 |
9 | const app = tree()
10 | .use(routerPlugin(router))
11 | .mount(element(App));
12 |
13 | router.start(function (err, state) {
14 | render(app, document.getElementById('app'));
15 | });
16 |
--------------------------------------------------------------------------------
/apps/deku/components/Nav.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import { Link } from 'deku-router5';
3 |
4 | const Nav = {
5 | render({ props }) {
6 | return element('nav', {}, [
7 | element(Link, { routeName: 'inbox', routeOptions: { reload: true } }, 'Inbox'),
8 | element(Link, { routeName: 'compose' }, 'Compose'),
9 | element(Link, { routeName: 'contacts' }, 'Contacts')
10 | ]);
11 | }
12 | };
13 |
14 | export default Nav;
15 |
--------------------------------------------------------------------------------
/apps/deku/components/InboxItem.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 |
3 | const InboxItem = {
4 | propTypes: {
5 | router: {source: 'router'},
6 | },
7 |
8 | render({ props }) {
9 | const { mailTitle, mailMessage, router, id } = props;
10 |
11 | return element('li', { onClick: () => router.navigate('inbox.message', { id }) }, [
12 | element('h4', {}, mailTitle),
13 | element('p', {}, mailMessage)
14 | ]);
15 | }
16 | };
17 |
18 | export default InboxItem;
19 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/InboxItem.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 |
3 | const InboxItem = {
4 | propTypes: {
5 | router: {source: 'router'},
6 | },
7 |
8 | render({ props }) {
9 | const { mailTitle, mailMessage, router, id } = props;
10 |
11 | return element('li', { onClick: () => router.navigate('inbox.message', { id }) }, [
12 | element('h4', {}, mailTitle),
13 | element('p', {}, mailMessage)
14 | ]);
15 | }
16 | };
17 |
18 | export default InboxItem;
19 |
--------------------------------------------------------------------------------
/apps/react/components/Main.js:
--------------------------------------------------------------------------------
1 | import React, { createElement } from 'react';
2 | import { routeNode } from 'react-router5';
3 | import Inbox from './Inbox';
4 | import Compose from './Compose';
5 | import NotFound from './NotFound';
6 |
7 | const components = {
8 | 'inbox': Inbox,
9 | 'compose': Compose
10 | };
11 |
12 | function Main(props) {
13 | const { route } = props;
14 | const segment = route.name.split('.')[0];
15 |
16 | return createElement(components[segment] || NotFound);
17 | }
18 |
19 | export default routeNode('')(Main);
20 |
--------------------------------------------------------------------------------
/apps/react/components/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BaseLink, withRoute } from 'react-router5';
3 |
4 | function Nav(props) {
5 | const { router } = props;
6 |
7 | return (
8 |
13 | );
14 | }
15 |
16 | export default withRoute(Nav);
17 |
--------------------------------------------------------------------------------
/apps/cycle2/create-router.js:
--------------------------------------------------------------------------------
1 | import createRouter, { loggerPlugin } from 'router5';
2 | import browserPlugin from 'router5/plugins/browser';
3 | import linkInterceptorPlugin from '../cycle/router5/link-interceptor-plugin';
4 |
5 | const configureRouter = (routes) => {
6 | return createRouter(routes, {
7 | defaultRoute: 'a'
8 | })
9 | .usePlugin(loggerPlugin)
10 | .usePlugin(browserPlugin({
11 | useHash: true
12 | }))
13 | .usePlugin(linkInterceptorPlugin());
14 | };
15 |
16 | export default configureRouter;
17 |
--------------------------------------------------------------------------------
/apps/deku-redux/reducers/draft.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | title: '',
3 | message: ''
4 | };
5 |
6 | export default function draft(state = initialState, action) {
7 | switch (action.type) {
8 | case 'UPDATE_TITLE':
9 | return {
10 | ...state,
11 | title: action.title
12 | };
13 |
14 | case 'UPDATE_MESSAGE':
15 | return {
16 | ...state,
17 | message: action.message
18 | };
19 |
20 | default:
21 | return state;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/react-redux/reducers/draft.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | title: '',
3 | message: ''
4 | };
5 |
6 | export default function draft(state = initialState, action) {
7 | switch (action.type) {
8 | case 'UPDATE_TITLE':
9 | return {
10 | ...state,
11 | title: action.title
12 | };
13 |
14 | case 'UPDATE_MESSAGE':
15 | return {
16 | ...state,
17 | message: action.message
18 | };
19 |
20 | default:
21 | return state;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/cycle2/components/Route.js:
--------------------------------------------------------------------------------
1 | import { div } from '@cycle/dom';
2 | import randomColor from 'randomcolor';
3 |
4 | const randomBgColor = () => ({ backgroundColor: randomColor() });
5 |
6 | const Route = node => sources => {
7 | console.log(node);
8 | const route$ = sources.router.routeNode$(node);
9 |
10 | const vDom$ = route$
11 | .map(route => div(
12 | { className: 'item row', style: randomBgColor() },
13 | route ? route.name : '')
14 | );
15 |
16 | return {
17 | DOM: vDom$
18 | };
19 | }
20 |
21 | export default Route;
22 |
--------------------------------------------------------------------------------
/apps/deku/components/Main.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import { routeNode } from 'deku-router5';
3 | import Inbox from './Inbox';
4 | import Compose from './Compose';
5 | import NotFound from './NotFound';
6 |
7 | const components = {
8 | 'inbox': Inbox,
9 | 'compose': Compose
10 | };
11 |
12 | const Main = {
13 | render({ props }) {
14 | const { route } = props;
15 | const segment = route ? route.name.split('.')[0] : undefined;
16 |
17 | return element(components[segment] || NotFound);
18 | }
19 | };
20 |
21 | export default routeNode('')(Main);
22 |
--------------------------------------------------------------------------------
/apps/react/components/Inbox.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import InboxList from './InboxList';
4 | import Message from './Message';
5 | import { routeNode } from 'react-router5';
6 | import { getEmails } from '../api';
7 |
8 | function Inbox(props) {
9 | const { route } = props;
10 |
11 | return (
12 |
13 |
14 | { route.name === 'inbox.message' ? : null }
15 |
16 | );
17 | }
18 |
19 | export default routeNode('inbox')(Inbox);
20 |
--------------------------------------------------------------------------------
/apps/deku-redux/main.js:
--------------------------------------------------------------------------------
1 | import { tree, render } from 'deku';
2 | import element from 'virtual-element';
3 | import { storePlugin } from 'deku-redux';
4 | import { routerPlugin } from 'deku-router5';
5 | import App from './components/App';
6 | import createRouter from '../create-router'
7 | import configureStore from './store';
8 |
9 | const router = createRouter();
10 | const store = configureStore(router);
11 |
12 | const app = tree()
13 | .use(storePlugin(store))
14 | .set('router', router)
15 | .mount(element(App));
16 |
17 | router.start((err, state) => {
18 | render(app, document.getElementById('app'));
19 | });
20 |
--------------------------------------------------------------------------------
/apps/react-redux/components/Inbox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import InboxList from './InboxList';
3 | import Message from './Message';
4 | import { connect } from 'react-redux';
5 | import { routeNodeSelector } from 'redux-router5';
6 |
7 | function Inbox(props) {
8 | const { route } = props;
9 |
10 | return (
11 |
12 |
13 | { route.name === 'inbox.message' ? : null }
14 |
15 | );
16 | }
17 |
18 | export default connect((state) => routeNodeSelector('inbox'))(Inbox);
19 |
--------------------------------------------------------------------------------
/apps/react-redux/components/Message.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import find from 'lodash.find';
4 |
5 | function mapStateToProps(state, props) {
6 | return {
7 | email: find(state.emails, { id: props.messageId })
8 | };
9 | }
10 |
11 | export default function Message(props) {
12 | const { mailTitle, mailMessage } = props.email;
13 |
14 | return (
15 |
16 | { mailTitle }
17 | { mailMessage }
18 |
19 | );
20 | }
21 |
22 | export default connect(mapStateToProps)(Message);
23 |
--------------------------------------------------------------------------------
/apps/cycle/components/Nav.js:
--------------------------------------------------------------------------------
1 | import { h, a } from '@cycle/dom';
2 |
3 | function Nav(sources) {
4 | const routerSource = sources.router;
5 |
6 | const nav$ = routerSource
7 | .route$
8 | .map(route =>
9 | h('nav', [
10 | a({ href: sources.router.buildUrl('inbox'), className: routerSource.isActive('inbox') ? 'active' : '' }, 'Inbox'),
11 | a({ href: sources.router.buildUrl('compose'), className: routerSource.isActive('compose') ? 'active' : '' }, 'Compose')
12 | ])
13 | );
14 |
15 | return {
16 | DOM: nav$
17 | };
18 | };
19 |
20 | export default Nav;
21 |
--------------------------------------------------------------------------------
/apps/react-redux/components/Main.js:
--------------------------------------------------------------------------------
1 | import React, { createElement } from 'react';
2 | import { connect } from 'react-redux';
3 | import { routeNodeSelector } from 'redux-router5';
4 | import Inbox from './Inbox';
5 | import Compose from './Compose';
6 | import NotFound from './NotFound';
7 |
8 | const components = {
9 | 'inbox': Inbox,
10 | 'compose': Compose
11 | };
12 |
13 | function Main(props) {
14 | const { route } = props;
15 | const segment = route ? route.name.split('.')[0] : undefined;
16 |
17 | return createElement(components[segment] || NotFound);
18 | }
19 |
20 | export default connect((state) => routeNodeSelector(''))(Main);
21 |
--------------------------------------------------------------------------------
/apps/cycle/router5/link-interceptor-plugin.js:
--------------------------------------------------------------------------------
1 | import { shouldInterceptEvent, onClick } from './link-on-click';
2 |
3 | const linkInterceptorPlugin = () => (router) => {
4 | const listener = (evt) => {
5 | if (shouldInterceptEvent(router)(evt)) {
6 | onClick(router)(evt);
7 | }
8 | };
9 |
10 | return {
11 | name: 'LINK_INTERCEPTOR',
12 | onStart: () => {
13 | document.addEventListener('click', listener, false);
14 | },
15 | onStop: () => {
16 | document.removeEventListener('click', listener);
17 | }
18 | }
19 | };
20 |
21 | export default linkInterceptorPlugin;
22 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/Main.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import Inbox from './Inbox';
3 | import Compose from './Compose';
4 | import NotFound from './NotFound';
5 | import { connect } from 'deku-redux';
6 | import { routeNodeSelector } from 'redux-router5';
7 |
8 | const components = {
9 | 'inbox': Inbox,
10 | 'compose': Compose
11 | };
12 |
13 | const Main = {
14 | render({ props }) {
15 | const { route } = props;
16 | const segment = route ? route.name.split('.')[0] : undefined;
17 |
18 | return element(components[segment] || NotFound);
19 | }
20 | };
21 |
22 | export default connect((state) => routeNodeSelector(''))(Main);
23 |
--------------------------------------------------------------------------------
/apps/deku/components/Inbox.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import InboxList from './InboxList';
3 | import Message from './Message';
4 | import { routeNode } from 'deku-router5';
5 | import { getEmails } from '../api';
6 |
7 | const Inbox = {
8 | displayName: 'Inbox',
9 | render({ props }) {
10 | const { route } = props;
11 |
12 | return element('div', { class: 'inbox' }, [
13 | element(InboxList, { emails: getEmails() }),
14 | route && route.name === 'inbox.message' ? element(Message, { messageId: route.params.id, key: route.params.id }) : null
15 | ]);
16 | }
17 | };
18 |
19 | export default routeNode('inbox')(Inbox);
20 |
--------------------------------------------------------------------------------
/apps/react-redux/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { RouterProvider } from 'react-router5';
4 | import ReactDOM from 'react-dom';
5 | import App from './components/App';
6 | import createRouter from '../create-router'
7 | import configureStore from './store';
8 |
9 | const router = createRouter();
10 | const store = configureStore(router);
11 | const wrappedApp = (
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 | router.start((err, state) => {
20 | ReactDOM.render(wrappedApp, document.getElementById('app'));
21 | });
22 |
--------------------------------------------------------------------------------
/apps/react-redux/components/InboxList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import InboxItem from './InboxItem';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators } from 'redux';
5 | import { actions } from 'redux-router5';
6 |
7 | function InboxList({ emails, navigateTo }) {
8 | return (
9 |
10 | { emails.map(mail => navigateTo('inbox.message', { id: mail.id }) } />) }
11 |
12 | );
13 | }
14 |
15 | export default connect(
16 | state => ({ emails: state.emails }),
17 | dispatch => bindActionCreators({ navigateTo: actions.navigateTo }, dispatch)
18 | )(InboxList);
19 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Router5 examples
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/apps/create-router.js:
--------------------------------------------------------------------------------
1 | import createRouter from 'router5';
2 | import loggerPlugin from 'router5/plugins/logger';
3 | import listenersPlugin from 'router5/plugins/listeners';
4 | import browserPlugin from 'router5/plugins/browser';
5 | import routes from './routes';
6 |
7 | export default function configureRouter(useListenersPlugin = false) {
8 | const router = createRouter(routes, {
9 | defaultRoute: 'inbox'
10 | })
11 | // Plugins
12 | .usePlugin(loggerPlugin)
13 | .usePlugin(browserPlugin({
14 | useHash: true
15 | }));
16 |
17 | if (useListenersPlugin) {
18 | router.usePlugin(listenersPlugin());
19 | }
20 |
21 | return router;
22 | }
23 |
--------------------------------------------------------------------------------
/apps/react-redux/store.js:
--------------------------------------------------------------------------------
1 | import { compose, createStore, applyMiddleware, combineReducers } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { router5Middleware, router5Reducer } from 'redux-router5';
4 | import emails from './reducers/emails';
5 | import draft from './reducers/draft';
6 | import logger from 'redux-logger';
7 |
8 | export default function configureStore(router, initialState = {}) {
9 | const createStoreWithMiddleware = applyMiddleware(router5Middleware(router), logger())(createStore);
10 | const store = createStoreWithMiddleware(combineReducers({
11 | router: router5Reducer,
12 | emails,
13 | draft
14 | }), initialState);
15 |
16 | window.store = store;
17 | return store;
18 | }
19 |
--------------------------------------------------------------------------------
/apps/deku-redux/store.js:
--------------------------------------------------------------------------------
1 | import { compose, createStore, applyMiddleware, combineReducers } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { router5Middleware, router5Reducer } from 'redux-router5';
4 | import emails from './reducers/emails';
5 | import draft from './reducers/draft';
6 | import logger from 'redux-logger';
7 |
8 | export default function configureStore(router, initialState = {}) {
9 | const createStoreWithMiddleware = applyMiddleware(router5Middleware(router), logger())(createStore);
10 |
11 | const store = createStoreWithMiddleware(combineReducers({
12 | router: router5Reducer,
13 | emails,
14 | draft
15 | }), initialState);
16 |
17 | window.store = store;
18 | return store;
19 | }
20 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/Inbox.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import InboxList from './InboxList';
3 | import Message from './Message';
4 | import { connect } from 'deku-redux';
5 | import { routeNodeSelector } from 'redux-router5';
6 | import { getEmails } from '../api';
7 |
8 | const Inbox = {
9 | displayName: 'Inbox',
10 | render({ props }) {
11 | const { route } = props;
12 |
13 | return element('div', { class: 'inbox' }, [
14 | element(InboxList, { emails: getEmails() }),
15 | route && route.name === 'inbox.message' ? element(Message, { messageId: route.params.id, key: route.params.id }) : null
16 | ]);
17 | }
18 | };
19 |
20 | export default connect((state) => routeNodeSelector('inbox'))(Inbox);
21 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/Nav.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import Link from './Link';
3 | import { connect } from 'deku-redux';
4 | import { actions } from 'redux-router5';
5 |
6 | const Nav = {
7 | propTypes: {
8 | router: { source: 'router' }
9 | },
10 |
11 | render({ props }) {
12 | const { router, navigateTo } = props;
13 |
14 | return element('nav', {}, [
15 | element(Link, { router, navigateTo, name: 'inbox', options: { reload: true } }, 'Inbox'),
16 | element(Link, { router, navigateTo, name: 'compose' }, 'Compose'),
17 | element(Link, { router, navigateTo, name: 'contacts' }, 'Contacts')
18 | ]);
19 | }
20 | };
21 |
22 | export default connect(
23 | state => state.router.route,
24 | { navigateTo: actions.navigateTo }
25 | )(Nav);
26 |
--------------------------------------------------------------------------------
/apps/react/components/InboxItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | class InboxItem extends Component {
4 | constructor(props, context) {
5 | super(props, context);
6 |
7 | this.router = context.router;
8 | this.clickHandler = this.clickHandler.bind(this);
9 | }
10 |
11 | clickHandler() {
12 | this.router.navigate('inbox.message', {id: this.props.id})
13 | }
14 |
15 | render() {
16 | var { mailTitle, mailMessage } = this.props;
17 |
18 | return (
19 |
20 | { mailTitle }
21 | { mailMessage }
22 |
23 | );
24 | }
25 | }
26 |
27 | InboxItem.contextTypes = {
28 | router: PropTypes.object.isRequired
29 | };
30 |
31 | export default InboxItem;
32 |
--------------------------------------------------------------------------------
/apps/cycle2/components/Nav.js:
--------------------------------------------------------------------------------
1 | import { h, a } from '@cycle/dom';
2 |
3 | const LinkFactory = router => route => a({
4 | href: router.buildUrl(route),
5 | className: router.isActive(route, {}, true) ? 'active' : ''
6 | }, route);
7 |
8 | function Nav(sources) {
9 | const routerSource = sources.router;
10 | const Link = LinkFactory(routerSource);
11 |
12 | const nav$ = routerSource
13 | .route$
14 | .map(route =>
15 | h('nav', [
16 | Link('a'),
17 | Link('a.a'),
18 | Link('a.b'),
19 | Link('a.c'),
20 | Link('b'),
21 | Link('b.a'),
22 | Link('b.b'),
23 | Link('b.c')
24 | ])
25 | );
26 |
27 | return {
28 | DOM: nav$
29 | };
30 | };
31 |
32 | export default Nav;
33 |
--------------------------------------------------------------------------------
/apps/react-redux/components/Link.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | function Link(props) {
4 | const { name, params, options, router, navigateTo } = props;
5 |
6 | const href = router.buildUrl(name, params);
7 | const onClick = (evt) => {
8 | evt.preventDefault();
9 | navigateTo(name, params, options);
10 | };
11 | const className = router.isActive(name, params) ? 'active' : '';
12 |
13 | return { props.children }
14 | }
15 |
16 | Link.propTypes = {
17 | name: PropTypes.string.isRequired,
18 | params: PropTypes.object,
19 | options: PropTypes.object,
20 | navigateTo: PropTypes.func.isRequired
21 | };
22 |
23 | Link.defaultProps = {
24 | params: {},
25 | options: {}
26 | };
27 |
28 | export default Link;
29 |
--------------------------------------------------------------------------------
/apps/deku/components/Compose.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import { routeNode } from 'deku-router5';
3 |
4 | const Compose = {
5 | intitalState(props) {
6 | return { title: '', message: '' };
7 | },
8 |
9 | render({ state }, setState) {
10 | const { title, message } = state;
11 |
12 | const updateState = prop => evt => setState(prop, evt.target.value);
13 |
14 | return element('div', { class: 'compose' }, [
15 | element('h4', {}, 'Compose a new message'),
16 | element('input', { name: 'title', value: title, onChange: updateState('title') }),
17 | element('textarea', { name: 'message', value: message, onChange: updateState('message') })
18 | ]);
19 | // { warning ? Clear inputs before continuing
: null }
20 | }
21 | };
22 |
23 | export default routeNode('compose')(Compose);
24 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/Link.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 |
3 | const Link = {
4 | propTypes: {
5 | name: { type: 'string' },
6 | params: { type: 'object' },
7 | options: { type: 'object' },
8 | navigateTo: { type: 'function' }
9 | },
10 |
11 | defaultProps: {
12 | params: {},
13 | options: {}
14 | },
15 |
16 | render({ props }) {
17 | const { name, params, options, router, navigateTo } = props;
18 |
19 | const href = router.buildUrl(name);
20 | const onClick = (evt) => {
21 | evt.preventDefault();
22 | navigateTo(name, params, options);
23 | };
24 | const className = router.isActive(name, params) ? 'active' : '';
25 |
26 | return element('a', { href, onClick, 'class': className }, props.children);
27 | }
28 | };
29 |
30 | export default Link;
31 |
--------------------------------------------------------------------------------
/example2.css:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Example 2
4 | */
5 |
6 | .box {
7 | position: absolute:
8 | top: 0;
9 | right: 0;
10 | bottom: 0;
11 | left: 0;
12 | }
13 |
14 | .box > aside {
15 | background-color: #ddd;
16 | position: absolute;
17 | top: 0;
18 | width: 40%;
19 | bottom: 0;
20 | left: 0;
21 | }
22 |
23 | .box > main {
24 | position: absolute;
25 | display: flex;
26 | top: 0;
27 | right: 0;
28 | bottom: 0;
29 | left: 40%;
30 | }
31 |
32 | .box > aside a {
33 | padding: 10px 40px 10px 20px;
34 | display: block;
35 | text-align: right;
36 | }
37 |
38 | .box > aside a.active {
39 | color: white;
40 | background-color: black;
41 | }
42 |
43 | .column {
44 | flex: 1;
45 | display: flex;
46 | flex-direction: column;
47 | }
48 |
49 | .item {
50 | display: flex;
51 | flex: 1;
52 | justify-content: center;
53 | align-items: center;
54 | font-weight: bold;
55 | font-size: 30px;
56 | color: white;
57 | }
58 |
--------------------------------------------------------------------------------
/styles2.css:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Example 2
4 | */
5 |
6 | .box {
7 | position: absolute:
8 | top: 0;
9 | right: 0;
10 | bottom: 0;
11 | left: 0;
12 | }
13 |
14 | .box > aside {
15 | background-color: #ddd;
16 | position: absolute;
17 | top: 0;
18 | width: 40%;
19 | bottom: 0;
20 | left: 0;
21 | }
22 |
23 | .box > main {
24 | position: absolute;
25 | display: flex;
26 | top: 0;
27 | right: 0;
28 | bottom: 0;
29 | left: 40%;
30 | }
31 |
32 | .box > aside a {
33 | padding: 10px 40px 10px 20px;
34 | display: block;
35 | text-align: right;
36 | }
37 |
38 | .box > aside a.active {
39 | color: white;
40 | background-color: black;
41 | }
42 |
43 | .column {
44 | flex: 1;
45 | display: flex;
46 | flex-direction: column;
47 | }
48 |
49 | .item {
50 | display: flex;
51 | flex: 1;
52 | justify-content: center;
53 | align-items: center;
54 | font-weight: bold;
55 | font-size: 30px;
56 | color: white;
57 | }
58 |
--------------------------------------------------------------------------------
/apps/cycle/data/emails.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | "id": "1",
4 | "mailTitle": "Why router5?",
5 | "mailMessage": "I imagine a lot of developers who will first see router5 will ask themselves the question: is it yet another router? is it any good? Why oh why do people keep writing new routers all the time? It is not always easy to see the potential of something straight away, or understand the motivations behind. I therefore decided to try to tell you more about router5, why I decided to develop an entire new routing solution, and what problems it tries to solve."
6 | },
7 | {
8 | "id": "2",
9 | "mailTitle": "Use with Cycle",
10 | "mailMessage": "Make a driver using your router instance, observe route and node changes and request navigation"
11 | },
12 | {
13 | "id": "3",
14 | "mailTitle": "Compose a new message",
15 | "mailMessage": "Click on compose, start to fill title and message fields and then try to navigate away by clicking on app links, or by using the back button."
16 | }
17 | ];
18 |
--------------------------------------------------------------------------------
/apps/cycle/components/Inbox.js:
--------------------------------------------------------------------------------
1 | import Rx from 'rx';
2 | import { h, div } from '@cycle/dom';
3 | import InboxList from './InboxList';
4 | import Message from './Message';
5 |
6 | function Inbox(sources) {
7 | const emails$ = Rx.Observable.combineLatest(
8 | sources.router.routeNode$('inbox'),
9 | sources.data.emails$,
10 | (route, emails) => {
11 | const email = route.name === 'inbox.message'
12 | ? emails.find(({ id }) => id === route.params.id)
13 | : null;
14 |
15 | return { emails, email };
16 | }
17 | );
18 |
19 | const inbox$ = emails$
20 | .map(({ emails, email }) => {
21 | const inboxList = InboxList({ emails, buildUrl: sources.router.buildUrl });
22 | const message = email && Message({ email });
23 |
24 | return div({ className: 'inbox' }, [
25 | inboxList,
26 | message
27 | ]);
28 | });
29 |
30 | return {
31 | DOM: inbox$
32 | };
33 | };
34 |
35 | export default Inbox;
36 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | var argv = require('yargs').argv;
5 | var app = argv.app || 'react';
6 | var isProd = process.env.NODE_ENV === 'production';
7 |
8 | module.exports = {
9 | entry: [
10 | './apps/' + app + '/main.js'
11 | ],
12 | output: {
13 | path: 'build/',
14 | filename: isProd ? 'router5-' + app + '-example.js' : 'app.js'
15 | },
16 | plugins: [
17 | new webpack.optimize.OccurenceOrderPlugin()
18 | ].concat(!isProd ? [] : [
19 | new webpack.optimize.UglifyJsPlugin({
20 | compressor: {
21 | warnings: false
22 | }
23 | }),
24 | new webpack.DefinePlugin({
25 | 'process.env': {
26 | 'NODE_ENV': JSON.stringify('production')
27 | }
28 | })
29 | ]),
30 | module: {
31 | loaders: [{
32 | test: /\.js$/,
33 | loaders: ['babel'],
34 | include: path.join(__dirname, 'apps')
35 | }]
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/apps/cycle/components/Main.js:
--------------------------------------------------------------------------------
1 | import Rx from 'rx';
2 | import { h, div, a, makeDOMDriver } from '@cycle/dom';
3 | import { startsWithSegment } from 'router5.helpers';
4 | import Inbox from './Inbox';
5 | import Compose from './Compose';
6 |
7 | function Main(sources) {
8 | const routerSource = sources.router;
9 |
10 | const routeComponent$ = routerSource
11 | .routeNode$('')
12 | .map(route => {
13 | const startsWith = startsWithSegment(route);
14 |
15 | if (startsWith('inbox')) {
16 | return Inbox(sources);
17 | }
18 |
19 | if (startsWith('compose')) {
20 | return Compose(sources);
21 | }
22 |
23 | return {
24 | DOM: Rx.Observable.of(div('Route component not implemented'))
25 | };
26 | });
27 |
28 | return {
29 | DOM: routeComponent$
30 | .flatMapLatest(component => component.DOM),
31 | router: routeComponent$
32 | .flatMapLatest(component => component.router || Rx.Observable.empty())
33 | };
34 | };
35 |
36 | export default Main;
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 router5
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/apps/react-redux/components/Nav.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import Link from './Link';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators } from 'redux';
5 | import { actions } from 'redux-router5';
6 |
7 | class Nav extends Component {
8 | constructor(props, context) {
9 | super(props);
10 | this.router = context.router;
11 | }
12 |
13 | render() {
14 | const { navigateTo, route } = this.props;
15 |
16 | return (
17 |
22 | );
23 | }
24 | }
25 |
26 | Nav.contextTypes = {
27 | router: PropTypes.object.isRequired
28 | };
29 |
30 | export default connect(
31 | state => state.router.route,
32 | dispatch => bindActionCreators({ navigateTo: actions.navigateTo }, dispatch)
33 | )(Nav);
34 |
--------------------------------------------------------------------------------
/apps/cycle/router5/router-to-observable.js:
--------------------------------------------------------------------------------
1 | const routerToObservable = (router, onCreate) =>
2 | Rx.Observable.create(observer => {
3 | const pushState = (type, isError) => (toState, fromState, ...args) => {
4 | const routerEvt = { type, toState, fromState };
5 | observer.onNext(isError ? { ...routerEvt, error: args[0] } : routerEvt);
6 | };
7 | const push = type => () => observer.onNext({ type });
8 |
9 | // A Router5 plugin to push any router event to the observer
10 | const cyclePlugin = () => ({
11 | name: 'CYCLE_DRIVER',
12 | onStart: push('start'),
13 | onStop: push('stop'),
14 | onTransitionSuccess: pushState('transitionSuccess'),
15 | onTransitionError: pushState('transitionError', true),
16 | onTransitionStart: pushState('transitionStart'),
17 | onTransitionCancel: pushState('transitionCancel')
18 | });
19 |
20 | // Register plugin and start
21 | router.usePlugin(cyclePlugin);
22 |
23 | // On observable create callback (used to start router)
24 | onCreate && onCreate();
25 | });
26 |
27 | export default routerToObservable;
28 |
--------------------------------------------------------------------------------
/apps/deku-redux/reducers/emails.js:
--------------------------------------------------------------------------------
1 | const initialState = [
2 | {
3 | "id": "1",
4 | "mailTitle": "Why router5?",
5 | "mailMessage": "I imagine a lot of developers who will first see router5 will ask themselves the question: is it yet another router? is it any good? Why oh why do people keep writing new routers all the time? It is not always easy to see the potential of something straight away, or understand the motivations behind. I therefore decided to try to tell you more about router5, why I decided to develop an entire new routing solution, and what problems it tries to solve."
6 | },
7 | {
8 | "id": "2",
9 | "mailTitle": "Use with React",
10 | "mailMessage": "I have just started playing with it. It does make sense to use a flux-like implementation, to provide a layer between the router and view updates."
11 | },
12 | {
13 | "id": "3",
14 | "mailTitle": "Compose a new message",
15 | "mailMessage": "Click on compose, start to fill title and message fields and then try to navigate away by clicking on app links, or by using the back button."
16 | }
17 | ];
18 |
19 | export default function emails(state = initialState, action) {
20 | switch (action.type) {
21 | default:
22 | return state;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/react-redux/reducers/emails.js:
--------------------------------------------------------------------------------
1 | const initialState = [
2 | {
3 | "id": "1",
4 | "mailTitle": "Why router5?",
5 | "mailMessage": "I imagine a lot of developers who will first see router5 will ask themselves the question: is it yet another router? is it any good? Why oh why do people keep writing new routers all the time? It is not always easy to see the potential of something straight away, or understand the motivations behind. I therefore decided to try to tell you more about router5, why I decided to develop an entire new routing solution, and what problems it tries to solve."
6 | },
7 | {
8 | "id": "2",
9 | "mailTitle": "Use with React",
10 | "mailMessage": "I have just started playing with it. It does make sense to use a flux-like implementation, to provide a layer between the router and view updates."
11 | },
12 | {
13 | "id": "3",
14 | "mailTitle": "Compose a new message",
15 | "mailMessage": "Click on compose, start to fill title and message fields and then try to navigate away by clicking on app links, or by using the back button."
16 | }
17 | ];
18 |
19 | export default function emails(state = initialState, action) {
20 | switch (action.type) {
21 | default:
22 | return state;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/deku/api.js:
--------------------------------------------------------------------------------
1 | const emails = [
2 | {
3 | "id": "1",
4 | "mailTitle": "Why router5?",
5 | "mailMessage": "I imagine a lot of developers who will first see router5 will ask themselves the question: is it yet another router? is it any good? Why oh why do people keep writing new routers all the time? It is not always easy to see the potential of something straight away, or understand the motivations behind. I therefore decided to try to tell you more about router5, why I decided to develop an entire new routing solution, and what problems it tries to solve."
6 | },
7 | {
8 | "id": "2",
9 | "mailTitle": "Use with React",
10 | "mailMessage": "I have just started playing with it. It does make sense to use a flux-like implementation, to provide a layer between the router and view updates."
11 | },
12 | {
13 | "id": "3",
14 | "mailTitle": "Compose a new message",
15 | "mailMessage": "Click on compose, start to fill title and message fields and then try to navigate away by clicking on app links, or by using the back button."
16 | }
17 | ];
18 |
19 | export function getEmails() {
20 | return emails;
21 | }
22 |
23 | export function getEmail(id) {
24 | let index;
25 |
26 | if (emails) {
27 | for (index in emails) {
28 | if (emails[index].id === id) return emails[index];
29 | }
30 | }
31 | return null;
32 | }
33 |
--------------------------------------------------------------------------------
/apps/react/api.js:
--------------------------------------------------------------------------------
1 | const emails = [
2 | {
3 | "id": "1",
4 | "mailTitle": "Why router5?",
5 | "mailMessage": "I imagine a lot of developers who will first see router5 will ask themselves the question: is it yet another router? is it any good? Why oh why do people keep writing new routers all the time? It is not always easy to see the potential of something straight away, or understand the motivations behind. I therefore decided to try to tell you more about router5, why I decided to develop an entire new routing solution, and what problems it tries to solve."
6 | },
7 | {
8 | "id": "2",
9 | "mailTitle": "Use with React",
10 | "mailMessage": "I have just started playing with it. It does make sense to use a flux-like implementation, to provide a layer between the router and view updates."
11 | },
12 | {
13 | "id": "3",
14 | "mailTitle": "Compose a new message",
15 | "mailMessage": "Click on compose, start to fill title and message fields and then try to navigate away by clicking on app links, or by using the back button."
16 | }
17 | ];
18 |
19 | export function getEmails() {
20 | return emails;
21 | }
22 |
23 | export function getEmail(id) {
24 | let index;
25 |
26 | if (emails) {
27 | for (index in emails) {
28 | if (emails[index].id === id) return emails[index];
29 | }
30 | }
31 | return null;
32 | }
33 |
--------------------------------------------------------------------------------
/apps/deku-redux/api.js:
--------------------------------------------------------------------------------
1 | const emails = [
2 | {
3 | "id": "1",
4 | "mailTitle": "Why router5?",
5 | "mailMessage": "I imagine a lot of developers who will first see router5 will ask themselves the question: is it yet another router? is it any good? Why oh why do people keep writing new routers all the time? It is not always easy to see the potential of something straight away, or understand the motivations behind. I therefore decided to try to tell you more about router5, why I decided to develop an entire new routing solution, and what problems it tries to solve."
6 | },
7 | {
8 | "id": "2",
9 | "mailTitle": "Use with React",
10 | "mailMessage": "I have just started playing with it. It does make sense to use a flux-like implementation, to provide a layer between the router and view updates."
11 | },
12 | {
13 | "id": "3",
14 | "mailTitle": "Compose a new message",
15 | "mailMessage": "Click on compose, start to fill title and message fields and then try to navigate away by clicking on app links, or by using the back button."
16 | }
17 | ];
18 |
19 | export function getEmails() {
20 | return emails;
21 | }
22 |
23 | export function getEmail(id) {
24 | let index;
25 |
26 | if (emails) {
27 | for (index in emails) {
28 | if (emails[index].id === id) return emails[index];
29 | }
30 | }
31 | return null;
32 | }
33 |
--------------------------------------------------------------------------------
/apps/cycle/components/Compose.js:
--------------------------------------------------------------------------------
1 | import { h, div, h4, input, textarea, p } from '@cycle/dom';
2 | import Rx from 'rx';
3 |
4 | function Compose(sources) {
5 | const initialState = { title: '', message: '' };
6 |
7 | const title$ = sources.DOM.select('.mail-title')
8 | .events('input')
9 | .map(evt => evt.target.value);
10 |
11 | const message$ = sources.DOM.select('.mail-message')
12 | .events('input')
13 | .map(evt => evt.target.value);
14 |
15 | const messageAndTitle$ = Rx.Observable.combineLatest(
16 | title$.startWith(''),
17 | message$.startWith(''),
18 | (title, message) => ({ title, message })
19 | );
20 |
21 | const compose$ = Rx.Observable.combineLatest(
22 | messageAndTitle$,
23 | sources.router.error$
24 | .filter(err => err && err.code === 'CANNOT_DEACTIVATE')
25 | .startWith(''),
26 | ({ title, message }, routerError) => div({ className: 'compose' }, [
27 | h4('Compose a new message'),
28 | input({ className: 'mail-title', name: 'title', value: title }),
29 | textarea({ className: 'mail-message', name: 'message', value: message }),
30 | routerError ? p('Clear inputs before continuing') : null
31 | ])
32 | );
33 |
34 | return {
35 | DOM: compose$,
36 | router: messageAndTitle$
37 | .map(({ message, title }) => [ 'canDeactivate', 'compose', !message && !title ])
38 | .distinctUntilChanged(_ => _[2])
39 | };
40 | };
41 |
42 | export default Compose;
43 |
--------------------------------------------------------------------------------
/apps/react/components/Compose.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { routeNode } from 'react-router5';
3 |
4 | class Compose extends Component {
5 | constructor(props, context) {
6 | super(props, context);
7 | this.state = {
8 | title: undefined,
9 | message: undefined
10 | };
11 | this.updateTitle = this.updateTitle.bind(this);
12 | this.updateMessage = this.updateMessage.bind(this);
13 | this.canDeactivate = this.canDeactivate.bind(this);
14 | this.props.router.canDeactivate('compose', () => this.canDeactivate.bind(this));
15 | }
16 |
17 | canDeactivate() {
18 | if (this.state.title || this.state.message) {
19 | this.setState({ warning: true })
20 | return false;
21 | }
22 |
23 | return true;
24 | }
25 |
26 | updateTitle(evt) {
27 | this.setState({title: evt.target.value, warning: false});
28 | }
29 |
30 | updateMessage(evt) {
31 | this.setState({message: evt.target.value, warning: false});
32 | }
33 |
34 | render() {
35 | const {title, message, warning} = this.state;
36 |
37 | return (
38 |
39 |
Compose a new message
40 |
41 |
42 |
43 |
44 | { warning ?
Clear inputs before continuing
: null }
45 |
46 | );
47 | }
48 | }
49 |
50 | export default routeNode('compose')(Compose);
51 |
--------------------------------------------------------------------------------
/apps/cycle/main.js:
--------------------------------------------------------------------------------
1 | import Cycle from '@cycle/core';
2 | import Rx from 'rx';
3 | import { h, div, a, makeDOMDriver } from '@cycle/dom';
4 | import makeRouter5Driver from './router5/driver';
5 | import createRouter from './create-router';
6 | import routes from './routes';
7 | import emails from './data/emails';
8 | import Nav from './components/Nav';
9 | import Main from './components/Main';
10 | import { shouldInterceptEvent, onClick } from './router5/link-on-click';
11 |
12 | function main(sources) {
13 | const navigationInstruction$ = Rx.Observable.fromEvent(document, 'click', 'a')
14 | .filter(shouldInterceptEvent(sources.router))
15 | // .map(_ => console.log(_) && _)
16 | .map(onClick(sources.router));
17 |
18 | const navSinks = Nav(sources);
19 | const nav$ = navSinks.DOM.startWith();
20 | // const nav$ = Rx.Observable.of('Nav');
21 |
22 | const mainSinks = Main(sources);
23 | const main$ = mainSinks.DOM;
24 | const routerInstructions$ = mainSinks.router;
25 |
26 | const vtree$ = Rx.Observable.combineLatest(
27 | nav$,
28 | main$,
29 | (nav, main) => div('.mail-client', [
30 | h('aside', nav),
31 | h('main', main)
32 | ])
33 | );
34 |
35 | return {
36 | DOM: vtree$,
37 | router: Rx.Observable.merge(navigationInstruction$, routerInstructions$)
38 | };
39 | }
40 |
41 | function makeDataDriver() {
42 | return () => ({
43 | emails$: Rx.Observable.of(emails)
44 | })
45 | }
46 |
47 | Cycle.run(main, {
48 | DOM: makeDOMDriver('#app'),
49 | router: makeRouter5Driver(createRouter(routes)),
50 | data: makeDataDriver()
51 | });
52 |
--------------------------------------------------------------------------------
/apps/deku-redux/components/Compose.js:
--------------------------------------------------------------------------------
1 | import element from 'virtual-element';
2 | import { connect } from 'deku-redux';
3 | import { createSelector } from 'reselect';
4 | import { updateTitle, updateMessage } from '../actions/draft';
5 |
6 | const draftSelector = createSelector(
7 | state => state.draft,
8 | state => state.router,
9 | (draft, router) => ({
10 | title: draft.title,
11 | message: draft.message,
12 | error: hasCannotDeactivateError(router.transitionError)
13 | })
14 | );
15 |
16 | function hasCannotDeactivateError(error) {
17 | return error && error.code === 'CANNOT_DEACTIVATE' && error.segment === 'compose';
18 | }
19 |
20 | const Compose = {
21 | propTypes: {
22 | router: { source: 'router' }
23 | },
24 |
25 | intitalState(props) {
26 | return { title: '', message: '' };
27 | },
28 |
29 | render({ state, props }, setState) {
30 | const { title, message, error, updateTitle, updateMessage, router } = props;
31 |
32 | const updateState = prop => evt => setState(prop, evt.target.value);
33 | router.canDeactivate('compose', !title && !message);
34 |
35 | return element('div', { class: 'compose' }, [
36 | element('h4', {}, 'Compose a new message'),
37 | element('input', { name: 'title', value: title, onChange: updateState('title') }),
38 | element('textarea', { name: 'message', value: message, onChange: updateState('message') }),
39 | error ? element('p', {}, 'Clear inputs before continuing') : null
40 | ]);
41 | }
42 | };
43 |
44 | export default connect(draftSelector, { updateTitle, updateMessage })(Compose);
45 |
--------------------------------------------------------------------------------
/apps/cycle/router5/link-on-click.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Event button.
3 | */
4 | const which = (evt = window.event) => null === evt.which ? evt.button : evt.which;
5 |
6 | /**
7 | * Check if `href` is the same origin.
8 | */
9 | const sameOrigin = (href) => {
10 | var origin = location.protocol + '//' + location.hostname;
11 | if (location.port) origin += ':' + location.port;
12 | return (href && (0 === href.indexOf(origin)));
13 | };
14 |
15 | export const shouldInterceptEvent = router => evt => {
16 | if (1 !== which(evt)) return false;
17 | if (evt.metaKey || evt.ctrlKey || evt.shiftKey) return false;
18 | if (evt.defaultPrevented) return false;
19 |
20 | // ensure link
21 | let el = evt.target;
22 | while (el && 'A' !== el.nodeName) el = el.parentNode;
23 | if (!el || 'A' !== el.nodeName) return false;
24 |
25 | // Ignore if tag has
26 | // 1. "download" attribute
27 | // 2. rel="external" attribute
28 | if (el.hasAttribute('download') || el.getAttribute('rel') === 'external') return false;
29 |
30 | // check target
31 | if (el.target) return false;
32 |
33 | if (!el.href) return false;
34 |
35 | return true;
36 | }
37 |
38 | export const onClick = router => evt => {
39 | let el = evt.target;
40 | while (el && 'A' !== el.nodeName) el = el.parentNode;
41 | const routeMatch = router.matchUrl(el.href);
42 |
43 | if (routeMatch) {
44 | evt.preventDefault();
45 | var name = routeMatch.name;
46 | var params = routeMatch.params;
47 | return ['navigate', name, params];
48 | } else {
49 | throw new Error(`[router5 driver] Could not match clicked hyplink href ${evt.target.href} to any known route`);
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/apps/cycle2/main.js:
--------------------------------------------------------------------------------
1 | import Cycle from '@cycle/core';
2 | import Rx from 'rx';
3 | import { h, div, a, makeDOMDriver } from '@cycle/dom';
4 | import makeRouter5Driver from '../cycle/router5/driver';
5 | import createRouter from './create-router';
6 | import routes from './routes';
7 | import Nav from './components/Nav';
8 | import randomColor from 'randomcolor';
9 |
10 | const randomBgColor = () => ({ backgroundColor: randomColor() });
11 |
12 | function main(sources) {
13 | const navSinks = Nav(sources);
14 | const nav$ = navSinks.DOM.startWith();
15 |
16 | const main$ = sources.router
17 | .routeNode$('')
18 | .flatMapLatest(route => {
19 | const childNode = route.name.split('.')[0];
20 |
21 | const routeElm = div(
22 | { className: 'item', style: randomBgColor() },
23 | childNode
24 | );
25 |
26 | const Route = segment => div({ className: 'column' }, [
27 | routeElm,
28 | segment
29 | ? div({ className: 'item', style: randomBgColor() }, segment)
30 | : div({ className: 'item' }, '_')
31 | ]);
32 |
33 | return sources.router
34 | .routeNode$(childNode)
35 | .map(route => Route(route.name.split('.')[1]));
36 | });
37 |
38 | const vtree$ = Rx.Observable.combineLatest(
39 | nav$,
40 | main$,
41 | (nav, main) => div('.box', [
42 | h('aside', nav),
43 | h('main', main)
44 | ])
45 | );
46 |
47 | return {
48 | DOM: vtree$
49 | };
50 | }
51 |
52 | Cycle.run(main, {
53 | DOM: makeDOMDriver('#app'),
54 | router: makeRouter5Driver(createRouter(routes))
55 | });
56 |
--------------------------------------------------------------------------------
/apps/react-redux/components/Compose.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 | import { createSelector } from 'reselect';
4 | import { bindActionCreators } from 'redux';
5 | import { updateTitle, updateMessage } from '../actions/draft';
6 |
7 | const draftSelector = createSelector(
8 | state => state.draft,
9 | state => state.router,
10 | (draft, router) => ({
11 | title: draft.title,
12 | message: draft.message,
13 | error: hasCannotDeactivateError(router.transitionError)
14 | })
15 | );
16 |
17 | function hasCannotDeactivateError(error) {
18 | return error && error.code === 'CANNOT_DEACTIVATE' && error.segment === 'compose';
19 | }
20 |
21 | function mapDispatchToProps(dispatch) {
22 | return bindActionCreators({ updateTitle, updateMessage }, dispatch);
23 | }
24 |
25 | class Compose extends Component {
26 | constructor(props, context) {
27 | super(props, context);
28 | this.router = context.router;
29 | }
30 |
31 | render() {
32 | const { title, message, error, updateTitle, updateMessage } = this.props
33 | this.router.canDeactivate('compose', !title && !message);
34 |
35 | return (
36 |
37 |
Compose a new message
38 |
39 |
updateTitle(evt.target.value) } />
40 |
44 | );
45 | }
46 | }
47 |
48 | Compose.contextTypes = {
49 | router: PropTypes.object.isRequired
50 | };
51 |
52 | export default connect(draftSelector, mapDispatchToProps)(Compose);
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "router5-examples",
3 | "version": "0.0.0",
4 | "description": "A mix of playground code and examples",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "webpack-dev-server",
9 | "build": "webpack",
10 | "build:prod": "NODE_ENV=production webpack",
11 | "build:all": "webpack --app=deku && webpack --app=deku-redux && webpack --app=react && webpack --app=react-redux && webpack --app=cycle && webpack --app=cycle2",
12 | "build:prod:all": "NODE_ENV=production npm run build:all"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/router5/examples.git"
17 | },
18 | "author": "Thomas Roch (http://blog.reactandbethankful.com/)",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/router5/examples/issues"
22 | },
23 | "homepage": "https://github.com/router5/examples#readme",
24 | "devDependencies": {
25 | "@cycle/core": "^6.0.0",
26 | "@cycle/dom": "^9.0.1",
27 | "babel-core": "^5.8.25",
28 | "babel-loader": "^5.3.2",
29 | "randomcolor": "^0.4.3",
30 | "redux-logger": "^2.3.2",
31 | "router5.helpers": "^1.1.0",
32 | "router5.transition-path": "^4.0.0",
33 | "rx": "^4.0.7",
34 | "webpack": "^1.12.2",
35 | "webpack-dev-server": "^1.14.1",
36 | "yargs": "^3.29.0"
37 | },
38 | "dependencies": {
39 | "chance": "^0.7.6",
40 | "deku": "^1.0.0",
41 | "deku-redux": "^1.0.0",
42 | "deku-router5": "^4.0.0",
43 | "lodash.find": "^3.2.1",
44 | "react": "~15.4.0",
45 | "react-dom": "~15.4.0",
46 | "react-redux": "^4.4.0",
47 | "react-router5": "^4.0.0",
48 | "redux": "^3.0.3",
49 | "redux-router5": "^4.0.0",
50 | "redux-thunk": "^1.0.0",
51 | "reselect": "^2.0.0",
52 | "router5": "~4.5.1",
53 | "virtual-element": "^1.2.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 20px;
3 | line-height: 1.5rem;
4 | font-family: sans-serif, Arial;
5 | padding: 0;
6 | }
7 |
8 | a {
9 | color: inherit;
10 | text-decoration: none;
11 | }
12 |
13 | #app {
14 | margin: 40px auto;
15 | max-width: 1050px;
16 | }
17 |
18 | .mail-client {
19 | widht: 100%;
20 | height: 450px;
21 | background-color: #f4f4f4;
22 | position: relative;
23 | border: 1px solid #CCC;
24 | }
25 |
26 | .mail-client > aside {
27 | position: absolute;
28 | top: 0;
29 | right: 0;
30 | height: 45px;
31 | left: 0;
32 | border-bottom: 1px solid #ccc;
33 | }
34 |
35 | .mail-client > main {
36 | position: absolute;
37 | top: 46px;
38 | right: 0;
39 | bottom: 0;
40 | left: 0;
41 | }
42 |
43 | .mail-client > aside > nav > a {
44 | display: inline-block;
45 | padding: .5rem 1rem;
46 | color: #336699;
47 | }
48 |
49 | .mail-client > aside > nav > a.active {
50 | background-color: #336699;
51 | color: white;
52 | }
53 |
54 | div.inbox {
55 | position: absolute;
56 | top: 0;
57 | right: 0;
58 | bottom: 0;
59 | left: 0;
60 |
61 | display: flex;
62 | flex-direction: row;
63 | }
64 |
65 | div.not-found {
66 | padding: .5rem 1rem;
67 | }
68 |
69 | ul.mail-list {
70 | list-style: none;
71 | margin: 0;
72 | padding: 0;
73 | flex: 1;
74 | overflow: hidden;
75 | }
76 |
77 | ul.mail-list > li {
78 | padding: 0;
79 | cursor: pointer;
80 | border-bottom: 1px solid #ddd;
81 | }
82 |
83 | ul.mail-list > li:hover {
84 | background-color: #eee;
85 | }
86 |
87 | ul.mail-list > li h4,
88 | section.mail > h4 {
89 | padding: .5rem 1rem 0;
90 | margin: 0;
91 | }
92 |
93 | ul.mail-list > li p,
94 | section.mail > p {
95 | margin: 0;
96 | padding: 0 1rem .5rem;
97 | line-height: 1.5em;
98 | font-size: 0.85rem;
99 | }
100 |
101 | ul.mail-list > li h4,
102 | ul.mail-list > li p {
103 | overflow: hidden;
104 | width: calc(100% - 2rem);
105 | white-space: nowrap;
106 | text-overflow: ellipsis;
107 | }
108 |
109 | section.mail {
110 | border-left: 1px solid #ddd;
111 | overflow: auto;
112 | flex: 2;
113 | }
114 |
115 | div.compose {
116 | padding: 0 1rem;
117 | }
118 |
119 | div.compose > h4 {
120 | padding: .5rem 0;
121 | margin: 0;
122 | }
123 |
124 | div.compose > input,
125 | div.compose > textarea {
126 | padding: .5rem;
127 | line-height: 1.2rem;
128 | font-size: 0.9rem;
129 | display: block;
130 | width: calc(100% - 2rem);
131 | max-width: 400px;
132 | margin-top: 1rem;
133 | }
134 |
135 | div.compose > textarea {
136 | min-height: 200px;
137 | }
138 |
139 | div.compose > p {
140 | color: red;
141 | margin: 0;
142 | padding: .5rem 0;
143 | line-height: 1.5em;
144 | font-size: 0.9rem;
145 | }
146 |
147 | @media (min-width: 1025px) {
148 | .mail-client > aside {
149 | height: auto;
150 | top: 0;
151 | width: 150px;
152 | bottom: 0;
153 | left: 0;
154 | border-bottom: none;
155 | border-right: 1px solid #ccc;
156 | }
157 |
158 | .mail-client > main {
159 | top: 0;
160 | right: 0;
161 | bottom: 0;
162 | left: 151px;
163 | }
164 |
165 | .mail-client > aside > nav > a {
166 | display: block;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/apps/cycle/router5/driver.js:
--------------------------------------------------------------------------------
1 | import Rx from 'rx';
2 | import transitionPath from 'router5.transition-path';
3 | import routerToObservable from './router-to-observable';
4 |
5 | const sourceMethods = [ 'getState', 'buildUrl', 'buildPath', 'matchUrl', 'matchPath', 'areStatesDescendants', 'isActive' ];
6 | const sinkMethods = [ 'cancel', 'start', 'stop', 'navigate', 'canActivate', 'canDeactivate' ];
7 |
8 | /**
9 | * Normalise a sink request to the router driver.
10 | * @param {String|Array} req A method name or array containing a method name and arguments
11 | * @return {Array} An array containing a method name and its arguments
12 | */
13 | const normaliseRequest = (req) => {
14 | const normReq = Array.isArray(req) || typeof req === 'string'
15 | ? [].concat(req)
16 | : [];
17 |
18 | if (sinkMethods.indexOf(normReq[0]) === -1) {
19 | throw new Error('A Router5 sink argument should be a string (method name) or' +
20 | ' an object which first element is a valid metod name, followed by its arguments.' +
21 | ' Available sink methods are: ' + sinkMethods.join(',') + '.'
22 | );
23 | }
24 |
25 | return normReq;
26 | }
27 |
28 | /**
29 | * Make a cycle router driver from a router5 instance
30 | * @param {Router5} router A Router5 instance
31 | * @param {Boolean} autostart Whether or not to start routing if not already started
32 | * @return {Function} A cycle sink function
33 | */
34 | const makeRouterDriver = (router, autostart = true) => {
35 | const startRouter = () => !router.started && autostart && router.start();
36 |
37 | // Observe router transitions
38 | const transition$ = routerToObservable(router, startRouter);
39 |
40 | // Helpers
41 | const filter = type => transition$.filter(_ => _.type === type);
42 | const slice = type => filter(type).map(_ => _.type);
43 | const sliceSlate = type => filter(type).map(({ toState, fromState }) => ({ toState, fromState }));
44 |
45 | // Filter router events observables
46 | const observables = {
47 | start$: slice('start'),
48 | stop$: slice('stop'),
49 | transitionStart$: sliceSlate('transitionStart'),
50 | transitionCancel$: sliceSlate('transitionCancel'),
51 | transitionSuccess$: sliceSlate('transitionSuccess'),
52 | transitionError$: sliceSlate('transitionError')
53 | };
54 |
55 | // Transition Route
56 | const transitionRoute$ = transition$
57 | .map(_ => _.type === 'transitionStart' ? _.toState : null)
58 | .startWith(null);
59 |
60 | // Error
61 | const error$ = transition$
62 | .map(_ => _.type === 'transitionError' ? _.error : null)
63 | .startWith(null);
64 |
65 | const routeState$ = observables.transitionSuccess$
66 | .filter(({ toState }) => toState !== null)
67 | .map(({ toState, fromState }) => {
68 | const { intersection } = transitionPath(toState, fromState);
69 | return { intersection, route: toState };
70 | });
71 |
72 | // Create a route observable
73 | const route$ = routeState$.map(({ route }) => route)
74 | .startWith(router.getState());
75 |
76 | // Create a route node observable
77 | const routeNode$ = node =>
78 | routeState$
79 | .filter(({ intersection }) => intersection === node)
80 | .map(({ route }) => route)
81 | .startWith(router.getState())
82 | .filter(route => route !== null);
83 |
84 | // Source API methods ready to be consumed
85 | const sourceApi = sourceMethods.reduce(
86 | (methods, method) => ({ ...methods, [method]: (...args) => router[method].apply(router, args) }),
87 | {}
88 | );
89 |
90 | return request$ => {
91 | request$
92 | .map(normaliseRequest)
93 | .subscribe(
94 | ([ method, ...args ]) => router[method].apply(router, args),
95 | err => console.error(err)
96 | );
97 |
98 | return {
99 | ...sourceApi,
100 | ...observables,
101 | route$,
102 | routeNode$,
103 | transitionRoute$,
104 | error$
105 | };
106 | };
107 | };
108 |
109 | export default makeRouterDriver;
110 |
--------------------------------------------------------------------------------