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; align-items: center;
margin-top: 5px; margin-top: 5px;
margin-bottom: -3px; 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 // 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; padding-inline-end: 24px;
} }
.module-message__metadata__date, .module-message__metadata__date {
.module-message__metadata__badge {
font-size: 11px; font-size: 11px;
line-height: 16px; line-height: 16px;
letter-spacing: 0.3px; letter-spacing: 0.3px;
@ -437,19 +424,10 @@
user-select: none; user-select: none;
} }
.module-message__metadata__badge {
font-weight: bold;
padding-inline-end: 5px;
}
.module-message__metadata__date--with-image-no-caption { .module-message__metadata__date--with-image-no-caption {
color: $color-white; color: $color-white;
} }
.module-message__metadata__spacer {
flex-grow: 1;
}
.module-message__metadata__status-icon { .module-message__metadata__status-icon {
width: 12px; width: 12px;
height: 12px; height: 12px;
@ -493,27 +471,6 @@
background-color: $color-white; 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 { .module-message__author-avatar {
flex-direction: column-reverse; flex-direction: column-reverse;
display: inline-flex; display: inline-flex;

@ -46,16 +46,6 @@
@include session-color-subtle(themed('receivedMessageText')); @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 { &__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 { .inbox {

@ -297,37 +297,6 @@
color: $color-gray-25; 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
.module-expire-timer { .module-expire-timer {
@ -501,23 +470,6 @@
color: $session-color-danger; 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 { .module-message-detail__contact__show-safety-number {
color: $color-white; color: $color-white;
background-color: $color-light-35; background-color: $color-light-35;

@ -38,6 +38,9 @@ import _ from 'lodash';
import { animation, contextMenu, Item, Menu } from 'react-contexify'; import { animation, contextMenu, Item, Menu } from 'react-contexify';
import uuid from 'uuid'; import uuid from 'uuid';
import { InView } from 'react-intersection-observer'; 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 // Same as MIN_WIDTH in ImageGrid.tsx
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200; 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 { direction, isPublic, senderIsModerator, id } = this.props;
const badges = [isPublic && 'Public', senderIsModerator && 'Mod']; return (
<MetadataBadges
return badges direction={direction}
.map(badgeText => { isPublic={isPublic}
if (typeof badgeText !== 'string') { senderIsModerator={senderIsModerator}
return null; id={id}
} withImageNoCaption={withImageNoCaption}
/>
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);
} }
// tslint:disable-next-line: cyclomatic-complexity // tslint:disable-next-line: cyclomatic-complexity
@ -303,7 +287,7 @@ export class Message extends React.PureComponent<Props, State> {
module="module-message__metadata__date" module="module-message__metadata__date"
/> />
)} )}
{this.renderMetadataBadges()} {this.renderMetadataBadges(withImageNoCaption)}
{expirationLength && expirationTimestamp ? ( {expirationLength && expirationTimestamp ? (
<ExpireTimer <ExpireTimer
direction={direction} direction={direction}
@ -312,13 +296,13 @@ export class Message extends React.PureComponent<Props, State> {
withImageNoCaption={withImageNoCaption} withImageNoCaption={withImageNoCaption}
/> />
) : null} ) : null}
<span className="module-message__metadata__spacer" /> <MetadataSpacer />
{textPending ? ( {bodyPending ? (
<div className="module-message__metadata__spinner-container"> <div className="module-message__metadata__spinner-container">
<Spinner size="mini" direction={direction} /> <Spinner size="mini" direction={direction} />
</div> </div>
) : null} ) : null}
<span className="module-message__metadata__spacer" /> <MetadataSpacer />
{showSending ? ( {showSending ? (
<div <div
className={classNames( 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" />} {!showHeader && <div className="spacer-lg" />}
<div className="session-modal__centered"> <div className="session-modal__centered">
{sessionIcon && ( {sessionIcon && iconSize && (
<div> <div>
<SessionIcon iconType={sessionIcon} iconSize={iconSize} /> <SessionIcon iconType={sessionIcon} iconSize={iconSize} />
<div className="spacer-lg" /> <div className="spacer-lg" />

@ -350,12 +350,12 @@ export class SessionCompositionBox extends React.Component<Props, State> {
const messagePlaceHolder = isKickedFromGroup const messagePlaceHolder = isKickedFromGroup
? i18n('youGotKickedFromGroup') ? i18n('youGotKickedFromGroup')
: leftGroup : leftGroup
? i18n('youLeftTheGroup') ? i18n('youLeftTheGroup')
: isBlocked && isPrivate : isBlocked && isPrivate
? i18n('unblockToSend') ? i18n('unblockToSend')
: isBlocked && !isPrivate : isBlocked && !isPrivate
? i18n('unblockGroupToSend') ? i18n('unblockGroupToSend')
: i18n('sendMessage'); : i18n('sendMessage');
const typingEnabled = this.isTypingEnabled(); const typingEnabled = this.isTypingEnabled();
return ( return (
@ -389,25 +389,25 @@ export class SessionCompositionBox extends React.Component<Props, State> {
_index, _index,
focused focused
) => ( ) => (
<MemberItem <MemberItem
i18n={window.i18n} i18n={window.i18n}
selected={focused} selected={focused}
// tslint:disable-next-line: no-empty // tslint:disable-next-line: no-empty
onClicked={() => {}} onClicked={() => { }}
existingMember={false} existingMember={false}
member={{ member={{
id: `${suggestion.id}`, id: `${suggestion.id}`,
authorPhoneNumber: `${suggestion.id}`, authorPhoneNumber: `${suggestion.id}`,
selected: false, selected: false,
authorProfileName: `${suggestion.display}`, authorProfileName: `${suggestion.display}`,
authorName: `${suggestion.display}`, authorName: `${suggestion.display}`,
existingMember: false, existingMember: false,
checkmarked: false, checkmarked: false,
authorAvatarPath: '', authorAvatarPath: '',
}} }}
checkmarked={false} checkmarked={false}
/> />
)} )}
/> />
</MentionsInput> </MentionsInput>
); );

@ -2,83 +2,85 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { icons, SessionIconSize, SessionIconType } from '../icon'; import { icons, SessionIconSize, SessionIconType } from '../icon';
import styled from 'styled-components';
export interface Props { export type Props = {
iconType: SessionIconType; iconType: SessionIconType;
iconSize: SessionIconSize | number; iconSize: SessionIconSize | number;
iconColor: string; iconColor?: string;
iconPadded: boolean; iconPadded?: boolean;
iconRotation: number; iconRotation?: number;
} };
export class SessionIcon extends React.PureComponent<Props> {
public static defaultProps = {
iconSize: SessionIconSize.Medium,
iconColor: '',
iconRotation: 0,
iconPadded: false,
};
constructor(props: any) { const getIconDimensionFromIconSize = (iconSize: SessionIconSize | number) => {
super(props); 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 Svg = styled.svg<StyledSvgProps>`
const { width: ${props => props.width};
iconType, height: ${props => props.height};
iconSize, transform: ${props => `rotate(${props.iconRotation}deg)`};
iconColor, `;
iconRotation,
iconPadded,
} = this.props;
let iconDimensions; const SessionSvg = (props: {
if (typeof iconSize === 'number') { className: string;
iconDimensions = iconSize; viewBox: string;
} else { path: string;
switch (iconSize) { width: string | number;
case SessionIconSize.Small: height: string | number;
iconDimensions = '15'; iconRotation: number;
break; }) => (
case SessionIconSize.Medium: <Svg {...props}>
iconDimensions = '20'; <path fill="currentColor" d={props.path} />
break; </Svg>
case SessionIconSize.Large: );
iconDimensions = '25';
break;
case SessionIconSize.Huge:
iconDimensions = '30';
break;
case SessionIconSize.Max:
iconDimensions = '80';
break;
default:
iconDimensions = '20';
}
}
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 = { const iconDimensions = getIconDimensionFromIconSize(iconSize);
transform: `rotate(${iconRotation}deg)`, const iconDef = icons[iconType];
};
return ( return (
<svg <SessionSvg
className={classNames( className={classNames(
'session-icon', 'session-icon',
iconType, iconType,
iconPadded ? 'padded' : '' iconPadded ? 'padded' : ''
)} )}
version="1.1" viewBox={iconDef.viewBox}
preserveAspectRatio="xMidYMid meet" path={iconDef.path}
viewBox={iconDef.viewBox} width={iconDimensions}
width={iconDimensions} height={iconDimensions}
height={iconDimensions} iconRotation={iconRotation}
style={styles} />
> );
<path d={iconDef.path} fill={iconColor} /> };
</svg>
);
}
}

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

@ -20,3 +20,12 @@ export function encode(value: string, encoding: Encoding): ArrayBuffer {
export function decode(buffer: BufferType, stringEncoding: Encoding): string { export function decode(buffer: BufferType, stringEncoding: Encoding): string {
return ByteBuffer.wrap(buffer).toString(stringEncoding); 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