Converting preview image modal to use react-bootstrap. Fixes issue where modal would appear behind background on iOS

Этот коммит содержится в:
Christopher Speller
2015-09-24 10:17:58 -04:00
родитель d5343f997d
Коммит c5d78f828a
8 изменённых файлов: 179 добавлений и 165 удалений

Просмотреть файл

@@ -143,10 +143,7 @@ export default class FileAttachment extends React.Component {
>
<a className='post-image__thumbnail'
href='#'
onClick={this.props.handleImageClick}
data-img-id={this.props.index}
data-toggle='modal'
data-target={'#' + this.props.modalId}
onClick={() => this.props.handleImageClick(this.props.index)}
>
{thumbnail}
</a>
@@ -187,9 +184,6 @@ FileAttachment.propTypes = {
// the index of this attachment preview in the parent FileAttachmentList
index: React.PropTypes.number.isRequired,
// the identifier of the modal dialog used to preview files
modalId: React.PropTypes.string.isRequired,
// handler for when the thumbnail is clicked
// handler for when the thumbnail is clicked passed the index above
handleImageClick: React.PropTypes.func
};

Просмотреть файл

@@ -11,23 +11,21 @@ export default class FileAttachmentList extends React.Component {
this.handleImageClick = this.handleImageClick.bind(this);
this.state = {startImgId: 0};
this.state = {showPreviewModal: false, startImgId: 0};
}
handleImageClick(e) {
this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'), 10)});
handleImageClick(indexClicked) {
this.setState({showPreviewModal: true, startImgId: indexClicked});
}
render() {
var filenames = this.props.filenames;
var modalId = this.props.modalId;
var postFiles = [];
for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) {
postFiles.push(
<FileAttachment
key={i}
key={'file_attachment_' + i}
filename={filenames[i]}
index={i}
modalId={modalId}
handleImageClick={this.handleImageClick}
/>
);
@@ -39,9 +37,10 @@ export default class FileAttachmentList extends React.Component {
{postFiles}
</div>
<ViewImageModal
show={this.state.showPreviewModal}
onModalDismissed={() => this.setState({showPreviewModal: false})}
channelId={this.props.channelId}
userId={this.props.userId}
modalId={modalId}
startId={this.state.startImgId}
filenames={filenames}
/>
@@ -55,9 +54,6 @@ FileAttachmentList.propTypes = {
// a list of file pathes displayed by this
filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
// the identifier of the modal dialog used to preview files
modalId: React.PropTypes.string.isRequired,
// the channel that this is part of
channelId: React.PropTypes.string,

Просмотреть файл

@@ -141,7 +141,6 @@ export default class PostBody extends React.Component {
fileAttachmentHolder = (
<FileAttachmentList
filenames={filenames}
modalId={`view_image_modal_${post.id}`}
channelId={post.channel_id}
userId={post.user_id}
/>

Просмотреть файл

@@ -163,7 +163,6 @@ export default class RhsComment extends React.Component {
fileAttachment = (
<FileAttachmentList
filenames={post.filenames}
modalId={'rhs_comment_view_image_modal_' + post.id}
channelId={post.channel_id}
userId={post.user_id}
/>

Просмотреть файл

@@ -111,7 +111,6 @@ export default class RhsRootPost extends React.Component {
fileAttachment = (
<FileAttachmentList
filenames={post.filenames}
modalId={'rhs_view_image_modal_' + post.id}
channelId={post.channel_id}
userId={post.user_id}
/>

Просмотреть файл

@@ -3,6 +3,8 @@
var Client = require('../utils/client.jsx');
var Utils = require('../utils/utils.jsx');
var ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
var Modal = ReactBootstrap.Modal;
export default class ViewImageModal extends React.Component {
constructor(props) {
@@ -16,6 +18,10 @@ export default class ViewImageModal extends React.Component {
this.handleKeyPress = this.handleKeyPress.bind(this);
this.getPublicLink = this.getPublicLink.bind(this);
this.getPreviewImagePath = this.getPreviewImagePath.bind(this);
this.onModalShown = this.onModalShown.bind(this);
this.onModalHidden = this.onModalHidden.bind(this);
this.onMouseEnterImage = this.onMouseEnterImage.bind(this);
this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this);
var loaded = [];
var progress = [];
@@ -23,9 +29,18 @@ export default class ViewImageModal extends React.Component {
loaded.push(false);
progress.push(0);
}
this.state = {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}};
this.state = {
imgId: this.props.startId,
imgHeight: '100%',
loaded: loaded,
progress: progress,
images: {},
fileSizes: {},
showFooter: false
};
}
handleNext() {
handleNext(e) {
e.stopPropagation();
var id = this.state.imgId + 1;
if (id > this.props.filenames.length - 1) {
id = 0;
@@ -33,7 +48,8 @@ export default class ViewImageModal extends React.Component {
this.setState({imgId: id});
this.loadImage(id);
}
handlePrev() {
handlePrev(e) {
e.stopPropagation();
var id = this.state.imgId - 1;
if (id < 0) {
id = this.props.filenames.length - 1;
@@ -50,15 +66,27 @@ export default class ViewImageModal extends React.Component {
this.handlePrev();
}
}
componentWillReceiveProps(nextProps) {
onModalShown(nextProps) {
this.setState({imgId: nextProps.startId});
this.loadImage(nextProps.startId);
}
onModalHidden() {
if (this.refs.video) {
var video = React.findDOMNode(this.refs.video);
video.pause();
video.currentTime = 0;
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.show === true && this.props.show === false) {
this.onModalShown(nextProps);
} else if (nextProps.show === false && this.props.show === true) {
this.onModalHidden();
}
}
loadImage(id) {
var imgHeight = $(window).height() - 100;
if (this.state.loaded[id] || this.state.images[id]) {
$('.modal .modal-image .image-wrapper img').css('max-height', imgHeight);
return;
}
this.setState({imgHeight});
var filename = this.props.filenames[id];
@@ -68,84 +96,27 @@ export default class ViewImageModal extends React.Component {
if (fileType === 'image') {
var img = new Image();
img.load(this.getPreviewImagePath(filename),
function load() {
var progress = this.state.progress;
progress[id] = img.completedPercentage;
this.setState({progress: progress});
}.bind(this));
img.onload = (function onload(imgid) {
return function onloadReturn() {
var loaded = this.state.loaded;
loaded[imgid] = true;
this.setState({loaded: loaded});
$(React.findDOMNode(this.refs.image)).css('max-height', imgHeight);
}.bind(this);
}.bind(this)(id));
() => {
const progress = this.state.progress;
progress[id] = img.completedPercentage;
this.setState({progress});
});
img.onload = () => {
const loaded = this.state.loaded;
loaded[id] = true;
this.setState({loaded});
};
var images = this.state.images;
images[id] = img;
this.setState({images: images});
this.setState({images});
} else {
// there's nothing to load for non-image files
var loaded = this.state.loaded;
loaded[id] = true;
this.setState({loaded: loaded});
}
}
componentDidUpdate() {
if (this.state.loaded[this.state.imgId]) {
if (this.refs.imageWrap) {
$(React.findDOMNode(this.refs.imageWrap)).removeClass('default');
}
this.setState({loaded});
}
}
componentDidMount() {
$('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() {
this.setState({viewed: true});
this.loadImage(this.state.imgId);
}.bind(this));
$('#' + this.props.modalId).on('hidden.bs.modal', function onModalHide() {
if (this.refs.video) {
var video = React.findDOMNode(this.refs.video);
video.pause();
video.currentTime = 0;
}
}.bind(this));
$(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) {
if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) {
$('.image_modal').modal('hide');
}
}.bind(this));
$(React.findDOMNode(this.refs.imageWrap)).hover(
function onModalHover() {
$(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
}.bind(this), function offModalHover() {
$(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
}.bind(this)
);
if (this.refs.previewArrowLeft) {
$(React.findDOMNode(this.refs.previewArrowLeft)).hover(
function onModalHover() {
$(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
}.bind(this), function offModalHover() {
$(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
}.bind(this)
);
}
if (this.refs.previewArrowRight) {
$(React.findDOMNode(this.refs.previewArrowRight)).hover(
function onModalHover() {
$(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
}.bind(this), function offModalHover() {
$(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
}.bind(this)
);
}
$(window).on('keyup', this.handleKeyPress);
// keep track of whether or not this component is mounted so we can safely set the state asynchronously
@@ -189,6 +160,12 @@ export default class ViewImageModal extends React.Component {
// only images have proper previews, so just use a placeholder icon for non-images
return Utils.getPreviewImagePathForFileType(fileType);
}
onMouseEnterImage() {
this.setState({showFooter: true});
}
onMouseLeaveImage() {
this.setState({showFooter: false});
}
render() {
if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) {
return <div/>;
@@ -299,23 +276,6 @@ export default class ViewImageModal extends React.Component {
bgClass = 'black-bg';
}
var publicLink = '';
if (global.window.config.EnablePublicLink === 'true') {
publicLink = (
<div>
<a
href='#'
className='public-link text'
data-title='Public Image'
onClick={this.getPublicLink}
>
Get Public Link
</a>
<span className='text'> | </span>
</div>
);
}
var leftArrow = '';
var rightArrow = '';
if (this.props.filenames.length > 1) {
@@ -342,65 +302,61 @@ export default class ViewImageModal extends React.Component {
);
}
let closeButtonClass = 'modal-close';
if (this.state.showFooter) {
closeButtonClass += ' modal-close--show';
}
return (
<div
className='modal fade image_modal'
ref='modal'
id={this.props.modalId}
tabIndex='-1'
role='dialog'
aria-hidden='true'
<Modal
show={this.props.show}
onHide={this.props.onModalDismissed}
className='image_modal'
dialogClassName='modal-image'
>
<div className='modal-dialog modal-image'>
<div className='modal-content image-content'>
<Modal.Body
modalClassName='image-body'
onClick={this.props.onModalDismissed}
>
<div
className={'image-wrapper ' + bgClass}
style={{maxHeight: this.state.imgHeight}}
onMouseEnter={this.onMouseEnterImage}
onMouseLeave={this.onMouseLeaveImage}
onClick={(e) => e.stopPropagation()}
>
<div
ref='imageBody'
className='modal-body image-body'
>
<div
ref='imageWrap'
className={'image-wrapper default ' + bgClass}
>
<div
className='modal-close'
data-dismiss='modal'
/>
{content}
<div
ref='imageFooter'
className='modal-button-bar'
>
<span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
<div className='image-links'>
{publicLink}
<a
href={fileUrl}
download={name}
className='text'
>
Download
</a>
</div>
</div>
</div>
{leftArrow}
{rightArrow}
</div>
className={closeButtonClass}
onClick={this.props.onModalDismissed}
/>
{content}
<ViewImagePopoverBar
show={this.state.showFooter}
fileId={this.state.imgId}
totalFiles={this.props.filenames.length}
filename={name}
fileURL={fileUrl}
onGetPublicLinkPressed={this.getPublicLink}
/>
</div>
</div>
</div>
{leftArrow}
{rightArrow}
</Modal.Body>
</Modal>
);
}
}
ViewImageModal.defaultProps = {
show: false,
filenames: [],
modalId: '',
channelId: '',
userId: '',
startId: 0
};
ViewImageModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func.isRequired,
filenames: React.PropTypes.array,
modalId: React.PropTypes.string,
channelId: React.PropTypes.string,

Просмотреть файл

@@ -0,0 +1,66 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
export default class ViewImagePopoverBar extends React.Component {
constructor(props) {
super(props);
}
render() {
var publicLink = '';
if (global.window.config.EnablePublicLink === 'true') {
publicLink = (
<div>
<a
href='#'
className='public-link text'
data-title='Public Image'
onClick={this.getPublicLink}
>
{'Get Public Link'}
</a>
<span className='text'>{' | '}</span>
</div>
);
}
var footerClass = 'modal-button-bar';
if (this.props.show) {
footerClass += ' footer--show';
}
return (
<div
ref='imageFooter'
className={footerClass}
>
<span className='pull-left text'>{'File ' + (this.props.fileId + 1) + ' of ' + this.props.totalFiles}</span>
<div className='image-links'>
{publicLink}
<a
href={this.props.fileURL}
download={this.props.filename}
className='text'
>
{'Download'}
</a>
</div>
</div>
);
}
}
ViewImagePopoverBar.defaultProps = {
show: false,
imgId: 0,
totalFiles: 0,
filename: '',
fileURL: ''
};
ViewImagePopoverBar.propTypes = {
show: React.PropTypes.bool.isRequired,
fileId: React.PropTypes.number.isRequired,
totalFiles: React.PropTypes.number.isRequired,
filename: React.PropTypes.string.isRequired,
fileURL: React.PropTypes.string.isRequired,
onGetPublicLinkPressed: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -190,16 +190,13 @@
position: relative;
max-width: 90%;
min-height: 100px;
min-width: 320px;
min-width: 240px;
@include border-radius(3px);
display: table;
margin: 0 auto;
&:hover {
@include border-radius(3px 3px 0 0);
}
&:hover .modal-close {
@include opacity(1);
}
&.default {
width: 100%;
height: 80%;
@@ -213,8 +210,15 @@
right: -13px;
top: -13px;
@include opacity(0);
-webkit-transition: opacity 0.6s;
-moz-transition: opacity 0.6s;
-o-transition: opacity 0.6s;
transition: opacity 0.6s;
cursor: pointer;
z-index: 9999;
&.modal-close--show {
@include opacity(1);
}
}
> a {
background: #FFF;
@@ -226,7 +230,7 @@
max-height: 100%;
}
}
.image-content {
.modal-content{
box-shadow: none;
background: rgba(0, 0, 0, 0);
width: 100%;
@@ -310,6 +314,7 @@
}
}
// Invite New Member
.invite {
margin-right: 40px;
@@ -324,4 +329,4 @@
padding-left: 0;
}
}
}
}