parent
68c93f8b85
commit
49520d6e62
@ -1,13 +0,0 @@ |
||||
App.timeline = App.cable.subscriptions.create("TimelineChannel", { |
||||
connected: function() { |
||||
console.log('Connected'); |
||||
}, |
||||
|
||||
disconnected: function() { |
||||
console.log('Disconnected'); |
||||
}, |
||||
|
||||
received: function(data) { |
||||
console.log(JSON.parse(data.message)); |
||||
} |
||||
}); |
@ -0,0 +1,9 @@ |
||||
//= require_self
|
||||
//= require react_ujs
|
||||
|
||||
window.React = require('react'); |
||||
window.ReactDOM = require('react-dom'); |
||||
|
||||
//= require_tree ./components
|
||||
|
||||
window.Root = require('./components/containers/root'); |
@ -0,0 +1,18 @@ |
||||
export const SET_TIMELINE = 'SET_TIMELINE'; |
||||
export const ADD_STATUS = 'ADD_STATUS'; |
||||
|
||||
export function setTimeline(timeline, statuses) { |
||||
return { |
||||
type: SET_TIMELINE, |
||||
timeline: timeline, |
||||
statuses: statuses |
||||
}; |
||||
} |
||||
|
||||
export function addStatus(timeline, status) { |
||||
return { |
||||
type: ADD_STATUS, |
||||
timeline: timeline, |
||||
status: status |
||||
}; |
||||
} |
@ -0,0 +1,19 @@ |
||||
import StatusListContainer from '../containers/status_list_container'; |
||||
import ColumnHeader from './column_header'; |
||||
|
||||
const Column = React.createClass({ |
||||
propTypes: { |
||||
type: React.PropTypes.string |
||||
}, |
||||
|
||||
render: function() { |
||||
return ( |
||||
<div style={{ width: '350px', flex: '0 0 auto', background: '#282c37', margin: '10px', marginRight: '0', display: 'flex', flexDirection: 'column' }}> |
||||
<ColumnHeader type={this.props.type} /> |
||||
<StatusListContainer type={this.props.type} /> |
||||
</div> |
||||
); |
||||
} |
||||
}); |
||||
|
||||
export default Column; |
@ -0,0 +1,15 @@ |
||||
const ColumnHeader = React.createClass({ |
||||
propTypes: { |
||||
type: React.PropTypes.string |
||||
}, |
||||
|
||||
render: function() { |
||||
return ( |
||||
<div style={{ padding: '15px', fontSize: '16px', background: '#2f3441', flex: '0 0 auto' }}> |
||||
{this.props.type} |
||||
</div> |
||||
); |
||||
} |
||||
}); |
||||
|
||||
export default ColumnHeader; |
@ -0,0 +1,15 @@ |
||||
import Column from './column'; |
||||
|
||||
const ColumnsArea = React.createClass({ |
||||
|
||||
render: function() { |
||||
return ( |
||||
<div style={{ display: 'flex', flexDirection: 'row', flex: '1' }}> |
||||
<Column type='home' /> |
||||
<Column type='mentions' /> |
||||
</div> |
||||
); |
||||
} |
||||
}); |
||||
|
||||
export default ColumnsArea; |
@ -0,0 +1,16 @@ |
||||
import NavBar from './nav_bar'; |
||||
import ColumnsArea from './columns_area'; |
||||
|
||||
const Frontend = React.createClass({ |
||||
|
||||
render: function() { |
||||
return ( |
||||
<div style={{ flex: '0 0 auto', display: 'flex', width: '100%', height: '100%', background: '#1a1c23' }}> |
||||
<NavBar /> |
||||
<ColumnsArea /> |
||||
</div> |
||||
); |
||||
} |
||||
}); |
||||
|
||||
export default Frontend; |
@ -0,0 +1,8 @@ |
||||
const NavBar = React.createClass({ |
||||
|
||||
render: function() { |
||||
return <div style={{ background: '#2f3441', width: '60px', margin: '10px', marginRight: '0' }} />; |
||||
} |
||||
}); |
||||
|
||||
export default NavBar; |
@ -0,0 +1,19 @@ |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
|
||||
const Status = React.createClass({ |
||||
propTypes: { |
||||
status: ImmutablePropTypes.map.isRequired |
||||
}, |
||||
|
||||
render: function() { |
||||
console.log(this.props.status.toJS()); |
||||
|
||||
return ( |
||||
<div style={{ height: '100px' }}> |
||||
{this.props.status.getIn(['account', 'username'])}: {this.props.status.get('content')} |
||||
</div> |
||||
); |
||||
} |
||||
}); |
||||
|
||||
export default Status; |
@ -0,0 +1,22 @@ |
||||
import Status from './status'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
|
||||
const StatusList = React.createClass({ |
||||
propTypes: { |
||||
statuses: ImmutablePropTypes.list.isRequired |
||||
}, |
||||
|
||||
render: function() { |
||||
return ( |
||||
<div style={{ overflowY: 'scroll', flex: '1 1 auto' }}> |
||||
<div> |
||||
{this.props.statuses.map((status) => { |
||||
return <Status key={status.get('id')} status={status} />; |
||||
})} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
}); |
||||
|
||||
export default StatusList; |
@ -0,0 +1,40 @@ |
||||
import { Provider } from 'react-redux'; |
||||
import configureStore from '../store/configureStore'; |
||||
import Frontend from '../components/frontend'; |
||||
import { setTimeline, addStatus } from '../actions/statuses'; |
||||
|
||||
const store = configureStore(); |
||||
|
||||
const Root = React.createClass({ |
||||
|
||||
componentWillMount() { |
||||
for (var timelineType in this.props.timelines) { |
||||
if (this.props.timelines.hasOwnProperty(timelineType)) { |
||||
store.dispatch(setTimeline(timelineType, JSON.parse(this.props.timelines[timelineType]))); |
||||
} |
||||
} |
||||
|
||||
if (typeof App !== 'undefined') { |
||||
App.timeline = App.cable.subscriptions.create("TimelineChannel", { |
||||
connected: function() {}, |
||||
|
||||
disconnected: function() {}, |
||||
|
||||
received: function(data) { |
||||
return store.dispatch(addStatus(data.timeline, JSON.parse(data.message))); |
||||
} |
||||
}); |
||||
} |
||||
}, |
||||
|
||||
render() { |
||||
return ( |
||||
<Provider store={store}> |
||||
<Frontend /> |
||||
</Provider> |
||||
); |
||||
} |
||||
|
||||
}); |
||||
|
||||
export default Root; |
@ -0,0 +1,10 @@ |
||||
import { connect } from 'react-redux'; |
||||
import StatusList from '../components/status_list'; |
||||
|
||||
const mapStateToProps = function (state, props) { |
||||
return { |
||||
statuses: state.getIn(['statuses', props.type]) |
||||
}; |
||||
}; |
||||
|
||||
export default connect(mapStateToProps)(StatusList); |
@ -0,0 +1,6 @@ |
||||
import { combineReducers } from 'redux-immutable'; |
||||
import statuses from './statuses'; |
||||
|
||||
export default combineReducers({ |
||||
statuses |
||||
}); |
@ -0,0 +1,17 @@ |
||||
import { SET_TIMELINE, ADD_STATUS } from '../actions/statuses'; |
||||
import Immutable from 'immutable'; |
||||
|
||||
const initialState = Immutable.Map(); |
||||
|
||||
export default function statuses(state = initialState, action) { |
||||
switch(action.type) { |
||||
case SET_TIMELINE: |
||||
return state.set(action.timeline, Immutable.fromJS(action.statuses)); |
||||
case ADD_STATUS: |
||||
return state.update(action.timeline, function (list) { |
||||
list.unshift(Immutable.fromJS(action.status)); |
||||
}); |
||||
default: |
||||
return state; |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
import { createStore } from 'redux'; |
||||
import appReducer from '../reducers'; |
||||
|
||||
export default function configureStore(initialState) { |
||||
return createStore(appReducer, initialState); |
||||
} |
@ -1,9 +1,9 @@ |
||||
class HomeController < ApplicationController |
||||
layout 'dashboard' |
||||
|
||||
before_action :authenticate_user! |
||||
|
||||
def index |
||||
@timeline = Feed.new(:home, current_user.account).get(10, params[:max_id]) |
||||
@body_classes = 'app-body' |
||||
@home = Feed.new(:home, current_user.account).get(20) |
||||
@mentions = Feed.new(:mentions, current_user.account).get(20) |
||||
end |
||||
end |
||||
|
@ -1,10 +1 @@ |
||||
= simple_form_for Status.new, url: statuses_path, method: :post do |f| |
||||
= f.input :text, required: true, autofocus: true, label: false, placeholder: 'What are you up to?' |
||||
|
||||
.form-actions |
||||
= f.button :submit, 'Post update' |
||||
|
||||
- content_for :raw_content do |
||||
.activity-stream.activity-stream-embedded |
||||
- @timeline.each do |status| |
||||
= render partial: 'stream_entries/status', locals: { status: status } |
||||
= react_component 'Root', { timelines: { home: render(file: 'api/statuses/home', locals: { statuses: @home }, formats: :json), mentions: render(file: 'api/statuses/mentions', locals: { statuses: @mentions }, formats: :json) }}, class: 'app-holder', prerender: false |
||||
|
@ -1,39 +0,0 @@ |
||||
- content_for :content do |
||||
.dashboard-wrapper |
||||
.dashboard__sidebar |
||||
.dashboard__top-bar.alternate |
||||
|
||||
.dashboard__current-user |
||||
= link_to account_path(current_user.account) do |
||||
= image_tag current_user.account.avatar.url(:medium), class: 'dashboard__current-user__avatar' |
||||
%strong.dashboard__current-user__display-name= display_name(current_user.account) |
||||
%span.dashboard__current-user__username= "@#{current_user.account.username}" |
||||
%ul |
||||
%li{ class: active_nav_class(root_path) } |
||||
= link_to root_path do |
||||
= fa_icon 'home' |
||||
Home |
||||
%li{ class: active_nav_class(oauth_authorized_applications_path) } |
||||
= link_to oauth_authorized_applications_path do |
||||
= fa_icon 'shield' |
||||
Authorized apps |
||||
%li{ class: active_nav_class(settings_path) } |
||||
= link_to settings_path do |
||||
= fa_icon 'user' |
||||
Edit profile |
||||
|
||||
.dashboard__content |
||||
.dashboard__top-bar |
||||
= content_for?(:page_title) ? yield(:page_title) : 'Mastodon' |
||||
%ul |
||||
%li= link_to fa_icon('gear'), edit_registration_path(current_user), title: 'Change password' |
||||
%li= link_to fa_icon('sign-out'), destroy_user_session_path, method: :delete, title: 'Sign out' |
||||
|
||||
.dashboard__content__content= yield |
||||
|
||||
= yield(:raw_content) |
||||
|
||||
.footer |
||||
.domain= Rails.configuration.x.local_domain |
||||
|
||||
= render template: "layouts/application" |
@ -0,0 +1,20 @@ |
||||
{ |
||||
"name": "mastodon", |
||||
"devDependencies": { |
||||
"babel-preset-es2015": "^6.13.2", |
||||
"babel-preset-react": "^6.11.1", |
||||
"babelify": "^7.3.0", |
||||
"browserify": "^13.1.0", |
||||
"browserify-incremental": "^3.1.1", |
||||
"react": "^15.3.0", |
||||
"react-dom": "^15.3.0", |
||||
"redux-devtools": "^3.3.1" |
||||
}, |
||||
"dependencies": { |
||||
"immutable": "^3.8.1", |
||||
"react-immutable-proptypes": "^2.1.0", |
||||
"react-redux": "^4.4.5", |
||||
"redux": "^3.5.2", |
||||
"redux-immutable": "^3.0.8" |
||||
} |
||||
} |
Loading…
Reference in new issue