-
- {this.formatError(this.state.error)}
+
+
+
+
+ { this.state.message_text }
+
+
X
-
+
);
}
}
-
-Notification.propTypes = {
- error: PropTypes.any
-}
diff --git a/client/components/notification.scss b/client/components/notification.scss
index 37c9eb72..86069670 100644
--- a/client/components/notification.scss
+++ b/client/components/notification.scss
@@ -1,22 +1,42 @@
.component_notification{
position: fixed;
- bottom: 0;
- left: 0;
+ bottom: 25px;
+ left: 25px;
right: 0;
- text-align: center;
+ font-size: 0.95em;
+
+ .component_notification--container{
+ width: 400px;
- > div{
- display: inline-block;
- background: #637d8b;
- min-width: 200px;
- max-width: 400px;
- margin: 0 auto;
- padding: 10px 15px;
- border-top-left-radius: 3px;
- border-top-right-radius: 3px;
- color: white;
text-align: left;
- cursor: pointer;
- box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px;
+ display: inline-block;
+ padding: 15px 25px 15px 15px;
+ border-radius: 2px;
+ box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px;
+ display: flex;
+ align-items: center;
+
+ &.info{
+ background: rgba(0,0,0,0.6);
+ color: white;
+ }
+ &.error{
+ background: var(--error);
+ color: var(--secondary);
+ }
+ &.success{
+ background: var(--success);
+ color: var(--secondary);
+ }
+
+ .message{
+ flex: 1 1 auto;
+ }
+ .close{
+ color: rgba(0,0,0,0.3);
+ cursor: pointer;
+ padding: 0 2px;
+ font-size: 0.95em;
+ }
}
}
diff --git a/client/helpers/index.js b/client/helpers/index.js
index 8f04e6cf..4016e64b 100644
--- a/client/helpers/index.js
+++ b/client/helpers/index.js
@@ -10,3 +10,4 @@ export { prepare } from './navigate';
export { invalidate, http_get, http_post, http_delete } from './ajax';
export { screenHeight } from './dom';
export { prompt } from './prompt';
+export { notify } from './notify';
diff --git a/client/helpers/notify.js b/client/helpers/notify.js
new file mode 100644
index 00000000..a671bdb3
--- /dev/null
+++ b/client/helpers/notify.js
@@ -0,0 +1,17 @@
+const Message = function (){
+ let fn = null;
+
+ return {
+ send: function(text, type){
+ if(['info', 'success', 'error'].indexOf(type) === -1){ type = 'info'; }
+ if(!fn){ return window.setTimeout(() => this.send(text,type), 50); }
+ fn(text, type);
+ return Promise.resolve();
+ },
+ subscribe: function(_fn){
+ fn = _fn;
+ }
+ };
+};
+
+export const notify = new Message();
diff --git a/client/model/files.js b/client/model/files.js
index fb8bba9f..9bc3ad7f 100644
--- a/client/model/files.js
+++ b/client/model/files.js
@@ -84,7 +84,7 @@ class FileSystem{
rm(path){
const url = '/api/files/rm?path='+prepare(path);
- this._replace(path, 'loading')
+ return this._replace(path, 'loading')
.then(() => http_get(url))
.then((res) => {
if(res.status === 'ok'){
@@ -127,14 +127,14 @@ class FileSystem{
mkdir(path){
const url = '/api/files/mkdir?path='+prepare(path);
- this._add(path, 'loading')
+ return this._add(path, 'loading')
.then(() => this._add(path, 'loading'))
.then(() => http_get(url))
.then((res) => res.status === 'ok'? this._replace(path) : this._replace(path, 'error'));
}
touch(path, file){
- this._add(path, 'loading')
+ return this._add(path, 'loading')
.then(() => {
if(file){
const url = '/api/files/cat?path='+prepare(path);
@@ -152,7 +152,7 @@ class FileSystem{
mv(from, to){
const url = '/api/files/mv?from='+prepare(from)+"&to="+prepare(to);
- ui_before_request(from, to)
+ return ui_before_request(from, to)
.then(() => this._ls_from_cache(dirname(from)))
.then(() => this._ls_from_cache(dirname(to)))
.then(() => http_get(url)
diff --git a/client/pages/connectpage.js b/client/pages/connectpage.js
index 73847c1c..398ae72a 100644
--- a/client/pages/connectpage.js
+++ b/client/pages/connectpage.js
@@ -5,7 +5,7 @@ import './connectpage.scss';
import { Session } from '../model/';
import { Container, NgIf, Loader, Notification } from '../components/';
import { ForkMe, RememberMe, Credentials, Form } from './connectpage/';
-import { cache } from '../helpers/';
+import { cache, notify } from '../helpers/';
import config from '../../config_client';
import { Alert } from '../components/';
@@ -17,7 +17,6 @@ export class ConnectPage extends React.Component {
credentials: {},
remember_me: window.localStorage.hasOwnProperty('credentials') ? true : false,
loading: false,
- error: null,
doing_a_third_party_login: false
};
}
@@ -52,9 +51,9 @@ export class ConnectPage extends React.Component {
const path = params.path && /^\//.test(params.path)? /\/$/.test(params.path) ? params.path : params.path+'/' : '/';
this.props.history.push('/files'+path);
})
- .catch(err => {
- this.setState({loading: false, error: err});
- window.setTimeout(() => this.setState({error: null}), 1000);
+ .catch((err) => {
+ this.setState({loading: false});
+ notify.send(err, 'error');
});
}
@@ -64,16 +63,16 @@ export class ConnectPage extends React.Component {
Session.url('dropbox').then((url) => {
window.location.href = url;
}).catch((err) => {
- this.setState({loading: false, error: err});
- window.setTimeout(() => this.setState({error: null}), 1000);
+ this.setState({loading: false});
+ notify.send(err, 'error');
});
}else if(source === 'google'){
this.setState({loading: true});
Session.url('gdrive').then((url) => {
window.location.href = url;
}).catch((err) => {
- this.setState({loading: false, error: err});
- window.setTimeout(() => this.setState({error: null}), 1000);
+ this.setState({loading: false});
+ notify.send(err, 'error');
});
}
}
@@ -118,7 +117,6 @@ export class ConnectPage extends React.Component {
onCredentialsFound={this.setCredentials.bind(this)}
credentials={this.state.credentials} />
-
);
diff --git a/client/pages/filespage.js b/client/pages/filespage.js
index 68629888..bbc82ab0 100644
--- a/client/pages/filespage.js
+++ b/client/pages/filespage.js
@@ -6,8 +6,8 @@ import Path from 'path';
import './filespage.scss';
import { Files } from '../model/';
-import { NgIf, Loader, Error, Uploader, EventReceiver } from '../components/';
-import { debounce, goToFiles, goToViewer, event, screenHeight } from '../helpers/';
+import { NgIf, Loader, Uploader, EventReceiver } from '../components/';
+import { notify, debounce, goToFiles, goToViewer, event, screenHeight } from '../helpers/';
import { BreadCrumb, FileSystem } from './filespage/';
@EventReceiver
@@ -81,6 +81,7 @@ export class FilesPage extends React.Component {
});
this.setState({files: files, loading: false});
}, (error) => {
+ notify.send(error, 'error');
this.setState({error: error});
});
this.setState({error: false});
@@ -88,18 +89,26 @@ export class FilesPage extends React.Component {
onCreate(path, type, file){
if(type === 'file'){
- return Files.touch(path, file);
+ return Files.touch(path, file)
+ .then(() => notify.send('A file named "'+Path.basename(path)+'" was created', 'success'))
+ .catch((err) => notify.send(err, 'error'));
}else if(type === 'directory'){
- return Files.mkdir(path);
+ return Files.mkdir(path)
+ .then(() => notify.send('A folder named "'+Path.basename(path)+'" was created', 'success'))
+ .catch((err) => notify.send(err, 'error'));
}else{
return Promise.reject({message: 'internal error: can\'t create a '+type.toString(), code: 'UNKNOWN_TYPE'});
}
}
onRename(from, to, type){
- return Files.mv(from, to, type);
+ return Files.mv(from, to, type)
+ .then(() => notify.send('The file "'+Path.basename(from)+'" was renamed', 'success'))
+ .catch((err) => notify.send(err, 'error'));
}
- onDelete(file, type){
- return Files.rm(file, type);
+ onDelete(path, type){
+ return Files.rm(path, type)
+ .then(() => notify.send('The file "'+Path.basename(path)+'" was deleted', 'success'))
+ .catch((err) => notify.send(err, 'error'));
}
onUpload(path, files){
@@ -205,10 +214,7 @@ export class FilesPage extends React.Component {