From 5d9f58cfebfc86b320e1ef8681507223d49ea661 Mon Sep 17 00:00:00 2001 From: Matthias Kleine Date: Fri, 8 Dec 2023 10:40:33 +0100 Subject: [PATCH] Import script as file (upload) --- README.md | 1 + src/src/Dialogs/Export.jsx | 4 +- src/src/Dialogs/Import.jsx | 149 ++++++++++++++++++++++++++++++++- src/src/Dialogs/ImportFile.jsx | 16 ++-- 4 files changed, 156 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ee2f44bb3..3f04456d5 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Since v5.5.0 of the JavaScript adapter the following locations (relative to the ### **WORK IN PROGRESS** * (klein0r) Download script as xml file (export) +* (klein0r) Import script as file (upload) ### 7.3.0 (2023-12-07) diff --git a/src/src/Dialogs/Export.jsx b/src/src/Dialogs/Export.jsx index 57e104dea..33eac1888 100644 --- a/src/src/Dialogs/Export.jsx +++ b/src/src/Dialogs/Export.jsx @@ -61,8 +61,8 @@ class DialogExport extends React.Component { render() { const classes = this.props.classes; - const file = new Blob([this.props.text], {type: 'text/plain'}); - const fileName = this.props.scriptId.substring('scripts.js.'.length) + '.xml'; + const file = new Blob([this.props.text], {type: 'application/xml'}); + const fileName = this.props.scriptId.substring('scripts.js'.length) + '.xml'; return ({ textArea: { width: 'calc(100% - 10px)', - height: '100%', - resize: 'none' + height: '80%', + resize: 'none', + fontFamily: 'monospace', + fontSize: '1em', + }, + dropzone: { + marginTop: 20, + width: '100%', + borderWidth: 5, + borderStyle: 'dashed', + borderColor: '#d0cccc', + textAlign: 'center', + boxSizing: 'border-box', + paddingTop: 45, + borderRadius: 10, + height: 'calc(100% - 10px)', + }, + dropzoneDiv: { + width: '100%', + height: '20%', + position: 'relative', + }, + dropzoneRejected: { + borderColor: '#970000', + }, + dropzoneAccepted: { + borderColor: '#17cd02', + }, + icon: { + height: '30%', + width: '30%', + color: '#eeeeee', + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%,-50%)', + zIndex: 0, + }, + iconError: { + color: '#ffc3c6', + }, + iconOk: { + color: '#aaeebc', + }, + text: { + top: '50%', + left: '50%', + transform: 'translate(-50%,-50%)', + color: '#868686', + position: 'absolute', + zIndex: 1, }, dialog: { - height: '95%' + height: '95%', }, fullHeight: { height: '100%', - overflow: 'hidden' + overflow: 'hidden', }, }); @@ -45,6 +98,47 @@ class DialogImport extends React.Component { }, 100); } + static readFileDataUrl(file, cb) { + const reader = new FileReader(); + reader.onload = () => { + cb(null, {data: reader.result, name: file.name}); + }; + reader.onabort = () => { + console.error('file reading was aborted'); + cb(I18n.t('file reading was aborted')); + }; + reader.onerror = (e) => { + console.error('file reading has failed'); + cb(I18n.t('file reading has failed: %s', e)); + }; + + reader.readAsText(file); + } + + handleDropFile(files) { + if (files && files.hasOwnProperty('target')) { + files = files.target.files; + } + + if (!files && !files.length) { + return; + } + + const file = files[files.length - 1]; + + if (!file) { + return; + } + + DialogImport.readFileDataUrl(file, (err, result) => { + if (err) { + this.setState({ error: err }); + } else { + this.setState({ text: result.data }); + } + }); + } + handleCancel() { this.props.onClose(); } @@ -59,6 +153,7 @@ class DialogImport extends React.Component { render() { const classes = this.props.classes; + const className = classes.dropzone + ' ' + (this.state.imageStatus === 'accepted' ? classes.dropzoneAccepted : (this.state.imageStatus === 'rejected' ? classes.dropzoneRejected : '')); return false} @@ -75,7 +170,53 @@ class DialogImport extends React.Component { id="import-text-area" className={classes.textArea} onChange={e => this.onChange(e)} + value={this.state.text} /> + this.handleDropFile(files)} + multiple={false} + accept='text/plain,text/xml,application/xml' + className={className}> + { + ({ getRootProps, getInputProps, isDragActive, isDragReject}) => { + if (isDragReject) { + if (this.state.imageStatus !== 'rejected') { + this.setState({imageStatus: 'rejected'}); + } + return ( +
+ + {I18n.t('Some files will be rejected')} + +
); + } else if (isDragActive) { + if (this.state.imageStatus !== 'accepted') { + this.setState({imageStatus: 'accepted'}); + } + + return ( +
+ + {I18n.t('All files will be accepted')} + +
); + } else { + if (this.state.imageStatus !== 'wait') { + this.setState({imageStatus: 'wait'}); + } + return ( +
+ + {I18n.t('Drop some files here or click...')} + +
); + } + } + } +
diff --git a/src/src/Dialogs/ImportFile.jsx b/src/src/Dialogs/ImportFile.jsx index aaebf21b1..f4108d636 100644 --- a/src/src/Dialogs/ImportFile.jsx +++ b/src/src/Dialogs/ImportFile.jsx @@ -18,11 +18,11 @@ import { I18n } from '@iobroker/adapter-react-v5'; const styles = theme => ({ dialog: { - height: '95%' + height: '95%', }, fullHeight: { height: '100%', - overflow: 'hidden' + overflow: 'hidden', }, dropzone: { marginTop: 20, @@ -34,11 +34,11 @@ const styles = theme => ({ boxSizing: 'border-box', paddingTop: 45, borderRadius: 10, - height: 'calc(100% - 10px)' + height: 'calc(100% - 10px)', }, dropzoneDiv: { width: '100%', - height: '100%' + height: '100%', }, dropzoneRejected: { borderColor: '#970000', @@ -69,7 +69,7 @@ const styles = theme => ({ color: '#868686', position: 'absolute', zIndex: 1, - } + }, }); class DialogImportFile extends React.Component { @@ -114,7 +114,7 @@ class DialogImportFile extends React.Component { cb(I18n.t('file reading has failed: %s', e)); }; - reader.readAsDataURL(file) + reader.readAsDataURL(file); } handleDropFile(files) { @@ -133,7 +133,7 @@ class DialogImportFile extends React.Component { } DialogImportFile.readFileDataUrl(file, (err, result) => { if (err) { - this.setState({error: err}) + this.setState({error: err}); } else { this.props.onClose(result && result.data); } @@ -147,7 +147,7 @@ class DialogImportFile extends React.Component { return false} maxWidth="lg" - classes={{paper: classes.dialog}} + classes={{ paper: classes.dialog }} fullWidth open={!0} aria-labelledby="import-dialog-title"