From 6ef19725f1613f22e097cba2ac72f527191d5cbb Mon Sep 17 00:00:00 2001 From: zmagod Date: Wed, 2 Aug 2017 17:27:05 +0200 Subject: [PATCH] adds redux --- Gemfile | 1 + Gemfile.lock | 6 +- .../client_api/activities_controller.rb | 27 + app/javascript/packs/app/reducers.js | 8 + app/javascript/packs/app/routes.js | 1 + app/javascript/packs/app/store.js | 16 + app/javascript/packs/locales/messages.js | 16 +- app/javascript/packs/locales/utils.js | 15 + app/javascript/packs/settings/app.jsx | 43 +- .../packs/shared/actions/ActivitiesActions.js | 24 + app/javascript/packs/shared/actions/types.js | 2 + .../packs/shared/constants/colors.js | 1 + .../components/ActivityDateElement.jsx | 22 + .../navigation/components/ActivityElement.jsx | 27 + .../components/GlobalActivitiesModal.jsx | 116 + .../navigation/components/TeamSwitch.jsx | 29 + .../packs/shared/navigation/index.js | 126 +- .../shared/reducers/ActivitiesReducers.js | 13 + .../packs/shared/reducers/TeamReducers.js | 8 + .../client_api/activities/index.json.jbuilder | 5 + .../layouts/application_client_api.html.erb | 3 +- config/initializers/assets.rb | 4 + config/routes.rb | 1 + package.json | 6 + .../bootstrap/css/bootstrap-theme.css | 587 ++ .../bootstrap/css/bootstrap-theme.css.map | 1 + .../bootstrap/css/bootstrap-theme.min.css | 6 + .../bootstrap/css/bootstrap-theme.min.css.map | 1 + .../stylesheets/bootstrap/css/bootstrap.css | 6757 +++++++++++++++++ .../bootstrap/css/bootstrap.css.map | 1 + .../bootstrap/css/bootstrap.min.css | 6 + .../bootstrap/css/bootstrap.min.css.map | 1 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../font-awesome/css/font-awesome.css | 2337 ++++++ .../font-awesome/css/font-awesome.min.css | 4 + .../font-awesome/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes .../fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes .../fonts/fontawesome-webfont.svg | 2671 +++++++ .../fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes yarn.lock | 96 +- 46 files changed, 13228 insertions(+), 48 deletions(-) create mode 100644 app/controllers/client_api/activities_controller.rb create mode 100644 app/javascript/packs/app/reducers.js create mode 100644 app/javascript/packs/app/routes.js create mode 100644 app/javascript/packs/app/store.js create mode 100644 app/javascript/packs/locales/utils.js create mode 100644 app/javascript/packs/shared/actions/ActivitiesActions.js create mode 100644 app/javascript/packs/shared/actions/types.js create mode 100644 app/javascript/packs/shared/constants/colors.js create mode 100644 app/javascript/packs/shared/navigation/components/ActivityDateElement.jsx create mode 100644 app/javascript/packs/shared/navigation/components/ActivityElement.jsx create mode 100644 app/javascript/packs/shared/navigation/components/GlobalActivitiesModal.jsx create mode 100644 app/javascript/packs/shared/navigation/components/TeamSwitch.jsx create mode 100644 app/javascript/packs/shared/reducers/ActivitiesReducers.js create mode 100644 app/javascript/packs/shared/reducers/TeamReducers.js create mode 100644 app/views/client_api/activities/index.json.jbuilder create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap-theme.css create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap-theme.css.map create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap-theme.min.css create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap-theme.min.css.map create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap.css create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap.css.map create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap.min.css create mode 100644 vendor/assets/stylesheets/bootstrap/css/bootstrap.min.css.map create mode 100644 vendor/assets/stylesheets/bootstrap/fonts/glyphicons-halflings-regular.eot create mode 100644 vendor/assets/stylesheets/bootstrap/fonts/glyphicons-halflings-regular.svg create mode 100644 vendor/assets/stylesheets/bootstrap/fonts/glyphicons-halflings-regular.ttf create mode 100644 vendor/assets/stylesheets/bootstrap/fonts/glyphicons-halflings-regular.woff create mode 100644 vendor/assets/stylesheets/bootstrap/fonts/glyphicons-halflings-regular.woff2 create mode 100644 vendor/assets/stylesheets/font-awesome/css/font-awesome.css create mode 100644 vendor/assets/stylesheets/font-awesome/css/font-awesome.min.css create mode 100644 vendor/assets/stylesheets/font-awesome/fonts/FontAwesome.otf create mode 100644 vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.eot create mode 100644 vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.svg create mode 100644 vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf create mode 100644 vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff create mode 100644 vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2 diff --git a/Gemfile b/Gemfile index eb9cc5a95..456904cf1 100644 --- a/Gemfile +++ b/Gemfile @@ -61,6 +61,7 @@ gem 'sneaky-save', git: 'https://github.com/einzige/sneaky-save' gem 'rails_autolink', '~> 1.1', '>= 1.1.6' gem 'delayed_paperclip' gem 'rubyzip' +gem 'jbuilder' # JSON structures via a Builder-style DSL gem 'paperclip', '~> 5.1' # File attachment, image attachment library gem 'aws-sdk', '~> 2' diff --git a/Gemfile.lock b/Gemfile.lock index 4aa7a1f26..6d3944d91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -194,6 +194,9 @@ GEM introjs-rails (1.0.0) sass-rails (>= 3.2) thor (~> 0.14) + jbuilder (2.7.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) jmespath (1.3.1) jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) @@ -478,6 +481,7 @@ DEPENDENCIES hammerjs-rails i18n-js (>= 3.0.0.rc11) introjs-rails + jbuilder jquery-rails jquery-scrollto-rails! jquery-turbolinks @@ -534,4 +538,4 @@ RUBY VERSION ruby 2.4.1p111 BUNDLED WITH - 1.15.1 + 1.15.3 diff --git a/app/controllers/client_api/activities_controller.rb b/app/controllers/client_api/activities_controller.rb new file mode 100644 index 000000000..0025ce2f3 --- /dev/null +++ b/app/controllers/client_api/activities_controller.rb @@ -0,0 +1,27 @@ +module ClientApi + class ActivitiesController < ApplicationController + include ActivityHelper + before_action :load_vars + + def index + @per_page = + @activities = current_user.last_activities(@last_activity_id, + Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT) + + respond_to do |format| + format.json do + render template: '/client_api/activities/index', + status: :ok, + locals: { activities: @activities } + end + end + end + + private + + def load_vars + @last_activity_id = params[:from].to_i || 0 + @last_activity = Activity.find_by_id(@last_activity_id) + end + end +end diff --git a/app/javascript/packs/app/reducers.js b/app/javascript/packs/app/reducers.js new file mode 100644 index 000000000..a92973ab2 --- /dev/null +++ b/app/javascript/packs/app/reducers.js @@ -0,0 +1,8 @@ +import { combineReducers } from "redux"; +import { setCurrentTeam } from "../shared/reducers/TeamReducers"; +import { globalActivities } from "../shared/reducers/ActivitiesReducers"; + +export default combineReducers({ + team: setCurrentTeam, + global_activities: globalActivities +}); diff --git a/app/javascript/packs/app/routes.js b/app/javascript/packs/app/routes.js new file mode 100644 index 000000000..bb5d8b412 --- /dev/null +++ b/app/javascript/packs/app/routes.js @@ -0,0 +1 @@ +export const ACTIVITIES_PATH = "/client_api/activities"; diff --git a/app/javascript/packs/app/store.js b/app/javascript/packs/app/store.js new file mode 100644 index 000000000..c73c866b4 --- /dev/null +++ b/app/javascript/packs/app/store.js @@ -0,0 +1,16 @@ +import { createStore, applyMiddleware, compose } from "redux"; +import thunk from "redux-thunk"; +import rootReducer from "./reducers"; + +const store = createStore( + rootReducer, + compose( + applyMiddleware(thunk), + typeof window === "object" && + typeof window.devToolsExtension !== "undefined" + ? window.devToolsExtension() + : f => f + ) +); + +export default store; diff --git a/app/javascript/packs/locales/messages.js b/app/javascript/packs/locales/messages.js index c47a2f943..000d15e06 100644 --- a/app/javascript/packs/locales/messages.js +++ b/app/javascript/packs/locales/messages.js @@ -1,5 +1,15 @@ export default { - 'en-US': { - + "en-US": { + general: { + close: "Close" + }, + navbar: { + page_title: "sciNote" + }, + activities: { + modal_title: "Activities", + no_data: "No Data", + more_activities: "More Activities" + } } -} +}; diff --git a/app/javascript/packs/locales/utils.js b/app/javascript/packs/locales/utils.js new file mode 100644 index 000000000..3b5c62876 --- /dev/null +++ b/app/javascript/packs/locales/utils.js @@ -0,0 +1,15 @@ +// with this utility function we can write nested locales like an json object +export function flattenMessages(nestedMessages, prefix = '') { + return Object.keys(nestedMessages).reduce((messages, key) => { + let value = nestedMessages[key]; + let prefixedKey = prefix ? `${prefix}.${key}` : key; + + if (typeof value === 'string') { + messages[prefixedKey] = value; + } else { + Object.assign(messages, flattenMessages(value, prefixedKey)); + } + + return messages; + }, {}); +} diff --git a/app/javascript/packs/settings/app.jsx b/app/javascript/packs/settings/app.jsx index df3a2f1ee..61b22bf50 100644 --- a/app/javascript/packs/settings/app.jsx +++ b/app/javascript/packs/settings/app.jsx @@ -1,22 +1,33 @@ -// Run this example by adding <%= javascript_pack_tag 'hello_react' %> to the head of your layout file, -// like app/views/layouts/application.html.erb. All it does is render
Hello React
at the bottom -// of the page. +import React from "react"; +import ReactDOM from "react-dom"; +import Navigation from "../shared/navigation"; +import { Provider } from "react-redux"; +import { IntlProvider } from "react-intl"; +import { addLocaleData } from "react-intl"; +import enLocaleData from "react-intl/locale-data/en"; +import { flattenMessages } from "../locales/utils"; +import store from "../app/store"; +import messages from "../locales/messages"; -import React from 'react'; -import ReactDOM from 'react-dom'; -import Navigation from '../shared/navigation'; -import messages from '../locales/messages'; +addLocaleData([...enLocaleData]); +let locale = "en-US"; -const App = () => ( +const SettingsPage = () =>
- + .... -
-) + ; -document.addEventListener('DOMContentLoaded', () => { +document.addEventListener("DOMContentLoaded", () => { ReactDOM.render( - , - document.getElementById('root') - ) -}) + + + + + , + document.getElementById("root") + ); +}); diff --git a/app/javascript/packs/shared/actions/ActivitiesActions.js b/app/javascript/packs/shared/actions/ActivitiesActions.js new file mode 100644 index 000000000..12517d6df --- /dev/null +++ b/app/javascript/packs/shared/actions/ActivitiesActions.js @@ -0,0 +1,24 @@ +import axios from "axios"; +import { ACTIVITIES_PATH } from "../../app/routes"; +import { GLOBAL_ACTIVITIES_DATA } from "./types"; + +export function addActivitiesData(data) { + return { + type: GLOBAL_ACTIVITIES_DATA, + payload: data + }; +} + +export function getActivities(last_id = 0) { + return dispatch => { + let path = `${ACTIVITIES_PATH}?from=${last_id}`; + axios + .get(path, { withCredentials: true }) + .then(response => { + dispatch(addActivitiesData(response.data)); + }) + .catch(error => { + console.log("get Activites Error: ", error); + }); + }; +} diff --git a/app/javascript/packs/shared/actions/types.js b/app/javascript/packs/shared/actions/types.js new file mode 100644 index 000000000..10c5192b8 --- /dev/null +++ b/app/javascript/packs/shared/actions/types.js @@ -0,0 +1,2 @@ +export const SET_CURRENT_TEAM = "SET_CURRENT_TEAM"; +export const GLOBAL_ACTIVITIES_DATA = "GLOBAL_ACTIVITIES_DATA"; diff --git a/app/javascript/packs/shared/constants/colors.js b/app/javascript/packs/shared/constants/colors.js new file mode 100644 index 000000000..1c9469df8 --- /dev/null +++ b/app/javascript/packs/shared/constants/colors.js @@ -0,0 +1 @@ +export const MAIN_COLOR_BLUE = '#37a0d9'; diff --git a/app/javascript/packs/shared/navigation/components/ActivityDateElement.jsx b/app/javascript/packs/shared/navigation/components/ActivityDateElement.jsx new file mode 100644 index 000000000..5de3ae7a3 --- /dev/null +++ b/app/javascript/packs/shared/navigation/components/ActivityDateElement.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { FormattedDate } from "react-intl"; + +const ActivityDateElement = ({ date }) => { + return ( +
  • + +
  • + ); +}; + +ActivityDateElement.propTypes = { + +} + +export default ActivityDateElement; diff --git a/app/javascript/packs/shared/navigation/components/ActivityElement.jsx b/app/javascript/packs/shared/navigation/components/ActivityElement.jsx new file mode 100644 index 000000000..a3cd30cb5 --- /dev/null +++ b/app/javascript/packs/shared/navigation/components/ActivityElement.jsx @@ -0,0 +1,27 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { FormattedTime } from "react-intl"; + +const ActivityElement = ({ activity }) => { + return ( +
  • + + + + +
  • + ); +}; + +ActivityElement.propTypes = { + activity: PropTypes.shape({ + message: PropTypes.string.isRequired, + created_at: PropTypes.string.isRequired + }) +}; + +export default ActivityElement; diff --git a/app/javascript/packs/shared/navigation/components/GlobalActivitiesModal.jsx b/app/javascript/packs/shared/navigation/components/GlobalActivitiesModal.jsx new file mode 100644 index 000000000..1848699d2 --- /dev/null +++ b/app/javascript/packs/shared/navigation/components/GlobalActivitiesModal.jsx @@ -0,0 +1,116 @@ +import React, { Component } from "react"; +import { connect } from "react-redux"; +import PropTypes from "prop-types"; +import { FormattedMessage } from "react-intl"; +import { Modal, Button } from "react-bootstrap"; +import _ from "lodash"; + +import { getActivities } from "../../actions/ActivitiesActions"; +import ActivityElement from "./ActivityElement"; +import ActivityDateElement from "./ActivityDateElement"; + +class GlobalActivitiesModal extends Component { + constructor(props) { + super(props); + this.displayActivities = this.displayActivities.bind(this); + this.addMoreActivities = this.addMoreActivities.bind(this); + } + + displayActivities() { + console.log(this.props.global_activities); + if (this.props.global_activities.length === 0) { + return ( +
  • + +
  • + ); + } + return this.props.global_activities.map((activity, i, arr) => { + let newDate = new Date(activity.created_at); + if (i > 0) { + let prevDate = new Date(arr[i - 1].created_at); + if (prevDate < newDate) { + return [ + , + + ]; + } + } else { + return [ + , + + ]; + } + return ; + }); + } + + addMoreActivities() { + let last_id = _.last(this.props.global_activities).id; + this.props.fetchActivities(last_id); + } + + addMoreButton() { + console.log(this.props.more_global_activities); + if(this.props.more_global_activities) { + return ( +
  • + +
  • + ); + } + } + + render() { + return ( + + + + + + + +
      + {this.displayActivities()} + {this.addMoreButton()} +
    +
    + + + +
    + ); + } +} + +GlobalActivitiesModal.propTypes = { + showModal: PropTypes.bool.isRequired, + onCloseModal: PropTypes.func.isRequired, + fetchActivities: PropTypes.func.isRequired, + more_global_activities: PropTypes.bool.isRequired, + global_activities: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + message: PropTypes.string.isRequired, + created_at: PropTypes.string.isRequired + }) + ).isRequired +}; + +const mapStateToProps = ({ global_activities, more_global_activities }) => { + return { global_activities, more_global_activities }; +}; + +const mapDispatchToProps = dispatch => ({ + fetchActivities(last_id) { + dispatch(getActivities(last_id)); + } +}); + +export default connect(mapStateToProps, mapDispatchToProps)( + GlobalActivitiesModal +); diff --git a/app/javascript/packs/shared/navigation/components/TeamSwitch.jsx b/app/javascript/packs/shared/navigation/components/TeamSwitch.jsx new file mode 100644 index 000000000..9079c88aa --- /dev/null +++ b/app/javascript/packs/shared/navigation/components/TeamSwitch.jsx @@ -0,0 +1,29 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { NavDropdown, MenuItem } from "react-bootstrap"; + +class TeamSwitch extends Component { + constructor(props) { + super(props); + + this.state = { currentTeam: { name: "" }, allTeams: [] }; + } + + render() { + return ( + + Action + + ); + } +} + +TeamSwitch.propTypes = { + eventKey: PropTypes.number.isRequired +} + +export default TeamSwitch; diff --git a/app/javascript/packs/shared/navigation/index.js b/app/javascript/packs/shared/navigation/index.js index 23d650ccc..a82c8b6ef 100644 --- a/app/javascript/packs/shared/navigation/index.js +++ b/app/javascript/packs/shared/navigation/index.js @@ -1,35 +1,113 @@ -import React, { Component } from 'react'; -import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap' +import React, { Component } from "react"; +import { connect } from "react-redux"; +import { Navbar, Nav, NavItem } from "react-bootstrap"; +import styled from "styled-components"; +import { MAIN_COLOR_BLUE } from "../constants/colors"; +import { getActivities } from "../actions/ActivitiesActions"; +import TeamSwitch from "./components/TeamSwitch"; +import GlobalActivitiesModal from "./components/GlobalActivitiesModal"; + +const StyledBrand = styled.a` + background-color: ${MAIN_COLOR_BLUE}; + + &:hover, + &:active, + &:focus { + background-color: ${MAIN_COLOR_BLUE} !important; + } + + & > img { + height: 20px; + } +`; class Navigation extends Component { constructor(props) { super(props); - this.state = { page: '' }; + this.state = { + showActivitesModal: false, + page: "", + currentTeam: { id: 0 } + }; + this.selectItemCallback = this.selectItemCallback.bind(this); + this.closeModalCallback = this.closeModalCallback.bind(this); + } + + selectItemCallback(key, ev) { + if (key === 4) { + ev.preventDefault(); + this.setState({ showActivitesModal: !this.state.showActivitesModal }); + // Call action creator to fetch activities from the server + this.props.fetchActivities(); + } + } + + closeModalCallback() { + this.setState({ showActivitesModal: false }); } render() { - - return( - - - - Current page: { this.props.page } - - - - + return ( +
    + + + + + Logo + + + +