gab-social/app/javascript/gabsocial/components/profile_header.js

395 lines
14 KiB
JavaScript
Raw Normal View History

2020-03-14 17:31:29 +00:00
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
2020-04-23 07:13:29 +01:00
import { defineMessages, injectIntl } from 'react-intl'
2020-04-17 06:35:46 +01:00
import Sticky from 'react-stickynode'
2020-04-24 04:17:27 +01:00
import {
CX,
POPOVER_PROFILE_OPTIONS,
PLACEHOLDER_MISSING_HEADER_SRC,
2020-04-28 06:33:58 +01:00
MODAL_EDIT_PROFILE,
2020-05-09 03:17:19 +01:00
BREAKPOINT_EXTRA_SMALL,
2020-04-24 04:17:27 +01:00
} from '../constants'
2020-04-28 06:33:58 +01:00
import { openModal } from '../actions/modal'
2020-04-23 07:13:29 +01:00
import { openPopover } from '../actions/popover'
import { me } from '../initial_state'
import AccountActionButton from './account_action_button'
2020-03-14 17:31:29 +00:00
import Avatar from './avatar'
import Button from './button'
import DisplayName from './display_name'
2020-04-07 02:53:23 +01:00
import Image from './image'
2020-04-11 23:29:19 +01:00
import MovedNote from './moved_note'
2020-03-14 17:31:29 +00:00
import TabBar from './tab_bar'
2020-04-07 02:53:23 +01:00
import Text from './text'
2020-05-09 03:17:19 +01:00
import Responsive from '../features/ui/util/responsive_component';
import ProfileHeaderXSPlaceholder from './placeholder/profile_header_xs_placeholder'
2020-03-14 17:31:29 +00:00
const messages = defineMessages({
followers: { id: 'account.followers', defaultMessage: 'Followers' },
follows: { id: 'account.follows', defaultMessage: 'Following' },
2020-03-14 17:31:29 +00:00
profile: { id: 'account.profile', defaultMessage: 'Profile' },
2020-04-23 07:13:29 +01:00
headerPhoto: { id: 'header_photo', defaultMessage: 'Header photo' },
2020-04-24 04:17:27 +01:00
timeline: { id: 'timeline', defaultMessage: 'Timeline' },
comments: { id: 'comments', defaultMessage: 'Comments' },
photos: { id: 'photos', defaultMessage: 'Photos' },
videos: { id: 'videos', defaultMessage: 'Videos' },
bookmarks: { id: 'bookmarks', defaultMessage: 'Bookmarks' },
2020-04-24 04:17:27 +01:00
accountFollowsYou: { id: 'account.follows_you', defaultMessage: 'Follows you' },
2020-04-28 06:33:58 +01:00
editProfile: { id: "account.edit_profile", defaultMessage: "Edit profile" },
2020-03-14 17:31:29 +00:00
})
2020-04-23 07:13:29 +01:00
const mapDispatchToProps = (dispatch) => ({
2020-03-14 17:31:29 +00:00
openProfileOptionsPopover(props) {
2020-04-24 04:17:27 +01:00
dispatch(openPopover(POPOVER_PROFILE_OPTIONS, props))
2020-03-14 17:31:29 +00:00
},
2020-04-28 06:33:58 +01:00
onEditProfile() {
dispatch(openModal(MODAL_EDIT_PROFILE))
},
2020-03-14 17:31:29 +00:00
});
export default
2020-04-23 07:13:29 +01:00
@connect(null, mapDispatchToProps)
2020-03-14 17:31:29 +00:00
@injectIntl
class ProfileHeader extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
2020-05-09 03:17:19 +01:00
children: PropTypes.any,
2020-03-14 17:31:29 +00:00
intl: PropTypes.object.isRequired,
2020-04-28 06:33:58 +01:00
onEditProfile: PropTypes.func.isRequired,
2020-03-14 17:31:29 +00:00
openProfileOptionsPopover: PropTypes.func.isRequired,
isXS: PropTypes.bool,
2020-03-14 17:31:29 +00:00
}
2020-04-17 06:35:46 +01:00
state = {
stickied: false,
}
2020-04-28 06:33:58 +01:00
handleOnEditProfile = () => {
this.props.onEditProfile()
}
2020-03-14 17:31:29 +00:00
handleOpenMore = () => {
const { openProfileOptionsPopover, account } = this.props
2020-04-24 04:17:27 +01:00
2020-03-14 17:31:29 +00:00
openProfileOptionsPopover({
2020-04-24 04:17:27 +01:00
account,
2020-03-14 17:31:29 +00:00
targetRef: this.openMoreNode,
position: 'left',
2020-03-14 17:31:29 +00:00
})
}
2020-04-17 06:35:46 +01:00
onStickyStateChange = (status) => {
2020-04-24 04:17:27 +01:00
if (status.status === Sticky.STATUS_FIXED) {
this.setState({ stickied: true })
} else {
this.setState({ stickied: false })
2020-04-17 06:35:46 +01:00
}
}
2020-03-14 17:31:29 +00:00
setOpenMoreNodeRef = (n) => {
this.openMoreNode = n
}
render() {
2020-05-09 03:17:19 +01:00
const {
account,
children,
intl,
isXS
2020-05-09 03:17:19 +01:00
} = this.props
2020-04-17 06:35:46 +01:00
const { stickied } = this.state
2020-03-14 17:31:29 +00:00
if (isXS && !account) {
return (
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.z1, _s.width100PC, _s.boxShadowBlock, _s.bgPrimary].join(' ')}>
<div className={[_s.default, _s.width100PC].join(' ')}>
<ProfileHeaderXSPlaceholder />
</div>
</div>
</div>
)
}
2020-03-14 17:31:29 +00:00
const tabs = !account ? null : [
{
to: `/${account.get('acct')}`,
2020-04-24 04:17:27 +01:00
title: intl.formatMessage(messages.timeline),
2020-03-14 17:31:29 +00:00
},
{
to: `/${account.get('acct')}/comments`,
2020-04-24 04:17:27 +01:00
title: intl.formatMessage(messages.comments),
2020-03-14 17:31:29 +00:00
},
{
to: `/${account.get('acct')}/photos`,
title: intl.formatMessage(messages.photos),
},
{
to: `/${account.get('acct')}/videos`,
title: intl.formatMessage(messages.videos),
2020-03-14 17:31:29 +00:00
},
]
const isMyProfile = !account ? false : account.get('id') === me
if (isMyProfile) {
tabs.push({
to: `/${account.get('acct')}/bookmarks`,
title: intl.formatMessage(messages.bookmarks),
})
}
const headerSrc = !!account ? account.get('header') : undefined
2020-04-24 04:17:27 +01:00
const headerMissing = headerSrc.indexOf(PLACEHOLDER_MISSING_HEADER_SRC) > -1 || !headerSrc
2020-04-23 07:13:29 +01:00
const avatarSize = headerMissing ? 75 : 150
2020-05-09 03:17:19 +01:00
const top = headerMissing ? -46 : -380
2020-04-08 02:06:59 +01:00
2020-04-24 04:17:27 +01:00
const avatarContainerClasses = CX({
default: 1,
2020-04-17 06:35:46 +01:00
circle: 1,
2020-04-23 07:13:29 +01:00
mtNeg75PX: !headerMissing,
2020-04-24 04:17:27 +01:00
boxShadowProfileAvatar: !headerMissing,
2020-04-17 06:35:46 +01:00
})
2020-04-24 04:17:27 +01:00
const stickyBarContainerClasses = CX({
2020-04-17 06:35:46 +01:00
default: 1,
flexRow: 1,
px15: 1,
alignItemsCenter: 1,
displayNone: !stickied,
})
2020-04-24 04:17:27 +01:00
const tabBarContainerClasses = CX({
2020-04-17 06:35:46 +01:00
default: 1,
displayNone: stickied,
})
2020-05-09 03:17:19 +01:00
const mobileAvatarContainerClasses = CX({
default: 1,
circle: 1,
boxShadowProfileAvatar: 1,
mtNeg50PX: !headerMissing,
})
const mobileDescriptionContainerClasses = CX({
default: 1,
width100PC: 1,
px15: 1,
mt5: !!me,
mb10: 1,
pt15: !!me,
pb10: 1,
})
2020-03-14 17:31:29 +00:00
return (
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
2020-05-09 03:17:19 +01:00
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
2020-04-29 23:32:49 +01:00
<div className={[_s.default, _s.z1, _s.width100PC, _s.alignItemsCenter, _s.boxShadowBlock, _s.bgPrimary].join(' ')}>
2020-03-24 04:39:12 +00:00
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.width100PC].join(' ')}>
2020-04-17 06:35:46 +01:00
{
2020-04-29 23:32:49 +01:00
!headerMissing &&
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.height200PX, _s.px10, _s.width100PC, _s.mt10, _s.overflowHidden].join(' ')}>
2020-04-29 23:32:49 +01:00
<Image
alt={intl.formatMessage(messages.headerPhoto)}
2020-05-09 03:17:19 +01:00
className={[_s.topRightRadiusSmall, _s.topLeftRadiusSmall, _s.height100PC].join(' ')}
2020-04-29 23:32:49 +01:00
src={headerSrc}
/>
2020-04-17 06:35:46 +01:00
</div>
}
2020-05-05 06:16:01 +01:00
{
headerMissing &&
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.height20PX, _s.width100PC].join(' ')} />
2020-05-05 06:16:01 +01:00
}
2020-03-24 04:39:12 +00:00
2020-04-29 23:32:49 +01:00
<div className={[_s.default, _s.width100PC].join(' ')}>
2020-04-17 06:35:46 +01:00
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.alignItemsCenter, _s.px15, _s.mb5].join(' ')}>
<div className={mobileAvatarContainerClasses}>
<Avatar size={100} account={account} noHover />
2020-04-29 23:32:49 +01:00
</div>
2020-04-17 06:35:46 +01:00
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.flexRow, _s.flexNormal, _s.py10].join(' ')}>
2020-05-10 04:26:58 +01:00
<DisplayName
account={account}
isMultiline
isLarge
isCentered
noHover
/>
2020-04-29 23:32:49 +01:00
</div>
2020-04-17 06:35:46 +01:00
</div>
2020-04-29 23:32:49 +01:00
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.bgPrimary, _s.alignItemsCenter].join(' ')}>
2020-04-29 23:32:49 +01:00
{
account && account.get('id') === me &&
2020-05-09 03:17:19 +01:00
<div className={[_s.default,_s.py5].join(' ')}>
2020-04-29 23:32:49 +01:00
<Button
isOutline
backgroundColor='none'
color='brand'
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
onClick={this.handleOnEditProfile}
2020-04-29 23:32:49 +01:00
>
<Text color='inherit' weight='bold' size='medium' className={_s.px15}>
{intl.formatMessage(messages.editProfile)}
</Text>
</Button>
</div>
}
{
account && account.get('id') !== me && !!me &&
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.flexRow, _s.py5].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.mr10].join(' ')}>
<AccountActionButton account={account} />
</div>
2020-04-29 23:32:49 +01:00
<div>
2020-04-29 23:32:49 +01:00
<Button
isOutline
icon='ellipsis'
2020-04-29 23:32:49 +01:00
iconSize='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.px10].join(' ')}
onClick={this.handleOpenMore}
buttonRef={this.setOpenMoreNodeRef}
2020-04-29 23:32:49 +01:00
/>
2020-05-09 03:17:19 +01:00
</div>
2020-04-29 23:32:49 +01:00
</div>
}
2020-05-09 03:17:19 +01:00
<div className={mobileDescriptionContainerClasses}>
2020-05-09 03:17:19 +01:00
{children}
</div>
<div className={[_s.default, _s.mt10].join(' ')}>
<TabBar tabs={tabs} isLarge />
</div>
2020-04-29 23:32:49 +01:00
</div>
</div>
2020-04-17 06:35:46 +01:00
</div>
2020-04-29 23:32:49 +01:00
</div>
2020-05-09 03:17:19 +01:00
</Responsive>
{ /** desktop */ }
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<Sticky top={top} enabled onStateChange={this.onStickyStateChange}>
<div className={[_s.default, _s.z1, _s.width100PC, _s.alignItemsCenter, _s.boxShadowBlock, _s.bgPrimary].join(' ')}>
<div className={[_s.default, _s.width1015PX].join(' ')}>
{
!headerMissing &&
<div className={[_s.default, _s.height350PX, _s.width100PC, _s.bottomRightRadiusSmall, _s.bottomLeftRadiusSmall, _s.overflowHidden].join(' ')}>
<Image
alt={intl.formatMessage(messages.headerPhoto)}
className={_s.height100PC}
src={headerSrc}
/>
</div>
}
{
headerMissing &&
<div className={[_s.default, _s.height20PX, _s.width100PC].join(' ')} />
}
<div className={[_s.default, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.pr15, _s.pl25, _s.mb5].join(' ')}>
<div className={avatarContainerClasses}>
{
account &&
<Avatar size={avatarSize} account={account} noHover />
}
{
!account &&
<div
className={[_s.default, _s.circle, _s.overflowHidden, _s.bgSecondary].join(' ')}
style={{
width: `${avatarSize}px`,
height: `${avatarSize}px`,
}}
/>
}
2020-05-09 03:17:19 +01:00
</div>
<div className={[_s.default, _s.flexRow, _s.px15, _s.flexNormal, _s.py10].join(' ')}>
2020-05-10 04:26:58 +01:00
<DisplayName account={account} isMultiline isLarge noHover />
2020-05-09 03:17:19 +01:00
</div>
</div>
<div className={[_s.default, _s.flexRow, _s.bgPrimary, _s.height53PX].join(' ')}>
<div className={tabBarContainerClasses}>
<TabBar tabs={tabs} isLarge />
</div>
<div className={stickyBarContainerClasses}>
<Avatar size={36} account={account} noHover />
<div className={[_s.default, _s.ml10].join(' ')}>
<DisplayName account={account} noUsername noRelationship noHover isLarge />
</div>
</div>
{
account && account.get('id') === me &&
<div className={[_s.default, _s.flexRow, _s.mlAuto, _s.py5].join(' ')}>
<Button
isOutline
backgroundColor='none'
color='brand'
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
onClick={this.handleOnEditProfile}
2020-05-09 03:17:19 +01:00
>
<Text color='inherit' weight='bold' size='medium' className={_s.px15}>
{intl.formatMessage(messages.editProfile)}
</Text>
</Button>
</div>
}
{
account && account.get('id') !== me &&
<div className={[_s.default, _s.flexRow, _s.mlAuto, _s.py5].join(' ')}>
2020-05-14 21:45:39 +01:00
{
!!me &&
<div>
<Button
isOutline
icon='ellipsis'
iconSize='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')}
onClick={this.handleOpenMore}
buttonRef={this.setOpenMoreNodeRef}
/>
</div>
}
2020-05-09 03:17:19 +01:00
<div className={[_s.default, _s.flexRow, _s.pb3].join(' ')}>
<AccountActionButton account={account} />
</div>
</div>
}
</div>
</div>
</div>
</div>
</Sticky>
</Responsive>
2020-03-14 17:31:29 +00:00
</div>
)
}
}