@@ -15,12 +15,15 @@ import { | |||
hashHistory, | |||
IndexRoute | |||
} from 'react-router'; | |||
import UI from '../features/ui'; | |||
import Account from '../features/account'; | |||
import Status from '../features/status'; | |||
import GettingStarted from '../features/getting_started'; | |||
import PublicTimeline from '../features/public_timeline'; | |||
import UI from '../features/ui'; | |||
import AccountTimeline from '../features/account_timeline'; | |||
import HomeTimeline from '../features/home_timeline'; | |||
import MentionsTimeline from '../features/mentions_timeline'; | |||
import Compose from '../features/compose'; | |||
const store = configureStore(); | |||
@@ -77,6 +80,9 @@ const Mastodon = React.createClass({ | |||
<Router history={hashHistory}> | |||
<Route path='/' component={UI}> | |||
<IndexRoute component={GettingStarted} /> | |||
<Route path='/statuses/new' component={Compose} /> | |||
<Route path='/statuses/home' component={HomeTimeline} /> | |||
<Route path='/statuses/mentions' component={MentionsTimeline} /> | |||
<Route path='/statuses/all' component={PublicTimeline} /> | |||
<Route path='/statuses/:statusId' component={Status} /> | |||
<Route path='/accounts/:accountId' component={Account}> | |||
@@ -0,0 +1,28 @@ | |||
import Drawer from '../ui/components/drawer'; | |||
import ComposeFormContainer from '../ui/containers/compose_form_container'; | |||
import FollowFormContainer from '../ui/containers/follow_form_container'; | |||
import UploadFormContainer from '../ui/containers/upload_form_container'; | |||
import NavigationContainer from '../ui/containers/navigation_container'; | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
const Compose = React.createClass({ | |||
mixins: [PureRenderMixin], | |||
render () { | |||
return ( | |||
<Drawer> | |||
<div style={{ flex: '1 1 auto' }}> | |||
<NavigationContainer /> | |||
<ComposeFormContainer /> | |||
<UploadFormContainer /> | |||
</div> | |||
<FollowFormContainer /> | |||
</Drawer> | |||
); | |||
} | |||
}); | |||
export default Compose; |
@@ -0,0 +1,19 @@ | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
import StatusListContainer from '../ui/containers/status_list_container'; | |||
import Column from '../ui/components/column'; | |||
const HomeTimeline = React.createClass({ | |||
mixins: [PureRenderMixin], | |||
render () { | |||
return ( | |||
<Column icon='home' heading='Home'> | |||
<StatusListContainer type='home' /> | |||
</Column> | |||
); | |||
}, | |||
}); | |||
export default HomeTimeline; |
@@ -0,0 +1,19 @@ | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
import StatusListContainer from '../ui/containers/status_list_container'; | |||
import Column from '../ui/components/column'; | |||
const MentionsTimeline = React.createClass({ | |||
mixins: [PureRenderMixin], | |||
render () { | |||
return ( | |||
<Column icon='at' heading='Mentions'> | |||
<StatusListContainer type='mentions' /> | |||
</Column> | |||
); | |||
}, | |||
}); | |||
export default MentionsTimeline; |
@@ -1,43 +1,14 @@ | |||
import { connect } from 'react-redux'; | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import StatusList from '../../components/status_list'; | |||
import StatusListContainer from '../ui/containers/status_list_container'; | |||
import Column from '../ui/components/column'; | |||
import Immutable from 'immutable'; | |||
import { makeGetTimeline } from '../../selectors'; | |||
import { | |||
updateTimeline, | |||
refreshTimeline, | |||
expandTimeline | |||
updateTimeline | |||
} from '../../actions/timelines'; | |||
import { deleteStatus } from '../../actions/statuses'; | |||
import { replyCompose } from '../../actions/compose'; | |||
import { | |||
favourite, | |||
reblog, | |||
unreblog, | |||
unfavourite | |||
} from '../../actions/interactions'; | |||
const makeMapStateToProps = () => { | |||
const getTimeline = makeGetTimeline(); | |||
const mapStateToProps = (state) => ({ | |||
statuses: getTimeline(state, 'public'), | |||
me: state.getIn(['timelines', 'me']) | |||
}); | |||
return mapStateToProps; | |||
}; | |||
const PublicTimeline = React.createClass({ | |||
propTypes: { | |||
statuses: ImmutablePropTypes.list.isRequired, | |||
me: React.PropTypes.number.isRequired, | |||
dispatch: React.PropTypes.func.isRequired | |||
}, | |||
mixins: [PureRenderMixin], | |||
componentWillMount () { | |||
@@ -62,44 +33,14 @@ const PublicTimeline = React.createClass({ | |||
} | |||
}, | |||
handleReply (status) { | |||
this.props.dispatch(replyCompose(status)); | |||
}, | |||
handleReblog (status) { | |||
if (status.get('reblogged')) { | |||
this.props.dispatch(unreblog(status)); | |||
} else { | |||
this.props.dispatch(reblog(status)); | |||
} | |||
}, | |||
handleFavourite (status) { | |||
if (status.get('favourited')) { | |||
this.props.dispatch(unfavourite(status)); | |||
} else { | |||
this.props.dispatch(favourite(status)); | |||
} | |||
}, | |||
handleDelete (status) { | |||
this.props.dispatch(deleteStatus(status.get('id'))); | |||
}, | |||
handleScrollToBottom () { | |||
this.props.dispatch(expandTimeline('public')); | |||
}, | |||
render () { | |||
const { statuses, me } = this.props; | |||
return ( | |||
<Column icon='globe' heading='Public'> | |||
<StatusList statuses={statuses} me={me} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} onDelete={this.handleDelete} /> | |||
<StatusListContainer type='public' /> | |||
</Column> | |||
); | |||
}, | |||
}); | |||
export default connect(makeMapStateToProps)(PublicTimeline); | |||
export default connect()(PublicTimeline); |
@@ -29,6 +29,15 @@ const scrollTop = (node) => { | |||
}; | |||
}; | |||
const style = { | |||
height: '100%', | |||
boxSizing: 'border-box', | |||
flex: '0 0 auto', | |||
background: '#282c37', | |||
display: 'flex', | |||
flexDirection: 'column' | |||
}; | |||
const Column = React.createClass({ | |||
propTypes: { | |||
@@ -56,10 +65,8 @@ const Column = React.createClass({ | |||
header = <ColumnHeader icon={this.props.icon} type={this.props.heading} onClick={this.handleHeaderClick} />; | |||
} | |||
const style = { width: '330px', flex: '0 0 auto', background: '#282c37', margin: '10px', marginRight: '0', marginBottom: '0', display: 'flex', flexDirection: 'column' }; | |||
return ( | |||
<div style={style} onWheel={this.handleWheel}> | |||
<div className='column' style={style} onWheel={this.handleWheel}> | |||
{header} | |||
{this.props.children} | |||
</div> | |||
@@ -1,12 +1,20 @@ | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
const style = { | |||
display: 'flex', | |||
flex: '1 1 auto', | |||
flexDirection: 'row', | |||
justifyContent: 'flex-start', | |||
overflowX: 'auto' | |||
}; | |||
const ColumnsArea = React.createClass({ | |||
mixins: [PureRenderMixin], | |||
render () { | |||
return ( | |||
<div style={{ display: 'flex', flexDirection: 'row', flex: '1', justifyContent: 'flex-start', marginRight: '10px', marginBottom: '10px', overflowX: 'auto' }}> | |||
<div className='columns-area' style={style}> | |||
{this.props.children} | |||
</div> | |||
); | |||
@@ -1,12 +1,22 @@ | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
const style = { | |||
height: '100%', | |||
flex: '0 0 auto', | |||
boxSizing: 'border-box', | |||
background: '#454b5e', | |||
padding: '0', | |||
display: 'flex', | |||
flexDirection: 'column' | |||
}; | |||
const Drawer = React.createClass({ | |||
mixins: [PureRenderMixin], | |||
render () { | |||
return ( | |||
<div style={{ width: '280px', flex: '0 0 auto', boxSizing: 'border-box', background: '#454b5e', margin: '10px', marginRight: '0', padding: '0', display: 'flex', flexDirection: 'column' }}> | |||
<div className='drawer' style={style}> | |||
{this.props.children} | |||
</div> | |||
); | |||
@@ -0,0 +1,38 @@ | |||
import { Link } from 'react-router'; | |||
const outerStyle = { | |||
background: '#373b4a', | |||
margin: '10px', | |||
flex: '0 0 auto', | |||
marginBottom: '0', | |||
display: 'flex' | |||
}; | |||
const tabStyle = { | |||
display: 'block', | |||
flex: '1 1 auto', | |||
padding: '10px', | |||
color: '#fff', | |||
textDecoration: 'none', | |||
fontSize: '12px', | |||
fontWeight: '500', | |||
borderBottom: '2px solid #373b4a' | |||
}; | |||
const tabActiveStyle = { | |||
borderBottom: '2px solid #2b90d9', | |||
color: '#2b90d9' | |||
}; | |||
const TabsBar = () => { | |||
return ( | |||
<div style={outerStyle}> | |||
<Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/new'><i className='fa fa-fw fa-pencil' /> Compose</Link> | |||
<Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/home'><i className='fa fa-fw fa-home' /> Home</Link> | |||
<Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/mentions'><i className='fa fa-fw fa-at' /> Mentions</Link> | |||
<Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/all'><i className='fa fa-fw fa-globe' /> Public</Link> | |||
</div> | |||
); | |||
}; | |||
export default TabsBar; |
@@ -1,47 +1,38 @@ | |||
import ColumnsArea from './components/columns_area'; | |||
import Column from './components/column'; | |||
import Drawer from './components/drawer'; | |||
import ComposeFormContainer from './containers/compose_form_container'; | |||
import FollowFormContainer from './containers/follow_form_container'; | |||
import UploadFormContainer from './containers/upload_form_container'; | |||
import StatusListContainer from './containers/status_list_container'; | |||
import NotificationsContainer from './containers/notifications_container'; | |||
import NavigationContainer from './containers/navigation_container'; | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
import LoadingBarContainer from './containers/loading_bar_container'; | |||
import HomeTimeline from '../home_timeline'; | |||
import MentionsTimeline from '../mentions_timeline'; | |||
import Compose from '../compose'; | |||
import MediaQuery from 'react-responsive'; | |||
import TabsBar from './components/tabs_bar'; | |||
const UI = React.createClass({ | |||
propTypes: { | |||
router: React.PropTypes.object | |||
}, | |||
mixins: [PureRenderMixin], | |||
render () { | |||
const layoutBreakpoint = 1024; | |||
return ( | |||
<div style={{ flex: '0 0 auto', display: 'flex', width: '100%', height: '100%', background: '#1a1c23' }}> | |||
<Drawer> | |||
<div style={{ flex: '1 1 auto' }}> | |||
<NavigationContainer /> | |||
<ComposeFormContainer /> | |||
<UploadFormContainer /> | |||
</div> | |||
<FollowFormContainer /> | |||
</Drawer> | |||
<ColumnsArea> | |||
<Column icon='home' heading='Home'> | |||
<StatusListContainer type='home' /> | |||
</Column> | |||
<Column icon='at' heading='Mentions'> | |||
<StatusListContainer type='mentions' /> | |||
</Column> | |||
<div style={{ flex: '0 0 auto', display: 'flex', flexDirection: 'column', width: '100%', height: '100%', background: '#1a1c23' }}> | |||
<MediaQuery maxWidth={layoutBreakpoint}> | |||
<TabsBar /> | |||
</MediaQuery> | |||
<MediaQuery maxWidth={layoutBreakpoint} component={ColumnsArea}> | |||
{this.props.children} | |||
</ColumnsArea> | |||
</MediaQuery> | |||
<MediaQuery minWidth={layoutBreakpoint}> | |||
<ColumnsArea> | |||
<Compose /> | |||
<HomeTimeline /> | |||
<MentionsTimeline /> | |||
{this.props.children} | |||
</ColumnsArea> | |||
</MediaQuery> | |||
<NotificationsContainer /> | |||
<LoadingBarContainer style={{ backgroundColor: '#2b90d9', left: '0', top: '0' }} /> | |||
@@ -227,3 +227,31 @@ | |||
margin-bottom: 20px; | |||
} | |||
} | |||
.columns-area { | |||
margin: 10px; | |||
margin-left: 0; | |||
} | |||
.column { | |||
width: 330px; | |||
} | |||
.drawer { | |||
width: 280px; | |||
} | |||
.column, .drawer { | |||
margin-left: 10px; | |||
} | |||
@media screen and (max-width: 1024px) { | |||
.column, .drawer { | |||
width: 100%; | |||
margin: 0; | |||
} | |||
.columns-area { | |||
margin: 10px; | |||
} | |||
} |
@@ -38,5 +38,8 @@ | |||
"redux-thunk": "^2.1.0", | |||
"reselect": "^2.5.4", | |||
"sinon": "^1.17.6" | |||
}, | |||
"dependencies": { | |||
"react-responsive": "^1.1.5" | |||
} | |||
} |
@@ -1541,6 +1541,10 @@ css-loader@0.25.0: | |||
postcss-modules-values "^1.1.0" | |||
source-list-map "^0.1.4" | |||
css-mediaquery@^0.1.2: | |||
version "0.1.2" | |||
resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" | |||
css-select@~1.2.0: | |||
version "1.2.0" | |||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" | |||
@@ -2359,6 +2363,10 @@ https-browserify@0.0.0: | |||
version "0.0.0" | |||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.0.tgz#b3ffdfe734b2a3d4a9efd58e8654c91fce86eafd" | |||
hyphenate-style-name@^1.0.0: | |||
version "1.0.1" | |||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.1.tgz#bc49b9446e02b4570641afdd29c1ce7609d1b9cc" | |||
iconv-lite@^0.4.13, iconv-lite@~0.4.13: | |||
version "0.4.13" | |||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" | |||
@@ -2928,6 +2936,12 @@ mantra-core@^1.6.1: | |||
react-komposer "^1.9.0" | |||
react-simple-di "^1.2.0" | |||
matchmedia@^0.1.2: | |||
version "0.1.2" | |||
resolved "https://registry.yarnpkg.com/matchmedia/-/matchmedia-0.1.2.tgz#cfd47f2bf68fbc7f5ea1bd3a3cf1715ecba3c1bd" | |||
dependencies: | |||
css-mediaquery "^0.1.2" | |||
math-expression-evaluator@^1.2.14: | |||
version "1.2.14" | |||
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.14.tgz#39511771ed9602405fba9affff17eb4d2a3843ab" | |||
@@ -3823,6 +3837,14 @@ react-redux@^5.0.0-beta.3: | |||
lodash-es "^4.2.0" | |||
loose-envify "^1.1.0" | |||
react-responsive: | |||
version "1.1.5" | |||
resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-1.1.5.tgz#a7019a28817dcb601ef31d10d72f798a0d710a17" | |||
dependencies: | |||
hyphenate-style-name "^1.0.0" | |||
matchmedia "^0.1.2" | |||
object-assign "^4.0.1" | |||
react-router@^2.8.0: | |||
version "2.8.1" | |||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-2.8.1.tgz#73e9491f6ceb316d0f779829081863e378ee4ed7" | |||