Move the metadata badges to use react functional components

pull/1381/head
Audric Ackermann 4 years ago
parent 8cc2cd6581
commit 4c0a988fe5
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -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;

@ -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 {

@ -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;

@ -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<Props, State> {
});
}
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 (
<div key={`${id}-${badgeText}`}>
<span className="module-message__metadata__badge--separator">
&nbsp;&nbsp;
</span>
<span
className={classNames(
'module-message__metadata__badge',
`module-message__metadata__badge--${direction}`,
`module-message__metadata__badge--${badgeText.toLowerCase()}`,
`module-message__metadata__badge--${badgeText.toLowerCase()}--${direction}`
)}
key={badgeText}
>
{badgeText}
</span>
</div>
);
})
.filter(i => !!i);
return (
<MetadataBadges
direction={direction}
isPublic={isPublic}
senderIsModerator={senderIsModerator}
id={id}
withImageNoCaption={withImageNoCaption}
/>
);
}
// tslint:disable-next-line: cyclomatic-complexity
@ -303,7 +287,7 @@ export class Message extends React.PureComponent<Props, State> {
module="module-message__metadata__date"
/>
)}
{this.renderMetadataBadges()}
{this.renderMetadataBadges(withImageNoCaption)}
{expirationLength && expirationTimestamp ? (
<ExpireTimer
direction={direction}
@ -312,13 +296,13 @@ export class Message extends React.PureComponent<Props, State> {
withImageNoCaption={withImageNoCaption}
/>
) : null}
<span className="module-message__metadata__spacer" />
{textPending ? (
<MetadataSpacer />
{bodyPending ? (
<div className="module-message__metadata__spinner-container">
<Spinner size="mini" direction={direction} />
</div>
) : null}
<span className="module-message__metadata__spacer" />
<MetadataSpacer />
{showSending ? (
<div
className={classNames(

@ -0,0 +1,84 @@
import React, { ReactNode } from 'react';
import styled from 'styled-components';
import { nonNullish } from '../../../session/utils/String';
type BadgeProps = {
badge: string;
direction: string;
withImageNoCaption: boolean;
children?: ReactNode;
};
const BadgeText = styled.span<BadgeProps>`
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 (
<>
<BadgeSeparator {...props}>&nbsp;&nbsp;</BadgeSeparator>
<BadgeText {...props} children={props.badge} />
</>
);
};
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 => (
<MetadataBadge
key={`${id}-${badgeText}`}
badge={badgeText}
direction={direction}
withImageNoCaption={withImageNoCaption}
/>
));
return <>{badgesElements}</>;
};

@ -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 <></>;
};

@ -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 (
<MessageReadReceiptContainer>
<SessionIcon
iconType={SessionIconType.Check}
iconSize={SessionIconSize.Small}
/>
</MessageReadReceiptContainer>
);
};

@ -67,7 +67,7 @@ export class SessionConfirm extends React.Component<Props> {
{!showHeader && <div className="spacer-lg" />}
<div className="session-modal__centered">
{sessionIcon && (
{sessionIcon && iconSize && (
<div>
<SessionIcon iconType={sessionIcon} iconSize={iconSize} />
<div className="spacer-lg" />

@ -350,12 +350,12 @@ export class SessionCompositionBox extends React.Component<Props, State> {
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<Props, State> {
_index,
focused
) => (
<MemberItem
i18n={window.i18n}
selected={focused}
// tslint:disable-next-line: no-empty
onClicked={() => {}}
existingMember={false}
member={{
id: `${suggestion.id}`,
authorPhoneNumber: `${suggestion.id}`,
selected: false,
authorProfileName: `${suggestion.display}`,
authorName: `${suggestion.display}`,
existingMember: false,
checkmarked: false,
authorAvatarPath: '',
}}
checkmarked={false}
/>
)}
<MemberItem
i18n={window.i18n}
selected={focused}
// tslint:disable-next-line: no-empty
onClicked={() => { }}
existingMember={false}
member={{
id: `${suggestion.id}`,
authorPhoneNumber: `${suggestion.id}`,
selected: false,
authorProfileName: `${suggestion.display}`,
authorName: `${suggestion.display}`,
existingMember: false,
checkmarked: false,
authorAvatarPath: '',
}}
checkmarked={false}
/>
)}
/>
</MentionsInput>
);

@ -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<Props> {
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<StyledSvgProps>`
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;
}) => (
<Svg {...props}>
<path fill="currentColor" d={props.path} />
</Svg>
);
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 (
<svg
className={classNames(
'session-icon',
iconType,
iconPadded ? 'padded' : ''
)}
version="1.1"
preserveAspectRatio="xMidYMid meet"
viewBox={iconDef.viewBox}
width={iconDimensions}
height={iconDimensions}
style={styles}
>
<path d={iconDef.path} fill={iconColor} />
</svg>
);
}
}
return (
<SessionSvg
className={classNames(
'session-icon',
iconType,
iconPadded ? 'padded' : ''
)}
viewBox={iconDef.viewBox}
path={iconDef.path}
width={iconDimensions}
height={iconDimensions}
iconRotation={iconRotation}
/>
);
};

@ -16,7 +16,6 @@ export class SessionIconButton extends React.PureComponent<SProps> {
isSelected: false,
};
public static readonly defaultProps = {
...SessionIcon.defaultProps,
...SessionIconButton.extendedDefaults,
};

@ -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): v is NonNullable<V> {
return v !== undefined && v !== null;
}

Loading…
Cancel
Save