diff --git a/package-lock.json b/package-lock.json index da2c6a63f5b8899629deecb5ea31212186d3ed56..085d53b6245f8d37a504c55ec286de6d71a37b4f 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 f20b7d4bd9136d3f5ee63927c78c24e607407f08..d8f260b2740c8660d6d4529f2fc8d8612e072cdc 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/App.js b/src/App.js index 676465d36b760623aa6d05f08ea1b7ce948c82c4..8376431b189bcd3447684f2c77c62162d9e5549f 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import { HashRouter, Switch, Route } from 'react-router-dom'; import EntryPage from './components/entry-page/entry-page.js'; import ErrorDialog from './components/dialog/error-dialog.js'; +import NotificationDialog from './components/dialog/notification-dialog.js'; import ExperimentOverview from './components/experiment-overview/experiment-overview.js'; class App extends React.Component { @@ -11,6 +12,7 @@ class App extends React.Component { return( <div> <ErrorDialog /> + <NotificationDialog/> <HashRouter> <Switch> <Route path='/experiments-overview'> diff --git a/src/components/dialog/error-dialog.css b/src/components/dialog/error-dialog.css index 02e9b3a1eae2d741aa5f835cdf59ecc137e57c69..63c319bee51416bdc4d6ad21d6ab2765c3df81b4 100644 --- a/src/components/dialog/error-dialog.css +++ b/src/components/dialog/error-dialog.css @@ -1,4 +1,4 @@ -.modal-dialog-wrapper { +.error-dialog-wrapper { position: fixed; width: 100%; height: 100%; @@ -7,6 +7,6 @@ } .modal-header{ - background-color: rgb(241, 185, 185); + background-color: rgb(238, 173, 173); color: rgb(138, 41, 41); } \ No newline at end of file diff --git a/src/components/dialog/error-dialog.js b/src/components/dialog/error-dialog.js index 67746983eafb8e1ea2d304d4ec6f73e3ff134ee0..6c69fa7ef95c0a8c8a4be2ffbdc54302a35c04ed 100644 --- a/src/components/dialog/error-dialog.js +++ b/src/components/dialog/error-dialog.js @@ -1,7 +1,7 @@ import React from 'react'; import { Modal, Button } from 'react-bootstrap'; -import ErrorHandlerService from '../../services/error-handler-service.js'; +import DialogService from '../../services/dialog-service.js'; import './error-dialog.css'; @@ -15,10 +15,16 @@ class ErrorDialog extends React.Component{ } async componentDidMount() { - ErrorHandlerService.instance.addListener( - ErrorHandlerService.EVENTS.ERROR, (error) => { - this.onError(error); - }); + this.onError = this.onError.bind(this); + DialogService.instance.addListener( + DialogService.EVENTS.ERROR, this.onError + ); + } + + componentWillUnmount() { + DialogService.instance.removeListener( + DialogService.EVENTS.ERROR, this.onError + ); } onError(error) { @@ -45,7 +51,7 @@ class ErrorDialog extends React.Component{ return ( <div> {error? - <div className="modal-dialog-wrapper"> + <div className="error-dialog-wrapper"> <Modal.Dialog> <Modal.Header className="modal-header"> <h4>{error.type}</h4> diff --git a/src/components/dialog/notification-dialog.css b/src/components/dialog/notification-dialog.css new file mode 100644 index 0000000000000000000000000000000000000000..b0ebdcec1e9cbf0955001ee874326c36633a4a3a --- /dev/null +++ b/src/components/dialog/notification-dialog.css @@ -0,0 +1,25 @@ +.toast-notification-wrapper{ + position: fixed; + bottom: 0; + right: 0; + z-index: 498; +} + +.no-style{ + list-style-type: none; +} + +.toast-width{ + max-width: none; + width: 350px; +} + +.warning{ + background-color:rgb(248, 248, 122); + color:rgb(126, 126, 45); +} + +.info{ + background-color: rgb(143, 143, 250); + color: rgb(48, 48, 134) +} diff --git a/src/components/dialog/notification-dialog.js b/src/components/dialog/notification-dialog.js new file mode 100644 index 0000000000000000000000000000000000000000..26e0763f1ec2df8edc275bcfae201d5df72d29d2 --- /dev/null +++ b/src/components/dialog/notification-dialog.js @@ -0,0 +1,81 @@ +import React from 'react'; +import { Toast } from 'react-bootstrap'; + +import DialogService from '../../services/dialog-service.js'; + +import './notification-dialog.css'; + +class NotificationDialog extends React.Component{ + constructor(props){ + super(props); + this.state = { + notifications: [] + }; + } + + async componentDidMount() { + this.onNotification = this.onNotification.bind(this); + DialogService.instance.addListener( + DialogService.EVENTS.NOTIFICATION, this.onNotification + ); + } + + componentWillUnmount() { + DialogService.instance.removeListener( + DialogService.EVENTS.NOTIFICATION, this.onNotification + ); + } + + onNotification(notification) { + // avoid duplicates + var isIn = false; + this.state.notifications.forEach((notif) =>{ + if (notification.type===notif.type && notification.message===notif.message){ + isIn = true; + } + }); + if (!isIn){ + this.setState({ + notifications: [...this.state.notifications, notification] + }); + } + } + + handleClose(index) { + var copy = [...this.state.notifications]; + copy.splice(index, 1); + this.setState({ + notifications: copy + }); + } + + render(){ + let notifications = this.state.notifications; + return( + <div className='toast-notification-wrapper'> + {notifications.length!==0? + <ol> + {notifications.map((notification, index) => { + return ( + <li key={index} className='no-style'> + <Toast className='toast-width' onClose={(index) => this.handleClose(index)} + delay={notification.type==='Warning'? 60000: 10000} autohide> + <Toast.Header className={notification.type==='Warning'? 'warning': 'info'} > + <strong className='mr-auto'>{notification.type}</strong> + </Toast.Header> + <Toast.Body> + {notification.message} + </Toast.Body> + </Toast> + </li> + ); + })} + </ol> + : null + } + </div> + ); + } +} + +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 222c1600500a988be4b9c8fbc92ad449d1c465bb..7accbc6b7f34e459a3f9baac908fd87bea7db936 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 4eb57d703094f83aa7e2f18e273bf78002beda5d..0c37870a1e5a527454302288981f562662bedb70 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-list/experiment-list.js b/src/components/experiment-list/experiment-list.js index 8581fceab98712af9faf094b3d42b37e3e423c64..cb263408867e57fa3fde26e9d219f426efe3cf94 100644 --- a/src/components/experiment-list/experiment-list.js +++ b/src/components/experiment-list/experiment-list.js @@ -13,7 +13,7 @@ export default class ExperimentList extends React.Component { <ol> {this.props.experiments.map(experiment => { return ( - <li key={experiment.id || experiment.configuration.id} className='nostyle'> + <li key={experiment.id || experiment.configuration.id} className='no-style'> <ExperimentListElement experiment={experiment} availableServers={this.props.availableServers} startingExperiment={this.props.startingExperiment} diff --git a/src/components/experiment-overview/experiment-overview.js b/src/components/experiment-overview/experiment-overview.js index 43bc7ff7a7580d9f6e21c9921b06952316e014c7..efca5cc231cedc10837d2a24aa90503ce51f1d31 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/error-handler-service.js b/src/services/dialog-service.js similarity index 54% rename from src/services/error-handler-service.js rename to src/services/dialog-service.js index 026e9396a03fe5f02f74bab0cd252721fba8104c..1398e8d4f2555104e5742fc46cfa3aaaa684d524 100644 --- a/src/services/error-handler-service.js +++ b/src/services/dialog-service.js @@ -12,7 +12,7 @@ const SINGLETON_ENFORCER = Symbol(); * - data: related content | optional * - stack: call stack | optional */ -class ErrorHandlerService extends EventEmitter { +class DialogService extends EventEmitter { constructor(enforcer) { super(); if (enforcer !== SINGLETON_ENFORCER) { @@ -22,7 +22,7 @@ class ErrorHandlerService extends EventEmitter { static get instance() { if (_instance == null) { - _instance = new ErrorHandlerService(SINGLETON_ENFORCER); + _instance = new DialogService(SINGLETON_ENFORCER); } return _instance; @@ -31,28 +31,37 @@ class ErrorHandlerService extends EventEmitter { // HTTP request error networkError(error) { error.type = 'Network Error'; - this.emit(ErrorHandlerService.EVENTS.ERROR, error); + this.emit(DialogService.EVENTS.ERROR, error); } // Handling data error dataError(error){ error.type = 'Data Error'; - this.emit(ErrorHandlerService.EVENTS.ERROR, error); + this.emit(DialogService.EVENTS.ERROR, error); } - startSimulationError(error) { - error.type = 'Start Simulation Error'; - this.emit(ErrorHandlerService.EVENTS.ERROR, error); + simulationError(error) { + error.type = 'Simulation Error'; + this.emit(DialogService.EVENTS.ERROR, error); } - updateSimulationError(error) { - error.type = 'Update Simulation Error'; - this.emit(ErrorHandlerService.EVENTS.ERROR, error); + progressNotification(notification) { + notification.type = 'Progress Status'; + this.emit(DialogService.EVENTS.NOTIFICATION, notification); + } + + warningNotification(notification) { + notification.type = 'Warning'; + this.emit(DialogService.EVENTS.NOTIFICATION, notification); } } -ErrorHandlerService.EVENTS = Object.freeze({ +DialogService.EVENTS = Object.freeze({ ERROR: 'ERROR' }); -export default ErrorHandlerService; \ No newline at end of file +DialogService.EVENTS = Object.freeze({ + NOTIFICATION: 'NOTIFICATION' +}); + +export default DialogService; \ No newline at end of file diff --git a/src/services/experiments/execution/__tests__/running-simulation-service.test.js b/src/services/experiments/execution/__tests__/running-simulation-service.test.js index 7af3b6e72eb1dbc0f5b53d53d0a9ffd960e99855..3948e481b95a231826e39f3d8bf4fec30438c106 100644 --- a/src/services/experiments/execution/__tests__/running-simulation-service.test.js +++ b/src/services/experiments/execution/__tests__/running-simulation-service.test.js @@ -8,7 +8,7 @@ import MockAvailableServers from '../../../../mocks/mock_available-servers.json' import MockSimulations from '../../../../mocks/mock_simulations.json'; import RunningSimulationService from '../running-simulation-service.js'; -import ErrorHandlerService from '../../../error-handler-service'; +import DialogService from '../../../dialog-service'; import RoslibService from '../../../roslib-service'; import { EXPERIMENT_STATE } from '../../experiment-constants.js'; @@ -41,11 +41,11 @@ test('initializes and gets the simulation resources', async () => { expect(resources).toBeDefined(); // failure case - jest.spyOn(ErrorHandlerService.instance, 'networkError').mockImplementation(() => { }); + jest.spyOn(DialogService.instance, 'networkError').mockImplementation(() => { }); let simIDFailure = 0; - expect(ErrorHandlerService.instance.networkError).not.toHaveBeenCalled(); + expect(DialogService.instance.networkError).not.toHaveBeenCalled(); resources = await RunningSimulationService.instance.initConfigFiles(serverBaseURL, simIDFailure); - expect(ErrorHandlerService.instance.networkError).toHaveBeenCalled(); + expect(DialogService.instance.networkError).toHaveBeenCalled(); }); test('verifies whether a simulation is ready', async () => { @@ -139,7 +139,7 @@ test('register for ROS status information', () => { test('can retrieve the state of a simulation', async () => { let returnValueGET = undefined; - jest.spyOn(ErrorHandlerService.instance, 'networkError').mockImplementation(); + jest.spyOn(DialogService.instance, 'networkError').mockImplementation(); jest.spyOn(RunningSimulationService.instance, 'httpRequestGET').mockImplementation(() => { if (RunningSimulationService.instance.httpRequestGET.mock.calls.length === 1) { returnValueGET = { state: EXPERIMENT_STATE.PAUSED }; // proper state msg @@ -161,12 +161,12 @@ test('can retrieve the state of a simulation', async () => { // call 2 => rejected simSate = await RunningSimulationService.instance.getState('test-url', 1); - expect(ErrorHandlerService.instance.networkError).toHaveBeenCalled(); + expect(DialogService.instance.networkError).toHaveBeenCalled(); }); test('can set the state of a simulation', async () => { let returnValuePUT = undefined; - jest.spyOn(ErrorHandlerService.instance, 'updateSimulationError').mockImplementation(); + jest.spyOn(DialogService.instance, 'simulationError').mockImplementation(); jest.spyOn(RunningSimulationService.instance, 'httpRequestPUT').mockImplementation(() => { if (RunningSimulationService.instance.httpRequestGET.mock.calls.length === 1) { returnValuePUT = {}; @@ -184,5 +184,5 @@ test('can set the state of a simulation', async () => { // call 2 => rejected returnValue = await RunningSimulationService.instance.updateState('test-url', 1, EXPERIMENT_STATE.PAUSED); - expect(ErrorHandlerService.instance.updateSimulationError).toHaveBeenCalled(); + expect(DialogService.instance.simulationError).toHaveBeenCalled(); }); \ No newline at end of file diff --git a/src/services/experiments/execution/__tests__/server-resources-service.test.js b/src/services/experiments/execution/__tests__/server-resources-service.test.js index 92ecf5b8b0ccf30ff1e1155aec4540ef8791a484..688b7075a9d903ab26fe4a7677ef25d3fa8798ae 100644 --- a/src/services/experiments/execution/__tests__/server-resources-service.test.js +++ b/src/services/experiments/execution/__tests__/server-resources-service.test.js @@ -7,7 +7,7 @@ import 'jest-fetch-mock'; import MockServerconfig from '../../../../mocks/mock_server-config.json'; import ServerResourcesService from '../../../../services/experiments/execution/server-resources-service'; -import ErrorHandlerService from '../../../error-handler-service'; +import DialogService from '../../../dialog-service'; jest.setTimeout(10000); @@ -67,9 +67,9 @@ test('can get a server config', async () => { jest.spyOn(ServerResourcesService.instance, 'httpRequestGET').mockImplementation(() => { return Promise.reject(); }); - jest.spyOn(ErrorHandlerService.instance, 'networkError').mockImplementation(); + jest.spyOn(DialogService.instance, 'networkError').mockImplementation(); config = await ServerResourcesService.instance.getServerConfig('test-server-id'); - expect(ErrorHandlerService.instance.networkError).toHaveBeenCalled(); + expect(DialogService.instance.networkError).toHaveBeenCalled(); }); test('should stop polling updates when window is unloaded', async () => { diff --git a/src/services/experiments/execution/experiment-execution-service.js b/src/services/experiments/execution/experiment-execution-service.js index 0076fbf602d0a6d4ab7e959881ac7265aee4fa31..4858fb3e08d5419b91f128323198b2d3112625a3 100644 --- a/src/services/experiments/execution/experiment-execution-service.js +++ b/src/services/experiments/execution/experiment-execution-service.js @@ -3,7 +3,7 @@ import _ from 'lodash'; //import NrpAnalyticsService from '../../nrp-analytics-service.js'; import ServerResourcesService from './server-resources-service.js'; import SimulationService from './running-simulation-service.js'; -import ErrorHandlerService from '../../error-handler-service'; +import DialogService from '../../dialog-service'; import { HttpService } from '../../http-service.js'; import { EXPERIMENT_STATE } from '../experiment-constants.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 = (msg) => { - console.info(msg); + let progressCallback = () => { + DialogService.instance.progressNotification({message:'The experiment is loading'}); }; let launchOnNextServer = async () => { @@ -90,7 +90,7 @@ class ExperimentExecutionService extends HttpService { progressCallback ).catch((failure) => { if (failure.error) { - ErrorHandlerService.instance.startSimulationError(failure.error); + DialogService.instance.simulationError(failure.error); } fatalErrorOccurred = fatalErrorOccurred || failure.isFatal; diff --git a/src/services/experiments/execution/running-simulation-service.js b/src/services/experiments/execution/running-simulation-service.js index 754ccc8d96d54ab3e014630e03654ce5905ea010..2ef55859c3ec42302871b772c9d3c58a5641e68c 100644 --- a/src/services/experiments/execution/running-simulation-service.js +++ b/src/services/experiments/execution/running-simulation-service.js @@ -1,4 +1,4 @@ -import ErrorHandlerService from '../../error-handler-service.js'; +import DialogService from '../../dialog-service.js'; import RoslibService from '../../roslib-service.js'; import { HttpService } from '../../http-service.js'; import { EXPERIMENT_STATE } from '../experiment-constants.js'; @@ -44,7 +44,7 @@ class SimulationService extends HttpService { cachedConfigFiles = response.resources; } catch (error) { - ErrorHandlerService.instance.networkError(error); + DialogService.instance.networkError(error); } return cachedConfigFiles; @@ -151,7 +151,7 @@ class SimulationService extends HttpService { return response; } catch (error) { - ErrorHandlerService.instance.networkError(error); + DialogService.instance.networkError(error); } } @@ -168,7 +168,7 @@ class SimulationService extends HttpService { return response; } catch (error) { - ErrorHandlerService.instance.updateSimulationError(error); + DialogService.instance.simulationError(error); } } } diff --git a/src/services/experiments/execution/server-resources-service.js b/src/services/experiments/execution/server-resources-service.js index 8a2eb78c48f3f299c872187fbd239b18279c2003..8499b472a8619e251ef7769cfb28561b6704809e 100644 --- a/src/services/experiments/execution/server-resources-service.js +++ b/src/services/experiments/execution/server-resources-service.js @@ -1,4 +1,4 @@ -import ErrorHandlerService from '../../error-handler-service.js'; +import DialogService from '../../dialog-service.js'; import { HttpService } from '../../http-service.js'; import endpoints from '../../proxy/data/endpoints.json'; @@ -79,7 +79,7 @@ class ServerResourcesService extends HttpService { .then(async (response) => { return await response.json(); }) - .catch(ErrorHandlerService.instance.networkError); + .catch(DialogService.instance.networkError); } } diff --git a/src/services/experiments/files/experiment-storage-service.js b/src/services/experiments/files/experiment-storage-service.js index 0cf994dac08ab916701831c86cfe1e71e75fe1f8..8d6326388acea05cc17ed61d00daeb35c439b230 100644 --- a/src/services/experiments/files/experiment-storage-service.js +++ b/src/services/experiments/files/experiment-storage-service.js @@ -3,7 +3,7 @@ import { EXPERIMENT_RIGHTS } from '../experiment-constants'; import endpoints from '../../proxy/data/endpoints.json'; import config from '../../../config.json'; -import ErrorHandlerService from '../../error-handler-service.js'; +import DialogService from '../../dialog-service.js'; const storageURL = `${config.api.proxy.url}${endpoints.proxy.storage.url}`; const storageExperimentsURL = `${config.api.proxy.url}${endpoints.proxy.storage.experiments.url}`; @@ -79,7 +79,7 @@ class ExperimentStorageService extends HttpService { this.emit(ExperimentStorageService.EVENTS.UPDATE_EXPERIMENTS, this.experiments); } catch (error) { - ErrorHandlerService.instance.networkError(error); + DialogService.instance.networkError(error); } } diff --git a/src/services/experiments/files/import-experiment-service.js b/src/services/experiments/files/import-experiment-service.js index 45709388d357a983d7d0e58d343506a9d696aa00..5ae1b74db8e043de07d94bd2cad534237542ca8a 100644 --- a/src/services/experiments/files/import-experiment-service.js +++ b/src/services/experiments/files/import-experiment-service.js @@ -3,7 +3,7 @@ import JSZip from 'jszip'; import endpoints from '../../proxy/data/endpoints.json'; import config from '../../../config.json'; -import ErrorHandlerService from '../../error-handler-service.js'; +import DialogService from '../../dialog-service.js'; const importExperimentURL = `${config.api.proxy.url}${endpoints.proxy.storage.importExperiment.url}`; const scanStorageURL = `${config.api.proxy.url}${endpoints.proxy.storage.scanStorage.url}`; @@ -69,7 +69,7 @@ export default class ImportExperimentService extends HttpService { async scanStorage() { return this.httpRequestPOST(scanStorageURL) .then(response => this.getScanStorageResponse(response)) - .catch(error => ErrorHandlerService.instance.networkError(error)); + .catch(error => DialogService.instance.networkError(error)); } async zipExperimentFolder(event) { @@ -106,7 +106,7 @@ export default class ImportExperimentService extends HttpService { ) ) .catch(error => { - ErrorHandlerService.instance.dataError(error); + DialogService.instance.dataError(error); return Promise.reject(error); }) ); @@ -115,7 +115,7 @@ export default class ImportExperimentService extends HttpService { return Promise.all(promises) .then(() => zip.generateAsync({ type: 'blob' })) .catch(error => { - ErrorHandlerService.instance.dataError(error); + DialogService.instance.dataError(error); return Promise.reject(error); }); } @@ -124,7 +124,7 @@ export default class ImportExperimentService extends HttpService { return this.zipExperimentFolder(event).then(async zipContent => { return this.httpRequestPOST(importExperimentURL, zipContent, options) .then(response => response.json()) - .catch(error => ErrorHandlerService.instance.networkError(error) + .catch(error => DialogService.instance.networkError(error) ); }); } @@ -151,7 +151,7 @@ export default class ImportExperimentService extends HttpService { zipContents.map(zipContent => this.httpRequestPOST(importExperimentURL, zipContent, options) .catch(error => { - ErrorHandlerService.instance.networkError(error); + DialogService.instance.networkError(error); return Promise.reject(error); }) ) diff --git a/src/services/experiments/files/remote-experiment-files-service.js b/src/services/experiments/files/remote-experiment-files-service.js index c9625dfd3e824321a419847c877322edb857708e..7b85af51c074e92a6df2cfdcafec5eec66ae039d 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(); @@ -39,6 +41,14 @@ class RemoteExperimentFilesService extends HttpService { return window.showDirectoryPicker !== undefined && window.showDirectoryPicker !== null; } + notifyNotSupported() { + if (!this.isSupported()){ + DialogService.instance.warningNotification({ + message : 'The remote experiment file system 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 0000000000000000000000000000000000000000..af1274f248c922cbd27dd58a94f908d96325f1bf --- /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