diff --git a/src/components/experiment-files-viewer/experiment-files-viewer.css b/src/components/experiment-files-viewer/experiment-files-viewer.css new file mode 100644 index 0000000000000000000000000000000000000000..7db810726bae0f462e0465c542fd786b1e147755 --- /dev/null +++ b/src/components/experiment-files-viewer/experiment-files-viewer.css @@ -0,0 +1,16 @@ +.experiment-files-viewer-wrapper { + height: 100vh; + display: grid; + grid-template-rows: auto; + grid-template-columns: 40% auto; + grid-template-areas: + "experiment-list experiment-files"; +} + +.experiment-list { + grid-area: experiment-list; +} + +.experiment-files { + grid-area: experiment-files; +} \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..867c07ffe871be1edd9e384824e27caf25f19ef7 --- /dev/null +++ b/src/components/experiment-files-viewer/experiment-files-viewer.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { FaDownload, FaUpload } from 'react-icons/fa'; + +import ExperimentsFilesRemoteEditService from '../../services/experiments/files/experiments-files-remote-edit-service'; + +import './experiment-files-viewer.css'; + +export default class ExperimentFilesViewer extends React.Component { + render() { + return ( + <div className='experiment-files-viewer-wrapper'> + <div className='experiment-list'> + Experiments + <ol> + {this.props.experiments.map(experiment => { + return ( + <li key={experiment.id || experiment.configuration.id} className='nostyle'> + {experiment.configuration.name} + <button + onClick={() => { + ExperimentsFilesRemoteEditService.instance.downloadExperimentToLocalFS(experiment); + }} + title='Clone to local filesystem' + > + <FaDownload /> + </button> + <button + disabled={!ExperimentsFilesRemoteEditService.instance.localDirectoryHandles.has(experiment.id)} + onClick={() => { + ExperimentsFilesRemoteEditService.instance.uploadLocalFSExperiment(experiment); + }} + title='Clone to local filesystem' + > + <FaUpload /> + </button> + </li> + ); + })} + </ol> + </div> + <div className='experiment-files'></div> + </div> + ); + } +} diff --git a/src/components/experiment-overview/experiment-overview.js b/src/components/experiment-overview/experiment-overview.js index 6b67ad2b22f13efd8520d3744fc8fb24c571d2ea..51af2c26dd82e215e7447e5923fb41e72fbc5e9c 100644 --- a/src/components/experiment-overview/experiment-overview.js +++ b/src/components/experiment-overview/experiment-overview.js @@ -8,6 +8,7 @@ import ExperimentExecutionService from '../../services/experiments/execution/exp import ExperimentList from '../experiment-list/experiment-list.js'; import NrpHeader from '../nrp-header/nrp-header.js'; +import ExperimentFilesViewer from '../experiment-files-viewer/experiment-files-viewer.js'; import './experiment-overview.css'; @@ -115,7 +116,7 @@ export default class ExperimentOverview extends React.Component { <h2>"Model Libraries" tab coming soon ...</h2> </TabPanel> <TabPanel> - <h2>"Experiment Files" tab coming soon ...</h2> + <ExperimentFilesViewer experiments={this.state.storageExperiments}/> </TabPanel> <TabPanel> <h2>"Templates" tab coming soon ...</h2> diff --git a/src/services/experiments/files/experiments-files-remote-edit-service.js b/src/services/experiments/files/experiments-files-remote-edit-service.js new file mode 100644 index 0000000000000000000000000000000000000000..a70ce7015e9d56f5009778f144cb7eb9430fb085 --- /dev/null +++ b/src/services/experiments/files/experiments-files-remote-edit-service.js @@ -0,0 +1,73 @@ +import { HttpService } from '../../http-service.js'; +import ExperimentStorageService from './experiment-storage-service'; + +let _instance = null; +const SINGLETON_ENFORCER = Symbol(); + +/** + * TODO + */ +class ExperimentsFilesRemoteEditService extends HttpService { + constructor(enforcer) { + super(); + if (enforcer !== SINGLETON_ENFORCER) { + throw new Error('Use ' + this.constructor.name + '.instance'); + } + + this.localDirectoryHandles = new Map(); + } + + static get instance() { + if (_instance == null) { + _instance = new ExperimentsFilesRemoteEditService(SINGLETON_ENFORCER); + } + + return _instance; + } + + async downloadExperimentToLocalFS(experiment) { + console.info(experiment); + + let localDirectoryHandle = await window.showDirectoryPicker(); + console.info(localDirectoryHandle); + if (!localDirectoryHandle) { + return; + } + this.localDirectoryHandles.set(experiment.id, localDirectoryHandle); + + let experimentFiles = await ExperimentStorageService.instance.getExperimentFiles(experiment.id); + console.info(experimentFiles); + experimentFiles.forEach(async (file) => { + try { + if (file.type === 'file') { + let fileContent = await ExperimentStorageService.instance.getBlob(experiment.id, file.name, true); + + let fileHandle = await localDirectoryHandle.getFileHandle(file.name, {create: true}); + //console.info(fileHandle); + let writable = await fileHandle.createWritable(); + await writable.write(fileContent); + await writable.close(); + } + } + catch (error) { + console.error(error); + } + }); + + //TODO: if everything is ok, save localStorage reference to indicate experiment ID has a local FS clone at <dir> + // this can be used later during initialization to get earlier setup back + localStorage.setItem(experiment.id, localDirectoryHandle); + } + + async uploadLocalFSExperiment(experiment) { + let localDirectoryHandle = this.localDirectoryHandles.get(experiment.id); + let iterator = await localDirectoryHandle.keys(); + console.info(iterator); + + for ( let entry of iterator) { + console.info(entry); + } + } +} + +export default ExperimentsFilesRemoteEditService;