From cd726eeacc85d3a41efb755bc1da621e30ac6158 Mon Sep 17 00:00:00 2001
From: Antoine Detailleur <detailleur@fortiss.org>
Date: Fri, 21 May 2021 15:44:25 +0200
Subject: [PATCH] [NRRPLT-8276] toast notifications for warning and progress
 functional (not CSSed)

---
 package-lock.json                             |  5 ++
 package.json                                  |  1 +
 src/components/dialog/error-dialog.js         | 11 ++-
 src/components/dialog/toast-notification.css  | 10 +++
 src/components/dialog/toast-notification.js   | 79 +++++++++++--------
 .../experiment-files-viewer.js                |  1 -
 .../experiment-list/experiment-list.css       |  2 +-
 .../experiment-overview.js                    | 10 ++-
 src/services/dialog-service.js                |  9 +--
 .../execution/experiment-execution-service.js |  4 +-
 .../files/remote-experiment-files-service.js  | 12 ++-
 src/utility/browser-name.js                   | 37 +++++++++
 12 files changed, 133 insertions(+), 48 deletions(-)
 create mode 100644 src/components/dialog/toast-notification.css
 create mode 100644 src/utility/browser-name.js

diff --git a/package-lock.json b/package-lock.json
index 4a62205..6f3d091 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10346,6 +10346,11 @@
         }
       }
     },
+    "jquery": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
+      "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
+    },
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
diff --git a/package.json b/package.json
index e4fa562..4fe6a73 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
     "@material-ui/lab": "4.0.0-alpha.57",
     "bootstrap": "4.5",
     "jszip": "3.2.0",
+    "jquery": "3.6.0",
     "react": "^17.0.1",
     "react-bootstrap": "1.4.0",
     "react-dom": "^17.0.1",
diff --git a/src/components/dialog/error-dialog.js b/src/components/dialog/error-dialog.js
index d5a6897..6c69fa7 100644
--- a/src/components/dialog/error-dialog.js
+++ b/src/components/dialog/error-dialog.js
@@ -15,10 +15,15 @@ class ErrorDialog extends React.Component{
   }
 
   async componentDidMount() {
+    this.onError = this.onError.bind(this);
     DialogService.instance.addListener(
-      DialogService.EVENTS.ERROR, (error) => {
-        this.onError(error);
-      }
+      DialogService.EVENTS.ERROR, this.onError
+    );
+  }
+
+  componentWillUnmount() {
+    DialogService.instance.removeListener(
+      DialogService.EVENTS.ERROR, this.onError
     );
   }
 
diff --git a/src/components/dialog/toast-notification.css b/src/components/dialog/toast-notification.css
new file mode 100644
index 0000000..9d29ecd
--- /dev/null
+++ b/src/components/dialog/toast-notification.css
@@ -0,0 +1,10 @@
+.toast-notification-wrapper{
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  z-index: 999;
+}
+
+.no-style{
+  list-style-type: none;
+}
\ No newline at end of file
diff --git a/src/components/dialog/toast-notification.js b/src/components/dialog/toast-notification.js
index 564b137..892938f 100644
--- a/src/components/dialog/toast-notification.js
+++ b/src/components/dialog/toast-notification.js
@@ -1,60 +1,71 @@
-import { Toast } from 'bootstrap';
-import React from 'react'
+import React from 'react';
+import { Toast } from 'react-bootstrap';
 
-import DialogService from '../../services/dialog-service.js'
+import DialogService from '../../services/dialog-service.js';
+
+import './toast-notification.css';
 
 class NotificationDialog extends React.Component{
   constructor(props){
     super(props);
     this.state = {
-      notifications: [],
+      notifications: []
     };
   }
 
   async componentDidMount() {
+    this.onNotification = this.onNotification.bind(this);
     DialogService.instance.addListener(
-      DialogService.EVENTS.NOTIFICATION, (notification) => {
-        this.onNotification(notification);
-      }
+      DialogService.EVENTS.NOTIFICATION, this.onNotification
+    );
+  }
+
+  componentWillUnmount() {
+    DialogService.instance.removeListener(
+      DialogService.EVENTS.NOTIFICATION, this.onNotification
     );
   }
 
   onNotification(notification) {
     this.setState({
-      notifications: this.state.notifications.append(notification)
+      notifications: [...this.state.notifications, notification]
     });
   }
 
-  onClose(index) {
+  handleClose(index) {
+    var copy = [...this.state.notifications];
+    copy.splice(index, 1);
     this.setState({
-      notifications: notifications.splice(index, 1)
+      notifications: copy
     });
   }
 
   render(){
+    let notifications = this.state.notifications;
     return(
-        <div>
-          {this.state.notifications?
-            <ol>
-              {this.state.notifications.map(notification => {
-                return (
-                  <div className="toast-dialog-wrapper">
-                    <Toast.Dialog onClose={this.onClose(index)}>
-                      <Toast.Header>
-                        <h4>{notification.type}</h4>
-                      </Toast.Header>
-                      <Toast.Body>
-                        {notification.message}
-                      </Toast.Body>
-                    </Toast.Dialog>
-                  </div>
-                  );
-                })
-              }
-            </ol>
-            : null
-          }
-        </div>
-    )
+      <div className='toast-notification-wrapper'>
+        {!notifications.length==0?
+          <ol>
+            {notifications.map((notification, index) => {
+              return (
+                <li key={index} className='no-style'>
+                  <Toast onClose={(index) => this.handleClose(index)}>
+                    <Toast.Header>
+                      <h4>{notification.type}</h4>
+                    </Toast.Header>
+                    <Toast.Body>
+                      {notification.message}
+                    </Toast.Body>
+                  </Toast>
+                </li>
+              );
+            })}
+          </ol>
+          : null
+        }
+      </div>
+    );
   }
-}
\ No newline at end of file
+}
+
+export default NotificationDialog;
\ No newline at end of file
diff --git a/src/components/experiment-files-viewer/experiment-files-viewer.js b/src/components/experiment-files-viewer/experiment-files-viewer.js
index 222c160..7accbc6 100644
--- a/src/components/experiment-files-viewer/experiment-files-viewer.js
+++ b/src/components/experiment-files-viewer/experiment-files-viewer.js
@@ -99,7 +99,6 @@ export default class ExperimentFilesViewer extends React.Component {
   render() {
     let selectedExperimentFiles = this.state.selectedExperiment ?
       RemoteExperimentFilesService.instance.mapFileInfos.get(this.state.selectedExperiment.uuid) : undefined;
-
     return (
       <div>
         {RemoteExperimentFilesService.instance.isSupported() ?
diff --git a/src/components/experiment-list/experiment-list.css b/src/components/experiment-list/experiment-list.css
index 4eb57d7..0c37870 100644
--- a/src/components/experiment-list/experiment-list.css
+++ b/src/components/experiment-list/experiment-list.css
@@ -1,4 +1,4 @@
-li.nostyle {
+.no-style {
     list-style-type: none;
 }
 
diff --git a/src/components/experiment-overview/experiment-overview.js b/src/components/experiment-overview/experiment-overview.js
index 43bc7ff..efca5cc 100644
--- a/src/components/experiment-overview/experiment-overview.js
+++ b/src/components/experiment-overview/experiment-overview.js
@@ -6,6 +6,7 @@ import ExperimentStorageService from '../../services/experiments/files/experimen
 import PublicExperimentsService from '../../services/experiments/files/public-experiments-service.js';
 import ExperimentServerService from '../../services/experiments/execution/server-resources-service.js';
 import ExperimentExecutionService from '../../services/experiments/execution/experiment-execution-service.js';
+import RemoteExperimentFilesService from '../../services/experiments/files/remote-experiment-files-service.js';
 
 import ImportExperimentButtons from '../experiment-list/import-experiment-buttons.js';
 import ExperimentList from '../experiment-list/experiment-list.js';
@@ -109,6 +110,13 @@ export default class ExperimentOverview extends React.Component {
     });
   }
 
+  onSelectTab(index, lastIndex){
+    this.setState({selectedTabIndex: index});
+    if (index===3 && lastIndex!==3){
+      RemoteExperimentFilesService.instance.notifyNotSupported();
+    }
+  }
+
   render() {
     return (
       <div className='experiment-overview-wrapper'>
@@ -118,7 +126,7 @@ export default class ExperimentOverview extends React.Component {
 
         <Tabs className="tabs-view" id="tabs-experiment-lists"
           selectedIndex={this.state.selectedTabIndex}
-          onSelect={(index) => this.setState({ selectedTabIndex: index })} >
+          onSelect={(index, lastIndex) => this.onSelectTab(index, lastIndex)} >
           <TabList>
             <Tab>My Experiments</Tab>
             <Tab>New Experiment</Tab>
diff --git a/src/services/dialog-service.js b/src/services/dialog-service.js
index 9a0f6b9..1398e8d 100644
--- a/src/services/dialog-service.js
+++ b/src/services/dialog-service.js
@@ -47,14 +47,13 @@ class DialogService extends EventEmitter {
 
   progressNotification(notification) {
     notification.type = 'Progress Status';
-    this.emit(DialogService.EVENTS.NOTIFICATION, notification)
+    this.emit(DialogService.EVENTS.NOTIFICATION, notification);
   }
 
   warningNotification(notification) {
-    notification.type = 'Warning'
-    this.emit(DialogService.EVENTS.NOTIFICATION, notification)
+    notification.type = 'Warning';
+    this.emit(DialogService.EVENTS.NOTIFICATION, notification);
   }
-  
 }
 
 DialogService.EVENTS = Object.freeze({
@@ -63,6 +62,6 @@ DialogService.EVENTS = Object.freeze({
 
 DialogService.EVENTS = Object.freeze({
   NOTIFICATION: 'NOTIFICATION'
-})
+});
 
 export default DialogService;
\ No newline at end of file
diff --git a/src/services/experiments/execution/experiment-execution-service.js b/src/services/experiments/execution/experiment-execution-service.js
index bcadae7..4858fb3 100644
--- a/src/services/experiments/execution/experiment-execution-service.js
+++ b/src/services/experiments/execution/experiment-execution-service.js
@@ -61,8 +61,8 @@ class ExperimentExecutionService extends HttpService {
     let brainProcesses = launchSingleMode ? 1 : experiment.configuration.brainProcesses;
 
     //TODO: placeholder, register actual progress callback later
-    let progressCallback = (notification) => {
-      DialogService.instance.progressNotification(notification);
+    let progressCallback = () => {
+      DialogService.instance.progressNotification({message:'The experiment is loading'});
     };
 
     let launchOnNextServer = async () => {
diff --git a/src/services/experiments/files/remote-experiment-files-service.js b/src/services/experiments/files/remote-experiment-files-service.js
index c9625df..4e49413 100644
--- a/src/services/experiments/files/remote-experiment-files-service.js
+++ b/src/services/experiments/files/remote-experiment-files-service.js
@@ -1,6 +1,8 @@
 import { HttpService } from '../../http-service.js';
 import ExperimentStorageService from './experiment-storage-service';
 import getMimeByExtension from '../../../utility/mime-type';
+import DialogService from '../../dialog-service';
+import browserName from '../../../utility/browser-name';
 
 let _instance = null;
 const SINGLETON_ENFORCER = Symbol();
@@ -35,10 +37,18 @@ class RemoteExperimentFilesService extends HttpService {
     return _instance;
   }
 
-  isSupported() {
+  isSupported (){
     return window.showDirectoryPicker !== undefined && window.showDirectoryPicker !== null;
   }
 
+  notifyNotSupported() {
+    if (!this.isSupported()){
+      DialogService.instance.warningNotification({
+        message : 'The remote experiment files is not supported on ' + browserName()
+      });
+    }
+  }
+
   toggleAutoSync() {
     this.autoSync = !this.autoSync;
   }
diff --git a/src/utility/browser-name.js b/src/utility/browser-name.js
new file mode 100644
index 0000000..af1274f
--- /dev/null
+++ b/src/utility/browser-name.js
@@ -0,0 +1,37 @@
+
+function browserName(){
+  // CHROME
+  if (navigator.userAgent.indexOf('Chrome') !== -1 ) {
+    return 'Chrome';
+  }
+  // FIREFOX
+  else if (navigator.userAgent.indexOf('Firefox') !== -1 ) {
+    return 'Firefox';
+  }
+  // INTERNET EXPLORER
+  else if (navigator.userAgent.indexOf('MSIE') !== -1 ) {
+    return 'Internet Explorer';
+  }
+  // EDGE
+  else if (navigator.userAgent.indexOf('Edge') !== -1 ) {
+    return 'Edge';
+  }
+  // SAFARI
+  else if (navigator.userAgent.indexOf('Safari') !== -1 ) {
+    return 'Safari';
+  }
+  // OPERA
+  else if (navigator.userAgent.indexOf('Opera') !== -1 ) {
+    return 'Opera';
+  }
+  // YANDEX
+  else if (navigator.userAgent.indexOf('YaBrowser') !== -1 ) {
+    return 'YaBrowser';
+  }
+  // OTHER
+  else {
+    return 'Other';
+  }
+}
+
+export default browserName;
\ No newline at end of file
-- 
GitLab