mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-26 09:42:46 +08:00
Merge pull request #803 from Ducz0r/lm-sci-1599
Add global flash notifications/alerts [SCI-1599]
This commit is contained in:
commit
f8fc2737d5
12 changed files with 282 additions and 4 deletions
|
@ -0,0 +1,81 @@
|
|||
import React, { Component } from "react";
|
||||
import styled from "styled-components";
|
||||
import { string, number, func } from "prop-types";
|
||||
import { Grid, Row, Col } from "react-bootstrap";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
class Alert extends Component {
|
||||
static alertClass(type) {
|
||||
const classes = {
|
||||
error: "alert-danger",
|
||||
alert: "alert-warning",
|
||||
notice: "alert-info",
|
||||
success: "alert-success"
|
||||
};
|
||||
return classes[type] || classes.success;
|
||||
}
|
||||
|
||||
static glyphiconClass(type) {
|
||||
const classes = {
|
||||
error: "glyphicon-exclamation-sign",
|
||||
alert: "glyphicon-exclamation-sign",
|
||||
notice: "glyphicon-info-sign",
|
||||
success: "glyphicon-ok-sign"
|
||||
};
|
||||
return classes[type] || classes.success;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.timer = setTimeout(
|
||||
this.props.onClose,
|
||||
this.props.timeout
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
render() {
|
||||
const alertClassName =
|
||||
`alert
|
||||
${Alert.alertClass(this.props.type)}
|
||||
alert-dismissable
|
||||
alert-floating`;
|
||||
const glyphiconClassName =
|
||||
`glyphicon
|
||||
${Alert.glyphiconClass(this.props.type)}`;
|
||||
|
||||
return (
|
||||
<Wrapper className={alertClassName}>
|
||||
<Grid>
|
||||
<Row>
|
||||
<Col>
|
||||
<button type="button"
|
||||
className="close"
|
||||
data-dismiss="alert"
|
||||
aria-label="Close"
|
||||
onClick={this.props.onClose}>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<span className={glyphiconClassName} />
|
||||
<span> {this.props.message}</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Alert.propTypes = {
|
||||
message: string.isRequired,
|
||||
type: string.isRequired,
|
||||
timeout: number.isRequired,
|
||||
onClose: func.isRequired
|
||||
};
|
||||
|
||||
export default Alert;
|
65
app/javascript/src/components/AlertsContainer/index.jsx
Normal file
65
app/javascript/src/components/AlertsContainer/index.jsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import TransitionGroup from 'react-transition-group/TransitionGroup';
|
||||
import CSSTransition from 'react-transition-group/CSSTransition';
|
||||
import { shape, arrayOf, string, number, func } from "prop-types";
|
||||
import { clearAlert } from "../actions/AlertsActions";
|
||||
import Alert from "./components/Alert";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
class AlertsContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.renderAlert = this.renderAlert.bind(this);
|
||||
}
|
||||
|
||||
renderAlert(alert) {
|
||||
return (
|
||||
<Alert message={alert.message}
|
||||
type={alert.type}
|
||||
timeout={alert.timeout}
|
||||
onClose={() => this.props.clearAlert(alert.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Wrapper>
|
||||
<TransitionGroup>
|
||||
{this.props.alerts.map((alert) =>
|
||||
<CSSTransition key={alert.id}
|
||||
timeout={500}
|
||||
classNames="alert-animated">
|
||||
{this.renderAlert(alert)}
|
||||
</CSSTransition>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlertsContainer.propTypes = {
|
||||
alerts: arrayOf(
|
||||
shape({
|
||||
message: string.isRequired,
|
||||
type: string.isRequired,
|
||||
id: string.isRequired,
|
||||
timeout: number,
|
||||
onClose: func
|
||||
}).isRequired
|
||||
).isRequired,
|
||||
clearAlert: func.isRequired
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ alerts }) => ({ alerts });
|
||||
|
||||
export default connect(mapStateToProps, { clearAlert })(AlertsContainer);
|
|
@ -20,6 +20,7 @@ import UserAccountDropdown from "./components/UserAccountDropdown";
|
|||
const StyledNavbar = styled(Navbar)`
|
||||
background-color: ${WHITE_COLOR};
|
||||
border-color: ${BORDER_GRAY_COLOR};
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
const StyledBrand = styled.a`
|
||||
|
|
29
app/javascript/src/components/actions/AlertsActions.js
Normal file
29
app/javascript/src/components/actions/AlertsActions.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import shortid from "shortid";
|
||||
import {
|
||||
ADD_ALERT,
|
||||
CLEAR_ALERT,
|
||||
CLEAR_ALL_ALERTS
|
||||
} from "../../config/action_types";
|
||||
|
||||
export function addAlert(message,
|
||||
type,
|
||||
id = shortid.generate(),
|
||||
timeout = 5000) {
|
||||
return {
|
||||
payload: {
|
||||
message,
|
||||
type,
|
||||
id,
|
||||
timeout
|
||||
},
|
||||
type: ADD_ALERT
|
||||
};
|
||||
}
|
||||
|
||||
export function clearAlert(id) {
|
||||
return { payload: id, type: CLEAR_ALERT }
|
||||
}
|
||||
|
||||
export function clearAllAlerts() {
|
||||
return { type: CLEAR_ALL_ALERTS };
|
||||
}
|
31
app/javascript/src/components/reducers/AlertsReducers.js
Normal file
31
app/javascript/src/components/reducers/AlertsReducers.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
ADD_ALERT,
|
||||
CLEAR_ALERT,
|
||||
CLEAR_ALL_ALERTS
|
||||
} from "../../config/action_types";
|
||||
|
||||
export const alerts = (
|
||||
state = [],
|
||||
action
|
||||
) => {
|
||||
switch(action.type) {
|
||||
case ADD_ALERT:
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
message: action.payload.message,
|
||||
type: action.payload.type,
|
||||
id: action.payload.id,
|
||||
timeout: action.payload.timeout
|
||||
}
|
||||
];
|
||||
case CLEAR_ALERT:
|
||||
return state.filter((alert) => (
|
||||
alert.id !== action.payload
|
||||
));
|
||||
case CLEAR_ALL_ALERTS:
|
||||
return [];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -35,3 +35,8 @@ export const UPDATE_TEAM_DESCRIPTION_MODAL = "UPDATE_TEAM_DESCRIPTION_MODAL";
|
|||
// spinner
|
||||
export const SPINNER_ON = "SPINNER_ON";
|
||||
export const SPINNER_OFF = "SPINNER_OFF";
|
||||
|
||||
// alerts
|
||||
export const ADD_ALERT = "ADD_ALERT";
|
||||
export const CLEAR_ALERT = "CLEAR_ALERT";
|
||||
export const CLEAR_ALL_ALERTS = "CLEAR_ALL_ALERTS";
|
|
@ -6,11 +6,13 @@ import {
|
|||
} from "../components/reducers/TeamReducers";
|
||||
import { globalActivities } from "../components/reducers/ActivitiesReducers";
|
||||
import { currentUser } from "../components/reducers/UsersReducer";
|
||||
import { alerts } from "../components/reducers/AlertsReducers";
|
||||
|
||||
export default combineReducers({
|
||||
current_team: setCurrentTeam,
|
||||
all_teams: getListOfTeams,
|
||||
global_activities: globalActivities,
|
||||
current_user: currentUser,
|
||||
showLeaveTeamModal
|
||||
showLeaveTeamModal,
|
||||
alerts
|
||||
});
|
||||
|
|
|
@ -3,11 +3,13 @@ import { BrowserRouter } from "react-router-dom";
|
|||
import { Provider } from "react-redux";
|
||||
import { IntlProvider, addLocaleData } from "react-intl";
|
||||
import enLocaleData from "react-intl/locale-data/en";
|
||||
import styled from "styled-components";
|
||||
import { flattenMessages } from "./config/locales/utils";
|
||||
import messages from "./config/locales/messages";
|
||||
import store from "./config/store";
|
||||
|
||||
import Spinner from "./components/Spinner";
|
||||
import AlertsContainer from "./components/AlertsContainer";
|
||||
import ModalsContainer from "./components/ModalsContainer";
|
||||
import SettingsPage from "./scenes/SettingsPage";
|
||||
import Navigation from "./components/Navigation";
|
||||
|
@ -15,14 +17,22 @@ import Navigation from "./components/Navigation";
|
|||
addLocaleData([...enLocaleData]);
|
||||
const locale = "en-US";
|
||||
|
||||
export default () =>
|
||||
const ContentWrapper = styled.div`
|
||||
margin-top: 15px;
|
||||
`;
|
||||
|
||||
const ScinoteApp = () =>
|
||||
<Provider store={store}>
|
||||
<IntlProvider locale={locale} messages={flattenMessages(messages[locale])}>
|
||||
<IntlProvider locale={locale}
|
||||
messages={flattenMessages(messages[locale])}>
|
||||
<div>
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<Navigation />
|
||||
<SettingsPage />
|
||||
<AlertsContainer />
|
||||
<ContentWrapper>
|
||||
<SettingsPage />
|
||||
</ContentWrapper>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
|
||||
|
@ -31,3 +41,5 @@ export default () =>
|
|||
</div>
|
||||
</IntlProvider>
|
||||
</Provider>;
|
||||
|
||||
export default ScinoteApp;
|
||||
|
|
30
app/javascript/src/styles/animations.scss
Normal file
30
app/javascript/src/styles/animations.scss
Normal file
|
@ -0,0 +1,30 @@
|
|||
.alert-animated-enter {
|
||||
opacity: .01;
|
||||
|
||||
&.alert-animated-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 150ms ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-animated-exit {
|
||||
opacity: 1;
|
||||
padding-bottom: 15px;
|
||||
padding-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
height: 50px;
|
||||
|
||||
&.alert-animated-exit-active {
|
||||
overflow: hidden;
|
||||
opacity: .01;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
height: 0;
|
||||
transition: opacity 300ms ease-in,
|
||||
padding-top 500ms ease-in,
|
||||
padding-bottom 500ms ease-in,
|
||||
margin-bottom 500ms ease-in,
|
||||
height 500ms ease-in;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
@import 'constants';
|
||||
@import 'animations';
|
||||
@import 'react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css';
|
||||
@import '~react-bootstrap-table/dist/react-bootstrap-table.min';
|
||||
@import 'react-tagsinput/react-tagsinput.css';
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
"react-bootstrap-timezone-picker": "^1.0.11",
|
||||
"react-data-grid": "^2.0.2",
|
||||
"react-tagsinput": "^3.17.0",
|
||||
"react-transition-group": "^2.2.0",
|
||||
"react-dom": "^15.6.1",
|
||||
"react-intl": "^2.3.0",
|
||||
"react-intl-redux": "^0.6.0",
|
||||
|
@ -82,6 +83,7 @@
|
|||
"redux-thunk": "^2.2.0",
|
||||
"resolve-url-loader": "^2.1.0",
|
||||
"sass-loader": "^6.0.6",
|
||||
"shortid": "^2.2.8",
|
||||
"style-loader": "^0.18.2",
|
||||
"styled-components": "^2.1.1",
|
||||
"webpack": "^3.2.0",
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -1156,6 +1156,10 @@ center-align@^0.1.1:
|
|||
align-text "^0.1.3"
|
||||
lazy-cache "^1.0.3"
|
||||
|
||||
chain-function@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
|
||||
|
||||
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
|
@ -4825,6 +4829,17 @@ react-timezone@^0.2.0:
|
|||
dependencies:
|
||||
classnames "^2.2.1"
|
||||
|
||||
react-transition-group@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.0.tgz#793bf8cb15bfe91b3101b24bce1c1d2891659575"
|
||||
dependencies:
|
||||
chain-function "^1.0.0"
|
||||
classnames "^2.2.5"
|
||||
dom-helpers "^3.2.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.5.8"
|
||||
warning "^3.0.0"
|
||||
|
||||
react@^15.6.1:
|
||||
version "15.6.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
|
||||
|
@ -5298,6 +5313,10 @@ shelljs@^0.7.5:
|
|||
interpret "^1.0.0"
|
||||
rechoir "^0.6.2"
|
||||
|
||||
shortid@^2.2.8:
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
|
||||
|
||||
signal-exit@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
|
|
Loading…
Reference in a new issue