Skip to content

Commit

Permalink
Import script as file (upload)
Browse files Browse the repository at this point in the history
  • Loading branch information
klein0r committed Dec 8, 2023
1 parent 9ac24dd commit 5d9f58c
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 14 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions src/src/Dialogs/Export.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Dialog
key="export-dialog"
Expand Down
149 changes: 145 additions & 4 deletions src/src/Dialogs/Import.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,77 @@ import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Dialog from '@mui/material/Dialog';
import Dropzone from 'react-dropzone';

import IconOk from '@mui/icons-material/Check';
import IconCancel from '@mui/icons-material/Cancel';
import {MdFileUpload as IconUpload} from 'react-icons/md';
import {MdCancel as IconNo} from 'react-icons/md';
import {MdPlusOne as IconPlus} from 'react-icons/md';

import { I18n } from '@iobroker/adapter-react-v5';

const styles = theme => ({
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',
},
});

Expand All @@ -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();
}
Expand All @@ -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 <Dialog
onClose={(event, reason) => false}
Expand All @@ -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}
/>
<Dropzone key='image-drop'
maxSize={50000000}
acceptClassName={classes.dropzoneAccepted}
rejectClassName={classes.dropzoneRejected}
onDrop={files => 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 (
<div className={this.props.classes.dropzoneDiv} {...getRootProps()}>
<input {...getInputProps()} />
<span key="text" className={this.props.classes.text}>{I18n.t('Some files will be rejected')}</span>
<IconNo key="icon" className={this.props.classes.icon + ' ' + this.props.classes.iconError}/>
</div>);
} else if (isDragActive) {
if (this.state.imageStatus !== 'accepted') {
this.setState({imageStatus: 'accepted'});
}

return (
<div className={this.props.classes.dropzoneDiv} {...getRootProps()}>
<input {...getInputProps()} />
<span key="text" className={this.props.classes.text}>{I18n.t('All files will be accepted')}</span>
<IconPlus key="icon" className={this.props.classes.icon + ' ' + this.props.classes.iconOk}/>
</div>);
} else {
if (this.state.imageStatus !== 'wait') {
this.setState({imageStatus: 'wait'});
}
return (
<div className={this.props.classes.dropzoneDiv} {...getRootProps()}>
<input {...getInputProps()} />
<span key="text" className={this.props.classes.text}>{I18n.t('Drop some files here or click...')}</span>
<IconUpload key="icon" className={this.props.classes.icon}/>
</div>);
}
}
}
</Dropzone>
</DialogContent>
<DialogActions>
<Button variant="contained" disabled={!this.state.text} onClick={event => this.handleOk()} color="primary" startIcon={<IconOk/>}>{I18n.t('Import')}</Button>
Expand Down
16 changes: 8 additions & 8 deletions src/src/Dialogs/ImportFile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -69,7 +69,7 @@ const styles = theme => ({
color: '#868686',
position: 'absolute',
zIndex: 1,
}
},
});

class DialogImportFile extends React.Component {
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand All @@ -147,7 +147,7 @@ class DialogImportFile extends React.Component {
return <Dialog
onClose={(event, reason) => false}
maxWidth="lg"
classes={{paper: classes.dialog}}
classes={{ paper: classes.dialog }}
fullWidth
open={!0}
aria-labelledby="import-dialog-title"
Expand Down

0 comments on commit 5d9f58c

Please sign in to comment.