[MM-9526] Preview text files (#1485)
* Add text preview screen * Add text/plain mime in supported docs format * Add previewTextFile method to view text file in TextPreview Screen * Fix delay for ui to update progress * Add styling for text preview screen * Align line numbers on right * Start read file for preview before delay
This commit is contained in:
parent
c20a01a0a2
commit
f1ea485ba0
6 changed files with 185 additions and 4 deletions
|
|
@ -24,6 +24,7 @@ export default class FileAttachment extends PureComponent {
|
|||
onInfoPress: PropTypes.func,
|
||||
onPreviewPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -61,7 +62,7 @@ export default class FileAttachment extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {file, onInfoPress, theme} = this.props;
|
||||
const {file, onInfoPress, theme, navigator} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let mime = file.mime_type;
|
||||
|
|
@ -86,6 +87,7 @@ export default class FileAttachment extends PureComponent {
|
|||
<FileAttachmentDocument
|
||||
file={file}
|
||||
theme={theme}
|
||||
navigator={navigator}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const SUPPORTED_DOCS_FORMAT = [
|
|||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/xml',
|
||||
'text/csv',
|
||||
'text/plain',
|
||||
];
|
||||
|
||||
export default class FileAttachmentDocument extends PureComponent {
|
||||
|
|
@ -42,6 +43,7 @@ export default class FileAttachmentDocument extends PureComponent {
|
|||
iconWidth: PropTypes.number,
|
||||
file: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
wrapperHeight: PropTypes.number,
|
||||
wrapperWidth: PropTypes.number,
|
||||
};
|
||||
|
|
@ -105,9 +107,15 @@ export default class FileAttachmentDocument extends PureComponent {
|
|||
path,
|
||||
};
|
||||
|
||||
const mime = file.mime_type.split(';')[0];
|
||||
let openDocument = this.openDocument;
|
||||
if (mime === 'text/plain') {
|
||||
openDocument = this.previewTextFile;
|
||||
}
|
||||
|
||||
const exist = await RNFetchBlob.fs.exists(path);
|
||||
if (exist) {
|
||||
this.openDocument(file, 0);
|
||||
openDocument(file, 0);
|
||||
} else {
|
||||
this.setState({downloading: true});
|
||||
this.downloadTask = RNFetchBlob.config(options).fetch('GET', getFileUrl(file.id));
|
||||
|
|
@ -124,7 +132,7 @@ export default class FileAttachmentDocument extends PureComponent {
|
|||
progress: 100,
|
||||
}, () => {
|
||||
// need to wait a bit for the progress circle UI to update to the give progress
|
||||
this.openDocument(file);
|
||||
openDocument(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -153,6 +161,36 @@ export default class FileAttachmentDocument extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
previewTextFile = (file, delay = 2000) => {
|
||||
const {navigator, theme} = this.props;
|
||||
const prefix = Platform.OS === 'android' ? 'file:/' : '';
|
||||
const path = `${DOCUMENTS_PATH}/${file.name}`;
|
||||
const readFile = RNFetchBlob.fs.readFile(`${prefix}${path}`, 'utf8');
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const content = await readFile;
|
||||
navigator.push({
|
||||
screen: 'TextPreview',
|
||||
title: file.name,
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
content,
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
});
|
||||
this.setState({downloading: false, progress: 0});
|
||||
} catch (error) {
|
||||
RNFetchBlob.fs.unlink(path);
|
||||
}
|
||||
}, delay);
|
||||
};
|
||||
|
||||
openDocument = (file, delay = 2000) => {
|
||||
// The animation for the progress circle takes about 2 seconds to finish
|
||||
// therefore we are delaying the opening of the document to have the UI
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export default class FileAttachmentList extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {fileIds, files, isFailed} = this.props;
|
||||
const {fileIds, files, isFailed, navigator} = this.props;
|
||||
|
||||
let fileAttachments;
|
||||
if (!files.length && fileIds.length > 0) {
|
||||
|
|
@ -106,6 +106,7 @@ export default class FileAttachmentList extends Component {
|
|||
onPressOut={this.handlePressOut}
|
||||
>
|
||||
<FileAttachment
|
||||
navigator={navigator}
|
||||
addFileToFetchCache={this.props.actions.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import EditChannel from 'app/screens/edit_channel';
|
|||
import EditPost from 'app/screens/edit_post';
|
||||
import EditProfile from 'app/screens/edit_profile';
|
||||
import ImagePreview from 'app/screens/image_preview';
|
||||
import TextPreview from 'app/screens/text_preview';
|
||||
import Login from 'app/screens/login';
|
||||
import LoginOptions from 'app/screens/login_options';
|
||||
import Mfa from 'app/screens/mfa';
|
||||
|
|
@ -80,6 +81,7 @@ export function registerScreens(store, Provider) {
|
|||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider);
|
||||
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(EditProfile), store, Provider);
|
||||
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(ImagePreview), store, Provider);
|
||||
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(TextPreview), store, Provider);
|
||||
Navigation.registerComponent('Login', () => wrapWithContextProvider(Login), store, Provider);
|
||||
Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(LoginOptions), store, Provider);
|
||||
Navigation.registerComponent('MFA', () => wrapWithContextProvider(Mfa), store, Provider);
|
||||
|
|
|
|||
16
app/screens/text_preview/index.js
Normal file
16
app/screens/text_preview/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import TextPreview from './text_preview';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(TextPreview);
|
||||
122
app/screens/text_preview/text_preview.js
Normal file
122
app/screens/text_preview/text_preview.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import {getCodeFont} from 'app/utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
|
||||
export default class TextPreview extends React.PureComponent {
|
||||
static propTypes = {
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.theme !== nextProps.theme) {
|
||||
setNavigatorStyles(this.props.navigator, nextProps.theme);
|
||||
}
|
||||
}
|
||||
|
||||
countLines = (content) => {
|
||||
return content.split('\n').length;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
const numberOfLines = this.countLines(this.props.content);
|
||||
let lineNumbers = '1';
|
||||
for (let i = 1; i < numberOfLines; i++) {
|
||||
const line = (i + 1).toString();
|
||||
|
||||
lineNumbers += '\n' + line;
|
||||
}
|
||||
|
||||
let lineNumbersStyle;
|
||||
if (numberOfLines >= 10) {
|
||||
lineNumbersStyle = [style.lineNumbers, style.lineNumbersRight];
|
||||
} else {
|
||||
lineNumbersStyle = style.lineNumbers;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={style.scrollContainer}
|
||||
contentContainerStyle={style.container}
|
||||
>
|
||||
<View style={lineNumbersStyle}>
|
||||
<Text style={style.lineNumbersText}>
|
||||
{lineNumbers}
|
||||
</Text>
|
||||
</View>
|
||||
<ScrollView
|
||||
style={style.codeContainer}
|
||||
horizontal={true}
|
||||
contentContainerStyle={style.code}
|
||||
>
|
||||
<Text
|
||||
style={style.codeText}
|
||||
selectable={true}
|
||||
>
|
||||
{this.props.content}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
scrollContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
minHeight: '100%',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
lineNumbers: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.05),
|
||||
borderRightColor: changeOpacity(theme.centerChannelColor, 0.15),
|
||||
borderRightWidth: StyleSheet.hairlineWidth,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
lineNumbersRight: {
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
lineNumbersText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
textAlign: 'right',
|
||||
},
|
||||
codeContainer: {
|
||||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
width: '100%',
|
||||
},
|
||||
code: {
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
codeText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.65),
|
||||
fontFamily: getCodeFont(),
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
},
|
||||
};
|
||||
});
|
||||
Loading…
Reference in a new issue