diff --git a/package-lock.json b/package-lock.json index 69b9218930ce48df2966dfa84d0d0a31786b6d14..3e6f31cba2658c2d4d48546718a28bce8af484f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1896,9 +1896,9 @@ } }, "@popperjs/core": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.5.4.tgz", - "integrity": "sha512-ZpKr+WTb8zsajqgDkvCEWgp6d5eJT6Q63Ng2neTbzBO76Lbe91vX/iVIW9dikq+Fs3yEo+ls4cxeXABD2LtcbQ==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.6.0.tgz", + "integrity": "sha512-cPqjjzuFWNK3BSKLm0abspP0sp/IGOli4p5I5fKFAzdS8fvjdOwDCfZqAaIiXd9lPkOWi3SUUfZof3hEb7J/uw==" }, "@restart/context": { "version": "2.1.4", @@ -1906,12 +1906,12 @@ "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" }, "@restart/hooks": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.25.tgz", - "integrity": "sha512-m2v3N5pxTsIiSH74/sb1yW8D9RxkJidGW+5Mfwn/lHb2QzhZNlaU1su7abSyT9EGf0xS/0waLjrf7/XxQHUk7w==", + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.26.tgz", + "integrity": "sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g==", "requires": { - "lodash": "^4.17.15", - "lodash-es": "^4.17.15" + "lodash": "^4.17.20", + "lodash-es": "^4.17.20" } }, "@rollup/plugin-node-resolve": { @@ -2462,9 +2462,9 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, "@types/react": { - "version": "16.9.56", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.56.tgz", - "integrity": "sha512-gIkl4J44G/qxbuC6r2Xh+D3CGZpJ+NdWTItAPmZbR5mUS+JQ8Zvzpl0ea5qT/ZT3ZNTUcDKUVqV3xBE8wv/DyQ==", + "version": "16.14.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.3.tgz", + "integrity": "sha512-zPrXn03hmPYqh9DznqSFQsoRtrQ4aHgnZDO+hMGvsE/PORvDTdJCHQ6XvJV31ic+0LzF73huPFXUb++W6Kri0Q==", "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3848,6 +3848,11 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "bootstrap": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.3.tgz", + "integrity": "sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5050,9 +5055,9 @@ } }, "csstype": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", - "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz", + "integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==" }, "cyclist": { "version": "1.0.1", @@ -7925,6 +7930,11 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "immer": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", @@ -8021,9 +8031,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "7.3.3", @@ -10329,6 +10339,41 @@ "object.assign": "^4.1.1" } }, + "jszip": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.0.tgz", + "integrity": "sha512-4WjbsaEtBK/DHeDZOPiPw5nzSGLDEDDreFRDEgnoMwmknPjTqa+23XuYFk6NiGbeiAeZCctiQ/X/z0lQBmDVOQ==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -10383,6 +10428,14 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -10443,9 +10496,9 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash-es": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", - "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.20.tgz", + "integrity": "sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -14556,6 +14609,11 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -16007,12 +16065,12 @@ "dev": true }, "uncontrollable": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz", - "integrity": "sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", "requires": { "@babel/runtime": "^7.6.3", - "@types/react": "^16.9.11", + "@types/react": ">=16.9.11", "invariant": "^2.2.4", "react-lifecycles-compat": "^3.0.4" } diff --git a/package.json b/package.json index 115952d7fa64ccbfeea9327502ebe0f3aead1531..fef55b15322cae2989d9ebf1206936db583ca439 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "bootstrap": "4.5", + "jszip": "3.2.0", "react": "^17.0.1", "react-bootstrap": "1.4.0", "react-dom": "^17.0.1", diff --git a/src/components/experiment-list/import-experiment-buttons.css b/src/components/experiment-list/import-experiment-buttons.css new file mode 100644 index 0000000000000000000000000000000000000000..429b61bbeb0046a0a3218f0b358a3fcd7cf82cd2 --- /dev/null +++ b/src/components/experiment-list/import-experiment-buttons.css @@ -0,0 +1,18 @@ +.import-popup { + width: 33.3%; + padding: 15px; + left: 0; + margin-left: 33.3%; + border: 1px solid #ccc; + border-radius: 10px; + background: white; + position: absolute; + top: 15%; + box-shadow: 5px 5px 5px #000; + z-index: 10001; + } + + #import-buttons { + vertical-align: middle; + margin: auto 0; + } \ No newline at end of file diff --git a/src/components/experiment-list/import-experiment-buttons.js b/src/components/experiment-list/import-experiment-buttons.js new file mode 100644 index 0000000000000000000000000000000000000000..8129213a7b674a8fd7fa8801d7e79a026147f865 --- /dev/null +++ b/src/components/experiment-list/import-experiment-buttons.js @@ -0,0 +1,175 @@ +import React from 'react'; + +import { FaFolder, FaFileArchive, FaAudible } from 'react-icons/fa'; +import { ButtonGroup, Button } from 'react-bootstrap'; + +import ImportExperimentService from '../../services/experiments/files/import-experiment-service.js'; +import ExperimentStorageService from '../../services/experiments/files/experiment-storage-service.js'; +import './experiment-list-element.css'; +import './import-experiment-buttons.css'; +export default class ImportExperimentButtons extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + + importFolderPopupClick() { + this.setState({ + importFolderResponse : undefined + }); + } + + importZipPopupClick() { + this.setState({ + importZipResponses : undefined + }); + } + + scanStoragePopupClick() { + this.setState({ + scanStorageResponse : undefined + }); + } + + importExperimentFolderChange(event) { + this.setState({ + isImporting : true + }); + ImportExperimentService.instance + .importExperimentFolder(event) + .then(async response => { + this.setState({ + importFolderResponse : await response.json() + }); + ExperimentStorageService.instance.getExperiments(true); + }) + .finally(() => { + this.setState({ + isImporting : false + }); + }); + }; + + importZippedExperimentChange(event) { + this.setState({ + isImporting : true + }); + ImportExperimentService.instance + .importZippedExperiment(event) + .then(async responses => { + this.setState({ + importZipResponses : await responses + }); + ExperimentStorageService.instance.getExperiments(true); + }) + .finally(() => { + this.setState({ + isImporting : false + }); + }); + }; + + scanStorageClick() { + this.setState({ + isImporting : true + }); + ImportExperimentService.instance + .scanStorage() + .then(async response => { + this.setState({ + scanStorageResponse : await response + }); + ExperimentStorageService.instance.getExperiments(true); + }) + .finally(() => { + this.setState({ + isImporting : false + }); + }); + } + + render() { + return ( + <div> + {/* Import folder pop-up */} + {this.state.importFolderResponse + ? <div className="import-popup"> + <div variant="success"> + <p>The experiment folder + <b>{' ' + this.state.importFolderResponse.zipBaseFolderName}</b> has been succesfully imported as + <b>{' ' + this.state.importFolderResponse.destFolderName}</b>. + </p> + </div> + <div className="text-right"> + <Button variant="success" onClick={() => this.importFolderPopupClick()}>Got it!</Button> + </div> + </div> + : null + } + + {/* Import zip pop-up */} + {this.state.importZipResponses + ? <div className="import-popup"> + <div> + <p>{this.state.importZipResponses.numberOfZips} successfully imported zip files.</p> + </div> + <p>The following experiments folders</p> + <p><b>{this.state.importZipResponses.zipBaseFolderName.join(', ')}</b></p> + <p>have been successfully imported as:</p> + <p><b>{this.state.importZipResponses.destFolderName.join(', ')}.</b></p> + <div className="text-right"> + <Button variant="success" onClick={() => this.importZipPopupClick()}>Got it!</Button> + </div> + </div> + : null + } + + {/* Scan pop-up */} + {this.state.scanStorageResponse + ? <div className="import-popup"> + <div> + <p>{this.state.scanStorageResponse.addedFoldersNumber} added folders, + {' ' + this.state.scanStorageResponse.deletedFoldersNumber} deleted folders.</p> + </div> + <p>Added:</p> + <p><b>{this.state.scanStorageResponse.addedFolders !== '' + ? this.state.scanStorageResponse.addedFolders + : 'none' } + </b></p> + <p>Deleted:</p> + <p><b>{this.state.scanStorageResponse.deletedFolders !== '' + ? this.state.scanStorageResponse.deletedFolders + : 'none' }</b></p> + <div className="text-right"> + <Button variant="success" onClick={() => this.scanStoragePopupClick()}>Got it!</Button> + </div> + </div> + : null + } + + {/* Import buttons */} + <div className="list-entry-buttons flex-container center"> + <input id="folder" type="file" style={{display:'none'}} + multiple directory="" webkitdirectory="" + onChange={(event) => this.importExperimentFolderChange(event)}/> + <input id="zip" type="file" style={{display:'none'}} + multiple accept='.zip' + onChange={(event) => this.importZippedExperimentChange(event)}/> + {!this.state.isImporting + ? <ButtonGroup role="group"> + <Button variant="outline-dark"> + <label htmlFor="folder"><FaFolder/> Import folder</label> + </Button> + <Button variant="outline-dark"> + <label htmlFor="zip"><FaFileArchive/> Import zip</label> + </Button > + <Button variant="outline-dark" onClick={() => this.scanStorageClick()}> + <FaAudible/> Scan Storage + </Button> + </ButtonGroup> + : null} + </div> + </div> + ); + } +} \ No newline at end of file diff --git a/src/components/experiment-overview/experiment-overview.js b/src/components/experiment-overview/experiment-overview.js index a8d1146854b3c85a8a584aa8b02f841ac63720ba..afa1f1becfd18912770efcf1a67559d89472b2a9 100644 --- a/src/components/experiment-overview/experiment-overview.js +++ b/src/components/experiment-overview/experiment-overview.js @@ -7,6 +7,7 @@ import PublicExperimentsService from '../../services/experiments/files/public-ex import ExperimentServerService from '../../services/experiments/execution/server-resources-service.js'; import ExperimentExecutionService from '../../services/experiments/execution/experiment-execution-service.js'; +import ImportExperimentButtons from '../experiment-list/import-experiment-buttons.js'; import ExperimentList from '../experiment-list/experiment-list.js'; import NrpHeader from '../nrp-header/nrp-header.js'; @@ -128,6 +129,7 @@ export default class ExperimentOverview extends React.Component { {/* My Experiments */} <TabPanel> + <ImportExperimentButtons /> <ExperimentList experiments={this.state.storageExperiments} availableServers={this.state.availableServers} startingExperiment={this.state.startingExperiment} /> diff --git a/src/index.js b/src/index.js index ef2edf8ea3fc42258464231e29140c8723458c1e..33f3dd6b06a99231aa04113f545f3f2c34dd0955 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; diff --git a/src/services/error-handler-service.js b/src/services/error-handler-service.js index ccc8bca5b87cb7301c834f3a8c5843ab4cfbc750..9a62e91d44dda85327f07733d89f962a716e43b6 100644 --- a/src/services/error-handler-service.js +++ b/src/services/error-handler-service.js @@ -28,6 +28,11 @@ class ErrorHandlerService { //TODO: needs proper UI implementation console.error(error); } + + displayError (error){ + //TODO: needs proper implementation + console.error(error.type + error.message); + } } export default ErrorHandlerService; diff --git a/src/services/experiments/files/experiment-storage-service.js b/src/services/experiments/files/experiment-storage-service.js index 14fd55bfe59b574135be7c769b34fb04973620ab..9fdd009e4b5345b64d0487276089302886654850 100644 --- a/src/services/experiments/files/experiment-storage-service.js +++ b/src/services/experiments/files/experiment-storage-service.js @@ -69,6 +69,8 @@ class ExperimentStorageService extends HttpService { async getExperiments(forceUpdate = false) { if (!this.experiments || forceUpdate) { let experimentList = await (await this.httpRequestGET(storageExperimentsURL)).json(); + // filter out experiments with incomplete configuration (probably storage corruption) + experimentList = experimentList.filter(experiment => experiment.configuration.experimentFile); this.sortExperiments(experimentList); await this.fillExperimentDetails(experimentList); this.experiments = experimentList; diff --git a/src/services/experiments/files/import-experiment-service.js b/src/services/experiments/files/import-experiment-service.js new file mode 100644 index 0000000000000000000000000000000000000000..7d00ffc80e75a1cff50debe545e829c2fc034983 --- /dev/null +++ b/src/services/experiments/files/import-experiment-service.js @@ -0,0 +1,175 @@ +import { HttpService } from '../../http-service.js'; +import ErrorHandlerService from '../../error-handler-service.js'; +import JSZip from 'jszip'; + +import endpoints from '../../proxy/data/endpoints.json'; +import config from '../../../config.json'; +const importExperimentURL = `${config.api.proxy.url}${endpoints.proxy.storage.importExperiment.url}`; +const scanStorageURL = `${config.api.proxy.url}${endpoints.proxy.storage.scanStorage.url}`; + +let _instance = null; +const SINGLETON_ENFORCER = Symbol(); +const options = { + mode: 'cors', // no-cors, *cors, same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'same-origin', // include, *same-origin, omit + headers: { + 'Content-Type': 'application/octet-stream', + Referer: 'http://localhost:9000/' + }, + // redirect: manual, *follow, error + redirect: 'follow', + // referrerPolicy: no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, + // strict-origin, strict-origin-when-cross-origin, unsafe-url + referrerPolicy: 'no-referrer', + //body: JSON.stringify(data) // body data type must match "Content-Type" header + method: 'POST' +}; + +export default class ImportExperimentService extends HttpService { + constructor(enforcer) { + super(); + if (enforcer !== SINGLETON_ENFORCER) { + throw new Error('Use ' + this.constructor.name + '.instance'); + } + } + + static get instance() { + if (_instance == null) { + _instance = new ImportExperimentService(SINGLETON_ENFORCER); + } + + return _instance; + } + + getImportZipResponses(responses) { + let importZipResponses = { + zipBaseFolderName: [], + destFolderName: [] + }; + importZipResponses.numberOfZips = responses.length; + responses.forEach(async response =>{ + response = await response.json(); + importZipResponses['zipBaseFolderName'].push(response['zipBaseFolderName']); + importZipResponses['destFolderName'].push(response['destFolderName']); + }); + return importZipResponses; + } + + async getScanStorageResponse(response) { + response = await response.json(); + let scanStorageResponse = {}; + ['deletedFolders', 'addedFolders'].forEach(name => { + scanStorageResponse[`${name}Number`] = response[name].length; + scanStorageResponse[name] = response[name].join(', '); + }); + return scanStorageResponse; + } + + async scanStorage() { + return this.httpRequestPOST(scanStorageURL) + .then(response => this.getScanStorageResponse(response)) + .catch(error => ErrorHandlerService.instance.displayError(error)); + } + + async zipExperimentFolder(event) { + let zip = new JSZip(); + let files = event.target.files; + if (files.length === 0){ + return; // The folder upload was aborted by user + } + let promises = []; + Array.from(files).forEach(file => { + promises.push( + new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onerror = error => { + reader.abort(); + return reject(error); + }; + reader.onload = f => + resolve([file.webkitRelativePath, f.target.result]); + if ( + file.type.startsWith('image') || + file.type === 'application/zip' || + file.webkitRelativePath.split('.').pop() === 'h5' + ) { + reader.readAsArrayBuffer(file); + } + else { + reader.readAsText(file); + } + }) + .then(([filepath, filecontent]) => + Promise.resolve( + zip.file(filepath, filecontent, { createFolders: true }) + ) + ) + .catch(error => { + ErrorHandlerService.instance.displayError(error); + return Promise.reject(error); + }) + ); + }); + + return Promise.all(promises) + .then(() => zip.generateAsync({ type: 'blob' })) + .catch(error => { + ErrorHandlerService.instance.displayError(error); + return Promise.reject(error); + }); + } + + async importExperimentFolder(event) { + return this.zipExperimentFolder(event).then(async zipContent => { + return this.httpRequestPOST(importExperimentURL, zipContent, options) + .catch(error => ErrorHandlerService.instance + .displayError(error) + ); + }); + } + + readZippedExperimentExperiment(event) { + let files = event.target.files; + let zipFiles = []; + Array.from(files).forEach(file => { + if (file.type !== 'application/zip') { + ErrorHandlerService.instance.displayError({ + type : 'Import Error.', + message :`The file ${file.name} cannot be imported because it is not a zip file.` + }); + } + else { + zipFiles.push(file); + } + }); + let promises = zipFiles.map(zipFile => { + return new Promise(resolve => { + let reader = new FileReader(); + reader.onload = f => resolve(f.target.result); + reader.readAsArrayBuffer(zipFile); + }); + }); + return Promise.all(promises); + } + + importZippedExperiment(event) { + let promises = this.readZippedExperimentExperiment(event) + .then(zipContents => + zipContents.map(zipContent => + this.httpRequestPOST(importExperimentURL, zipContent, options) + .catch(error => { + ErrorHandlerService.instance.displayError(error); + return Promise.reject(error); + }) + ) + ) + .then(responses => + Promise + .all(responses) + .then(responses => this.getImportZipResponses(responses)) + ); + return promises; + } + +} \ No newline at end of file diff --git a/src/services/proxy/data/endpoints.json b/src/services/proxy/data/endpoints.json index bf01f00031af2a2d34b346a4ae20b41414683183..2ab2653147217a8d0853ad87705cd1b8d10fbcb0 100644 --- a/src/services/proxy/data/endpoints.json +++ b/src/services/proxy/data/endpoints.json @@ -28,6 +28,12 @@ }, "experiments": { "url": "/storage/experiments" + }, + "importExperiment":{ + "url": "/storage/importExperiment" + }, + "scanStorage":{ + "url":"/storage/scanStorage" } } }