This commit is contained in:
mgabdev
2020-05-02 02:25:55 -04:00
parent e9f01c0b16
commit 196a906cec
62 changed files with 866 additions and 509 deletions

View File

@@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import isObject from 'lodash.isobject'
import classNames from 'classnames/bind'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Textarea from 'react-textarea-autosize'
import { isRtl } from '../utils/rtl'
import { textAtCursorMatchesToken } from '../utils/cursor_token_match'
import AutosuggestAccount from './autosuggest_account'
@@ -226,27 +227,56 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
direction: isRtl(value) ? 'rtl' : 'ltr',
}
const textClasses = cx({
const textareaClasses = cx({
default: 1,
lineHeight125: 1,
font: 1,
wrap: 1,
resizeNone: 1,
text: 1,
displayBlock: 1,
bgTransparent: 1,
outlineNone: 1,
bgPrimary: !small,
bgSubtle: small,
py15: !small,
py10: small,
lineHeight125: 1,
height100PC: small,
width100PC: !small,
pt15: !small,
px15: !small,
px10: small,
pb10: !small,
fs16PX: !small,
fs14PX: small,
mr5: small,
heightMax200PX: small,
heightMax80VH: !small,
heightMin100PX: !small,
})
const textareaContainerClasses = cx({
default: 1,
maxWidth100PC: 1,
flexGrow1: small,
height100PC: small,
justifyContentCenter: small,
})
if (textarea) {
return (
<Fragment>
<div className={[_s.default, _s.flexGrow1, _s.maxWidth100PC].join(' ')}>
<Composer
<div className={textareaContainerClasses}>
<Textarea
inputRef={this.setTextbox}
className={textareaClasses}
disabled={disabled}
placeholder={placeholder}
autoFocus={false}
value={value}
onChange={this.onChange}
// onKeyDown={this.onKeyDown}
// onKeyUp={onKeyUp}
// onFocus={this.onFocus}
// onBlur={this.onBlur}
// onPaste={this.onPaste}
aria-autocomplete='list'
/>
{/*<Composer
inputRef={this.setTextbox}
disabled={disabled}
placeholder={placeholder}
@@ -259,7 +289,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
onBlur={this.onBlur}
onPaste={this.onPaste}
small={small}
/>
/>*/}
{children}
</div>

View File

@@ -1,52 +1,125 @@
import { NavLink } from 'react-router-dom'
import { defineMessages, injectIntl } from 'react-intl'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { makeGetStatus } from '../selectors';
import {
favorite,
unfavorite,
} from '../actions/interactions'
import { replyCompose } from '../actions/compose'
import { openModal } from '../actions/modal'
import { openPopover } from '../actions/popover'
import { makeGetStatus } from '../selectors'
import { me } from '../initial_state'
import Avatar from './avatar'
import Button from './button'
import CommentHeader from './comment_header'
import StatusContent from './status_content'
import StatusMedia from './status_media'
import { defaultMediaVisibility } from './status'
import Text from './text'
const messages = defineMessages({
reply: { id: 'status.reply', defaultMessage: 'Reply' },
like: { id: 'status.like', defaultMessage: 'Like' },
unlike: { id: 'status.unlike', defaultMessage: 'Unlike' },
})
const makeMapStateToProps = (state, props) => ({
status: makeGetStatus()(state, props)
})
const mapDispatchToProps = (dispatch) => ({
onReply (status, router) {
if (!me) return dispatch(openModal('UNAUTHORIZED'))
dispatch((_, getState) => {
const state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.reply.message' defaultMessage='Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' />,
confirm: <FormattedMessage id='confirmations.reply.confirm' defaultMessage='Reply' />,
onConfirm: () => dispatch(replyCompose(status, router)),
}))
} else {
dispatch(replyCompose(status, router, true))
}
})
},
onFavorite (status) {
if (!me) return dispatch(openModal('UNAUTHORIZED'))
if (status.get('favourited')) {
dispatch(unfavorite(status))
} else {
dispatch(favorite(status))
}
},
onOpenStatusOptions(status) {
dispatch(openPopover('STATUS_OPTOINS', { status }))
},
})
export default
@injectIntl
@connect(makeMapStateToProps)
@connect(makeMapStateToProps, mapDispatchToProps)
class Comment extends ImmutablePureComponent {
static propTypes = {
indent: PropTypes.number,
intl: PropTypes.object.isRequired,
status: ImmutablePropTypes.map.isRequired,
isHidden: PropTypes.bool,
isIntersecting: PropTypes.bool,
onReply: PropTypes.func.isRequired,
onFavorite: PropTypes.func.isRequired,
onOpenStatusOptions: PropTypes.func.isRequired,
}
updateOnProps = [
'status',
'indent',
'isHidden',
'isIntersecting',
]
state = {
showMedia: defaultMediaVisibility(this.props.status),
}
handleOnReply = () => {
this.props.onReply(this.props.status)
}
handleOnFavorite = () => {
this.props.onFavorite(this.props.status)
}
handleOnOpenStatusOptions = () => {
this.props.onOpenStatusOptions(this.props.status)
}
render() {
const {
indent,
intl,
status,
isHidden,
} = this.props
if (isHidden) {
return (
<div tabIndex='0'>
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
{status.get('content')}
</div>
)
}
const style = {
paddingLeft: `${indent * 40}px`,
}
// : todo : add media
return (
<div className={[_s.default, _s.px15, _s.mb10, _s.py5].join(' ')} data-comment={status.get('id')}>
<div className={[_s.default].join(' ')} style={style}>
@@ -69,12 +142,34 @@ class Comment extends ImmutablePureComponent {
isComment
collapsable
/>
<div className={[_s.default].join(' ')}>
<StatusMedia
isComment
status={status}
onOpenMedia={this.props.onOpenMedia}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
width={this.props.cachedMediaWidth}
onOpenVideo={this.handleOpenVideo}
/>
</div>
</div>
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
<CommentButton title={intl.formatMessage(messages.like)} />
<CommentButton title={intl.formatMessage(messages.reply)} />
<CommentButton title='···' />
<CommentButton
title={intl.formatMessage(status.get('favourited') ? messages.unlike: messages.like)}
onClick={this.handleOnFavorite}
/>
<CommentButton
title={intl.formatMessage(messages.reply)}
onClick={this.handleOnReply}
/>
<CommentButton
title='···'
onClick={this.handleOnOpenStatusOptions}
/>
</div>
</div>

View File

@@ -2,7 +2,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from './button'
import Comment from './comment'
import ScrollableList from './scrollable_list'
import Text from './text'
import { PureComponent } from 'react';
export default class CommentList extends ImmutablePureComponent {
@@ -11,34 +13,44 @@ export default class CommentList extends ImmutablePureComponent {
descendants: ImmutablePropTypes.list,
}
handleLoadMore = () => {
//
}
render() {
const {
descendants,
commentsLimited,
} = this.props
const upperLimit = 6
const size = descendants.size
const max = Math.min(commentsLimited ? 2 : 6, size)
console.log("max:", size, max)
const max = Math.min(commentsLimited ? 2 : upperLimit, size)
const Wrapper = !commentsLimited ? ScrollableList : DummyContainer
console.log("Wrapper:", Wrapper)
return (
<div>
<Wrapper scrollKey='comments'>
{
descendants.slice(0, max).map((descendant, i) => (
<Comment
key={`comment-${descendant.get('statusId')}-${i}`}
id={descendant.get('statusId')}
indent={descendant.get('indent')}
/>
))
}
</Wrapper>
{
descendants.slice(0, max).map((descendant, i) => (
<Comment
key={`comment-${descendant.get('statusId')}-${i}`}
id={descendant.get('statusId')}
indent={descendant.get('indent')}
/>
))
}
{
size > 0 && size > max &&
size > 0 && size > max && commentsLimited &&
<div className={[_s.default, _s.flexRow, _s.px15, _s.pb5, _s.mb10, _s.alignItemsCenter].join(' ')}>
<Button
isText
backgroundColor='none'
color='tertiary'
onClick={this.handleLoadMore}
>
<Text weight='bold' color='inherit'>
View more comments
@@ -58,3 +70,9 @@ export default class CommentList extends ImmutablePureComponent {
}
}
class DummyContainer extends PureComponent {
render() {
return <div>{this.props.children}</div>
}
}

View File

@@ -100,8 +100,22 @@ class Composer extends PureComponent {
}
state = {
markdownText: '',
plainText: '',
editorState: EditorState.createEmpty(compositeDecorator),
}
static getDerivedStateFromProps(nextProps, prevState) {
// if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.fetched) {
// return {
// fetched: true
// }
// }
return null
}
// EditorState.createWithContent(ContentState.createFromText('Hello'))
onChange = (editorState) => {
this.setState({ editorState })
@@ -123,15 +137,14 @@ class Composer extends PureComponent {
this.props.onChange(null, text, selectionStart, markdownString)
}
// **bold**
// *italic*
// __underline__
// ~strikethrough~
// # title
// > quote
// `code`
// ```code```
// **bold**
// *italic*
// __underline__
// ~strikethrough~
// # title
// > quote
// `code`
// ```code```
focus = () => {
this.textbox.editor.focus()
@@ -168,7 +181,7 @@ class Composer extends PureComponent {
disabled,
placeholder,
autoFocus,
// value,
value,
onChange,
onKeyDown,
onKeyUp,

View File

@@ -122,7 +122,7 @@ class DisplayName extends ImmutablePureComponent {
const iconSize =
!!isLarge ? '19px' :
!!isSmall ? '14px' : '16px'
!!isSmall ? '14px' : '15px'
const domain = account.get('acct').split('@')[1]
const isRemoteUser = !!domain

View File

@@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => {
// if you want additional emoji handler, add statements below which set replacement and return true.
if (shortname in customEmojis) {
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
replacement = `<img draggable="false" style="height:20px;width:20px;margin:-3px 0 0;" alt="${shortname}" title="${shortname}" src="${filename}" />`;
replacement = `<img draggable="false" style="height:16px;width:16px;margin:-3px 0 0;font-family: 'object-fit:contain',inherit;vertical-align: middle;-o-object-fit: contain;object-fit: contain;" alt="${shortname}" title="${shortname}" src="${filename}" />`;
return true;
}
return false;
@@ -60,7 +60,7 @@ const emojify = (str, customEmojis = {}) => {
} else { // matched to unicode emoji
const { filename, shortCode } = unicodeMapping[match];
const title = shortCode ? `:${shortCode}:` : '';
replacement = `<img draggable="false" style="height:20px;width:20px;margin: -3px 0 0;" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`;
replacement = `<img draggable="false" style="height:16px;width:16px;margin:-3px 0 0;font-family: 'object-fit:contain',inherit;vertical-align: middle;-o-object-fit: contain;object-fit: contain;" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`;
rend = i + match.length;
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
if (str.codePointAt(rend) === 65038) {

View File

@@ -9,48 +9,39 @@ export default class ExtendedVideoPlayer extends PureComponent {
controls: PropTypes.bool.isRequired,
muted: PropTypes.bool.isRequired,
onClick: PropTypes.func,
};
}
handleLoadedData = () => {
if (this.props.time) {
this.video.currentTime = this.props.time;
this.video.currentTime = this.props.time
}
}
componentDidMount () {
this.video.addEventListener('loadeddata', this.handleLoadedData);
this.video.addEventListener('loadeddata', this.handleLoadedData)
}
componentWillUnmount () {
this.video.removeEventListener('loadeddata', this.handleLoadedData);
this.video.removeEventListener('loadeddata', this.handleLoadedData)
}
setRef = (c) => {
this.video = c;
this.video = c
}
handleClick = e => {
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
e.stopPropagation()
const handler = this.props.onClick
if (handler) handler()
}
render () {
const { src, muted, controls, alt } = this.props;
// .extended-video-player {
// @include size(100%);
// @include flex(center, center);
// video {
// @include max-size($media-modal-media-max-width, $media-modal-media-max-height);
// }
// }
const { src, muted, controls, alt } = this.props
return (
<div className='extended-video-player'>
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
<video
className={[_s.default, _s.maxWidth100PC, _s.heightMax100PC].join(' ')}
playsInline
ref={this.setRef}
src={src}
@@ -64,7 +55,7 @@ export default class ExtendedVideoPlayer extends PureComponent {
onClick={this.handleClick}
/>
</div>
);
)
}
}

View File

@@ -21,7 +21,7 @@ const messages = defineMessages({
const mapDispatchToProps = (dispatch, { intl }) => ({
toggleMembership(group, relationships) {
onToggleMembership(group, relationships) {
if (relationships.get('member')) {
dispatch(leaveGroup(group.get('id')));
} else {
@@ -116,6 +116,7 @@ class GroupHeader extends ImmutablePureComponent {
<Button
radiusSmall
className={_s.mr5}
onClick={this.handleOnToggleMembership}
{...actionButtonOptions}
>
<Text color='inherit' size='small'>

View File

@@ -24,6 +24,7 @@ export default class Input extends PureComponent {
inputRef: PropTypes.func,
id: PropTypes.string.isRequired,
hideLabel: PropTypes.bool,
maxLength: PropTypes.number,
}
handleOnChange = (e) => {
@@ -46,7 +47,8 @@ export default class Input extends PureComponent {
readOnly,
inputRef,
id,
hideLabel
hideLabel,
maxLength,
} = this.props
const inputClasses = cx({
@@ -87,7 +89,7 @@ export default class Input extends PureComponent {
</Text>
</div>
}
<div className={[_s.default, _s.bgPrimary, _s.border1PX, _s.borderColorSecondary, _s.flexRow, _s.circle, _s.alignItemsCenter].join(' ')}>
<div className={[_s.default, _s.flexGrow1, _s.bgPrimary, _s.border1PX, _s.borderColorSecondary, _s.flexRow, _s.circle, _s.alignItemsCenter].join(' ')}>
{
!!prependIcon &&
<Icon id={prependIcon} size='16px' className={[_s.fillPrimary, _s.ml15, _s.mr5].join(' ')} />
@@ -110,6 +112,7 @@ export default class Input extends PureComponent {
onFocus={onFocus}
onBlur={onBlur}
readOnly={readOnly}
maxLength={maxLength}
/>
{

View File

@@ -22,7 +22,7 @@ class LoadMore extends PureComponent {
}
handleClick = (e) => {
this.props.onClick()
this.props.onClick(e)
}
render() {

View File

@@ -309,10 +309,11 @@ class MediaGallery extends PureComponent {
} = this.props
const { visible } = this.state
const width = this.state.width || defaultWidth;
let width = this.state.width || defaultWidth
if (reduced) width = width / 2
const style = {};
const size = media.take(4).size;
const style = {}
const size = media.take(4).size
const standard169 = width / (16 / 9);
const standard169_percent = 100 / (16 / 9);
@@ -517,9 +518,9 @@ class MediaGallery extends PureComponent {
//If reduced (i.e. like in a quoted post)
//then we need to make media smaller
if (reduced) {
style.height = width / 2 || '50%'
}
// if (reduced) {
// style.height = width / 2 || '50%'
// }
if (!visible) {
style.height = 'auto'
@@ -573,7 +574,7 @@ class MediaGallery extends PureComponent {
<Button
title={intl.formatMessage(messages.toggle_visible)}
icon='hidden'
backgroundColor='none'
backgroundColor='black'
className={[_s.px10, _s.bgBlackOpaque_onHover].join(' ')}
onClick={this.handleOpen}
/>

View File

@@ -9,9 +9,9 @@ export default
class ConfirmationModal extends PureComponent {
static propTypes = {
title: PropTypes.node.isRequired,
message: PropTypes.node.isRequired,
confirm: PropTypes.string.isRequired,
title: PropTypes.any.isRequired,
message: PropTypes.any.isRequired,
confirm: PropTypes.any.isRequired,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
secondary: PropTypes.string,

View File

@@ -49,8 +49,9 @@ export const mapDispatchToProps = (dispatch, { onClose }) => ({
}
},
handleSelectResult: (resultId) => {
handleSelectResult: (result) => {
dispatch(setSelectedGif(result))
onClose()
},
// dispatchSubmit: (e) => {
@@ -70,11 +71,11 @@ class GifPickerModal extends PureComponent {
handleCloseModal: PropTypes.func.isRequired,
handleFetchCategories: PropTypes.func.isRequired,
handleOnChange: PropTypes.func.isRequired,
handleSelectResult: PropTypes.func.isRequired,
categories: PropTypes.array.isRequired,
results: PropTypes.array.isRequired,
loading: PropTypes.bool,
error: PropTypes.bool,
chosenUrl: PropTypes.string,
searchText: PropTypes.string,
}
@@ -98,8 +99,8 @@ class GifPickerModal extends PureComponent {
this.props.handleOnChange(category)
}
handleSelectGifResult = (resultId) => {
console.log("handleSelectGifResult:", resultId)
handleSelectGifResult = (resultBlock) => {
this.props.handleSelectResult(resultBlock)
}
render() {
@@ -109,7 +110,7 @@ class GifPickerModal extends PureComponent {
results,
loading,
error,
searchText
searchText,
} = this.props
return (
@@ -165,6 +166,7 @@ class GifPickerModal extends PureComponent {
}
class GifResultsCollectionColumn extends PureComponent {
static propTypes = {
results: PropTypes.array.isRequired,
handleSelectGifResult: PropTypes.func.isRequired,
@@ -183,8 +185,8 @@ class GifResultsCollectionColumn extends PureComponent {
results.map((result, i) => (
<button
key={`gif-result-item-${i}`}
onClick={() => this.onClick(result.id)}
className={[_s.default, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
onClick={() => this.onClick(result)}
className={[_s.default, _s.outlineNone, _s.bgTransparent, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
>
<Image
height={result.media[0].tinygif.dims[1]}
@@ -196,9 +198,11 @@ class GifResultsCollectionColumn extends PureComponent {
</div>
)
}
}
class GifResultsCollection extends PureComponent {
static propTypes = {
results: PropTypes.array.isRequired,
handleSelectGifResult: PropTypes.func.isRequired,
@@ -250,7 +254,7 @@ class GifCategoriesCollection extends PureComponent {
<button
key={`gif-category-${i}`}
onClick={() => this.onClick(category.searchterm)}
className={[_s.default, _s.px2, _s.py2, _s.width50PC].join(' ')}
className={[_s.default, _s.outlineNone, _s.bgTransparent, _s.px2, _s.py2, _s.width50PC].join(' ')}
>
<div className={[_s.default, _s.cursorPointer].join(' ')}>
<Image
@@ -269,4 +273,5 @@ class GifCategoriesCollection extends PureComponent {
</div>
)
}
}

View File

@@ -158,19 +158,23 @@ class MediaModal extends ImmutablePureComponent {
pagination = media.map((item, i) => {
const btnClasses = CX({
default: 1,
px5: 1,
py5: 1,
width10PX: 1,
height10PX: 1,
outlineNone: 1,
colorPrimary: 1,
circle: 1,
cursorPointer: 1,
colorPrimary: i === index,
lineHeight0825: i === index,
bgPrimaryOpaque: i !== index,
bgPrimary: i === index,
})
const activeText = i === index ? '•' : ''
return (
<li className={[_s.default, _s.px5].join(' ')} key={`media-pagination-${i}`}>
<button tabIndex='0' className={btnClasses} onClick={this.handleChangeIndex} data-index={i} />
<button tabIndex='0' className={btnClasses} onClick={this.handleChangeIndex} data-index={i}>
{activeText}
</button>
</li>
)
})
@@ -234,6 +238,9 @@ class MediaModal extends ImmutablePureComponent {
const swipeableViewsStyle = {
width: '100%',
height: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
}
const navigationClasses = CX({
@@ -244,7 +251,7 @@ class MediaModal extends ImmutablePureComponent {
return (
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
<div
className={[_s.default, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')}
className={[_s.default, _s.posAbs, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')}
role='presentation'
onClick={onClose}
>
@@ -252,6 +259,7 @@ class MediaModal extends ImmutablePureComponent {
style={swipeableViewsStyle}
containerStyle={{
alignItems: 'center',
width: '100%',
}}
onChangeIndex={this.handleSwipe}
onSwitching={this.handleSwitching}
@@ -286,7 +294,7 @@ class MediaModal extends ImmutablePureComponent {
</div>
<ul className={[_s.default, _s.posAbsolute, _s.bottom0, _s.mb15, _s.flexRow, _s.bgBlackOpaque, _s.circle, _s.py10, _s.px15, _s.listStyleNone].join(' ')}>
<ul className={[_s.default, _s.posAbs, _s.bottom0, _s.mb15, _s.flexRow, _s.bgBlackOpaque, _s.circle, _s.py10, _s.px15, _s.listStyleNone].join(' ')}>
{pagination}
</ul>
</div>

View File

@@ -135,7 +135,7 @@ class ModalRoot extends PureComponent {
}
renderLoading = () => {
return <ModalLoading />
return null
}
renderError = () => {

View File

@@ -45,12 +45,7 @@ class ProUpgradeModal extends ImmutablePureComponent {
</div>
<Button
backgroundColor='brand'
color='white'
icon='pro'
href='https://pro.gab.com'
className={_s.justifyContentCenter}
iconClassName={[_s.mr5, _s.fillWhite].join(' ')}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.title)}

View File

@@ -56,8 +56,8 @@ class ReportModal extends ImmutablePureComponent {
onClose: PropTypes.func.isRequired,
}
handleCommentChange = e => {
this.props.dispatch(changeReportComment(e.target.value))
handleCommentChange = (e) => {
this.props.dispatch(changeReportComment(value))
}
handleForwardChange = e => {

View File

@@ -75,9 +75,11 @@ class Poll extends ImmutablePureComponent {
const { selected } = this.state
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'))
const optionHasNoVotes = option.get('votes_count') === 0
const active = !!selected[`${optionIndex}`]
const showResults = poll.get('voted') || poll.get('expired')
const multiple = poll.get('multiple')
const correctedWidthPercent = optionHasNoVotes ? 100 : percent
let titleEmojified = option.get('title_emojified')
if (!titleEmojified) {
@@ -92,10 +94,12 @@ class Poll extends ImmutablePureComponent {
left0: 1,
radiusSmall: 1,
height100PC: 1,
bgSecondary: !leading,
bgSecondary: !leading && !optionHasNoVotes,
bgTertiary: !leading && optionHasNoVotes,
bgBrandLight: leading,
})
// : todo :
const inputClasses = cx('poll__input', {
'poll__input--checkbox': multiple,
'poll__input--active': active,
@@ -107,7 +111,7 @@ class Poll extends ImmutablePureComponent {
py10: showResults,
mb10: 1,
border1PX: !showResults,
fillSecondary: !showResults,
borderColorSecondary: !showResults,
circle: !showResults,
cursorPointer: !showResults,
bgSubtle_onHover: !showResults,
@@ -127,7 +131,7 @@ class Poll extends ImmutablePureComponent {
<li className={listItemClasses} key={option.get('title')}>
{
showResults && (
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(correctedWidthPercent, { stiffness: 180, damping: 24 }) }}>
{({ width }) =>
<span className={chartClasses} style={{ width: `${width}%` }} />
}
@@ -139,7 +143,7 @@ class Poll extends ImmutablePureComponent {
<Text
size='medium'
color='primary'
weight={leading ? 'bold' : 'normal'}
weight={(leading && showResults) ? 'bold' : 'normal'}
className={[_s.displayFlex, _s.flexRow, _s.width100PC, _s.alignItemsCenter].join(' ')}
>
{

View File

@@ -1,8 +1,17 @@
import DatePicker from 'react-datepicker'
import { FormattedMessage } from 'react-intl'
import moment from 'moment-mini'
import { changeScheduledAt } from '../../actions/compose'
import { openModal } from '../../actions/modal'
import { closePopover } from '../../actions/popover'
import { me } from '../../initial_state'
import {
MODAL_PRO_UPGRADE,
} from '../../constants'
import { isMobile } from '../../utils/is_mobile'
import PopoverLayout from './popover_layout'
import Button from '../button'
import Text from '../text'
import '!style-loader!css-loader!react-datepicker/dist/react-datepicker.css'
@@ -11,9 +20,18 @@ const mapStateToProps = (state) => ({
isPro: state.getIn(['accounts', me, 'is_pro']),
})
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = (dispatch, { isPro }) => ({
setScheduledAt (date) {
if (!isPro) {
dispatch(closePopover())
return dispatch(openModal(MODAL_PRO_UPGRADE))
}
dispatch(changeScheduledAt(date))
if (!date) {
dispatch(closePopover())
}
},
})
@@ -31,42 +49,70 @@ class DatePickerPopover extends PureComponent {
handleSetDate = (date) => {
this.props.setScheduledAt(date)
}
handleRemoveDate = () => {
this.props.setScheduledAt(null)
}
render() {
const { date, isPro, position } = this.props
const { date, isPro } = this.props
const open = !!date
const datePickerDisabled = !isPro
const withPortal = isMobile(window.innerWidth)
return (
<PopoverLayout width={331}>
<DatePicker
inline
target={this}
className='schedule-post-dropdown__datepicker'
minDate={new Date()}
selected={date}
onChange={date => this.handleSetDate(date)}
timeFormat='p'
timeIntervals={15}
timeCaption='Time'
dateFormat='MMM d, yyyy h:mm aa'
disabled={datePickerDisabled}
showTimeSelect
withPortal={withPortal}
popperModifiers={{
offset: {
enabled: true,
offset: '0px, 5px'
},
preventOverflow: {
enabled: true,
escapeWithReference: false,
boundariesElement: 'viewport'
}
}}
/>
<div className={[_s.default].join(' ')}>
<DatePicker
inline
target={this}
className='schedule-post-dropdown__datepicker'
minDate={new Date()}
selected={date}
onChange={date => this.handleSetDate(date)}
timeFormat='p'
timeIntervals={15}
timeCaption='Time'
dateFormat='MMM d, yyyy h:mm aa'
disabled={datePickerDisabled}
showTimeSelect
withPortal={withPortal}
popperModifiers={{
offset: {
enabled: true,
offset: '0px, 5px'
},
preventOverflow: {
enabled: true,
escapeWithReference: false,
boundariesElement: 'viewport'
}
}}
/>
</div>
{
date &&
<div className={[_s.default, _s.alignItemsCenter, _s.flexRow, _s.px10, _s.py10, _s.borderTop1PX, _s.borderColorSecondary].join(' ')}>
<Text size='extraSmall' color='secondary'>
<FormattedMessage id='scheduled_for_datetime' defaultMessage='Scheduled for {datetime}' values={{
datetime: moment.utc(date).format('lll'),
}}/>
</Text>
<div className={_s.mlAuto}>
<Button
isNarrow
radiusSmall
color='primary'
backgroundColor='tertiary'
onClick={this.handleRemoveDate}
>
<Text color='inherit' size='small'>
<FormattedMessage id='remove' defaultMessage='Remove' />
</Text>
</Button>
</div>
</div>
}
</PopoverLayout>
)
}

View File

@@ -7,6 +7,7 @@ import detectPassiveEvents from 'detect-passive-events'
import { changeSetting } from '../../actions/settings'
import { useEmoji } from '../../actions/emojis'
import { closePopover } from '../../actions/popover'
import { insertEmojiCompose } from '../../actions/compose'
import { EmojiPicker as EmojiPickerAsync } from '../../features/ui/util/async_components'
import { buildCustomEmojis } from '../emoji/emoji'
import PopoverLayout from './popover_layout'
@@ -209,21 +210,19 @@ const mapStateToProps = (state) => ({
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
})
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
const mapDispatchToProps = (dispatch) => ({
onClosePopover() {
dispatch(closePopover())
},
onSkinTone: skinTone => {
onSkinTone: (skinTone) => {
dispatch(changeSetting(['skinTone'], skinTone))
},
onPickEmoji: emoji => {
onPickEmoji: (emoji) => {
dispatch(useEmoji(emoji))
if (onPickEmoji) {
onPickEmoji(emoji)
}
console.log("emoji:", emoji)
dispatch(insertEmojiCompose(0, emoji, false))
},
})
@@ -274,7 +273,7 @@ class EmojiPickerPopover extends ImmutablePureComponent {
} = this.props
const { loading } = this.state
return (
<PopoverLayout width={340}>
<EmojiPickerMenu

View File

@@ -57,12 +57,6 @@ class PopoverRoot extends PureComponent {
props: PropTypes.object,
}
getSnapshotBeforeUpdate() {
return {
visible: !!this.props.type
}
}
renderEmpty = () => {
return <div />
}

View File

@@ -197,7 +197,7 @@ export default class ScrollableList extends PureComponent {
}
handleLoadMore = (e) => {
e.preventDefault();
e.preventDefault()
this.props.onLoadMore();
}

View File

@@ -67,6 +67,10 @@ export default
@injectIntl
class Sidebar extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
intl: PropTypes.object.isRequired,
account: ImmutablePropTypes.map,
@@ -94,6 +98,18 @@ class Sidebar extends ImmutablePureComponent {
})
}
historyBack = () => {
if (window.history && window.history.length === 1) {
this.context.router.history.push('/home')
} else {
this.context.router.history.goBack()
}
}
handleBackClick = () => {
this.historyBack()
}
setMoreButtonRef = n => {
this.moreBtnRef = n
}
@@ -213,7 +229,17 @@ class Sidebar extends ImmutablePureComponent {
<div className={_s.default}>
{
!!title &&
<div className={[_s.default, _s.px5, _s.py10].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.px5, _s.py10].join(' ')}>
<Button
noClasses
color='primary'
backgroundColor='none'
className={[_s.alignItemsCenter, _s.bgTransparent, _s.mr5, _s.cursorPointer, _s.outlineNone, _s.default, _s.justifyContentCenter].join(' ')}
icon='back'
iconSize='20px'
iconClassName={[_s.mr5, _s.fillPrimary].join(' ')}
onClick={this.handleBackClick}
/>
<Heading size='h1'>
{title}
</Heading>

View File

@@ -10,6 +10,7 @@ import ComposeFormContainer from '../features/compose/containers/compose_form_co
import StatusContent from './status_content'
import StatusPrepend from './status_prepend'
import StatusActionBar from './status_action_bar'
import StatusMedia from './status_media'
import Poll from './poll'
import StatusHeader from './status_header'
import CommentList from './comment_list'
@@ -87,6 +88,7 @@ class Status extends ImmutablePureComponent {
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
contextType: PropTypes.string,
commentsLimited: PropTypes.bool,
}
// Avoid checking props that are functions (and whose equality will always
@@ -124,7 +126,7 @@ class Status extends ImmutablePureComponent {
}
static getDerivedStateFromProps(nextProps, prevState) {
if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.loadedComments) {
if (!nextProps.isHidden && (nextProps.isIntersecting || !nextProps.commentsLimited) && !prevState.loadedComments) {
return {
loadedComments: true
}
@@ -132,6 +134,7 @@ class Status extends ImmutablePureComponent {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return {
loadedComments: false,
showMedia: defaultMediaVisibility(nextProps.status),
statusId: nextProps.status.get('id'),
}
@@ -142,6 +145,7 @@ class Status extends ImmutablePureComponent {
// Compensate height changes
componentDidUpdate(prevProps, prevState, snapshot) {
// timeline lazy loading comments
if (!prevState.loadedComments && this.state.loadedComments && this.props.status) {
const commentCount = this.props.status.get('replies_count')
if (commentCount > 0) {
@@ -163,7 +167,7 @@ class Status extends ImmutablePureComponent {
}
}
handleMoveUp = id => {
handleMoveUp = (id) => {
const { status, ancestorsIds, descendantsIds } = this.props
if (id === status.get('id')) {
@@ -347,7 +351,7 @@ class Status extends ImmutablePureComponent {
if (!status) return null
let media, reblogContent, rebloggedByText = null
let reblogContent, rebloggedByText = null
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
rebloggedByText = intl.formatMessage(
@@ -386,62 +390,6 @@ class Status extends ImmutablePureComponent {
return null
}
if (status.get('poll')) {
media = <Poll pollId={status.get('poll')} />
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0])
media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingMedia}>
{Component => (
<Component
inline
preview={video.get('preview_url')}
blurhash={video.get('blurhash')}
src={video.get('url')}
alt={video.get('description')}
aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
width={this.props.cachedMediaWidth}
height={110}
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
cacheWidth={this.props.cacheMediaWidth}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/>
)}
</Bundle>
)
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMedia}>
{Component => (
<Component
reduced={isChild}
media={status.get('media_attachments')}
sensitive={status.get('sensitive')}
onOpenMedia={this.props.onOpenMedia}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/>
)}
</Bundle>
)
}
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
media = (
<StatusCard
onOpenMedia={this.props.onOpenMedia}
card={status.get('card')}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
/>
)
}
const containerClasses = cx({
default: 1,
pb15: isFeatured,
@@ -476,7 +424,7 @@ class Status extends ImmutablePureComponent {
data-featured={isFeatured ? 'true' : null}
aria-label={textForScreenReader(intl, status, rebloggedByText)}
ref={this.handleRef}
// onClick={this.handleClick}
onClick={isChild ? this.handleClick : undefined}
>
<div className={innerContainerClasses}>
@@ -497,7 +445,17 @@ class Status extends ImmutablePureComponent {
/>
</div>
{media}
<StatusMedia
isChild={isChild}
status={status}
onOpenMedia={this.props.onOpenMedia}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
width={this.props.cachedMediaWidth}
onOpenVideo={this.handleOpenVideo}
/>
{
!!status.get('quote') && !isChild &&

View File

@@ -42,7 +42,7 @@ class StatusActionBar extends ImmutablePureComponent {
updateOnProps = ['status']
handleReplyClick = () => {
this.props.onReply(this.props.status)
this.props.onReply(this.props.status, null, true)
}
handleFavoriteClick = () => {

View File

@@ -10,8 +10,8 @@ import Text from './text'
const MAX_HEIGHT = 200
const messages = defineMessages({
showMore: { id: 'status.show_more', defaultMessage: 'Show more' },
showLess: { id: 'status.show_less', defaultMessage: 'Show less' },
show: { id: 'status.show_more', defaultMessage: 'Show' },
hide: { id: 'status.show_less', defaultMessage: 'Hide' },
readMore: { id: 'status.read_more', defaultMessage: 'Read more' },
})
@@ -220,7 +220,7 @@ class StatusContent extends ImmutablePureComponent {
)
}
const toggleText = intl.formatMessage(hidden ? messages.showMore : messages.showLess)
const toggleText = intl.formatMessage(hidden ? messages.show : messages.hide)
const spoilerContainerClasses = cx({
default: 1,

View File

@@ -1,4 +1,5 @@
import { Fragment } from 'react'
import { injectIntl, defineMessages } from 'react-intl'
import { NavLink } from 'react-router-dom'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
@@ -14,6 +15,14 @@ import Icon from './icon'
import Button from './button'
import Avatar from './avatar'
const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for anyone on or off Gab' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for your followers only' },
})
const cx = classNames.bind(_s)
const mapDispatchToProps = (dispatch) => ({
@@ -33,10 +42,12 @@ const mapDispatchToProps = (dispatch) => ({
})
export default
@injectIntl
@connect(null, mapDispatchToProps)
class StatusHeader extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
status: ImmutablePropTypes.map,
onOpenStatusRevisionsPopover: PropTypes.func.isRequired,
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
@@ -56,7 +67,11 @@ class StatusHeader extends ImmutablePureComponent {
}
render() {
const { status, reduced } = this.props
const {
intl,
reduced,
status,
} = this.props
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`
@@ -68,7 +83,22 @@ class StatusHeader extends ImmutablePureComponent {
})
const avatarSize = reduced ? 20 : 46
const visibilityIcon = 'globe'
const visibility = status.get('visibility')
let visibilityIcon
let visibilityText
if (visibility === 'private') {
visibilityIcon = 'lock-filled'
visibilityText = intl.formatMessage(messages.private_long)
} else if (visibility === 'unlisted') {
visibilityIcon = 'unlock-filled'
visibilityText = `${intl.formatMessage(messages.unlisted_short)} - ${intl.formatMessage(messages.unlisted_long)}`
} else {
visibilityIcon = 'globe'
visibilityText = `${intl.formatMessage(messages.public_short)} - ${intl.formatMessage(messages.public_long)}`
}
return (
<div className={containerClasses}>
@@ -126,8 +156,10 @@ class StatusHeader extends ImmutablePureComponent {
</Button>
<DotTextSeperator />
<Icon id={visibilityIcon} size='12px' className={[_s.default, _s.displayInline, _s.ml5, _s.fillSecondary].join(' ')} />
<span title={visibilityText} className={[_s.default, _s.displayInline, _s.ml5].join(' ')}>
<Icon id={visibilityIcon} size='12px' className={[_s.default, _s.fillSecondary].join(' ')} />
</span>
{
!!status.get('group') &&

View File

@@ -0,0 +1,120 @@
import { injectIntl } from 'react-intl'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import StatusCard from './status_card'
import { MediaGallery, Video } from '../features/ui/util/async_components'
import Poll from './poll'
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
import Bundle from '../features/ui/util/bundle'
export default class StatusMedia extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
isChild: PropTypes.bool,
isComment: PropTypes.bool,
onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func,
width: PropTypes.number,
onToggleVisibility: PropTypes.func,
visible: PropTypes.bool,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.number,
}
// Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
'status',
'isChild',
'isComment',
'cacheWidth',
'defaultWidth',
'visible',
'width',
]
renderLoadingMedia() {
return <div className={_s.backgroundColorPanel} style={{ height: '110px' }} />
}
render() {
const {
status,
isChild,
isComment,
onOpenMedia,
onOpenVideo,
width,
onToggleVisibility,
visible,
defaultWidth,
cacheWidth,
} = this.props
if (!status) return null
let media = null
if (status.get('poll')) {
media = <Poll pollId={status.get('poll')} />
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0])
media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingMedia}>
{Component => (
<Component
inline
preview={video.get('preview_url')}
blurhash={video.get('blurhash')}
src={video.get('url')}
alt={video.get('description')}
aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
sensitive={status.get('sensitive')}
height={110}
width={width}
onOpenVideo={onOpenVideo}
cacheWidth={cacheWidth}
visible={visible}
onToggleVisibility={onToggleVisibility}
/>
)}
</Bundle>
)
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMedia}>
{Component => (
<Component
reduced={isChild}
media={status.get('media_attachments')}
sensitive={status.get('sensitive')}
onOpenMedia={onOpenMedia}
cacheWidth={cacheWidth}
defaultWidth={defaultWidth}
visible={visible}
onToggleVisibility={onToggleVisibility}
/>
)}
</Bundle>
)
}
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
media = (
<StatusCard
card={status.get('card')}
onOpenMedia={onOpenMedia}
cacheWidth={cacheWidth}
defaultWidth={defaultWidth}
/>
)
}
return media
}
}

View File

@@ -1,30 +1,18 @@
const MIN_SCALE = 1;
const MAX_SCALE = 4;
const MIN_SCALE = 1
const MAX_SCALE = 4
const getMidpoint = (p1, p2) => ({
x: (p1.clientX + p2.clientX) / 2,
y: (p1.clientY + p2.clientY) / 2,
});
})
const getDistance = (p1, p2) =>
Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
const getDistance = (p1, p2) => {
return Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2))
}
const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
// .zoomable-image {
// position: relative;
// @include flex(center, center);
// @include size(100%);
// img {
// object-fit: contain;
// @include size(auto);
// @include max-size($media-modal-media-max-width, $media-modal-media-max-height);
// }
// }
// : todo :
const clamp = (min, max, value) => {
return Math.min(max, Math.max(min, value))
}
export default class ZoomableImage extends PureComponent {
@@ -40,69 +28,69 @@ export default class ZoomableImage extends PureComponent {
alt: '',
width: null,
height: null,
};
}
state = {
scale: MIN_SCALE,
}
removers = [];
container = null;
image = null;
lastTouchEndTime = 0;
lastDistance = 0;
removers = []
container = null
image = null
lastTouchEndTime = 0
lastDistance = 0
componentDidMount () {
let handler = this.handleTouchStart;
this.container.addEventListener('touchstart', handler);
this.removers.push(() => this.container.removeEventListener('touchstart', handler));
handler = this.handleTouchMove;
let handler = this.handleTouchStart
this.container.addEventListener('touchstart', handler)
this.removers.push(() => this.container.removeEventListener('touchstart', handler))
handler = this.handleTouchMove
// on Chrome 56+, touch event listeners will default to passive
// https://www.chromestatus.com/features/5093566007214080
this.container.addEventListener('touchmove', handler, { passive: false });
this.removers.push(() => this.container.removeEventListener('touchend', handler));
this.container.addEventListener('touchmove', handler, { passive: false })
this.removers.push(() => this.container.removeEventListener('touchend', handler))
}
componentWillUnmount () {
this.removeEventListeners();
this.removeEventListeners()
}
removeEventListeners () {
this.removers.forEach(listeners => listeners());
this.removers = [];
this.removers.forEach(listeners => listeners())
this.removers = []
}
handleTouchStart = e => {
if (e.touches.length !== 2) return;
if (e.touches.length !== 2) return
this.lastDistance = getDistance(...e.touches);
this.lastDistance = getDistance(...e.touches)
}
handleTouchMove = e => {
const { scrollTop, scrollHeight, clientHeight } = this.container;
const { scrollTop, scrollHeight, clientHeight } = this.container
if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
// prevent propagating event to MediaModal
e.stopPropagation();
return;
e.stopPropagation()
return
}
if (e.touches.length !== 2) return;
if (e.touches.length !== 2) return
e.preventDefault();
e.stopPropagation();
e.preventDefault()
e.stopPropagation()
const distance = getDistance(...e.touches);
const midpoint = getMidpoint(...e.touches);
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
const distance = getDistance(...e.touches)
const midpoint = getMidpoint(...e.touches)
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance)
this.zoom(scale, midpoint);
this.zoom(scale, midpoint)
this.lastMidpoint = midpoint;
this.lastDistance = distance;
this.lastMidpoint = midpoint
this.lastDistance = distance
}
zoom(nextScale, midpoint) {
const { scale } = this.state;
const { scrollLeft, scrollTop } = this.container;
const { scale } = this.state
const { scrollLeft, scrollTop } = this.container
// math memo:
// x = (scrollLeft + midpoint.x) / scrollWidth
@@ -110,38 +98,44 @@ export default class ZoomableImage extends PureComponent {
// scrollWidth = clientWidth * scale
// scrollWidth' = clientWidth * nextScale
// Solve x = x' for nextScrollLeft
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y
this.setState({ scale: nextScale }, () => {
this.container.scrollLeft = nextScrollLeft;
this.container.scrollTop = nextScrollTop;
});
this.container.scrollLeft = nextScrollLeft
this.container.scrollTop = nextScrollTop
})
}
handleClick = e => {
// don't propagate event to MediaModal
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
e.stopPropagation()
const handler = this.props.onClick
if (handler) handler()
}
setContainerRef = c => {
this.container = c;
this.container = c
}
setImageRef = c => {
this.image = c;
this.image = c
}
render () {
const { alt, src } = this.props;
const { scale } = this.state;
const overflow = scale === 1 ? 'hidden' : 'scroll';
const { alt, src } = this.props
const { scale } = this.state
const overflow = scale === 1 ? 'hidden' : 'scroll'
return (
<div className='zoomable-image' ref={this.setContainerRef} style={{ overflow }}>
<div
className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}
ref={this.setContainerRef}
style={{ overflow }}
>
<img
className={[_s.default, _s.objectFitContain, _s.heightAuto, _s.widthAuto, _s.maxWidth100PC, _s.heightMax100PC].join(' ')}
role='presentation'
ref={this.setImageRef}
alt={alt}
@@ -154,7 +148,7 @@ export default class ZoomableImage extends PureComponent {
onClick={this.handleClick}
/>
</div>
);
)
}
}