Progress on dms, code cleanup

Progress on dms, code cleanup
This commit is contained in:
mgabdev
2020-12-02 23:22:51 -05:00
parent 20d4fc09af
commit 9a43c51085
103 changed files with 3656 additions and 859 deletions

View File

@@ -24,10 +24,13 @@ import Avatar from './avatar'
import DisplayName from './display_name'
import Button from './button'
import Text from './text'
class Account extends ImmutablePureComponent {
handleAction = (e) => {
this.props.onActionClick(this.props.account, e)
e.preventDefault()
return false
}
handleUnrequest = () => {

View File

@@ -0,0 +1,61 @@
import React from 'react'
import PropTypes from 'prop-types'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Image from './image'
/**
* Renders an avatar component
* @param {list} [props.accounts] - the accounts for images
* @param {number} [props.size=40] - the size of the avatar
*/
class AvatarGroup extends ImmutablePureComponent {
render() {
const { accounts, size } = this.props
return (
<div className={[_s.d].join(' ')}>
{
accounts.map((account) => {
const isPro = account.get('is_pro')
const alt = `${account.get('display_name')} ${isPro ? '(PRO)' : ''}`.trim()
const className = [_s.d, _s.circle, _s.overflowHidden]
if (isPro) {
className.push(_s.boxShadowAvatarPro)
}
const options = {
alt,
className,
src: account.get('avatar_static'),
style: {
width: `${size}px`,
height: `${size}px`,
},
}
return (
<div className={[_s.d].join(' ')}>
<Image {...options} />
</div>
)
})
}
</div>
)
}
}
AvatarGroup.propTypes = {
accounts: ImmutablePropTypes.list,
size: PropTypes.number,
}
AvatarGroup.defaultProps = {
size: 40,
}
export default AvatarGroup

View File

@@ -0,0 +1,81 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { me } from '../initial_state'
import { CX } from '../constants'
import Icon from './icon'
import Text from './text'
class DisplayNameGroup extends ImmutablePureComponent {
render() {
const {
accounts,
isMultiline,
isLarge,
noHover,
isSmall,
} = this.props
if (!account) return null
const containerClassName = CX({
d: 1,
maxW100PC: 1,
aiCenter: !isMultiline,
flexRow: !isMultiline,
cursorPointer: !noHover,
aiCenter: isCentered,
})
const displayNameClasses = CX({
text: 1,
overflowWrapBreakWord: 1,
whiteSpaceNoWrap: 1,
fw600: 1,
cPrimary: 1,
mr2: 1,
lineHeight125: !isSmall,
fs14PX: isSmall,
fs15PX: !isLarge,
fs24PX: isLarge && !isSmall,
})
const usernameClasses = CX({
text: 1,
displayFlex: 1,
flexNormal: 1,
flexShrink1: 1,
overflowWrapBreakWord: 1,
textOverflowEllipsis: 1,
cSecondary: 1,
fw400: 1,
lineHeight15: isMultiline,
lineHeight125: !isMultiline,
ml5: !isMultiline,
fs14PX: isSmall,
fs15PX: !isLarge,
fs16PX: isLarge && !isSmall,
})
const iconSize =
!!isLarge ? 19 :
!!isComment ? 12 :
!!isSmall ? 14 : 15
return (
<div />
)
}
}
DisplayNameGroup.propTypes = {
accounts: ImmutablePropTypes.map,
isLarge: PropTypes.bool,
isMultiline: PropTypes.bool,
isSmall: PropTypes.bool,
}
export default DisplayNameGroup

View File

@@ -3,21 +3,44 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { me } from '../initial_state'
import { CX } from '../constants'
import { getWindowDimension } from '../utils/is_mobile'
import {
CX,
MODAL_COMPOSE,
BREAKPOINT_EXTRA_SMALL,
} from '../constants'
import { openModal } from '../actions/modal'
import Button from './button'
const initialState = getWindowDimension()
class FloatingActionButton extends React.PureComponent {
state = {
width: initialState.width,
}
componentDidMount() {
this.handleResize()
window.addEventListener('resize', this.handleResize, false)
}
handleResize = () => {
const { width } = getWindowDimension()
this.setState({ width })
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize, false)
}
render() {
const {
intl,
onOpenCompose,
isDesktop,
} = this.props
const { intl, onOpenCompose } = this.props
const { width } = this.state
if (!me) return null
const isDesktop = width > BREAKPOINT_EXTRA_SMALL
const message = intl.formatMessage(messages.gab)
const containerClasses = CX({
@@ -56,13 +79,12 @@ const messages = defineMessages({
})
const mapDispatchToProps = (dispatch) => ({
onOpenCompose: () => dispatch(openModal('COMPOSE')),
onOpenCompose: () => dispatch(openModal(MODAL_COMPOSE)),
})
FloatingActionButton.propTypes = {
intl: PropTypes.object.isRequired,
onOpenCompose: PropTypes.func.isRequired,
isDesktop: PropTypes.bool,
}
export default injectIntl(connect(null, mapDispatchToProps)(FloatingActionButton))

View File

@@ -0,0 +1,31 @@
import React from 'react'
import PropTypes from 'prop-types'
import ModalLayout from './modal_layout'
import { ChatConversationCreate } from '../../features/ui/util/async_components'
import WrappedBundle from '../../features/ui/util/wrapped_bundle'
class ChatConversationCreateModal extends React.PureComponent {
render() {
const { onClose, chatConversationId } = this.props
return (
<ModalLayout
title='New Conversation'
width={440}
onClose={onClose}
noPadding
>
<WrappedBundle component={ChatConversationCreate} componentParams={{ chatConversationId, onCloseModal: onClose }} />
</ModalLayout>
)
}
}
ChatConversationCreateModal.propTypes = {
onClose: PropTypes.func.isRequired,
chatConversationId: PropTypes.string,
}
export default ChatConversationCreateModal

View File

@@ -0,0 +1,40 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { deleteChatConversation } from '../../actions/chat_conversations'
import ConfirmationModal from './confirmation_modal'
class ChatConversationDeleteModal extends React.PureComponent {
handleClick = () => {
this.props.onConfirm(this.props.chatConversationId)
}
render() {
const { onClose } = this.props
return (
<ConfirmationModal
title='Delete Conversation'
message='Are you sure you want to delete this chat conversation? The messages will not be deleted and you the other participant can still view messages.'
confirm='Delete'
onConfirm={this.handleClick}
onClose={onClose}
/>
)
}
}
const mapDispatchToProps = (dispatch) => ({
onDeleteChatConversation: (chatConversationId) => {
dispatch(deleteChatConversation(chatConversationId))
},
})
ChatConversationDeleteModal.propTypes = {
chatConversationId: PropTypes.string.isRequired,
onDeleteChatConversation: PropTypes.func.isRequired,
}
export default connect(null, mapDispatchToProps)(ChatConversationDeleteModal)

View File

@@ -10,6 +10,8 @@ import LoadingModal from './loading_modal'
import {
MODAL_BLOCK_ACCOUNT,
MODAL_BOOST,
MODAL_CHAT_CONVERSATION_CREATE,
MODAL_CHAT_CONVERSATION_DELETE,
MODAL_COMMUNITY_TIMELINE_SETTINGS,
MODAL_COMPOSE,
MODAL_CONFIRM,
@@ -42,6 +44,8 @@ import {
import {
BlockAccountModal,
BoostModal,
ChatConversationCreateModal,
ChatConversationDeleteModal,
CommunityTimelineSettingsModal,
ComposeModal,
ConfirmationModal,
@@ -74,37 +78,40 @@ import {
VideoModal,
} from '../../features/ui/util/async_components'
const MODAL_COMPONENTS = {}
MODAL_COMPONENTS[MODAL_BLOCK_ACCOUNT] = BlockAccountModal
MODAL_COMPONENTS[MODAL_BOOST] = BoostModal
MODAL_COMPONENTS[MODAL_COMMUNITY_TIMELINE_SETTINGS] = CommunityTimelineSettingsModal
MODAL_COMPONENTS[MODAL_COMPOSE] = ComposeModal
MODAL_COMPONENTS[MODAL_CONFIRM] = ConfirmationModal
MODAL_COMPONENTS[MODAL_DISPLAY_OPTIONS] = DisplayOptionsModal
MODAL_COMPONENTS[MODAL_EDIT_SHORTCUTS] = EditShortcutsModal
MODAL_COMPONENTS[MODAL_EDIT_PROFILE] = EditProfileModal
MODAL_COMPONENTS[MODAL_EMAIL_CONFIRMATION_REMINDER] = EmailConfirmationReminderModal
MODAL_COMPONENTS[MODAL_GROUP_CREATE] = GroupCreateModal
MODAL_COMPONENTS[MODAL_GROUP_DELETE] = GroupDeleteModal
MODAL_COMPONENTS[MODAL_GROUP_PASSWORD] = GroupPasswordModal
MODAL_COMPONENTS[MODAL_HASHTAG_TIMELINE_SETTINGS] = HashtagTimelineSettingsModal
MODAL_COMPONENTS[MODAL_HOME_TIMELINE_SETTINGS] = HomeTimelineSettingsModal
MODAL_COMPONENTS[MODAL_HOTKEYS] = HotkeysModal
MODAL_COMPONENTS[MODAL_LIST_ADD_USER] = ListAddUserModal
MODAL_COMPONENTS[MODAL_LIST_CREATE] = ListCreateModal
MODAL_COMPONENTS[MODAL_LIST_DELETE] = ListDeleteModal
MODAL_COMPONENTS[MODAL_LIST_EDITOR] = ListEditorModal
MODAL_COMPONENTS[MODAL_LIST_TIMELINE_SETTINGS] = ListTimelineSettingsModal
MODAL_COMPONENTS[MODAL_MEDIA] = MediaModal
MODAL_COMPONENTS[MODAL_MUTE] = MuteModal
MODAL_COMPONENTS[MODAL_PRO_UPGRADE] = ProUpgradeModal
MODAL_COMPONENTS[MODAL_REPORT] = ReportModal
MODAL_COMPONENTS[MODAL_STATUS_LIKES] = StatusLikesModal
MODAL_COMPONENTS[MODAL_STATUS_REPOSTS] = StatusRepostsModal
MODAL_COMPONENTS[MODAL_STATUS_REVISIONS] = StatusRevisionsModal
MODAL_COMPONENTS[MODAL_UNAUTHORIZED] = UnauthorizedModal
MODAL_COMPONENTS[MODAL_UNFOLLOW] = UnfollowModal
MODAL_COMPONENTS[MODAL_VIDEO] = VideoModal
const MODAL_COMPONENTS = {
[MODAL_BLOCK_ACCOUNT]: BlockAccountModal,
[MODAL_BOOST]: BoostModal,
[MODAL_CHAT_CONVERSATION_CREATE]: ChatConversationCreateModal,
[MODAL_CHAT_CONVERSATION_DELETE]: ChatConversationDeleteModal,
[MODAL_COMMUNITY_TIMELINE_SETTINGS]: CommunityTimelineSettingsModal,
[MODAL_COMPOSE]: ComposeModal,
[MODAL_CONFIRM]: ConfirmationModal,
[MODAL_DISPLAY_OPTIONS]: DisplayOptionsModal,
[MODAL_EDIT_SHORTCUTS]: EditShortcutsModal,
[MODAL_EDIT_PROFILE]: EditProfileModal,
[MODAL_EMAIL_CONFIRMATION_REMINDER]: EmailConfirmationReminderModal,
[MODAL_GROUP_CREATE]: GroupCreateModal,
[MODAL_GROUP_DELETE]: GroupDeleteModal,
[MODAL_GROUP_PASSWORD]: GroupPasswordModal,
[MODAL_HASHTAG_TIMELINE_SETTINGS]: HashtagTimelineSettingsModal,
[MODAL_HOME_TIMELINE_SETTINGS]: HomeTimelineSettingsModal,
[MODAL_HOTKEYS]: HotkeysModal,
[MODAL_LIST_ADD_USER]: ListAddUserModal,
[MODAL_LIST_CREATE]: ListCreateModal,
[MODAL_LIST_DELETE]: ListDeleteModal,
[MODAL_LIST_EDITOR]: ListEditorModal,
[MODAL_LIST_TIMELINE_SETTINGS]: ListTimelineSettingsModal,
[MODAL_MEDIA]: MediaModal,
[MODAL_MUTE]: MuteModal,
[MODAL_PRO_UPGRADE]: ProUpgradeModal,
[MODAL_REPORT]: ReportModal,
[MODAL_STATUS_LIKES]: StatusLikesModal,
[MODAL_STATUS_REPOSTS]: StatusRepostsModal,
[MODAL_STATUS_REVISIONS]: StatusRevisionsModal,
[MODAL_UNAUTHORIZED]: UnauthorizedModal,
[MODAL_UNFOLLOW]: UnfollowModal,
[MODAL_VIDEO]: VideoModal,
}
const CENTERED_XS_MODALS = [
MODAL_BLOCK_ACCOUNT,

View File

@@ -0,0 +1,31 @@
import React from 'react'
import PropTypes from 'prop-types'
import { getRandomInt } from '../../utils/numbers'
import PlaceholderLayout from './placeholder_layout'
export default class ChatMessagePlaceholder extends React.PureComponent {
render() {
const alt = getRandomInt(0, 1) === 1
const width = getRandomInt(120, 240)
const height = getRandomInt(40, 110)
if (alt) {
return (
<PlaceholderLayout viewBox='0 0 400 110' preserveAspectRatio='xMaxYMin meet'>
<rect x='80' y='0' rx='20' ry='20' width='260' height={height} />
<circle cx='380' cy='20' r='20' />
</PlaceholderLayout>
)
}
return (
<PlaceholderLayout viewBox='0 0 400 110' preserveAspectRatio='xMinYMax meet'>
<circle cx='20' cy='20' r='20' />
<rect x='60' y='0' rx='20' ry='20' width={width} height={height} />
</PlaceholderLayout>
)
}
}

View File

@@ -12,6 +12,7 @@ class PlaceholderLayout extends React.PureComponent {
intl,
theme,
viewBox,
preserveAspectRatio,
} = this.props
const isLight = ['light', 'white', ''].indexOf(theme) > -1
@@ -26,6 +27,7 @@ class PlaceholderLayout extends React.PureComponent {
viewBox={viewBox}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
preserveAspectRatio={preserveAspectRatio}
>
{this.props.children}
</ContentLoader>
@@ -47,6 +49,7 @@ PlaceholderLayout.propTypes = {
intl: PropTypes.object.isRequired,
theme: PropTypes.string.isRequired,
viewBox: PropTypes.string.isRequired,
preserveAspectRatio: PropTypes.string,
}
export default injectIntl(connect(mapStateToProps)(PlaceholderLayout))

View File

@@ -94,30 +94,32 @@ class ScrollableList extends React.PureComponent {
handleScroll = throttle(() => {
if (this.window) {
const { scrollTop, scrollHeight } = this.documentElement;
const { innerHeight } = this.window;
const offset = scrollHeight - scrollTop - innerHeight;
const { scrollTop, scrollHeight } = this.documentElement
const { innerHeight } = this.window
const offset = scrollHeight - scrollTop - innerHeight
if (600 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading && !this.props.disableInfiniteScroll) {
this.props.onLoadMore();
this.props.onLoadMore()
}
if (scrollTop < 100 && this.props.onScrollToTop) {
this.props.onScrollToTop();
this.props.onScrollToTop()
} else if (scrollTop < 100 && this.props.onScrollToBottom) {
this.props.onScrollToBottom()
} else if (this.props.onScroll) {
this.props.onScroll();
this.props.onScroll()
}
if (!this.lastScrollWasSynthetic) {
// If the last scroll wasn't caused by setScrollTop(), assume it was
// intentional and cancel any pending scroll reset on mouse idle
this.scrollToTopOnMouseIdle = false;
this.scrollToTopOnMouseIdle = false
}
this.lastScrollWasSynthetic = false;
this.lastScrollWasSynthetic = false
}
}, 150, {
trailing: true,
});
})
handleWheel = throttle(() => {
this.scrollToTopOnMouseIdle = false;
@@ -175,6 +177,11 @@ class ScrollableList extends React.PureComponent {
this.props.onLoadMore();
}
setRef = (c) => {
this.node = c
if (this.props.scrollRef) this.props.scrollRef(c)
}
render() {
const {
children,
@@ -186,6 +193,8 @@ class ScrollableList extends React.PureComponent {
onLoadMore,
placeholderComponent: Placeholder,
placeholderCount,
onScrollToTop,
onScrollToBottom,
} = this.props
const childrenCount = React.Children.count(children);
@@ -210,8 +219,18 @@ class ScrollableList extends React.PureComponent {
return <ColumnIndicator type='loading' />
} else if (isLoading || childrenCount > 0 || hasMore || !emptyMessage) {
return (
<div onMouseMove={this.handleMouseMove}>
<div onMouseMove={this.handleMouseMove} ref={this.setRef}>
<div role='feed'>
{
(hasMore && onLoadMore && !isLoading) && !!onScrollToBottom &&
<LoadMore onClick={this.handleLoadMore} />
}
{
isLoading && !!onScrollToBottom &&
<ColumnIndicator type='loading' />
}
{
!!this.props.children &&
React.Children.map(this.props.children, (child, index) => (
@@ -234,12 +253,12 @@ class ScrollableList extends React.PureComponent {
}
{
(hasMore && onLoadMore && !isLoading) &&
(hasMore && onLoadMore && !isLoading) && !!onScrollToTop &&
<LoadMore onClick={this.handleLoadMore} />
}
{
isLoading &&
isLoading && !!onScrollToTop &&
<ColumnIndicator type='loading' />
}
</div>
@@ -268,9 +287,10 @@ ScrollableList.propTypes = {
]),
children: PropTypes.node,
onScrollToTop: PropTypes.func,
onScrollToBottom: PropTypes.func,
onScroll: PropTypes.func,
placeholderComponent: PropTypes.node,
placeholderCount: PropTypes.node,
placeholderCount: PropTypes.number,
disableInfiniteScroll: PropTypes.bool,
}

View File

@@ -1,27 +1,18 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {
CX,
BREAKPOINT_SMALL,
} from '../../constants'
import { CX } from '../../constants'
import {
me,
emailConfirmed,
} from '../../initial_state'
import Button from '../button'
import { openModal } from '../../actions/modal'
import Responsive from '../../features/ui/util/responsive_component'
import Heading from '../heading'
import BackButton from '../back_button'
import Pills from '../pills'
class SidebarLayout extends React.PureComponent {
handleOpenComposeModal = () => {
this.props.onOpenComposeModal()
}
render() {
const {
actions,
@@ -91,30 +82,6 @@ class SidebarLayout extends React.PureComponent {
{children}
</nav>
{
!!me &&
<Responsive min={BREAKPOINT_SMALL}>
<Button
onClick={this.handleOpenComposeModal}
className={_s.py15}
icon='pencil'
iconSize='18px'
iconClassName={[_s.py5, _s.px5].join(' ')}
/>
</Responsive>
}
{
!!me &&
<Responsive max={BREAKPOINT_SMALL}>
<Button
onClick={this.handleOpenComposeModal}
className={_s.py15}
icon='pencil'
/>
</Responsive>
}
</div>
</div>
</div>
@@ -124,18 +91,11 @@ class SidebarLayout extends React.PureComponent {
}
const mapDispatchToProps = (dispatch) => ({
onOpenComposeModal() {
dispatch(openModal('COMPOSE'))
},
})
SidebarLayout.propTypes = {
onOpenComposeModal: PropTypes.func.isRequired,
actions: PropTypes.array,
tabs: PropTypes.array,
title: PropTypes.string,
showBackBtn: PropTypes.bool,
}
export default connect(null, mapDispatchToProps)(SidebarLayout)
export default SidebarLayout

View File

@@ -290,7 +290,7 @@ class StatusList extends ImmutablePureComponent {
hasMore={hasMore}
/>
<ScrollableList
ref={this.setRef}
scrollRef={this.setRef}
isLoading={isLoading || isRefreshing}
showLoading={isRefreshing || (isLoading && statusIds.size === 0)}
onLoadMore={onLoadMore && this.handleLoadOlder}

View File

@@ -4,6 +4,7 @@ import { CX } from '../constants'
// Define colors for enumeration for Text component `color` prop
const COLORS = {
alt: 'alt',
primary: 'primary',
secondary: 'secondary',
tertiary: 'tertiary',
@@ -76,6 +77,7 @@ class Text extends React.PureComponent {
lineHeight15: isBadge,
px5: isBadge,
cAlt: color === COLORS.alt,
cPrimary: color === COLORS.primary,
cSecondary: color === COLORS.secondary,
cTertiary: color === COLORS.tertiary,

View File

@@ -35,19 +35,31 @@ class Toast extends React.PureComponent {
message,
date,
to,
type,
} = this.props
const contentClasses = CX({
default: 1,
const containerClasses = CX({
d: 1,
radiusSmall: 1,
w228PX: 1,
mt5: 1,
pt2: 1,
maxWidth240PX: 1,
mb5: 1,
px15: 1,
pt10: 1,
pb15: !!title,
pb10: !title,
bgToast: 1,
boxShadowToast: 1,
})
const contentClasses = CX({
d: 1,
mt5: !!title,
pt2: !!title,
flexRow: !!image,
})
const innerContentClasses = CX({
default: 1,
d: 1,
flexNormal: 1,
pl10: !!image,
pt2: !!image,
@@ -65,19 +77,11 @@ class Toast extends React.PureComponent {
})
return (
<div className={[_s.default, _s.radiusSmall, _s.mb5, _s.px15, _s.pt10, _s.pb15, _s.bgPrimary, _s.boxShadowToast].join(' ')}>
<div className={containerClasses}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
<Text size='media' weight='medium' className={[_s.mr15, _s.minWidth160PX].join(' ')}>
<Text size='medium' color='alt' weight='bold'>
{title}
</Text>
<Button
backgroundColor='secondary'
color='primary'
icon='close'
iconSize='6px'
onClick={this.handleOnDismiss}
className={[_s.mlAuto, _s.px10].join(' ')}
/>
</div>
<div className={contentClasses}>
{
@@ -90,12 +94,16 @@ class Toast extends React.PureComponent {
/>
}
<div className={innerContentClasses}>
<Text size='small'>
<Text size='small' color='alt'>
{message}
</Text>
{
date &&
<Text color='secondary' size='extraSmall' className={dateClasses}>
<Text color='tertiary' size='extraSmall' className={dateClasses}>
{
!image &&
<Text size='small' color='tertiary' className={[_s.ml5, _s.mr5].join(' ')}>·</Text>
}
<RelativeTimestamp timestamp={date} />
</Text>
}
@@ -116,10 +124,6 @@ Toast.propTypes = {
onDismiss: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
to: PropTypes.string,
type: PropTypes.oneOf([
TOAST_TYPE_ERROR,
TOAST_TYPE_SUCCESS,
]).isRequired,
}
export default Toast