Show "no groups in common" warning for relevant message requests
This commit is contained in:
parent
05703c2719
commit
fe772af251
12 changed files with 321 additions and 122 deletions
|
@ -3142,6 +3142,10 @@
|
||||||
"message": "No groups in common",
|
"message": "No groups in common",
|
||||||
"description": "Shown to indicate this user is not a member of any groups"
|
"description": "Shown to indicate this user is not a member of any groups"
|
||||||
},
|
},
|
||||||
|
"no-groups-in-common-warning": {
|
||||||
|
"message": "No groups in common. Review requests carefully.",
|
||||||
|
"description": "When a user has no common groups, show this warning"
|
||||||
|
},
|
||||||
"acceptCall": {
|
"acceptCall": {
|
||||||
"message": "Answer",
|
"message": "Answer",
|
||||||
"description": "Shown in tooltip for the button to accept a call (audio or video)"
|
"description": "Shown in tooltip for the button to accept a call (audio or video)"
|
||||||
|
@ -5165,6 +5169,18 @@
|
||||||
"message": "Continue",
|
"message": "Continue",
|
||||||
"description": "aria-label for the 'next' button in the forward a message modal dialog"
|
"description": "aria-label for the 'next' button in the forward a message modal dialog"
|
||||||
},
|
},
|
||||||
|
"MessageRequestWarning__learn-more": {
|
||||||
|
"message": "Learn more",
|
||||||
|
"description": "Shown on the message request warning. Clicking this button will open a dialog with more information"
|
||||||
|
},
|
||||||
|
"MessageRequestWarning__dialog__details": {
|
||||||
|
"message": "You have no groups in common with this person. Review requests carefully before accepting to avoid unwanted messages.",
|
||||||
|
"description": "Shown in the message request warning dialog. Gives more information about message requests"
|
||||||
|
},
|
||||||
|
"MessageRequestWarning__dialog__learn-even-more": {
|
||||||
|
"message": "About Message Requests",
|
||||||
|
"description": "Shown in the message request warning dialog. Clicking this button will open a page on Signal's support site"
|
||||||
|
},
|
||||||
"ContactSpoofing__same-name": {
|
"ContactSpoofing__same-name": {
|
||||||
"message": "Review requests carefully. Signal found another contact with the same name. $link$",
|
"message": "Review requests carefully. Signal found another contact with the same name. $link$",
|
||||||
"description": "Shown in the timeline warning when you have a message request from someone with the same name as someone else",
|
"description": "Shown in the timeline warning when you have a message request from someone with the same name as someone else",
|
||||||
|
|
|
@ -106,7 +106,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smooth scrolling
|
// Utilities
|
||||||
|
|
||||||
|
@mixin rounded-corners() {
|
||||||
|
// This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
@mixin smooth-scroll() {
|
@mixin smooth-scroll() {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
@ -472,7 +477,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin button-small {
|
@mixin button-small {
|
||||||
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
|
@include rounded-corners;
|
||||||
padding: 7px 14px;
|
padding: 7px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3934,6 +3934,46 @@ button.module-conversation-details__action-button {
|
||||||
@include font-body-2-bold;
|
@include font-body-2-bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__message-request-warning {
|
||||||
|
@include font-body-2;
|
||||||
|
|
||||||
|
&__message {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: $color-gray-60;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
color: $color-gray-25;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 14px;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/info-outline-24.svg',
|
||||||
|
$color-gray-60
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/info-solid-24.svg',
|
||||||
|
$color-gray-25
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module: Message Request Actions
|
// Module: Message Request Actions
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
@include font-body-1-bold;
|
|
||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -37,6 +35,16 @@
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--medium {
|
||||||
|
@include font-body-1-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--small {
|
||||||
|
@include font-body-2;
|
||||||
|
@include rounded-corners;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
&--primary {
|
&--primary {
|
||||||
$color: $color-white;
|
$color: $color-white;
|
||||||
$background-color: $ultramarine-ui-light;
|
$background-color: $ultramarine-ui-light;
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
.module-ContactPill {
|
.module-ContactPill {
|
||||||
|
@include rounded-corners;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -279,9 +279,9 @@
|
||||||
|
|
||||||
&--join-call {
|
&--join-call {
|
||||||
@include font-body-1;
|
@include font-body-1;
|
||||||
|
@include rounded-corners;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: $color-accent-green;
|
background-color: $color-accent-green;
|
||||||
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
|
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
display: flex;
|
display: flex;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -5,30 +5,39 @@ import React from 'react';
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
import { Button, ButtonVariant } from './Button';
|
import { Button, ButtonSize, ButtonVariant } from './Button';
|
||||||
|
|
||||||
const story = storiesOf('Components/Button', module);
|
const story = storiesOf('Components/Button', module);
|
||||||
|
|
||||||
story.add('Kitchen sink', () => (
|
story.add('Kitchen sink', () => (
|
||||||
<>
|
<>
|
||||||
{[
|
{[ButtonSize.Medium, ButtonSize.Small].map(size => (
|
||||||
ButtonVariant.Primary,
|
<React.Fragment key={size}>
|
||||||
ButtonVariant.Secondary,
|
{[
|
||||||
ButtonVariant.SecondaryAffirmative,
|
ButtonVariant.Primary,
|
||||||
ButtonVariant.SecondaryDestructive,
|
ButtonVariant.Secondary,
|
||||||
ButtonVariant.Destructive,
|
ButtonVariant.SecondaryAffirmative,
|
||||||
].map(variant => (
|
ButtonVariant.SecondaryDestructive,
|
||||||
<React.Fragment key={variant}>
|
ButtonVariant.Destructive,
|
||||||
<p>
|
].map(variant => (
|
||||||
<Button onClick={action('onClick')} variant={variant}>
|
<React.Fragment key={variant}>
|
||||||
Hello world
|
<p>
|
||||||
</Button>
|
<Button onClick={action('onClick')} size={size} variant={variant}>
|
||||||
</p>
|
Hello world
|
||||||
<p>
|
</Button>
|
||||||
<Button disabled onClick={action('onClick')} variant={variant}>
|
</p>
|
||||||
Hello world
|
<p>
|
||||||
</Button>
|
<Button
|
||||||
</p>
|
disabled
|
||||||
|
onClick={action('onClick')}
|
||||||
|
size={size}
|
||||||
|
variant={variant}
|
||||||
|
>
|
||||||
|
Hello world
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -6,6 +6,11 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
|
|
||||||
|
export enum ButtonSize {
|
||||||
|
Medium,
|
||||||
|
Small,
|
||||||
|
}
|
||||||
|
|
||||||
export enum ButtonVariant {
|
export enum ButtonVariant {
|
||||||
Primary,
|
Primary,
|
||||||
Secondary,
|
Secondary,
|
||||||
|
@ -17,6 +22,7 @@ export enum ButtonVariant {
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
size?: ButtonSize;
|
||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
|
@ -41,6 +47,11 @@ type PropsType = {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const SIZE_CLASS_NAMES = new Map<ButtonSize, string>([
|
||||||
|
[ButtonSize.Medium, 'module-Button--medium'],
|
||||||
|
[ButtonSize.Small, 'module-Button--small'],
|
||||||
|
]);
|
||||||
|
|
||||||
const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([
|
const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([
|
||||||
[ButtonVariant.Primary, 'module-Button--primary'],
|
[ButtonVariant.Primary, 'module-Button--primary'],
|
||||||
[ButtonVariant.Secondary, 'module-Button--secondary'],
|
[ButtonVariant.Secondary, 'module-Button--secondary'],
|
||||||
|
@ -61,6 +72,7 @@ export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
size = ButtonSize.Medium,
|
||||||
variant = ButtonVariant.Primary,
|
variant = ButtonVariant.Primary,
|
||||||
} = props;
|
} = props;
|
||||||
const ariaLabel = props['aria-label'];
|
const ariaLabel = props['aria-label'];
|
||||||
|
@ -75,13 +87,21 @@ export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
|
||||||
({ type } = props);
|
({ type } = props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sizeClassName = SIZE_CLASS_NAMES.get(size);
|
||||||
|
assert(sizeClassName, '<Button> size not found');
|
||||||
|
|
||||||
const variantClassName = VARIANT_CLASS_NAMES.get(variant);
|
const variantClassName = VARIANT_CLASS_NAMES.get(variant);
|
||||||
assert(variantClassName, '<Button> variant not found');
|
assert(variantClassName, '<Button> variant not found');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
className={classNames('module-Button', variantClassName, className)}
|
className={classNames(
|
||||||
|
'module-Button',
|
||||||
|
sizeClassName,
|
||||||
|
variantClassName,
|
||||||
|
className
|
||||||
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
@ -20,12 +20,15 @@ const getAvatarPath = () =>
|
||||||
text('avatarPath', '/fixtures/kitten-4-112-112.jpg');
|
text('avatarPath', '/fixtures/kitten-4-112-112.jpg');
|
||||||
const getPhoneNumber = () => text('phoneNumber', '+1 (646) 327-2700');
|
const getPhoneNumber = () => text('phoneNumber', '+1 (646) 327-2700');
|
||||||
|
|
||||||
|
const updateSharedGroups = action('updateSharedGroups');
|
||||||
|
|
||||||
storiesOf('Components/Conversation/ConversationHero', module)
|
storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
.add('Direct (Three Other Groups)', () => {
|
.add('Direct (Three Other Groups)', () => {
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
about={getAbout()}
|
about={getAbout()}
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={getTitle()}
|
title={getTitle()}
|
||||||
avatarPath={getAvatarPath()}
|
avatarPath={getAvatarPath()}
|
||||||
|
@ -33,6 +36,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
profileName={getProfileName()}
|
profileName={getProfileName()}
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party', 'Friends 🌿']}
|
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party', 'Friends 🌿']}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
/>
|
/>
|
||||||
|
@ -44,6 +48,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
about={getAbout()}
|
about={getAbout()}
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={getTitle()}
|
title={getTitle()}
|
||||||
avatarPath={getAvatarPath()}
|
avatarPath={getAvatarPath()}
|
||||||
|
@ -51,6 +56,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
profileName={getProfileName()}
|
profileName={getProfileName()}
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
/>
|
/>
|
||||||
|
@ -62,6 +68,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
about={getAbout()}
|
about={getAbout()}
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={getTitle()}
|
title={getTitle()}
|
||||||
avatarPath={getAvatarPath()}
|
avatarPath={getAvatarPath()}
|
||||||
|
@ -69,6 +76,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
profileName={getProfileName()}
|
profileName={getProfileName()}
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
sharedGroupNames={['NYC Rock Climbers']}
|
sharedGroupNames={['NYC Rock Climbers']}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
/>
|
/>
|
||||||
|
@ -80,6 +88,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
about={getAbout()}
|
about={getAbout()}
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={getTitle()}
|
title={getTitle()}
|
||||||
avatarPath={getAvatarPath()}
|
avatarPath={getAvatarPath()}
|
||||||
|
@ -87,6 +96,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
profileName={text('profileName', '')}
|
profileName={text('profileName', '')}
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
/>
|
/>
|
||||||
|
@ -98,6 +108,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
about={getAbout()}
|
about={getAbout()}
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={text('title', 'Cayce Bollard (profile)')}
|
title={text('title', 'Cayce Bollard (profile)')}
|
||||||
avatarPath={getAvatarPath()}
|
avatarPath={getAvatarPath()}
|
||||||
|
@ -105,6 +116,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
profileName={getProfileName()}
|
profileName={getProfileName()}
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
/>
|
/>
|
||||||
|
@ -116,6 +128,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
about={getAbout()}
|
about={getAbout()}
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={text('title', '+1 (646) 327-2700')}
|
title={text('title', '+1 (646) 327-2700')}
|
||||||
avatarPath={getAvatarPath()}
|
avatarPath={getAvatarPath()}
|
||||||
|
@ -123,6 +136,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
profileName={text('profileName', '')}
|
profileName={text('profileName', '')}
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
/>
|
/>
|
||||||
|
@ -135,6 +149,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={text('title', 'Unknown contact')}
|
title={text('title', 'Unknown contact')}
|
||||||
|
acceptedMessageRequest
|
||||||
avatarPath={getAvatarPath()}
|
avatarPath={getAvatarPath()}
|
||||||
name={text('name', '')}
|
name={text('name', '')}
|
||||||
profileName={text('profileName', '')}
|
profileName={text('profileName', '')}
|
||||||
|
@ -142,6 +157,26 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add('Direct (No Groups, No Data, Not Accepted)', () => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '480px' }}>
|
||||||
|
<ConversationHero
|
||||||
|
i18n={i18n}
|
||||||
|
title={text('title', 'Unknown contact')}
|
||||||
|
acceptedMessageRequest={false}
|
||||||
|
avatarPath={getAvatarPath()}
|
||||||
|
name={text('name', '')}
|
||||||
|
profileName={text('profileName', '')}
|
||||||
|
phoneNumber={text('phoneNumber', '')}
|
||||||
|
conversationType="direct"
|
||||||
|
sharedGroupNames={[]}
|
||||||
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -150,12 +185,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={text('title', 'NYC Rock Climbers')}
|
title={text('title', 'NYC Rock Climbers')}
|
||||||
name={text('groupName', 'NYC Rock Climbers')}
|
name={text('groupName', 'NYC Rock Climbers')}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
membersCount={numberKnob('membersCount', 22)}
|
membersCount={numberKnob('membersCount', 22)}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -164,12 +201,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={text('title', 'NYC Rock Climbers')}
|
title={text('title', 'NYC Rock Climbers')}
|
||||||
name={text('groupName', 'NYC Rock Climbers')}
|
name={text('groupName', 'NYC Rock Climbers')}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
membersCount={1}
|
membersCount={1}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -178,12 +217,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={text('title', 'NYC Rock Climbers')}
|
title={text('title', 'NYC Rock Climbers')}
|
||||||
name={text('groupName', 'NYC Rock Climbers')}
|
name={text('groupName', 'NYC Rock Climbers')}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
membersCount={0}
|
membersCount={0}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -192,12 +233,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
|
acceptedMessageRequest
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={text('title', 'Unknown group')}
|
title={text('title', 'Unknown group')}
|
||||||
name={text('groupName', '')}
|
name={text('groupName', '')}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
membersCount={0}
|
membersCount={0}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -212,6 +255,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as React from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import Measure from 'react-measure';
|
||||||
import { Avatar, AvatarBlur, Props as AvatarProps } from '../Avatar';
|
import { Avatar, AvatarBlur, Props as AvatarProps } from '../Avatar';
|
||||||
import { ContactName } from './ContactName';
|
import { ContactName } from './ContactName';
|
||||||
import { About } from './About';
|
import { About } from './About';
|
||||||
import { SharedGroupNames } from '../SharedGroupNames';
|
import { SharedGroupNames } from '../SharedGroupNames';
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { LocalizerType } from '../../types/Util';
|
||||||
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||||
|
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||||
|
import { assert } from '../../util/assert';
|
||||||
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
|
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
@ -14,25 +18,34 @@ export type Props = {
|
||||||
acceptedMessageRequest?: boolean;
|
acceptedMessageRequest?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isMe?: boolean;
|
isMe?: boolean;
|
||||||
sharedGroupNames?: Array<string>;
|
|
||||||
membersCount?: number;
|
membersCount?: number;
|
||||||
phoneNumber?: string;
|
|
||||||
onHeightChange?: () => unknown;
|
onHeightChange?: () => unknown;
|
||||||
|
phoneNumber?: string;
|
||||||
|
sharedGroupNames?: Array<string>;
|
||||||
unblurAvatar: () => void;
|
unblurAvatar: () => void;
|
||||||
unblurredAvatarPath?: string;
|
unblurredAvatarPath?: string;
|
||||||
updateSharedGroups?: () => unknown;
|
updateSharedGroups: () => unknown;
|
||||||
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
|
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
|
||||||
|
|
||||||
const renderMembershipRow = ({
|
const renderMembershipRow = ({
|
||||||
i18n,
|
acceptedMessageRequest,
|
||||||
phoneNumber,
|
|
||||||
sharedGroupNames = [],
|
|
||||||
conversationType,
|
conversationType,
|
||||||
|
i18n,
|
||||||
isMe,
|
isMe,
|
||||||
|
onClickMessageRequestWarning,
|
||||||
|
phoneNumber,
|
||||||
|
sharedGroupNames,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
Props,
|
Props,
|
||||||
'i18n' | 'phoneNumber' | 'sharedGroupNames' | 'conversationType' | 'isMe'
|
| 'acceptedMessageRequest'
|
||||||
>) => {
|
| 'conversationType'
|
||||||
|
| 'i18n'
|
||||||
|
| 'isMe'
|
||||||
|
| 'phoneNumber'
|
||||||
|
> &
|
||||||
|
Required<Pick<Props, 'sharedGroupNames'>> & {
|
||||||
|
onClickMessageRequestWarning: () => void;
|
||||||
|
}) => {
|
||||||
const className = 'module-conversation-hero__membership';
|
const className = 'module-conversation-hero__membership';
|
||||||
|
|
||||||
if (conversationType !== 'direct') {
|
if (conversationType !== 'direct') {
|
||||||
|
@ -54,12 +67,27 @@ const renderMembershipRow = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (acceptedMessageRequest) {
|
||||||
if (!phoneNumber) {
|
if (phoneNumber) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return <div className={className}>{i18n('no-groups-in-common')}</div>;
|
return <div className={className}>{i18n('no-groups-in-common')}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return (
|
||||||
|
<div className="module-conversation-hero__message-request-warning">
|
||||||
|
<div className="module-conversation-hero__message-request-warning__message">
|
||||||
|
{i18n('no-groups-in-common-warning')}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={onClickMessageRequestWarning}
|
||||||
|
size={ButtonSize.Small}
|
||||||
|
variant={ButtonVariant.SecondaryAffirmative}
|
||||||
|
>
|
||||||
|
{i18n('MessageRequestWarning__learn-more')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ConversationHero = ({
|
export const ConversationHero = ({
|
||||||
|
@ -81,37 +109,31 @@ export const ConversationHero = ({
|
||||||
unblurredAvatarPath,
|
unblurredAvatarPath,
|
||||||
updateSharedGroups,
|
updateSharedGroups,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const firstRenderRef = React.useRef(true);
|
const firstRenderRef = useRef(true);
|
||||||
|
|
||||||
// TODO: DESKTOP-686
|
const [height, setHeight] = useState<undefined | number>();
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
const [
|
||||||
React.useEffect(() => {
|
isShowingMessageRequestWarning,
|
||||||
// If any of the depenencies for this hook change then the height of this
|
setIsShowingMessageRequestWarning,
|
||||||
// component may have changed. The cleanup function notifies listeners of
|
] = useState(false);
|
||||||
// any potential height changes.
|
const closeMessageRequestWarning = () => {
|
||||||
return () => {
|
setIsShowingMessageRequestWarning(false);
|
||||||
// Kick off the expensive hydration of the current sharedGroupNames
|
};
|
||||||
if (updateSharedGroups) {
|
|
||||||
updateSharedGroups();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onHeightChange && !firstRenderRef.current) {
|
useEffect(() => {
|
||||||
onHeightChange();
|
// Kick off the expensive hydration of the current sharedGroupNames
|
||||||
} else {
|
updateSharedGroups();
|
||||||
firstRenderRef.current = false;
|
}, [updateSharedGroups]);
|
||||||
}
|
|
||||||
};
|
useEffect(() => {
|
||||||
}, [
|
firstRenderRef.current = false;
|
||||||
firstRenderRef,
|
}, []);
|
||||||
onHeightChange,
|
|
||||||
// Avoid collisions in these dependencies by prefixing them
|
useEffect(() => {
|
||||||
// These dependencies may be dynamic, and therefore may cause height changes
|
if (!firstRenderRef.current && onHeightChange) {
|
||||||
`mc-${membersCount}`,
|
onHeightChange();
|
||||||
`n-${name}`,
|
}
|
||||||
`pn-${profileName}`,
|
}, [height, onHeightChange]);
|
||||||
sharedGroupNames.map(g => `g-${g}`).join(' '),
|
|
||||||
]);
|
|
||||||
/* eslint-enable react-hooks/exhaustive-deps */
|
|
||||||
|
|
||||||
let avatarBlur: AvatarBlur;
|
let avatarBlur: AvatarBlur;
|
||||||
let avatarOnClick: undefined | (() => void);
|
let avatarOnClick: undefined | (() => void);
|
||||||
|
@ -136,58 +158,92 @@ export const ConversationHero = ({
|
||||||
|
|
||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
return (
|
return (
|
||||||
<div className="module-conversation-hero">
|
<>
|
||||||
<Avatar
|
<Measure
|
||||||
i18n={i18n}
|
bounds
|
||||||
blur={avatarBlur}
|
onResize={({ bounds }) => {
|
||||||
color={color}
|
assert(bounds, 'We should be measuring the bounds');
|
||||||
noteToSelf={isMe}
|
setHeight(bounds.height);
|
||||||
avatarPath={avatarPath}
|
}}
|
||||||
conversationType={conversationType}
|
>
|
||||||
name={name}
|
{({ measureRef }) => (
|
||||||
onClick={avatarOnClick}
|
<div className="module-conversation-hero" ref={measureRef}>
|
||||||
profileName={profileName}
|
<Avatar
|
||||||
title={title}
|
i18n={i18n}
|
||||||
size={112}
|
blur={avatarBlur}
|
||||||
className="module-conversation-hero__avatar"
|
color={color}
|
||||||
/>
|
noteToSelf={isMe}
|
||||||
<h1 className="module-conversation-hero__profile-name">
|
avatarPath={avatarPath}
|
||||||
{isMe ? (
|
conversationType={conversationType}
|
||||||
i18n('noteToSelf')
|
name={name}
|
||||||
) : (
|
onClick={avatarOnClick}
|
||||||
<ContactName
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
name={name}
|
size={112}
|
||||||
profileName={profileName}
|
className="module-conversation-hero__avatar"
|
||||||
phoneNumber={phoneNumber}
|
/>
|
||||||
i18n={i18n}
|
<h1 className="module-conversation-hero__profile-name">
|
||||||
/>
|
{isMe ? (
|
||||||
|
i18n('noteToSelf')
|
||||||
|
) : (
|
||||||
|
<ContactName
|
||||||
|
title={title}
|
||||||
|
name={name}
|
||||||
|
profileName={profileName}
|
||||||
|
phoneNumber={phoneNumber}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
{about && !isMe && (
|
||||||
|
<div className="module-about__container">
|
||||||
|
<About text={about} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isMe ? (
|
||||||
|
<div className="module-conversation-hero__with">
|
||||||
|
{membersCount === 1
|
||||||
|
? i18n('ConversationHero--members-1')
|
||||||
|
: membersCount !== undefined
|
||||||
|
? i18n('ConversationHero--members', [`${membersCount}`])
|
||||||
|
: phoneNumberOnly
|
||||||
|
? null
|
||||||
|
: phoneNumber}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{renderMembershipRow({
|
||||||
|
acceptedMessageRequest,
|
||||||
|
conversationType,
|
||||||
|
i18n,
|
||||||
|
isMe,
|
||||||
|
onClickMessageRequestWarning() {
|
||||||
|
setIsShowingMessageRequestWarning(true);
|
||||||
|
},
|
||||||
|
phoneNumber,
|
||||||
|
sharedGroupNames,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</h1>
|
</Measure>
|
||||||
{about && !isMe && (
|
{isShowingMessageRequestWarning && (
|
||||||
<div className="module-about__container">
|
<ConfirmationDialog
|
||||||
<About text={about} />
|
i18n={i18n}
|
||||||
</div>
|
onClose={closeMessageRequestWarning}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
text: i18n('MessageRequestWarning__dialog__learn-even-more'),
|
||||||
|
action: () => {
|
||||||
|
window.location.href =
|
||||||
|
'https://support.signal.org/hc/articles/360007459591';
|
||||||
|
closeMessageRequestWarning();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{i18n('MessageRequestWarning__dialog__details')}
|
||||||
|
</ConfirmationDialog>
|
||||||
)}
|
)}
|
||||||
{!isMe ? (
|
</>
|
||||||
<div className="module-conversation-hero__with">
|
|
||||||
{membersCount === 1
|
|
||||||
? i18n('ConversationHero--members-1')
|
|
||||||
: membersCount !== undefined
|
|
||||||
? i18n('ConversationHero--members', [`${membersCount}`])
|
|
||||||
: phoneNumberOnly
|
|
||||||
? null
|
|
||||||
: phoneNumber}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{renderMembershipRow({
|
|
||||||
conversationType,
|
|
||||||
i18n,
|
|
||||||
isMe,
|
|
||||||
phoneNumber,
|
|
||||||
sharedGroupNames,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
/* eslint-enable no-nested-ternary */
|
/* eslint-enable no-nested-ternary */
|
||||||
};
|
};
|
||||||
|
|
|
@ -315,6 +315,7 @@ const renderHeroRow = () => (
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
|
updateSharedGroups={noop}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const renderLoadingRow = () => <TimelineLoadingRow state="loading" />;
|
const renderLoadingRow = () => <TimelineLoadingRow state="loading" />;
|
||||||
|
|
|
@ -16509,8 +16509,8 @@
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/conversation/ConversationHero.js",
|
"path": "ts/components/conversation/ConversationHero.js",
|
||||||
"line": " const firstRenderRef = React.useRef(true);",
|
"line": " const firstRenderRef = react_1.useRef(true);",
|
||||||
"lineNumber": 49,
|
"lineNumber": 61,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-10-26T19:12:24.410Z",
|
"updated": "2020-10-26T19:12:24.410Z",
|
||||||
"reasonDetail": "Doesn't refer to a DOM element."
|
"reasonDetail": "Doesn't refer to a DOM element."
|
||||||
|
@ -16518,8 +16518,8 @@
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/conversation/ConversationHero.tsx",
|
"path": "ts/components/conversation/ConversationHero.tsx",
|
||||||
"line": " const firstRenderRef = React.useRef(true);",
|
"line": " const firstRenderRef = useRef(true);",
|
||||||
"lineNumber": 84,
|
"lineNumber": 112,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-10-26T19:12:24.410Z",
|
"updated": "2020-10-26T19:12:24.410Z",
|
||||||
"reasonDetail": "Doesn't refer to a DOM element."
|
"reasonDetail": "Doesn't refer to a DOM element."
|
||||||
|
|
Loading…
Reference in a new issue