UNPKG

@iobroker/adapter-react-v5

Version:

React components to develop ioBroker interfaces with react.

247 lines 11.1 kB
// 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