From c6467b169d8e222d8295c040b2d2d5d43871f91f Mon Sep 17 00:00:00 2001 From: irismarie Date: Wed, 10 Feb 2021 16:26:11 +0100 Subject: [PATCH 1/6] display/handle result files correctly;resultfile remove endpoint --- src/result/edit-result.js | 115 ++++++++++++++++------------- src/result/result-store.js | 34 +++------ src/result/results-data-manager.js | 28 +++++-- src/scenario/scenario.js | 7 +- 4 files changed, 101 insertions(+), 83 deletions(-) diff --git a/src/result/edit-result.js b/src/result/edit-result.js index 4833251..6a09a6a 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, Button, ProgressBar } from 'react-bootstrap'; import AppDispatcher from "../common/app-dispatcher"; import FileStore from "../file/file-store" @@ -53,26 +53,38 @@ class EditResultDialog extends React.Component { this.setState({ [event.target.id]: event.target.value }); }; - 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))}); + static isEmpty(val) { + return (val === undefined || val == null || val.length <= 0); + }; + + static getDerivedStateFromProps(props, state) { + let result = props.results[props.resultId]; + + if (result && Object.keys(result).length != 0) { + let hasFiles = !EditResultDialog.isEmpty(result.resultFileIDs); + if (hasFiles) { + return { + id: result.id, + description: result.description, + files: FileStore.getState().filter(file => result.resultFileIDs.includes(file.id)), + } + } else { + return { + id: result.id, + description: result.description, + files: null, + } + } + } else { + return {} } - 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)), - }) - } - } + }; selectUploadFile(event) { this.setState({ uploadFile: event.target.files[0] }); }; - startFileUpload(){ + startFileUpload() { const formData = new FormData(); formData.append("file", this.state.uploadFile); @@ -97,24 +109,27 @@ 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, + scenarioID: this.props.scenarioID, 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'>
@@ -125,40 +140,40 @@ class EditResultDialog extends React.Component { - - - - - 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..afd37dc 100644 --- a/src/result/result-store.js +++ b/src/result/result-store.js @@ -25,30 +25,6 @@ class ResultStore extends ArrayStore { 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,7 +43,11 @@ 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': @@ -78,6 +58,10 @@ class ResultStore extends ArrayStore { 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.scenarioID, 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..c7c0143 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,24 @@ class ResultsDataManager extends RestDataManager{ }); } + removeFile(resultID, fileID, scenarioID, 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, + }); + console.log(response); + }); + } } export default new ResultsDataManager(); \ No newline at end of file diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index e65b2e6..0172b33 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -114,7 +114,7 @@ class Scenario extends React.Component { editResultsModal: prevState.editResultsModal || false, modalResultsData: {}, - modalResultsIndex: 0, + modalResultsIndex: prevState.modalResultsIndex, newResultModal: false, filesToDownload: [], @@ -760,7 +760,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 +770,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)} /> From b3a5dd3537e31c83efaa3fe4a303dfbc2ef4d59d Mon Sep 17 00:00:00 2001 From: irismarie Date: Wed, 10 Feb 2021 18:04:55 +0100 Subject: [PATCH 2/6] change result description --- src/result/edit-result.js | 78 ++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/src/result/edit-result.js b/src/result/edit-result.js index 6a09a6a..ced8609 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,31 +51,31 @@ class EditResultDialog extends React.Component { this.setState({ [event.target.id]: event.target.value }); }; - static isEmpty(val) { + isEmpty(val) { return (val === undefined || val == null || val.length <= 0); - }; + }; - static getDerivedStateFromProps(props, state) { - let result = props.results[props.resultId]; + componentDidUpdate(prevProps, prevState) { + 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 = !EditResultDialog.isEmpty(result.resultFileIDs); - if (hasFiles) { - return { - id: result.id, - description: result.description, - files: FileStore.getState().filter(file => result.resultFileIDs.includes(file.id)), - } - } else { - return { - id: result.id, - description: result.description, - files: null, + 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, + }) } } - } else { - return {} - } + } }; selectUploadFile(event) { @@ -121,6 +119,18 @@ class EditResultDialog extends React.Component { } + 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 - Description - - + + + + Description + + + + + + + + + + + From 6769455a36eb8ff043ee91d888e688e1dd170e7a Mon Sep 17 00:00:00 2001 From: irismarie Date: Wed, 10 Feb 2021 18:07:29 +0100 Subject: [PATCH 3/6] ignore package-lock.json --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From 59725511f6716acb210b56456acc05a6cbb29f9d Mon Sep 17 00:00:00 2001 From: irismarie Date: Thu, 11 Feb 2021 10:04:33 +0100 Subject: [PATCH 4/6] simplify timestamp after editing --- src/result/result-store.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/result/result-store.js b/src/result/result-store.js index afd37dc..1ba1fde 100644 --- a/src/result/result-store.js +++ b/src/result/result-store.js @@ -15,10 +15,8 @@ * 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() { @@ -54,6 +52,10 @@ class ResultStore extends ArrayStore { 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; From f968c0221c8dc273283d1a26733197d910d96f72 Mon Sep 17 00:00:00 2001 From: irismarie Date: Thu, 11 Feb 2021 11:45:29 +0100 Subject: [PATCH 5/6] download files on first click, more meaningful zipfile name --- src/scenario/scenario.js | 61 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 293910d..5397bb5 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -116,7 +116,9 @@ class Scenario extends React.Component { modalResultsData: {}, 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) { From 4038a998df7048612032eed2657971c234618f89 Mon Sep 17 00:00:00 2001 From: irismarie Date: Thu, 11 Feb 2021 11:59:06 +0100 Subject: [PATCH 6/6] scenarioID not necessary when removing result files --- src/result/edit-result.js | 1 - src/result/result-store.js | 2 +- src/result/results-data-manager.js | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/result/edit-result.js b/src/result/edit-result.js index ced8609..e1992c1 100644 --- a/src/result/edit-result.js +++ b/src/result/edit-result.js @@ -113,7 +113,6 @@ class EditResultDialog extends React.Component { type: 'resultfiles/start-remove', resultID: this.state.id, fileID: file.id, - scenarioID: this.props.scenarioID, token: this.props.sessionToken }); diff --git a/src/result/result-store.js b/src/result/result-store.js index 1ba1fde..519c94a 100644 --- a/src/result/result-store.js +++ b/src/result/result-store.js @@ -61,7 +61,7 @@ class ResultStore extends ArrayStore { return state; case 'resultfiles/start-remove': - ResultsDataManager.removeFile(action.resultID, action.fileID, action.scenarioID, action.token); + ResultsDataManager.removeFile(action.resultID, action.fileID, action.token); return state; default: diff --git a/src/result/results-data-manager.js b/src/result/results-data-manager.js index c7c0143..3584c67 100644 --- a/src/result/results-data-manager.js +++ b/src/result/results-data-manager.js @@ -57,7 +57,7 @@ class ResultsDataManager extends RestDataManager { }); } - removeFile(resultID, fileID, scenarioID, token) { + removeFile(resultID, fileID, token) { RestAPI.delete(this.makeURL(this.url + '/' + resultID + '/file/' + fileID), token).then(response => { // reload result AppDispatcher.dispatch({ @@ -72,7 +72,6 @@ class ResultsDataManager extends RestDataManager { data: fileID, token: token, }); - console.log(response); }); } }