Converting preview image modal to use react-bootstrap. Fixes issue where modal would appear behind background on iOS
Этот коммит содержится в:
@@ -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,
|
||||
|
||||
66
web/react/components/view_image_popover_bar.jsx
Обычный файл
66
web/react/components/view_image_popover_bar.jsx
Обычный файл
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user