diff --git a/client/constants.js b/client/constants.js
index 12769f4a..c2e6c5be 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -78,6 +78,9 @@ export const SHOW_EDITOR_OPTIONS = 'SHOW_EDITOR_OPTIONS';
export const CLOSE_EDITOR_OPTIONS = 'CLOSE_EDITOR_OPTIONS';
export const SHOW_KEYBOARD_SHORTCUT_MODAL = 'SHOW_KEYBOARD_SHORTCUT_MODAL';
export const CLOSE_KEYBOARD_SHORTCUT_MODAL = 'CLOSE_KEYBOARD_SHORTCUT_MODAL';
+export const SHOW_TOAST = 'SHOW_TOAST';
+export const HIDE_TOAST = 'HIDE_TOAST';
+export const SET_TOAST_TEXT = 'SET_TOAST_TEXT';
// eventually, handle errors more specifically and better
export const ERROR = 'ERROR';
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index c6358397..18ad523d 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -5,6 +5,7 @@ import JSZip from 'jszip';
import JSZipUtils from 'jszip-utils';
import { saveAs } from 'file-saver';
import { getBlobUrl } from './files';
+import { showToast, setToastText } from './toast';
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
@@ -60,6 +61,8 @@ export function saveProject() {
dispatch({
type: ActionTypes.PROJECT_SAVE_SUCCESS
});
+ dispatch(showToast());
+ dispatch(setToastText('Project saved.'));
})
.catch((response) => dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL,
@@ -76,6 +79,8 @@ export function saveProject() {
owner: response.data.user,
files: response.data.files
});
+ dispatch(showToast());
+ dispatch(setToastText('Project saved.'));
})
.catch(response => dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL,
diff --git a/client/modules/IDE/actions/toast.js b/client/modules/IDE/actions/toast.js
new file mode 100644
index 00000000..d39ed79a
--- /dev/null
+++ b/client/modules/IDE/actions/toast.js
@@ -0,0 +1,23 @@
+import * as ActionTypes from '../../../constants';
+
+export function hideToast() {
+ return {
+ type: ActionTypes.HIDE_TOAST
+ };
+}
+
+export function showToast() {
+ return (dispatch) => {
+ dispatch({
+ type: ActionTypes.SHOW_TOAST
+ });
+ setTimeout(() => dispatch(hideToast()), 1500);
+ };
+}
+
+export function setToastText(text) {
+ return {
+ type: ActionTypes.SET_TOAST_TEXT,
+ text
+ };
+}
diff --git a/client/modules/IDE/components/Toast.js b/client/modules/IDE/components/Toast.js
new file mode 100644
index 00000000..8b56c5fd
--- /dev/null
+++ b/client/modules/IDE/components/Toast.js
@@ -0,0 +1,34 @@
+import React, { PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import InlineSVG from 'react-inlinesvg';
+const exitUrl = require('../../../images/exit.svg');
+import * as ToastActions from '../actions/toast';
+
+function Toast(props) {
+ return (
+
+
+ {props.text}
+
+
+
+ );
+}
+
+Toast.propTypes = {
+ text: PropTypes.string.isRequired,
+ hideToast: PropTypes.func.isRequired
+};
+
+function mapStateToProps(state) {
+ return state.toast;
+}
+
+function mapDispatchToProps(dispatch) {
+ return bindActionCreators(ToastActions, dispatch);
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Toast);
diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js
index 1fcb6148..04b36d06 100644
--- a/client/modules/IDE/pages/IDEView.js
+++ b/client/modules/IDE/pages/IDEView.js
@@ -11,6 +11,7 @@ import ShareModal from '../components/ShareModal';
import KeyboardShortcutModal from '../components/KeyboardShortcutModal';
import Nav from '../../../components/Nav';
import Console from '../components/Console';
+import Toast from '../components/Toast';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as FileActions from '../actions/files';
@@ -19,6 +20,7 @@ import * as ProjectActions from '../actions/project';
import * as EditorAccessibilityActions from '../actions/editorAccessibility';
import * as PreferencesActions from '../actions/preferences';
import * as UserActions from '../../User/actions';
+import * as ToastActions from '../actions/toast';
import { getHTMLFile, getJSFiles, getCSSFiles } from '../reducers/files';
import SplitPane from 'react-split-pane';
import Overlay from '../../App/components/Overlay';
@@ -114,6 +116,7 @@ class IDEView extends React.Component {
render() {
return (
+ {this.props.toast.isVisible && }
{
+ switch (action.type) {
+ case ActionTypes.SHOW_TOAST:
+ return Object.assign({}, state, { isVisible: true });
+ case ActionTypes.HIDE_TOAST:
+ return Object.assign({}, state, { isVisible: false });
+ case ActionTypes.SET_TOAST_TEXT:
+ return Object.assign({}, state, { text: action.text });
+ default:
+ return state;
+ }
+};
+
+export default toast;
diff --git a/client/reducers.js b/client/reducers.js
index d827a927..8581c55b 100644
--- a/client/reducers.js
+++ b/client/reducers.js
@@ -6,6 +6,7 @@ import project from './modules/IDE/reducers/project';
import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
import user from './modules/User/reducers';
import sketches from './modules/Sketch/reducers';
+import toast from './modules/IDE/reducers/toast';
import { reducer as form } from 'redux-form';
const rootReducer = combineReducers({
@@ -16,7 +17,8 @@ const rootReducer = combineReducers({
user,
project,
sketches,
- editorAccessibility
+ editorAccessibility,
+ toast
});
export default rootReducer;
diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss
index 87d5051d..7ab71052 100644
--- a/client/styles/abstracts/_variables.scss
+++ b/client/styles/abstracts/_variables.scss
@@ -25,6 +25,9 @@ $light-icon-color: #8b8b8b;
$light-icon-hover-color: $light-primary-text-color;
$light-shadow-color: rgba(0, 0, 0, 0.16);
+$toast-background-color: #979797;
+$toast-text-color: $white;
+
$dark-primary-text-color: $white;
$dark-secondary-text-color: #c2c2c2;
$dark-inactive-color: #7d7d7d;
diff --git a/client/styles/components/_toast.scss b/client/styles/components/_toast.scss
new file mode 100644
index 00000000..da22fe5f
--- /dev/null
+++ b/client/styles/components/_toast.scss
@@ -0,0 +1,18 @@
+.toast {
+ background-color: $toast-background-color;
+ color: $toast-text-color;
+ padding: #{20 / $base-font-size}rem;
+ position: absolute;
+ top: 0;
+ right: 40%;
+ display: flex;
+ z-index: 9999;
+}
+
+.toast__close {
+ @extend %icon;
+ & g {
+ fill: $toast-text-color;
+ }
+ margin-left: #{10 / $base-font-size}rem;
+}
\ No newline at end of file
diff --git a/client/styles/main.scss b/client/styles/main.scss
index 404fdb9a..b7c01a16 100644
--- a/client/styles/main.scss
+++ b/client/styles/main.scss
@@ -25,6 +25,7 @@
@import 'components/about';
@import 'components/github-button';
@import 'components/forms';
+@import 'components/toast';
@import 'layout/ide';
@import 'layout/sketch-list';