@iobroker/adapter-react-v5
Version:
React components to develop ioBroker interfaces with react.
247 lines • 11.1 kB
JavaScript
// File viewer in adapter-react does not support write
// import { Buffer } from 'buffer';
import React, { Component } from 'react';
import { TextField, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton } from '@mui/material';
// Icons
import { Close as CloseIcon, Save as SaveIcon, Brightness6 as Brightness5Icon, ContentCopy as CopyIcon, } from '@mui/icons-material';
import { IconNoIcon } from '../icons/IconNoIcon';
import { withWidth } from './withWidth';
import { Utils } from './Utils';
import { Icon } from './Icon';
const styles = {
dialog: {
height: '100%',
},
paper: {
height: 'calc(100% - 64px)',
},
content: {
textAlign: 'center',
},
textarea: {
width: '100%',
height: '100%',
},
img: {
width: 'auto',
height: 'calc(100% - 5px)',
objectFit: 'contain',
},
dialogTitle: {
justifyContent: 'space-between',
display: 'flex',
},
};
export const EXTENSIONS = {
images: ['png', 'jpg', 'svg', 'jpeg', 'bmp', 'gif', 'apng', 'avif', 'webp', 'ico'],
code: ['js', 'json', 'json5', 'md'],
txt: ['log', 'txt', 'html', 'css', 'xml', 'ics', 'csv'],
audio: ['mp3', 'wav', 'ogg', 'acc'],
video: ['mp4', 'mov', 'avi'],
};
function bufferToBase64(buffer, isFull) {
let binary = '';
const bytes = new Uint8Array(buffer?.data || buffer);
const len = bytes.byteLength;
for (let i = 0; i < len && (isFull || i < 50); i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
export class FileViewerClass extends Component {
timeout = null;
constructor(props) {
super(props);
const ext = Utils.getFileExtension(props.href);
this.state = {
text: null,
code: null,
ext,
// File viewer in adapter-react does not support write
editing: /* !!this.props.formatEditFile || */ false,
editingValue: null,
copyPossible: !!ext && (EXTENSIONS.code.includes(ext) || EXTENSIONS.txt.includes(ext)),
forceUpdate: Date.now(),
changed: false,
imgError: false,
};
}
readFile() {
if (this.props.href) {
const parts = this.props.href.split('/');
parts.splice(0, 2);
const adapter = parts[0];
const name = parts.splice(1).join('/');
this.props.socket
.readFile(adapter, name)
.then((data) => {
let fileData = '';
if (data.file !== undefined) {
fileData = data.file;
}
const newState = {
copyPossible: this.state.copyPossible,
ext: this.state.ext,
};
// try to detect valid extension
if (fileData.type === 'Buffer') {
if (name.toLowerCase().endsWith('.json5')) {
newState.ext = 'json5';
newState.copyPossible = true;
try {
fileData = atob(bufferToBase64(fileData, true));
}
catch {
console.error('Cannot convert base64 to string');
fileData = '';
}
}
else {
const ext = Utils.detectMimeType(bufferToBase64(fileData));
if (ext) {
newState.ext = ext;
newState.copyPossible = EXTENSIONS.code.includes(ext) || EXTENSIONS.txt.includes(ext);
}
}
}
if (newState.copyPossible) {
if (newState.ext && EXTENSIONS.txt.includes(newState.ext)) {
newState.text = fileData;
newState.editingValue = fileData;
}
else if (newState.ext && EXTENSIONS.code.includes(newState.ext)) {
newState.code = fileData;
newState.editingValue = fileData;
}
}
this.setState(newState);
})
.catch(e => window.alert(`Cannot read file: ${e}`));
}
}
componentDidMount() {
this.readFile();
const parts = this.props.href.split('/');
parts.splice(0, 2);
const adapter = parts[0];
const name = parts.splice(1).join('/');
if (this.props.supportSubscribes) {
this.props.socket
.subscribeFiles(adapter, name, this.onFileChanged)
.catch(e => window.alert(`Cannot subscribe on file: ${e}`));
}
}
componentWillUnmount() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
const parts = this.props.href.split('/');
parts.splice(0, 2);
const adapter = parts[0];
const name = parts.splice(1).join('/');
if (this.props.supportSubscribes) {
this.props.socket
.subscribeFiles(adapter, name, this.onFileChanged)
.catch(e => window.alert(`Cannot subscribe on file: ${e}`));
}
}
onFileChanged = (_id, _fileName, size) => {
if (!this.state.changed) {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
this.timeout = null;
if (size === null) {
window.alert('Show file was deleted!');
}
else if (this.state.text !== null || this.state.code !== null) {
this.readFile();
}
else {
this.setState({ forceUpdate: Date.now() });
}
}, 300);
}
};
getEditorOrViewer() {
return (React.createElement(TextField, { variant: "standard", style: styles.textarea, multiline: true, value: this.state.editingValue || this.state.code || this.state.text,
// onChange={newValue => this.setState({ editingValue: newValue, changed: true })}
slotProps: {
htmlInput: {
readOnly: !this.state.editing,
},
} }));
}
getContent() {
if (this.state.ext && EXTENSIONS.images.includes(this.state.ext)) {
if (this.state.imgError) {
return React.createElement(IconNoIcon, { style: { ...styles.img, ...this.props.getStyleBackgroundImage() } });
}
return (React.createElement(Icon, { onError: e => {
e.target.onerror = null;
this.setState({ imgError: true });
}, style: { ...styles.img, ...this.props.getStyleBackgroundImage() }, src: `${this.props.href}?ts=${this.state.forceUpdate}`, alt: this.props.href }));
}
if (this.state.ext && EXTENSIONS.audio.includes(this.state.ext)) {
return (React.createElement("div", { style: {
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
} },
React.createElement("audio", { style: { width: '100%' }, src: this.props.href, controls: true })));
}
if (this.state.ext && EXTENSIONS.video.includes(this.state.ext)) {
return (React.createElement("div", { style: {
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
} },
React.createElement("video", { style: { width: '100%', height: '100%' }, controls: true },
React.createElement("source", { src: this.props.href, type: `video/${this.state.ext}}` }))));
}
if (this.state.code !== null || this.state.text !== null || this.state.editing) {
// File viewer in adapter-react does not support write
// return <Editor
// mode={this.getEditFile(this.props.formatEditFile)}
// themeType={this.props.themeType}
// value={this.state.editingValue || this.state.code || this.state.text}
// onChange={this.state.editing ? newValue => this.setState({ editingValue: newValue, changed: true }) : undefined}
// />;
return this.getEditorOrViewer();
}
return null;
}
// eslint-disable-next-line class-methods-use-this
onSave() {
// Do nothing as the file viewer in adapter-react does not support writing
}
render() {
return (React.createElement(Dialog, { sx: {
'&.MuiDialog-scrollPaper': styles.dialog,
'& .MuiDialog-paper': styles.paper,
}, scroll: "paper", open: !!this.props.href, onClose: () => this.props.onClose(), fullWidth: true, maxWidth: "xl", "aria-labelledby": "ar_dialog_file_view_title" },
React.createElement("div", { style: styles.dialogTitle },
React.createElement(DialogTitle, { id: "ar_dialog_file_view_title" }, `${this.props.t(this.state.editing ? 'Edit' : 'View')}: ${this.props.href}`),
this.state.ext && EXTENSIONS.images.includes(this.state.ext) && (React.createElement("div", null,
React.createElement(IconButton, { size: "large", color: "inherit", onClick: this.props.setStateBackgroundImage },
React.createElement(Brightness5Icon, null))))),
React.createElement(DialogContent, { style: styles.content }, this.getContent()),
React.createElement(DialogActions, null,
this.state.copyPossible ? (React.createElement(Button, { color: "grey", onClick: e => {
e.stopPropagation();
e.preventDefault();
Utils.copyToClipboard(this.state.text || this.state.code || '');
}, startIcon: React.createElement(CopyIcon, null) }, this.props.t('Copy content'))) : null,
this.state.editing ? (React.createElement(Button, { color: "grey", disabled: this.state.editingValue === this.state.code ||
this.state.editingValue === this.state.text, variant: "contained", onClick: () => this.onSave(), startIcon: React.createElement(SaveIcon, null) }, this.props.t('Save'))) : null,
React.createElement(Button, { variant: "contained", onClick: () => this.props.onClose(), color: "primary", startIcon: React.createElement(CloseIcon, null) }, this.props.t('Close')))));
}
}
export const FileViewer = withWidth()(FileViewerClass);
//# sourceMappingURL=FileViewer.js.map