diff --git a/src/components/entry-page/entry-page.js b/src/components/entry-page/entry-page.js index 0c32b9cecbb8c1c7f56d70cede0ed895b5049521..125f71c507184e8dd51e47c9d32ec53711116f7e 100644 --- a/src/components/entry-page/entry-page.js +++ b/src/components/entry-page/entry-page.js @@ -34,7 +34,7 @@ export default class EntryPage extends React.Component { <div><b>!!! NRP Core testing !!!</b></div> </div> <NrpCoreDashboard /> - <TransceiverFunctionEditor /> + <TransceiverFunctionEditor experimentId='mqtt_simple_1'/> </div> ); } diff --git a/src/components/tf-editor/tf-editor.js b/src/components/tf-editor/tf-editor.js index 1d8efde77d2b34edb3cab5b35c956e13adea33dd..49b79999ef16aaf0a4c6fe5c1392b044fe1f7b0f 100644 --- a/src/components/tf-editor/tf-editor.js +++ b/src/components/tf-editor/tf-editor.js @@ -1,5 +1,6 @@ import React from 'react'; import CodeMirror from '@uiw/react-codemirror'; +import { Modal, Button } from 'react-bootstrap'; import ExperimentStorageService from '../../services/experiments/files/experiment-storage-service'; @@ -13,7 +14,7 @@ export default class TransceiverFunctionEditor extends React.Component { this.state = { selectedFilename: this.testListTfFiles[0], code: '', - unsavedChanges: false + showDialogUnsavedChanges: false }; } @@ -22,27 +23,57 @@ export default class TransceiverFunctionEditor extends React.Component { } onChangeSelectedFile(event) { - //TODO: check for unsaved changes let filename = event.target.value; - console.info('onChangeSelectedFile'); - console.info(filename); - this.setState({selectedFilename: filename}); - this.loadFileContent(filename); + if (this.hasUnsavedChanges) { + this.pendingFileChange = { + newFilename: filename, + oldFilename: this.state.selectedFilename + }; + this.setState({showDialogUnsavedChanges: true}); + } + else { + this.loadFileContent(filename); + } + } + + onUnsavedChangesDiscard() { + this.loadFileContent(this.pendingFileChange.newFilename); + } + + async onUnsavedChangesSave() { + let success = await this.saveTF(); + if (success) { + this.loadFileContent(this.pendingFileChange.newFilename); + } } async loadFileContent(filename) { - let fileBlob = await ExperimentStorageService.instance.getBlob('mqtt_simple_1', filename, true); - let fileContent = await fileBlob.text(); - this.setState({code: fileContent}); + let fileContent = await ExperimentStorageService.instance.getFileText(this.props.experimentId, filename); + this.fileLoading = true; + this.setState({selectedFilename: filename, code: fileContent, showDialogUnsavedChanges: false}); } - onChangeCodemirror(change) { - //console.info('onChangeCodemirror'); - //console.info(change); + onChangeCodemirror(change, viewUpdate) { + //console.info(['onChangeCodemirror', viewUpdate]); + this.setState({code: change}); + this.hasUnsavedChanges = !this.fileLoading; + this.fileLoading = false; + console.info(['this.hasUnsavedChanges', this.hasUnsavedChanges]); } - onClickSave() { - console.info('Save clicked!'); + // setFile(directoryPath, filename, data, byname = true, contentType = 'text/plain') + async saveTF() { + let response = await ExperimentStorageService.instance.setFile( + this.props.experimentId, this.state.selectedFilename, this.state.code); + if (response.ok) { + this.hasUnsavedChanges = false; + return true; + } + else { + console.error('Error trying to save TF!'); + console.error(response); + return false; + } } render() { @@ -56,8 +87,36 @@ export default class TransceiverFunctionEditor extends React.Component { return (<option key={file} value={file}>{file}</option>); })} </select> - <button onClick={this.onClickSave}>Save</button> - <CodeMirror value={this.state.code} maxHeight="100%" onChange={(change) => this.onChangeCodemirror(change)}/> + <button onClick={() => this.saveTF()}>Save</button> + <CodeMirror + value={this.state.code} + maxHeight="100%" + onChange={(change, viewUpdate) => this.onChangeCodemirror(change, viewUpdate)}/> + {this.state.showDialogUnsavedChanges ? + <div> + <Modal show={this.state.showDialogUnsavedChanges} + onHide={() => this.setState({showDialogUnsavedChanges: false})}> + <Modal.Header> + <Modal.Title>Unsaved Changes</Modal.Title> + </Modal.Header> + <Modal.Body>You have unsaved changes for "{this.pendingFileChange.oldFilename}". + What would you like to do?</Modal.Body> + <Modal.Footer> + <div> + <Button variant="danger" onClick={() => this.setState({showDialogUnsavedChanges: false})}> + Cancel + </Button> + <Button variant="danger" onClick={() => this.onUnsavedChangesDiscard()}> + Discard changes + </Button> + <Button variant="light" onClick={() => this.onUnsavedChangesSave()}> + Save + </Button> + </div> + </Modal.Footer> + </Modal> + </div> + : null} </div> ); } diff --git a/src/services/experiments/files/experiment-storage-service.js b/src/services/experiments/files/experiment-storage-service.js index ac20e1a442fb67f751a645406d7390bc3425e2f0..561052f63916063c1d1dcbe821dd2d1b2d6165f7 100644 --- a/src/services/experiments/files/experiment-storage-service.js +++ b/src/services/experiments/files/experiment-storage-service.js @@ -187,6 +187,19 @@ class ExperimentStorageService extends HttpService { } + /** + * Gets a file from the storage as text. + * @param {string} experimentName - name of the experiment + * @param {string} filename - name of the file + * @param {Boolean} byName - whether to check for the file by name or not (default TRUE) + * + * @returns {Blob} the contents of the file as text + */ + async getFileText(experimentName, filename, byName = true) { + return await (await this.getBlob(experimentName, filename, byName)).text(); + } + + /** * Deletes an experiment entity (folder or file) from the storage. * Called by other functions, not to be called independently.