diff --git a/.gitignore b/.gitignore index 801e1dd..699692c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ yarn-debug.log* yarn-error.log* .vscode/ *.code-workspace +package-lock.json diff --git a/src/result/edit-result.js b/src/result/edit-result.js index 4833251..e1992c1 100644 --- a/src/result/edit-result.js +++ b/src/result/edit-result.js @@ -16,7 +16,7 @@ ******************************************************************************/ import React from 'react'; -import {FormGroup, FormControl, FormLabel, Col, Button, ProgressBar} from 'react-bootstrap'; +import { FormGroup, FormControl, FormLabel, Col, Row, Button, ProgressBar } from 'react-bootstrap'; import AppDispatcher from "../common/app-dispatcher"; import FileStore from "../file/file-store" @@ -38,8 +38,6 @@ class EditResultDialog extends React.Component { uploadFile: null, uploadProgress: 0, files: null, - result: null, - resultExists: false, }; } @@ -53,26 +51,38 @@ class EditResultDialog extends React.Component { this.setState({ [event.target.id]: event.target.value }); }; + isEmpty(val) { + return (val === undefined || val == null || val.length <= 0); + }; + componentDidUpdate(prevProps, prevState) { - if (this.state.resultExists && this.props.files != prevProps.files) { - this.setState({files: FileStore.getState().filter(file => this.state.result.resultFileIDs.includes(file.id))}); - } - if (this.props.result != prevProps.result && Object.keys(this.props.result).length != 0) { - this.setState({ - id: this.props.result.id, - description: this.props.result.description, - result: this.props.result, - resultExists: true, - files: FileStore.getState().filter(file => this.props.result.resultFileIDs.includes(file.id)), - }) - } - } + if (this.props.resultId != prevProps.resultId || this.props.results != prevProps.results) { + let result = this.props.results[this.props.resultId]; + + if (result && Object.keys(result).length != 0) { + let hasFiles = !this.isEmpty(result.resultFileIDs); + if (hasFiles) { + this.setState({ + id: result.id, + description: result.description, + files: FileStore.getState().filter(file => result.resultFileIDs.includes(file.id)), + }) + } else { + this.setState({ + id: result.id, + description: result.description, + files: null, + }) + } + } + } + }; selectUploadFile(event) { this.setState({ uploadFile: event.target.files[0] }); }; - startFileUpload(){ + startFileUpload() { const formData = new FormData(); formData.append("file", this.state.uploadFile); @@ -97,68 +107,98 @@ class EditResultDialog extends React.Component { this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) }); }; - deleteFile(index){ + deleteFile(index) { let file = this.state.files[index]; AppDispatcher.dispatch({ - type: 'files/start-remove', - data: file, + type: 'resultfiles/start-remove', + resultID: this.state.id, + fileID: file.id, token: this.props.sessionToken }); + + } + + submitDescription() { + let result = this.props.results[this.props.resultId]; + if (!this.isEmpty(result)) { + result.description = this.state.description; + AppDispatcher.dispatch({ + type: 'results/start-edit', + data: result, + token: this.props.sessionToken + }); + } } render() { return this.onClose()} - blendOutCancel = {true} - valid={true} - size = 'lg'> + title={'Edit Result No. ' + this.state.id} + buttonTitle='Close' + onClose={() => this.onClose()} + blendOutCancel={true} + valid={true} + size='lg'>
- Description - - + + + + Description + + + + + + + + + + + - - - - - this.deleteFile(index)} - /> + + + + + this.deleteFile(index)} + />
- Add Result File - this.selectUploadFile(event)} /> + Add Result File + this.selectUploadFile(event)} /> - - - + + + + + - - - -
; } diff --git a/src/result/result-store.js b/src/result/result-store.js index d824b1e..519c94a 100644 --- a/src/result/result-store.js +++ b/src/result/result-store.js @@ -15,40 +15,14 @@ * along with VILLASweb. If not, see . ******************************************************************************/ - import ArrayStore from '../common/array-store'; import ResultsDataManager from './results-data-manager'; -import FilesDataManager from '../file/files-data-manager' class ResultStore extends ArrayStore { constructor() { super('results', ResultsDataManager); } - saveFile(state, action){ - - let fileID = parseInt(action.id) - state.forEach((element, index, array) => { - if (element.id === fileID) { - // save blob object - array[index]["data"] = new Blob([action.data.data], {type: action.data.type}); - // update file type - array[index]["type"] = action.data.type; - - if (array[index]["objectURL"] !== ''){ - // free memory of previously generated object URL - URL.revokeObjectURL(array[index]["objectURL"]); - } - // create an object URL for the file - array[index]["objectURL"] = URL.createObjectURL(array[index]["data"]) - } - }); - - // announce change to listeners - this.__emitChange(); - return state - } - simplify(timestamp) { let parts = timestamp.split("T"); let datestr = parts[0]; @@ -67,17 +41,29 @@ class ResultStore extends ArrayStore { reduce(state, action) { switch (action.type) { case 'results/loaded': - this.simplifyTimestamps(action.data); + if (Array.isArray(action.data)) { + this.simplifyTimestamps(action.data); + } else { + this.simplifyTimestamps([action.data]); + } return super.reduce(state, action); case 'results/added': this.simplifyTimestamps([action.data]); return super.reduce(state, action); + case 'results/edited': + this.simplifyTimestamps([action.data]); + return super.reduce(state, action); + case 'resultfiles/start-upload': ResultsDataManager.uploadFile(action.data, action.resultID, action.token, action.progressCallback, action.finishedCallback, action.scenarioID); return state; + case 'resultfiles/start-remove': + ResultsDataManager.removeFile(action.resultID, action.fileID, action.token); + return state; + default: return super.reduce(state, action); } diff --git a/src/result/results-data-manager.js b/src/result/results-data-manager.js index 5c7e2e0..3584c67 100644 --- a/src/result/results-data-manager.js +++ b/src/result/results-data-manager.js @@ -19,24 +19,24 @@ import RestDataManager from '../common/data-managers/rest-data-manager'; import RestAPI from '../common/api/rest-api'; import AppDispatcher from '../common/app-dispatcher'; -class ResultsDataManager extends RestDataManager{ +class ResultsDataManager extends RestDataManager { constructor() { super('result', '/results'); } uploadFile(file, resultID, token = null, progressCallback = null, finishedCallback = null, scenarioID) { - RestAPI.upload(this.makeURL(this.url + '/' + resultID + '/file') , file, token, progressCallback, scenarioID).then(response => { + RestAPI.upload(this.makeURL(this.url + '/' + resultID + '/file'), file, token, progressCallback, scenarioID).then(response => { AppDispatcher.dispatch({ type: 'files/uploaded', }); - // Trigger a results reload + // Trigger a result reload AppDispatcher.dispatch({ type: 'results/start-load', - param: '?scenarioID=' + scenarioID, - token: token + data: resultID, + token: token, }); // Trigger a files reload @@ -57,6 +57,23 @@ class ResultsDataManager extends RestDataManager{ }); } + removeFile(resultID, fileID, token) { + RestAPI.delete(this.makeURL(this.url + '/' + resultID + '/file/' + fileID), token).then(response => { + // reload result + AppDispatcher.dispatch({ + type: 'results/start-load', + data: resultID, + token: token, + }); + + // update files + AppDispatcher.dispatch({ + type: 'files/removed', + data: fileID, + token: token, + }); + }); + } } export default new ResultsDataManager(); \ No newline at end of file diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 01045fe..5397bb5 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -114,9 +114,11 @@ class Scenario extends React.Component { editResultsModal: prevState.editResultsModal || false, modalResultsData: {}, - modalResultsIndex: 0, + modalResultsIndex: prevState.modalResultsIndex, newResultModal: false, - filesToDownload: [], + filesToDownload: prevState.filesToDownload, + zipfiles: prevState.zipfiles || false, + resultNodl: prevState.resultNodl, editOutputSignalsModal: prevState.editOutputSignalsModal || false, editInputSignalsModal: prevState.editInputSignalsModal || false, @@ -159,25 +161,28 @@ class Scenario extends React.Component { componentDidUpdate(prevProps, prevState) { // check whether file data has been loaded - if (this.state.filesToDownload.length > 0 ) { - if (this.state.filesToDownload.length === 1) { - let fileToDownload = FileStore.getState().filter(file => file.id === this.state.filesToDownload[0]) - if (fileToDownload.length === 1 && fileToDownload[0].data) { - const blob = new Blob([fileToDownload[0].data], {type: fileToDownload[0].type}); - FileSaver.saveAs(blob, fileToDownload[0].name); - this.setState({ filesToDownload: [] }); - } - } else { // zip and save several files - let filesToDownload = FileStore.getState().filter(file => this.state.filesToDownload.includes(file.id) && file.data); - if (filesToDownload.length === this.state.filesToDownload.length) { // all requested files have been loaded - var zip = new JSZip(); - filesToDownload.forEach(file => { - zip.file(file.name, file.data); - }); - zip.generateAsync({type: "blob"}).then(function(content) { - saveAs(content, "results.zip"); - }); - this.setState({ filesToDownload: [] }); + if (this.state.filesToDownload && this.state.filesToDownload.length > 0 ) { + if (this.state.files != prevState.files) { + if (!this.state.zipfiles) { + let fileToDownload = FileStore.getState().filter(file => file.id === this.state.filesToDownload[0]) + if (fileToDownload.length === 1 && fileToDownload[0].data) { + const blob = new Blob([fileToDownload[0].data], {type: fileToDownload[0].type}); + FileSaver.saveAs(blob, fileToDownload[0].name); + this.setState({ filesToDownload: [] }); + } + } else { // zip and save one or more files (download all button) + let filesToDownload = FileStore.getState().filter(file => this.state.filesToDownload.includes(file.id) && file.data); + if (filesToDownload.length === this.state.filesToDownload.length) { // all requested files have been loaded + var zip = new JSZip(); + filesToDownload.forEach(file => { + zip.file(file.name, file.data); + }); + let zipname = "result_" + this.state.resultNodl + "_" + Date.now(); + zip.generateAsync({type: "blob"}).then(function(content) { + saveAs(content, zipname); + }); + this.setState({ filesToDownload: [] }); + } } } } @@ -637,21 +642,19 @@ class Scenario extends React.Component { closeEditResultsModal() { this.setState({ editResultsModal: false }); - - AppDispatcher.dispatch({ - type: 'results/start-load', - token: this.state.sessionToken, - param: '?scenarioID=' + this.state.scenario.id - }) } downloadResultData(param) { let toDownload = []; + let zip = false; - if (typeof(param) === 'object') { + if (typeof(param) === 'object') { // download all files toDownload = param.resultFileIDs; - } else { + zip = true; + this.setState({ filesToDownload: toDownload, zipfiles: zip, resultNodl: param.id }); + } else { // download one file toDownload.push(param); + this.setState({ filesToDownload: toDownload, zipfiles: zip}); } toDownload.forEach(fileid => { @@ -661,8 +664,6 @@ class Scenario extends React.Component { token: this.state.sessionToken }); }); - - this.setState({ filesToDownload: toDownload }); } closeDeleteResultsModal(confirmDelete) { @@ -760,7 +761,7 @@ class Scenario extends React.Component { editButton downloadAllButton deleteButton - onEdit={index => this.setState({ editResultsModal: true, modalResultsData: this.state.results[index], modalResultsIndex: index })} + onEdit={index => this.setState({ editResultsModal: true, modalResultsIndex: index })} onDownloadAll={(index) => this.downloadResultData(this.state.results[index])} onDelete={(index) => this.setState({ deleteResultsModal: true, modalResultsData: this.state.results[index], modalResultsIndex: index })} /> @@ -770,7 +771,8 @@ class Scenario extends React.Component { sessionToken={this.state.sessionToken} show={this.state.editResultsModal} files={this.state.files} - result={this.state.modalResultsData} + results={this.state.results} + resultId={this.state.modalResultsIndex} scenarioID={this.state.scenario.id} onClose={this.closeEditResultsModal.bind(this)} /> this.closeDeleteResultsModal(e)} />