From 4c0a988fe57401567192c31a193a6b43ec746c9a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 2 Dec 2020 16:22:07 +1100 Subject: [PATCH] Move the metadata badges to use react functional components --- stylesheets/_modules.scss | 45 +----- stylesheets/_session_theme.scss | 27 ---- stylesheets/_theme_dark.scss | 48 ------ ts/components/conversation/Message.tsx | 50 +++--- .../conversation/message/MetadataBadge.tsx | 84 +++++++++++ .../conversation/message/MetadataData.tsx | 12 ++ .../message/MetadataUtilComponent.tsx | 30 ++++ ts/components/session/SessionConfirm.tsx | 2 +- .../conversation/SessionCompositionBox.tsx | 50 +++--- ts/components/session/icon/SessionIcon.tsx | 142 +++++++++--------- .../session/icon/SessionIconButton.tsx | 1 - ts/session/utils/String.ts | 9 ++ 12 files changed, 251 insertions(+), 249 deletions(-) create mode 100644 ts/components/conversation/message/MetadataBadge.tsx create mode 100644 ts/components/conversation/message/MetadataData.tsx create mode 100644 ts/components/conversation/message/MetadataUtilComponent.tsx diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index a73b7e98c..dcdafedab 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -402,18 +402,6 @@ align-items: center; margin-top: 5px; margin-bottom: -3px; - - span { - opacity: 0.5; - transition: $session-transition-duration; - - &:not(.module-message__metadata__badge--separator):hover { - opacity: 1; - } - } - .module-message__metadata__badge--separator { - margin-top: -2px; - } } // With an image and no caption, this section needs to be on top of the image overlay @@ -428,8 +416,7 @@ padding-inline-end: 24px; } -.module-message__metadata__date, -.module-message__metadata__badge { +.module-message__metadata__date { font-size: 11px; line-height: 16px; letter-spacing: 0.3px; @@ -437,19 +424,10 @@ user-select: none; } -.module-message__metadata__badge { - font-weight: bold; - padding-inline-end: 5px; -} - .module-message__metadata__date--with-image-no-caption { color: $color-white; } -.module-message__metadata__spacer { - flex-grow: 1; -} - .module-message__metadata__status-icon { width: 12px; height: 12px; @@ -493,27 +471,6 @@ background-color: $color-white; } -.module-message__send-message-button { - cursor: pointer; - font-weight: 300; - font-size: 13px; - line-height: 18px; - color: $color-loki-green; - background-color: $color-light-02; - border: 1px solid $color-black-012; - - margin-top: 8px; - margin-bottom: -10px; - margin-inline-start: -12px; - margin-inline-end: -12px; - - text-align: center; - padding: 10px; - - border-bottom-left-radius: $session_message-container-border-radius; - border-bottom-right-radius: $session_message-container-border-radius; -} - .module-message__author-avatar { flex-direction: column-reverse; display: inline-flex; diff --git a/stylesheets/_session_theme.scss b/stylesheets/_session_theme.scss index 153da53d5..cb3597fed 100644 --- a/stylesheets/_session_theme.scss +++ b/stylesheets/_session_theme.scss @@ -46,16 +46,6 @@ @include session-color-subtle(themed('receivedMessageText')); } } - - // when no caption, we have a black shadow behind the metadata field. So fallback to white - .module-message__metadata--with-image-no-caption { - .module-message__metadata, - .module-message__metadata__date, - .module-message__metadata__badge, - .module-message__metadata__badge--separator { - color: white; - } - } } &__container--outgoing { @@ -97,23 +87,6 @@ } } } - - // when no caption, we have to force white as we have a black shadow on the back - .module-message__metadata--with-image-no-caption { - .message-read-receipt-container { - .session-icon.check { - fill: white; - } - } - } - .message-read-receipt-container { - margin-inline-start: 5px; - .session-icon.check { - @include themify($themes) { - fill: subtle(themed('sentMessageText')); - } - } - } } .inbox { diff --git a/stylesheets/_theme_dark.scss b/stylesheets/_theme_dark.scss index b970ff91d..fedf6af59 100644 --- a/stylesheets/_theme_dark.scss +++ b/stylesheets/_theme_dark.scss @@ -297,37 +297,6 @@ color: $color-gray-25; } - .module-message__metadata__status-icon--sending { - @include color-svg('../images/sending.svg', $color-white-08); - } - - .module-message__metadata__status-icon--pow { - @include color-svg('../images/pow.svg', $color-white-08); - } - - .module-message__metadata__status-icon--sent { - @include color-svg('../images/check-circle-outline.svg', $color-white-08); - } - - .module-message__metadata__status-icon--delivered { - @include color-svg('../images/double-check.svg', $color-white-08); - } - - .module-message__metadata__status-icon--read { - @include color-svg('../images/read.svg', $color-white-08); - } - - // When status indicators are overlaid on top of an image, they use different colors - .module-message__metadata__status-icon--with-image-no-caption { - background-color: $color-dark-05; - } - - .module-message__send-message-button { - color: $color-loki-green; - background-color: $color-dark-70; - border: 1px solid $color-dark-60; - } - // Module: Expire Timer .module-expire-timer { @@ -501,23 +470,6 @@ color: $session-color-danger; } - .module-message-detail__contact__status-icon--sending { - @include color-svg('../images/sending.svg', $color-light-35); - } - - .module-message-detail__contact__status-icon--sent { - @include color-svg('../images/check-circle-outline.svg', $color-light-35); - } - .module-message-detail__contact__status-icon--delivered { - @include color-svg('../images/double-check.svg', $color-light-35); - } - .module-message-detail__contact__status-icon--read { - @include color-svg('../images/read.svg', $color-light-35); - } - .module-message-detail__contact__status-icon--error { - @include color-svg('../images/error.svg', $session-color-danger); - } - .module-message-detail__contact__show-safety-number { color: $color-white; background-color: $color-light-35; diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 3d75104bb..b61304bc2 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -38,6 +38,9 @@ import _ from 'lodash'; import { animation, contextMenu, Item, Menu } from 'react-contexify'; import uuid from 'uuid'; import { InView } from 'react-intersection-observer'; +import { MetadataBadge, MetadataBadges } from './message/MetadataBadge'; +import { nonNullish } from '../../session/utils/String'; +import { MetadataSpacer } from './message/MetadataUtilComponent'; // Same as MIN_WIDTH in ImageGrid.tsx const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200; @@ -207,37 +210,18 @@ export class Message extends React.PureComponent { }); } - public renderMetadataBadges() { + public renderMetadataBadges(withImageNoCaption: boolean) { const { direction, isPublic, senderIsModerator, id } = this.props; - const badges = [isPublic && 'Public', senderIsModerator && 'Mod']; - - return badges - .map(badgeText => { - if (typeof badgeText !== 'string') { - return null; - } - - return ( -
- -  •  - - - {badgeText} - -
- ); - }) - .filter(i => !!i); + return ( + + ); } // tslint:disable-next-line: cyclomatic-complexity @@ -303,7 +287,7 @@ export class Message extends React.PureComponent { module="module-message__metadata__date" /> )} - {this.renderMetadataBadges()} + {this.renderMetadataBadges(withImageNoCaption)} {expirationLength && expirationTimestamp ? ( { withImageNoCaption={withImageNoCaption} /> ) : null} - - {textPending ? ( + + {bodyPending ? (
) : null} - + {showSending ? (
` + font-weight: bold; + padding-inline-end: 5px; + font-size: 11px; + line-height: 16px; + letter-spacing: 0.3px; + text-transform: uppercase; + user-select: none; + opacity: 0.5; + transition: ${props => props.theme.common.animations.defaultDuration}; + color: ${props => + props.withImageNoCaption ? 'white' : props.theme.colors.textColor}; + &:hover { + opacity: 1; + } +`; + +const BadgeSeparator = styled.span<{ withImageNoCaption: boolean }>` + margin-top: -2px; + color: ${props => + props.withImageNoCaption ? 'white' : props.theme.colors.textColor}; + opacity: 0.5; + transition: ${props => props.theme.common.animations.defaultDuration}; + &:hover { + opacity: 1; + } +`; + +export const MetadataBadge = (props: BadgeProps): JSX.Element => { + return ( + <> +  •  + + + ); +}; + +type BadgesProps = { + id: string; + direction: string; + isPublic?: boolean; + senderIsModerator?: boolean; + withImageNoCaption: boolean; +}; + +export const MetadataBadges = (props: BadgesProps): JSX.Element => { + const { + id, + direction, + isPublic, + senderIsModerator, + withImageNoCaption, + } = props; + const badges = [ + (isPublic && 'Public') || null, + (senderIsModerator && 'Mod') || null, + ].filter(nonNullish); + + if (!badges || badges.length === 0) { + return <>; + } + + const badgesElements = badges.map(badgeText => ( + + )); + + return <>{badgesElements}; +}; diff --git a/ts/components/conversation/message/MetadataData.tsx b/ts/components/conversation/message/MetadataData.tsx new file mode 100644 index 000000000..0fa44c1f5 --- /dev/null +++ b/ts/components/conversation/message/MetadataData.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import styled from 'styled-components'; + +type Props = { + date: string; + direction: string; + withImageNoCaption: boolean; +}; + +export const MetadataDate = (props: Props) => { + return <>; +}; diff --git a/ts/components/conversation/message/MetadataUtilComponent.tsx b/ts/components/conversation/message/MetadataUtilComponent.tsx new file mode 100644 index 000000000..b77474080 --- /dev/null +++ b/ts/components/conversation/message/MetadataUtilComponent.tsx @@ -0,0 +1,30 @@ +import styled from 'styled-components'; +import React from 'react'; +import { + SessionIcon, + SessionIconSize, + SessionIconType, +} from '../../session/icon'; + +export const MetadataSpacer = styled.span` + flex-grow: 1; +`; +/* .session-icon.check { + @include themify($themes) { + fill: subtle(themed("sentMessageText")); + } */ + +const MessageReadReceiptContainer = styled.div` + margin-inline-start: 5px; +`; + +export const MessageReadReceipt = () => { + return ( + + + + ); +}; diff --git a/ts/components/session/SessionConfirm.tsx b/ts/components/session/SessionConfirm.tsx index f0b611834..2e9eab906 100644 --- a/ts/components/session/SessionConfirm.tsx +++ b/ts/components/session/SessionConfirm.tsx @@ -67,7 +67,7 @@ export class SessionConfirm extends React.Component { {!showHeader &&
}
- {sessionIcon && ( + {sessionIcon && iconSize && (
diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index e7e0eeca9..03d7e3e5b 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -350,12 +350,12 @@ export class SessionCompositionBox extends React.Component { const messagePlaceHolder = isKickedFromGroup ? i18n('youGotKickedFromGroup') : leftGroup - ? i18n('youLeftTheGroup') - : isBlocked && isPrivate - ? i18n('unblockToSend') - : isBlocked && !isPrivate - ? i18n('unblockGroupToSend') - : i18n('sendMessage'); + ? i18n('youLeftTheGroup') + : isBlocked && isPrivate + ? i18n('unblockToSend') + : isBlocked && !isPrivate + ? i18n('unblockGroupToSend') + : i18n('sendMessage'); const typingEnabled = this.isTypingEnabled(); return ( @@ -389,25 +389,25 @@ export class SessionCompositionBox extends React.Component { _index, focused ) => ( - {}} - existingMember={false} - member={{ - id: `${suggestion.id}`, - authorPhoneNumber: `${suggestion.id}`, - selected: false, - authorProfileName: `${suggestion.display}`, - authorName: `${suggestion.display}`, - existingMember: false, - checkmarked: false, - authorAvatarPath: '', - }} - checkmarked={false} - /> - )} + { }} + existingMember={false} + member={{ + id: `${suggestion.id}`, + authorPhoneNumber: `${suggestion.id}`, + selected: false, + authorProfileName: `${suggestion.display}`, + authorName: `${suggestion.display}`, + existingMember: false, + checkmarked: false, + authorAvatarPath: '', + }} + checkmarked={false} + /> + )} /> ); diff --git a/ts/components/session/icon/SessionIcon.tsx b/ts/components/session/icon/SessionIcon.tsx index 28844ba19..e642fd643 100644 --- a/ts/components/session/icon/SessionIcon.tsx +++ b/ts/components/session/icon/SessionIcon.tsx @@ -2,83 +2,85 @@ import React from 'react'; import classNames from 'classnames'; import { icons, SessionIconSize, SessionIconType } from '../icon'; +import styled from 'styled-components'; -export interface Props { +export type Props = { iconType: SessionIconType; iconSize: SessionIconSize | number; - iconColor: string; - iconPadded: boolean; - iconRotation: number; -} - -export class SessionIcon extends React.PureComponent { - public static defaultProps = { - iconSize: SessionIconSize.Medium, - iconColor: '', - iconRotation: 0, - iconPadded: false, - }; + iconColor?: string; + iconPadded?: boolean; + iconRotation?: number; +}; - constructor(props: any) { - super(props); +const getIconDimensionFromIconSize = (iconSize: SessionIconSize | number) => { + if (typeof iconSize === 'number') { + return iconSize; + } else { + switch (iconSize) { + case SessionIconSize.Small: + return '15'; + case SessionIconSize.Medium: + return '20'; + case SessionIconSize.Large: + return '25'; + case SessionIconSize.Huge: + return '30'; + case SessionIconSize.Max: + return '80'; + default: + return '20'; + } } +}; + +type StyledSvgProps = { + width: string | number; + height: string | number; + iconRotation: number; +}; - public render() { - const { - iconType, - iconSize, - iconColor, - iconRotation, - iconPadded, - } = this.props; +const Svg = styled.svg` + width: ${props => props.width}; + height: ${props => props.height}; + transform: ${props => `rotate(${props.iconRotation}deg)`}; +`; - let iconDimensions; - if (typeof iconSize === 'number') { - iconDimensions = iconSize; - } else { - switch (iconSize) { - case SessionIconSize.Small: - iconDimensions = '15'; - break; - case SessionIconSize.Medium: - iconDimensions = '20'; - break; - case SessionIconSize.Large: - iconDimensions = '25'; - break; - case SessionIconSize.Huge: - iconDimensions = '30'; - break; - case SessionIconSize.Max: - iconDimensions = '80'; - break; - default: - iconDimensions = '20'; - } - } +const SessionSvg = (props: { + className: string; + viewBox: string; + path: string; + width: string | number; + height: string | number; + iconRotation: number; +}) => ( + + + +); - const iconDef = icons[iconType]; +export const SessionIcon = (props: Props) => { + const { iconType } = props; + let { iconSize, iconColor, iconRotation, iconPadded } = props; + iconSize = iconSize || SessionIconSize.Medium; + iconColor = iconColor || ''; + iconRotation = iconRotation || 0; + iconPadded = iconPadded || false; - const styles = { - transform: `rotate(${iconRotation}deg)`, - }; + const iconDimensions = getIconDimensionFromIconSize(iconSize); + const iconDef = icons[iconType]; - return ( - - - - ); - } -} + return ( + + ); +}; diff --git a/ts/components/session/icon/SessionIconButton.tsx b/ts/components/session/icon/SessionIconButton.tsx index 2a6128972..4f629d30b 100644 --- a/ts/components/session/icon/SessionIconButton.tsx +++ b/ts/components/session/icon/SessionIconButton.tsx @@ -16,7 +16,6 @@ export class SessionIconButton extends React.PureComponent { isSelected: false, }; public static readonly defaultProps = { - ...SessionIcon.defaultProps, ...SessionIconButton.extendedDefaults, }; diff --git a/ts/session/utils/String.ts b/ts/session/utils/String.ts index 820021757..120d4e91a 100644 --- a/ts/session/utils/String.ts +++ b/ts/session/utils/String.ts @@ -20,3 +20,12 @@ export function encode(value: string, encoding: Encoding): ArrayBuffer { export function decode(buffer: BufferType, stringEncoding: Encoding): string { return ByteBuffer.wrap(buffer).toString(stringEncoding); } + +/** + * Typescript which can be used to filter out undefined or null values from an array. + * And making typescript realize that there is no nullish value in the type anymore. + * @param v the value to evaluate + */ +export function nonNullish(v: V): v is NonNullable { + return v !== undefined && v !== null; +}