Add story entry points around the app
This commit is contained in:
parent
1d5b361159
commit
5dfe30d235
16 changed files with 367 additions and 533 deletions
|
@ -119,6 +119,7 @@
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
margin-right: var(--button-spacing);
|
margin-right: var(--button-spacing);
|
||||||
padding: $padding;
|
padding: $padding;
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
@include keyboard-mode {
|
@include keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
|
@ -3,20 +3,20 @@
|
||||||
|
|
||||||
import type { Meta, Story } from '@storybook/react';
|
import type { Meta, Story } from '@storybook/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { isBoolean } from 'lodash';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { expect } from '@storybook/jest';
|
import { expect } from '@storybook/jest';
|
||||||
|
import { isBoolean } from 'lodash';
|
||||||
import { within, userEvent } from '@storybook/testing-library';
|
import { within, userEvent } from '@storybook/testing-library';
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
|
|
||||||
import type { Props } from './Avatar';
|
|
||||||
import { Avatar, AvatarBlur, AvatarSize, AvatarStoryRing } from './Avatar';
|
|
||||||
import { setupI18n } from '../util/setupI18n';
|
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
|
||||||
import type { AvatarColorType } from '../types/Colors';
|
import type { AvatarColorType } from '../types/Colors';
|
||||||
|
import type { Props } from './Avatar';
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
import { Avatar, AvatarBlur, AvatarSize } from './Avatar';
|
||||||
import { AvatarColors } from '../types/Colors';
|
import { AvatarColors } from '../types/Colors';
|
||||||
import { getFakeBadge } from '../test-both/helpers/getFakeBadge';
|
import { HasStories } from '../types/Stories';
|
||||||
import { ThemeType } from '../types/Util';
|
import { ThemeType } from '../types/Util';
|
||||||
|
import { getFakeBadge } from '../test-both/helpers/getFakeBadge';
|
||||||
|
import { setupI18n } from '../util/setupI18n';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ export default {
|
||||||
},
|
},
|
||||||
storyRing: {
|
storyRing: {
|
||||||
control: { type: 'radio' },
|
control: { type: 'radio' },
|
||||||
options: [undefined, ...Object.values(AvatarStoryRing)],
|
options: [undefined, ...Object.values(HasStories)],
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
control: { type: 'radio' },
|
control: { type: 'radio' },
|
||||||
|
@ -263,7 +263,7 @@ BlurredWithClickToView.story = {
|
||||||
export const StoryUnread = TemplateSingle.bind({});
|
export const StoryUnread = TemplateSingle.bind({});
|
||||||
StoryUnread.args = createProps({
|
StoryUnread.args = createProps({
|
||||||
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
||||||
storyRing: AvatarStoryRing.Unread,
|
storyRing: HasStories.Unread,
|
||||||
});
|
});
|
||||||
StoryUnread.story = {
|
StoryUnread.story = {
|
||||||
name: 'Story: unread',
|
name: 'Story: unread',
|
||||||
|
@ -272,7 +272,7 @@ StoryUnread.story = {
|
||||||
export const StoryRead = TemplateSingle.bind({});
|
export const StoryRead = TemplateSingle.bind({});
|
||||||
StoryRead.args = createProps({
|
StoryRead.args = createProps({
|
||||||
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
avatarPath: '/fixtures/kitten-3-64-64.jpg',
|
||||||
storyRing: AvatarStoryRing.Read,
|
storyRing: HasStories.Read,
|
||||||
});
|
});
|
||||||
StoryRead.story = {
|
StoryRead.story = {
|
||||||
name: 'Story: read',
|
name: 'Story: read',
|
||||||
|
|
|
@ -12,19 +12,19 @@ import React, { useEffect, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
import { Spinner } from './Spinner';
|
|
||||||
|
|
||||||
import { getInitials } from '../util/getInitials';
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
|
||||||
import { ThemeType } from '../types/Util';
|
|
||||||
import type { AvatarColorType } from '../types/Colors';
|
import type { AvatarColorType } from '../types/Colors';
|
||||||
import type { BadgeType } from '../badges/types';
|
import type { BadgeType } from '../badges/types';
|
||||||
|
import type { LocalizerType } from '../types/Util';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { assert } from '../util/assert';
|
|
||||||
import { shouldBlurAvatar } from '../util/shouldBlurAvatar';
|
|
||||||
import { getBadgeImageFileLocalPath } from '../badges/getBadgeImageFileLocalPath';
|
|
||||||
import { isBadgeVisible } from '../badges/isBadgeVisible';
|
|
||||||
import { BadgeImageTheme } from '../badges/BadgeImageTheme';
|
import { BadgeImageTheme } from '../badges/BadgeImageTheme';
|
||||||
|
import { HasStories } from '../types/Stories';
|
||||||
|
import { Spinner } from './Spinner';
|
||||||
|
import { ThemeType } from '../types/Util';
|
||||||
|
import { assert } from '../util/assert';
|
||||||
|
import { getBadgeImageFileLocalPath } from '../badges/getBadgeImageFileLocalPath';
|
||||||
|
import { getInitials } from '../util/getInitials';
|
||||||
|
import { isBadgeVisible } from '../badges/isBadgeVisible';
|
||||||
|
import { shouldBlurAvatar } from '../util/shouldBlurAvatar';
|
||||||
import { shouldShowBadges } from '../badges/shouldShowBadges';
|
import { shouldShowBadges } from '../badges/shouldShowBadges';
|
||||||
|
|
||||||
export enum AvatarBlur {
|
export enum AvatarBlur {
|
||||||
|
@ -45,11 +45,6 @@ export enum AvatarSize {
|
||||||
ONE_HUNDRED_TWELVE = 112,
|
ONE_HUNDRED_TWELVE = 112,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AvatarStoryRing {
|
|
||||||
Unread = 'Unread',
|
|
||||||
Read = 'Read',
|
|
||||||
}
|
|
||||||
|
|
||||||
type BadgePlacementType = { bottom: number; right: number };
|
type BadgePlacementType = { bottom: number; right: number };
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
@ -70,7 +65,7 @@ export type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
unblurredAvatarPath?: string;
|
unblurredAvatarPath?: string;
|
||||||
searchResult?: boolean;
|
searchResult?: boolean;
|
||||||
storyRing?: AvatarStoryRing;
|
storyRing?: HasStories;
|
||||||
|
|
||||||
onClick?: (event: MouseEvent<HTMLButtonElement>) => unknown;
|
onClick?: (event: MouseEvent<HTMLButtonElement>) => unknown;
|
||||||
onClickBadge?: (event: MouseEvent<HTMLButtonElement>) => unknown;
|
onClickBadge?: (event: MouseEvent<HTMLButtonElement>) => unknown;
|
||||||
|
@ -308,9 +303,8 @@ export const Avatar: FunctionComponent<Props> = ({
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-Avatar',
|
'module-Avatar',
|
||||||
hasImage ? 'module-Avatar--with-image' : 'module-Avatar--no-image',
|
hasImage ? 'module-Avatar--with-image' : 'module-Avatar--no-image',
|
||||||
storyRing && 'module-Avatar--with-story',
|
Boolean(storyRing) && 'module-Avatar--with-story',
|
||||||
storyRing === AvatarStoryRing.Unread &&
|
storyRing === HasStories.Unread && 'module-Avatar--with-story--unread',
|
||||||
'module-Avatar--with-story--unread',
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -5,9 +5,10 @@ import React, { useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { ConversationStoryType, StoryViewType } from '../types/Stories';
|
import type { ConversationStoryType, StoryViewType } from '../types/Stories';
|
||||||
import { Avatar, AvatarSize, AvatarStoryRing } from './Avatar';
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||||
import { ContextMenuPopper } from './ContextMenu';
|
import { ContextMenuPopper } from './ContextMenu';
|
||||||
|
import { HasStories } from '../types/Stories';
|
||||||
import { MessageTimestamp } from './conversation/MessageTimestamp';
|
import { MessageTimestamp } from './conversation/MessageTimestamp';
|
||||||
import { StoryImage } from './StoryImage';
|
import { StoryImage } from './StoryImage';
|
||||||
import { getAvatarColor } from '../types/Colors';
|
import { getAvatarColor } from '../types/Colors';
|
||||||
|
@ -57,9 +58,9 @@ export const StoryListItem = ({
|
||||||
title,
|
title,
|
||||||
} = sender;
|
} = sender;
|
||||||
|
|
||||||
let avatarStoryRing: AvatarStoryRing | undefined;
|
let avatarStoryRing: HasStories | undefined;
|
||||||
if (attachment) {
|
if (attachment) {
|
||||||
avatarStoryRing = isUnread ? AvatarStoryRing.Unread : AvatarStoryRing.Read;
|
avatarStoryRing = isUnread ? HasStories.Unread : HasStories.Read;
|
||||||
}
|
}
|
||||||
|
|
||||||
let repliesElement: JSX.Element | undefined;
|
let repliesElement: JSX.Element | undefined;
|
||||||
|
|
|
@ -1,147 +1,147 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { Meta, Story } from '@storybook/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import casual from 'casual';
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
import { boolean } from '@storybook/addon-knobs';
|
|
||||||
|
|
||||||
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
|
|
||||||
import type { PropsType } from './ContactModal';
|
|
||||||
import { ContactModal } from './ContactModal';
|
|
||||||
import { setupI18n } from '../../util/setupI18n';
|
|
||||||
import enMessages from '../../../_locales/en/messages.json';
|
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type { ConversationType } from '../../state/ducks/conversations';
|
||||||
import { getFakeBadges } from '../../test-both/helpers/getFakeBadge';
|
import type { PropsType } from './ContactModal';
|
||||||
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
|
import { ContactModal } from './ContactModal';
|
||||||
|
import { HasStories } from '../../types/Stories';
|
||||||
import { ThemeType } from '../../types/Util';
|
import { ThemeType } from '../../types/Util';
|
||||||
|
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
|
||||||
|
import { getFakeBadges } from '../../test-both/helpers/getFakeBadge';
|
||||||
|
import { setupI18n } from '../../util/setupI18n';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Components/Conversation/ContactModal',
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultContact: ConversationType = getDefaultConversation({
|
const defaultContact: ConversationType = getDefaultConversation({
|
||||||
id: 'abcdef',
|
|
||||||
title: 'Pauline Oliveros',
|
|
||||||
phoneNumber: '(333) 444-5515',
|
|
||||||
about: '👍 Free to chat',
|
about: '👍 Free to chat',
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultGroup: ConversationType = getDefaultConversation({
|
const defaultGroup: ConversationType = getDefaultConversation({
|
||||||
id: 'abcdef',
|
|
||||||
areWeAdmin: true,
|
areWeAdmin: true,
|
||||||
title: "It's a group",
|
groupLink: casual.url,
|
||||||
groupLink: 'something',
|
title: casual.title,
|
||||||
|
type: 'group',
|
||||||
});
|
});
|
||||||
|
|
||||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
export default {
|
||||||
areWeASubscriber: false,
|
title: 'Components/Conversation/ContactModal',
|
||||||
areWeAdmin: boolean('areWeAdmin', overrideProps.areWeAdmin || false),
|
component: ContactModal,
|
||||||
badges: overrideProps.badges || [],
|
argTypes: {
|
||||||
contact: overrideProps.contact || defaultContact,
|
i18n: {
|
||||||
conversation: overrideProps.conversation || defaultGroup,
|
defaultValue: i18n,
|
||||||
hideContactModal: action('hideContactModal'),
|
},
|
||||||
i18n,
|
areWeASubscriber: {
|
||||||
isAdmin: boolean('isAdmin', overrideProps.isAdmin || false),
|
defaultValue: false,
|
||||||
isMember: boolean('isMember', overrideProps.isMember || true),
|
},
|
||||||
removeMemberFromGroup: action('removeMemberFromGroup'),
|
areWeAdmin: {
|
||||||
showConversation: action('showConversation'),
|
defaultValue: false,
|
||||||
theme: ThemeType.light,
|
},
|
||||||
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
badges: {
|
||||||
toggleAdmin: action('toggleAdmin'),
|
defaultValue: [],
|
||||||
updateConversationModelSharedGroups: action(
|
},
|
||||||
'updateConversationModelSharedGroups'
|
contact: {
|
||||||
),
|
defaultValue: defaultContact,
|
||||||
});
|
},
|
||||||
|
conversation: {
|
||||||
|
defaultValue: defaultGroup,
|
||||||
|
},
|
||||||
|
hasStories: {
|
||||||
|
defaultValue: undefined,
|
||||||
|
},
|
||||||
|
hideContactModal: { action: true },
|
||||||
|
isAdmin: {
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
isMember: {
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
removeMemberFromGroup: { action: true },
|
||||||
|
showConversation: { action: true },
|
||||||
|
theme: {
|
||||||
|
defaultValue: ThemeType.light,
|
||||||
|
},
|
||||||
|
toggleAdmin: { action: true },
|
||||||
|
toggleSafetyNumberModal: { action: true },
|
||||||
|
updateConversationModelSharedGroups: { action: true },
|
||||||
|
viewUserStories: { action: true },
|
||||||
|
},
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
export const AsNonAdmin = (): JSX.Element => {
|
const Template: Story<PropsType> = args => <ContactModal {...args} />;
|
||||||
const props = createProps({
|
|
||||||
areWeAdmin: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return <ContactModal {...props} />;
|
export const AsNonAdmin = Template.bind({});
|
||||||
|
AsNonAdmin.args = {
|
||||||
|
areWeAdmin: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
AsNonAdmin.story = {
|
AsNonAdmin.story = {
|
||||||
name: 'As non-admin',
|
name: 'As non-admin',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AsAdmin = (): JSX.Element => {
|
export const AsAdmin = Template.bind({});
|
||||||
const props = createProps({
|
AsAdmin.args = {
|
||||||
areWeAdmin: true,
|
areWeAdmin: true,
|
||||||
});
|
|
||||||
return <ContactModal {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AsAdmin.story = {
|
AsAdmin.story = {
|
||||||
name: 'As admin',
|
name: 'As admin',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AsAdminWithNoGroupLink = (): JSX.Element => {
|
export const AsAdminWithNoGroupLink = Template.bind({});
|
||||||
const props = createProps({
|
AsAdminWithNoGroupLink.args = {
|
||||||
areWeAdmin: true,
|
areWeAdmin: true,
|
||||||
conversation: {
|
conversation: {
|
||||||
...defaultGroup,
|
...defaultGroup,
|
||||||
groupLink: undefined,
|
groupLink: undefined,
|
||||||
},
|
},
|
||||||
});
|
|
||||||
return <ContactModal {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AsAdminWithNoGroupLink.story = {
|
AsAdminWithNoGroupLink.story = {
|
||||||
name: 'As admin with no group link',
|
name: 'As admin with no group link',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AsAdminViewingNonMemberOfGroup = (): JSX.Element => {
|
export const AsAdminViewingNonMemberOfGroup = Template.bind({});
|
||||||
const props = createProps({
|
AsAdminViewingNonMemberOfGroup.args = {
|
||||||
isMember: false,
|
isMember: false,
|
||||||
});
|
|
||||||
|
|
||||||
return <ContactModal {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AsAdminViewingNonMemberOfGroup.story = {
|
AsAdminViewingNonMemberOfGroup.story = {
|
||||||
name: 'As admin, viewing non-member of group',
|
name: 'As admin, viewing non-member of group',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithoutPhoneNumber = (): JSX.Element => {
|
export const WithoutPhoneNumber = Template.bind({});
|
||||||
const props = createProps({
|
WithoutPhoneNumber.args = {
|
||||||
contact: {
|
contact: {
|
||||||
...defaultContact,
|
...defaultContact,
|
||||||
phoneNumber: undefined,
|
phoneNumber: undefined,
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
return <ContactModal {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
WithoutPhoneNumber.story = {
|
WithoutPhoneNumber.story = {
|
||||||
name: 'Without phone number',
|
name: 'Without phone number',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewingSelf = (): JSX.Element => {
|
export const ViewingSelf = Template.bind({});
|
||||||
const props = createProps({
|
ViewingSelf.args = {
|
||||||
contact: {
|
contact: {
|
||||||
...defaultContact,
|
...defaultContact,
|
||||||
isMe: true,
|
isMe: true,
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
return <ContactModal {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ViewingSelf.story = {
|
ViewingSelf.story = {
|
||||||
name: 'Viewing self',
|
name: 'Viewing self',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithBadges = (): JSX.Element => {
|
export const WithBadges = Template.bind({});
|
||||||
const props = createProps({
|
WithBadges.args = {
|
||||||
badges: getFakeBadges(2),
|
badges: getFakeBadges(2),
|
||||||
});
|
|
||||||
|
|
||||||
return <ContactModal {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
WithBadges.story = {
|
WithBadges.story = {
|
||||||
name: 'With badges',
|
name: 'With badges',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithUnreadStories = Template.bind({});
|
||||||
|
WithUnreadStories.args = {
|
||||||
|
hasStories: HasStories.Unread,
|
||||||
|
};
|
||||||
|
WithUnreadStories.storyName = 'Unread Stories';
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
import * as log from '../../logging/log';
|
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
|
||||||
import { About } from './About';
|
|
||||||
import { Avatar } from '../Avatar';
|
|
||||||
import { AvatarLightbox } from '../AvatarLightbox';
|
|
||||||
import type {
|
import type {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
ShowConversationType,
|
ShowConversationType,
|
||||||
} from '../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import { Modal } from '../Modal';
|
|
||||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
|
||||||
import { BadgeDialog } from '../BadgeDialog';
|
|
||||||
import type { BadgeType } from '../../badges/types';
|
import type { BadgeType } from '../../badges/types';
|
||||||
import { SharedGroupNames } from '../SharedGroupNames';
|
import type { HasStories } from '../../types/Stories';
|
||||||
|
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||||
|
import * as log from '../../logging/log';
|
||||||
|
import { About } from './About';
|
||||||
|
import { Avatar } from '../Avatar';
|
||||||
|
import { AvatarLightbox } from '../AvatarLightbox';
|
||||||
|
import { BadgeDialog } from '../BadgeDialog';
|
||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||||
|
import { Modal } from '../Modal';
|
||||||
import { RemoveGroupMemberConfirmationDialog } from './RemoveGroupMemberConfirmationDialog';
|
import { RemoveGroupMemberConfirmationDialog } from './RemoveGroupMemberConfirmationDialog';
|
||||||
|
import { SharedGroupNames } from '../SharedGroupNames';
|
||||||
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
areWeASubscriber: boolean;
|
areWeASubscriber: boolean;
|
||||||
|
@ -27,6 +28,7 @@ export type PropsDataType = {
|
||||||
badges: ReadonlyArray<BadgeType>;
|
badges: ReadonlyArray<BadgeType>;
|
||||||
contact?: ConversationType;
|
contact?: ConversationType;
|
||||||
conversation?: ConversationType;
|
conversation?: ConversationType;
|
||||||
|
hasStories?: HasStories;
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
isMember: boolean;
|
isMember: boolean;
|
||||||
|
@ -40,6 +42,7 @@ type PropsActionType = {
|
||||||
toggleAdmin: (conversationId: string, contactId: string) => void;
|
toggleAdmin: (conversationId: string, contactId: string) => void;
|
||||||
toggleSafetyNumberModal: (conversationId: string) => unknown;
|
toggleSafetyNumberModal: (conversationId: string) => unknown;
|
||||||
updateConversationModelSharedGroups: (conversationId: string) => void;
|
updateConversationModelSharedGroups: (conversationId: string) => void;
|
||||||
|
viewUserStories: (cid: string) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropsType = PropsDataType & PropsActionType;
|
export type PropsType = PropsDataType & PropsActionType;
|
||||||
|
@ -62,6 +65,7 @@ export const ContactModal = ({
|
||||||
badges,
|
badges,
|
||||||
contact,
|
contact,
|
||||||
conversation,
|
conversation,
|
||||||
|
hasStories,
|
||||||
hideContactModal,
|
hideContactModal,
|
||||||
i18n,
|
i18n,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
|
@ -72,6 +76,7 @@ export const ContactModal = ({
|
||||||
toggleAdmin,
|
toggleAdmin,
|
||||||
toggleSafetyNumberModal,
|
toggleSafetyNumberModal,
|
||||||
updateConversationModelSharedGroups,
|
updateConversationModelSharedGroups,
|
||||||
|
viewUserStories,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
if (!contact) {
|
if (!contact) {
|
||||||
throw new Error('Contact modal opened without a matching contact');
|
throw new Error('Contact modal opened without a matching contact');
|
||||||
|
@ -172,14 +177,21 @@ export const ContactModal = ({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={contact.isMe}
|
isMe={contact.isMe}
|
||||||
name={contact.name}
|
name={contact.name}
|
||||||
|
onClick={() => {
|
||||||
|
if (conversation && hasStories) {
|
||||||
|
viewUserStories(conversation.id);
|
||||||
|
} else {
|
||||||
|
setView(ContactModalView.ShowingAvatar);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClickBadge={() => setView(ContactModalView.ShowingBadges)}
|
||||||
profileName={contact.profileName}
|
profileName={contact.profileName}
|
||||||
sharedGroupNames={contact.sharedGroupNames}
|
sharedGroupNames={contact.sharedGroupNames}
|
||||||
size={96}
|
size={96}
|
||||||
|
storyRing={hasStories}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={contact.title}
|
title={contact.title}
|
||||||
unblurredAvatarPath={contact.unblurredAvatarPath}
|
unblurredAvatarPath={contact.unblurredAvatarPath}
|
||||||
onClick={() => setView(ContactModalView.ShowingAvatar)}
|
|
||||||
onClickBadge={() => setView(ContactModalView.ShowingBadges)}
|
|
||||||
/>
|
/>
|
||||||
<div className="ContactModal__name">{contact.title}</div>
|
<div className="ContactModal__name">{contact.title}</div>
|
||||||
<div className="module-about__container">
|
<div className="module-about__container">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ComponentProps } from 'react';
|
import type { ComponentProps } from 'react';
|
||||||
|
@ -56,6 +56,7 @@ const commonProps = {
|
||||||
onMarkUnread: action('onMarkUnread'),
|
onMarkUnread: action('onMarkUnread'),
|
||||||
onMoveToInbox: action('onMoveToInbox'),
|
onMoveToInbox: action('onMoveToInbox'),
|
||||||
onSetPin: action('onSetPin'),
|
onSetPin: action('onSetPin'),
|
||||||
|
viewUserStories: action('viewUserStories'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PrivateConvo = (): JSX.Element => {
|
export const PrivateConvo = (): JSX.Element => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2018-2021 Signal Messenger, LLC
|
// Copyright 2018-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
@ -20,6 +20,7 @@ import { InContactsIcon } from '../InContactsIcon';
|
||||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type { ConversationType } from '../../state/ducks/conversations';
|
||||||
import type { BadgeType } from '../../badges/types';
|
import type { BadgeType } from '../../badges/types';
|
||||||
|
import type { HasStories } from '../../types/Stories';
|
||||||
import { getMuteOptions } from '../../util/getMuteOptions';
|
import { getMuteOptions } from '../../util/getMuteOptions';
|
||||||
import * as expirationTimer from '../../util/expirationTimer';
|
import * as expirationTimer from '../../util/expirationTimer';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
@ -39,6 +40,7 @@ export enum OutgoingCallButtonStyle {
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
badge?: BadgeType;
|
badge?: BadgeType;
|
||||||
conversationTitle?: string;
|
conversationTitle?: string;
|
||||||
|
hasStories?: HasStories;
|
||||||
isMissingMandatoryProfileSharing?: boolean;
|
isMissingMandatoryProfileSharing?: boolean;
|
||||||
outgoingCallButtonStyle: OutgoingCallButtonStyle;
|
outgoingCallButtonStyle: OutgoingCallButtonStyle;
|
||||||
showBackButton?: boolean;
|
showBackButton?: boolean;
|
||||||
|
@ -88,6 +90,7 @@ export type PropsActionsType = {
|
||||||
onArchive: () => void;
|
onArchive: () => void;
|
||||||
onMarkUnread: () => void;
|
onMarkUnread: () => void;
|
||||||
onMoveToInbox: () => void;
|
onMoveToInbox: () => void;
|
||||||
|
viewUserStories: (cid: string) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropsHousekeepingType = {
|
export type PropsHousekeepingType = {
|
||||||
|
@ -199,6 +202,8 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
avatarPath,
|
avatarPath,
|
||||||
badge,
|
badge,
|
||||||
color,
|
color,
|
||||||
|
hasStories,
|
||||||
|
id,
|
||||||
i18n,
|
i18n,
|
||||||
type,
|
type,
|
||||||
isMe,
|
isMe,
|
||||||
|
@ -209,6 +214,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
unblurredAvatarPath,
|
unblurredAvatarPath,
|
||||||
|
viewUserStories,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -221,14 +227,22 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
conversationType={type}
|
conversationType={type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
isMe={isMe}
|
||||||
noteToSelf={isMe}
|
|
||||||
title={title}
|
|
||||||
name={name}
|
name={name}
|
||||||
|
noteToSelf={isMe}
|
||||||
|
onClick={
|
||||||
|
hasStories
|
||||||
|
? () => {
|
||||||
|
viewUserStories(id);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
storyRing={hasStories}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
title={title}
|
||||||
unblurredAvatarPath={unblurredAvatarPath}
|
unblurredAvatarPath={unblurredAvatarPath}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
@ -488,30 +502,32 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
throw missingCaseError(type);
|
throw missingCaseError(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const avatar = this.renderAvatar();
|
||||||
const contents = (
|
const contents = (
|
||||||
<>
|
<div className="module-ConversationHeader__header__info">
|
||||||
{this.renderAvatar()}
|
{this.renderHeaderInfoTitle()}
|
||||||
<div className="module-ConversationHeader__header__info">
|
{this.renderHeaderInfoSubtitle()}
|
||||||
{this.renderHeaderInfoTitle()}
|
</div>
|
||||||
{this.renderHeaderInfoSubtitle()}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
return (
|
return (
|
||||||
<button
|
<div className="module-ConversationHeader__header">
|
||||||
type="button"
|
{avatar}
|
||||||
className="module-ConversationHeader__header module-ConversationHeader__header--clickable"
|
<button
|
||||||
onClick={onClick}
|
type="button"
|
||||||
>
|
className="module-ConversationHeader__header--clickable"
|
||||||
{contents}
|
onClick={onClick}
|
||||||
</button>
|
>
|
||||||
|
{contents}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-ConversationHeader__header" ref={this.headerRef}>
|
<div className="module-ConversationHeader__header" ref={this.headerRef}>
|
||||||
|
{avatar}
|
||||||
{contents}
|
{contents}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,458 +1,213 @@
|
||||||
// Copyright 2020-2022 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as React from 'react';
|
import type { Meta, Story } from '@storybook/react';
|
||||||
import { number as numberKnob, text } from '@storybook/addon-knobs';
|
import React, { useContext } from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import casual from 'casual';
|
||||||
|
|
||||||
import { ConversationHero } from './ConversationHero';
|
|
||||||
import { setupI18n } from '../../util/setupI18n';
|
|
||||||
import enMessages from '../../../_locales/en/messages.json';
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
|
import type { Props } from './ConversationHero';
|
||||||
|
import { ConversationHero } from './ConversationHero';
|
||||||
|
import { HasStories } from '../../types/Stories';
|
||||||
import { StorybookThemeContext } from '../../../.storybook/StorybookThemeContext';
|
import { StorybookThemeContext } from '../../../.storybook/StorybookThemeContext';
|
||||||
|
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
|
||||||
|
import { setupI18n } from '../../util/setupI18n';
|
||||||
|
import { ThemeType } from '../../types/Util';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
const getAbout = () => text('about', '👍 Free to chat');
|
|
||||||
const getTitle = () => text('name', 'Cayce Bollard');
|
|
||||||
const getName = () => text('name', 'Cayce Bollard');
|
|
||||||
const getProfileName = () => text('profileName', 'Cayce Bollard (profile)');
|
|
||||||
const getAvatarPath = () =>
|
|
||||||
text('avatarPath', '/fixtures/kitten-4-112-112.jpg');
|
|
||||||
const getPhoneNumber = () => text('phoneNumber', '+1 (646) 327-2700');
|
|
||||||
|
|
||||||
const updateSharedGroups = action('updateSharedGroups');
|
|
||||||
|
|
||||||
const Wrapper = (
|
|
||||||
props: Omit<React.ComponentProps<typeof ConversationHero>, 'theme'>
|
|
||||||
) => {
|
|
||||||
const theme = React.useContext(StorybookThemeContext);
|
|
||||||
return <ConversationHero {...props} theme={theme} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Components/Conversation/ConversationHero',
|
title: 'Components/Conversation/ConversationHero',
|
||||||
};
|
component: ConversationHero,
|
||||||
|
argTypes: {
|
||||||
|
conversationType: {
|
||||||
|
defaultValue: 'direct',
|
||||||
|
},
|
||||||
|
i18n: {
|
||||||
|
defaultValue: i18n,
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
defaultValue: ThemeType.light,
|
||||||
|
},
|
||||||
|
unblurAvatar: { action: true },
|
||||||
|
updateSharedGroups: { action: true },
|
||||||
|
viewUserStories: { action: true },
|
||||||
|
},
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
export const DirectFiveOtherGroups = (): JSX.Element => {
|
const Template: Story<Props> = args => {
|
||||||
|
const theme = useContext(StorybookThemeContext);
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '480px' }}>
|
<div style={{ width: '480px' }}>
|
||||||
<Wrapper
|
<ConversationHero {...getDefaultConversation()} {...args} theme={theme} />
|
||||||
about={getAbout()}
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={getTitle()}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={getName()}
|
|
||||||
profileName={getProfileName()}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={[
|
|
||||||
'NYC Rock Climbers',
|
|
||||||
'Dinner Party',
|
|
||||||
'Friends 🌿',
|
|
||||||
'Fourth',
|
|
||||||
'Fifth',
|
|
||||||
]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DirectFiveOtherGroups = Template.bind({});
|
||||||
|
DirectFiveOtherGroups.args = {
|
||||||
|
sharedGroupNames: Array.from(Array(5), () => casual.title),
|
||||||
|
};
|
||||||
DirectFiveOtherGroups.story = {
|
DirectFiveOtherGroups.story = {
|
||||||
name: 'Direct (Five Other Groups)',
|
name: 'Direct (Five Other Groups)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectFourOtherGroups = (): JSX.Element => {
|
export const DirectFourOtherGroups = Template.bind({});
|
||||||
return (
|
DirectFourOtherGroups.args = {
|
||||||
<div style={{ width: '480px' }}>
|
sharedGroupNames: Array.from(Array(4), () => casual.title),
|
||||||
<Wrapper
|
|
||||||
about={getAbout()}
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={getTitle()}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={getName()}
|
|
||||||
profileName={getProfileName()}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={[
|
|
||||||
'NYC Rock Climbers',
|
|
||||||
'Dinner Party',
|
|
||||||
'Friends 🌿',
|
|
||||||
'Fourth',
|
|
||||||
]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectFourOtherGroups.story = {
|
DirectFourOtherGroups.story = {
|
||||||
name: 'Direct (Four Other Groups)',
|
name: 'Direct (Four Other Groups)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectThreeOtherGroups = (): JSX.Element => {
|
export const DirectThreeOtherGroups = Template.bind({});
|
||||||
return (
|
DirectThreeOtherGroups.args = {
|
||||||
<div style={{ width: '480px' }}>
|
sharedGroupNames: Array.from(Array(3), () => casual.title),
|
||||||
<Wrapper
|
|
||||||
about={getAbout()}
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={getTitle()}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={getName()}
|
|
||||||
profileName={getProfileName()}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party', 'Friends 🌿']}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectThreeOtherGroups.story = {
|
DirectThreeOtherGroups.story = {
|
||||||
name: 'Direct (Three Other Groups)',
|
name: 'Direct (Three Other Groups)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectTwoOtherGroups = (): JSX.Element => {
|
export const DirectTwoOtherGroups = Template.bind({});
|
||||||
return (
|
DirectTwoOtherGroups.args = {
|
||||||
<div style={{ width: '480px' }}>
|
sharedGroupNames: Array.from(Array(2), () => casual.title),
|
||||||
<Wrapper
|
|
||||||
about={getAbout()}
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={getTitle()}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={getName()}
|
|
||||||
profileName={getProfileName()}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectTwoOtherGroups.story = {
|
DirectTwoOtherGroups.story = {
|
||||||
name: 'Direct (Two Other Groups)',
|
name: 'Direct (Two Other Groups)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectOneOtherGroup = (): JSX.Element => {
|
export const DirectOneOtherGroup = Template.bind({});
|
||||||
return (
|
DirectOneOtherGroup.args = {
|
||||||
<div style={{ width: '480px' }}>
|
sharedGroupNames: [casual.title],
|
||||||
<Wrapper
|
|
||||||
about={getAbout()}
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={getTitle()}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={getName()}
|
|
||||||
profileName={getProfileName()}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={['NYC Rock Climbers']}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectOneOtherGroup.story = {
|
DirectOneOtherGroup.story = {
|
||||||
name: 'Direct (One Other Group)',
|
name: 'Direct (One Other Group)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectNoGroupsName = (): JSX.Element => {
|
export const DirectNoGroupsName = Template.bind({});
|
||||||
return (
|
DirectNoGroupsName.args = {
|
||||||
<div style={{ width: '480px' }}>
|
about: '👍 Free to chat',
|
||||||
<Wrapper
|
|
||||||
about={getAbout()}
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={getTitle()}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={getName()}
|
|
||||||
profileName={text('profileName', '')}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectNoGroupsName.story = {
|
DirectNoGroupsName.story = {
|
||||||
name: 'Direct (No Groups, Name)',
|
name: 'Direct (No Groups, Name)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectNoGroupsJustProfile = (): JSX.Element => {
|
export const DirectNoGroupsJustProfile = Template.bind({});
|
||||||
return (
|
DirectNoGroupsJustProfile.args = {
|
||||||
<div style={{ width: '480px' }}>
|
phoneNumber: casual.phone,
|
||||||
<Wrapper
|
|
||||||
about={getAbout()}
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={text('title', 'Cayce Bollard (profile)')}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={text('name', '')}
|
|
||||||
profileName={getProfileName()}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectNoGroupsJustProfile.story = {
|
DirectNoGroupsJustProfile.story = {
|
||||||
name: 'Direct (No Groups, Just Profile)',
|
name: 'Direct (No Groups, Just Profile)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectNoGroupsJustPhoneNumber = (): JSX.Element => {
|
export const DirectNoGroupsJustPhoneNumber = Template.bind({});
|
||||||
return (
|
DirectNoGroupsJustPhoneNumber.args = {
|
||||||
<div style={{ width: '480px' }}>
|
name: '',
|
||||||
<Wrapper
|
phoneNumber: casual.phone,
|
||||||
about={getAbout()}
|
profileName: '',
|
||||||
acceptedMessageRequest
|
title: '',
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={text('title', '+1 (646) 327-2700')}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={text('name', '')}
|
|
||||||
profileName={text('profileName', '')}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
conversationType="direct"
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectNoGroupsJustPhoneNumber.story = {
|
DirectNoGroupsJustPhoneNumber.story = {
|
||||||
name: 'Direct (No Groups, Just Phone Number)',
|
name: 'Direct (No Groups, Just Phone Number)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectNoGroupsNoData = (): JSX.Element => {
|
export const DirectNoGroupsNoData = Template.bind({});
|
||||||
return (
|
DirectNoGroupsNoData.args = {
|
||||||
<div style={{ width: '480px' }}>
|
avatarPath: undefined,
|
||||||
<Wrapper
|
name: '',
|
||||||
i18n={i18n}
|
phoneNumber: '',
|
||||||
isMe={false}
|
profileName: '',
|
||||||
title={text('title', 'Unknown contact')}
|
title: '',
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={text('name', '')}
|
|
||||||
profileName={text('profileName', '')}
|
|
||||||
phoneNumber={text('phoneNumber', '')}
|
|
||||||
conversationType="direct"
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectNoGroupsNoData.story = {
|
DirectNoGroupsNoData.story = {
|
||||||
name: 'Direct (No Groups, No Data)',
|
name: 'Direct (No Groups, No Data)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DirectNoGroupsNoDataNotAccepted = (): JSX.Element => {
|
export const DirectNoGroupsNoDataNotAccepted = Template.bind({});
|
||||||
return (
|
DirectNoGroupsNoDataNotAccepted.args = {
|
||||||
<div style={{ width: '480px' }}>
|
acceptedMessageRequest: false,
|
||||||
<Wrapper
|
avatarPath: undefined,
|
||||||
i18n={i18n}
|
name: '',
|
||||||
isMe={false}
|
phoneNumber: '',
|
||||||
title={text('title', 'Unknown contact')}
|
profileName: '',
|
||||||
acceptedMessageRequest={false}
|
title: '',
|
||||||
badge={undefined}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={text('name', '')}
|
|
||||||
profileName={text('profileName', '')}
|
|
||||||
phoneNumber={text('phoneNumber', '')}
|
|
||||||
conversationType="direct"
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DirectNoGroupsNoDataNotAccepted.story = {
|
DirectNoGroupsNoDataNotAccepted.story = {
|
||||||
name: 'Direct (No Groups, No Data, Not Accepted)',
|
name: 'Direct (No Groups, No Data, Not Accepted)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupManyMembers = (): JSX.Element => {
|
export const GroupManyMembers = Template.bind({});
|
||||||
return (
|
GroupManyMembers.args = {
|
||||||
<div style={{ width: '480px' }}>
|
conversationType: 'group',
|
||||||
<Wrapper
|
groupDescription: casual.sentence,
|
||||||
acceptedMessageRequest
|
membersCount: casual.integer(20, 100),
|
||||||
badge={undefined}
|
title: casual.title,
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={text('title', 'NYC Rock Climbers')}
|
|
||||||
name={text('groupName', 'NYC Rock Climbers')}
|
|
||||||
conversationType="group"
|
|
||||||
membersCount={numberKnob('membersCount', 22)}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GroupManyMembers.story = {
|
GroupManyMembers.story = {
|
||||||
name: 'Group (many members)',
|
name: 'Group (many members)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupOneMember = (): JSX.Element => {
|
export const GroupOneMember = Template.bind({});
|
||||||
return (
|
GroupOneMember.args = {
|
||||||
<div style={{ width: '480px' }}>
|
avatarPath: undefined,
|
||||||
<Wrapper
|
conversationType: 'group',
|
||||||
acceptedMessageRequest
|
groupDescription: casual.sentence,
|
||||||
badge={undefined}
|
membersCount: 1,
|
||||||
i18n={i18n}
|
title: casual.title,
|
||||||
isMe={false}
|
|
||||||
title={text('title', 'NYC Rock Climbers')}
|
|
||||||
name={text('groupName', 'NYC Rock Climbers')}
|
|
||||||
conversationType="group"
|
|
||||||
membersCount={1}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GroupOneMember.story = {
|
GroupOneMember.story = {
|
||||||
name: 'Group (one member)',
|
name: 'Group (one member)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupZeroMembers = (): JSX.Element => {
|
export const GroupZeroMembers = Template.bind({});
|
||||||
return (
|
GroupZeroMembers.args = {
|
||||||
<div style={{ width: '480px' }}>
|
avatarPath: undefined,
|
||||||
<Wrapper
|
conversationType: 'group',
|
||||||
acceptedMessageRequest
|
groupDescription: casual.sentence,
|
||||||
badge={undefined}
|
membersCount: 0,
|
||||||
i18n={i18n}
|
title: casual.title,
|
||||||
isMe={false}
|
|
||||||
title={text('title', 'NYC Rock Climbers')}
|
|
||||||
name={text('groupName', 'NYC Rock Climbers')}
|
|
||||||
conversationType="group"
|
|
||||||
groupDescription="This is a group for all the rock climbers of NYC"
|
|
||||||
membersCount={0}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GroupZeroMembers.story = {
|
GroupZeroMembers.story = {
|
||||||
name: 'Group (zero members)',
|
name: 'Group (zero members)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupLongGroupDescription = (): JSX.Element => {
|
export const GroupLongGroupDescription = Template.bind({});
|
||||||
return (
|
GroupLongGroupDescription.args = {
|
||||||
<div style={{ width: '480px' }}>
|
conversationType: 'group',
|
||||||
<Wrapper
|
groupDescription:
|
||||||
acceptedMessageRequest
|
"This is a group for all the rock climbers of NYC. We really like to climb rocks and these NYC people climb any rock. No rock is too small or too big to be climbed. We will ascend upon all rocks, and not just in NYC, in the whole world. We are just getting started, NYC is just the beginning, watch out rocks in the galaxy. Kuiper belt I'm looking at you. We will put on a space suit and climb all your rocks. No rock is near nor far for the rock climbers of NYC.",
|
||||||
badge={undefined}
|
membersCount: casual.integer(1, 10),
|
||||||
i18n={i18n}
|
title: casual.title,
|
||||||
isMe={false}
|
|
||||||
title={text('title', 'NYC Rock Climbers')}
|
|
||||||
name={text('groupName', 'NYC Rock Climbers')}
|
|
||||||
conversationType="group"
|
|
||||||
groupDescription="This is a group for all the rock climbers of NYC. We really like to climb rocks and these NYC people climb any rock. No rock is too small or too big to be climbed. We will ascend upon all rocks, and not just in NYC, in the whole world. We are just getting started, NYC is just the beginning, watch out rocks in the galaxy. Kuiper belt I'm looking at you. We will put on a space suit and climb all your rocks. No rock is near nor far for the rock climbers of NYC."
|
|
||||||
membersCount={0}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GroupLongGroupDescription.story = {
|
GroupLongGroupDescription.story = {
|
||||||
name: 'Group (long group description)',
|
name: 'Group (long group description)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupNoName = (): JSX.Element => {
|
export const GroupNoName = Template.bind({});
|
||||||
return (
|
GroupNoName.args = {
|
||||||
<div style={{ width: '480px' }}>
|
conversationType: 'group',
|
||||||
<Wrapper
|
membersCount: 0,
|
||||||
acceptedMessageRequest
|
name: '',
|
||||||
badge={undefined}
|
title: '',
|
||||||
i18n={i18n}
|
|
||||||
isMe={false}
|
|
||||||
title={text('title', 'Unknown group')}
|
|
||||||
name={text('groupName', '')}
|
|
||||||
conversationType="group"
|
|
||||||
membersCount={0}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GroupNoName.story = {
|
GroupNoName.story = {
|
||||||
name: 'Group (No name)',
|
name: 'Group (No name)',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoteToSelf = (): JSX.Element => {
|
export const NoteToSelf = Template.bind({});
|
||||||
return (
|
NoteToSelf.args = {
|
||||||
<div style={{ width: '480px' }}>
|
isMe: true,
|
||||||
<Wrapper
|
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
|
||||||
i18n={i18n}
|
|
||||||
isMe
|
|
||||||
title={getTitle()}
|
|
||||||
conversationType="direct"
|
|
||||||
phoneNumber={getPhoneNumber()}
|
|
||||||
sharedGroupNames={[]}
|
|
||||||
unblurAvatar={action('unblurAvatar')}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NoteToSelf.story = {
|
NoteToSelf.story = {
|
||||||
name: 'Note to Self',
|
name: 'Note to Self',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const UnreadStories = Template.bind({});
|
||||||
|
UnreadStories.args = {
|
||||||
|
hasStories: HasStories.Unread,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReadStories = Template.bind({});
|
||||||
|
ReadStories.args = {
|
||||||
|
hasStories: HasStories.Read,
|
||||||
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { About } from './About';
|
||||||
import { GroupDescription } from './GroupDescription';
|
import { GroupDescription } from './GroupDescription';
|
||||||
import { SharedGroupNames } from '../SharedGroupNames';
|
import { SharedGroupNames } from '../SharedGroupNames';
|
||||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||||
|
import type { HasStories } from '../../types/Stories';
|
||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||||
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
|
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
|
||||||
|
@ -18,6 +19,8 @@ export type Props = {
|
||||||
about?: string;
|
about?: string;
|
||||||
acceptedMessageRequest?: boolean;
|
acceptedMessageRequest?: boolean;
|
||||||
groupDescription?: string;
|
groupDescription?: string;
|
||||||
|
hasStories?: HasStories;
|
||||||
|
id: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
membersCount?: number;
|
membersCount?: number;
|
||||||
|
@ -27,6 +30,7 @@ export type Props = {
|
||||||
unblurredAvatarPath?: string;
|
unblurredAvatarPath?: string;
|
||||||
updateSharedGroups: () => unknown;
|
updateSharedGroups: () => unknown;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
|
viewUserStories: (cid: string) => unknown;
|
||||||
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
|
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
|
||||||
|
|
||||||
const renderMembershipRow = ({
|
const renderMembershipRow = ({
|
||||||
|
@ -101,6 +105,8 @@ export const ConversationHero = ({
|
||||||
color,
|
color,
|
||||||
conversationType,
|
conversationType,
|
||||||
groupDescription,
|
groupDescription,
|
||||||
|
hasStories,
|
||||||
|
id,
|
||||||
isMe,
|
isMe,
|
||||||
membersCount,
|
membersCount,
|
||||||
sharedGroupNames = [],
|
sharedGroupNames = [],
|
||||||
|
@ -112,6 +118,7 @@ export const ConversationHero = ({
|
||||||
unblurAvatar,
|
unblurAvatar,
|
||||||
unblurredAvatarPath,
|
unblurredAvatarPath,
|
||||||
updateSharedGroups,
|
updateSharedGroups,
|
||||||
|
viewUserStories,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const [isShowingMessageRequestWarning, setIsShowingMessageRequestWarning] =
|
const [isShowingMessageRequestWarning, setIsShowingMessageRequestWarning] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
@ -124,7 +131,7 @@ export const ConversationHero = ({
|
||||||
updateSharedGroups();
|
updateSharedGroups();
|
||||||
}, [updateSharedGroups]);
|
}, [updateSharedGroups]);
|
||||||
|
|
||||||
let avatarBlur: AvatarBlur;
|
let avatarBlur: AvatarBlur = AvatarBlur.NoBlur;
|
||||||
let avatarOnClick: undefined | (() => void);
|
let avatarOnClick: undefined | (() => void);
|
||||||
if (
|
if (
|
||||||
shouldBlurAvatar({
|
shouldBlurAvatar({
|
||||||
|
@ -137,8 +144,10 @@ export const ConversationHero = ({
|
||||||
) {
|
) {
|
||||||
avatarBlur = AvatarBlur.BlurPictureWithClickToView;
|
avatarBlur = AvatarBlur.BlurPictureWithClickToView;
|
||||||
avatarOnClick = unblurAvatar;
|
avatarOnClick = unblurAvatar;
|
||||||
} else {
|
} else if (hasStories) {
|
||||||
avatarBlur = AvatarBlur.NoBlur;
|
avatarOnClick = () => {
|
||||||
|
viewUserStories(id);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const phoneNumberOnly = Boolean(
|
const phoneNumberOnly = Boolean(
|
||||||
|
@ -165,6 +174,7 @@ export const ConversationHero = ({
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={112}
|
size={112}
|
||||||
|
storyRing={hasStories}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -491,19 +491,21 @@ const renderHeroRow = () => {
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
about={getAbout()}
|
about={getAbout()}
|
||||||
acceptedMessageRequest
|
acceptedMessageRequest
|
||||||
|
avatarPath={getAvatarPath()}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
|
conversationType="direct"
|
||||||
|
id={getDefaultConversation().id}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
isMe={false}
|
||||||
title={getTitle()}
|
|
||||||
avatarPath={getAvatarPath()}
|
|
||||||
name={getName()}
|
name={getName()}
|
||||||
profileName={getProfileName()}
|
|
||||||
phoneNumber={getPhoneNumber()}
|
phoneNumber={getPhoneNumber()}
|
||||||
conversationType="direct"
|
profileName={getProfileName()}
|
||||||
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
title={getTitle()}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
unblurAvatar={action('unblurAvatar')}
|
||||||
updateSharedGroups={noop}
|
updateSharedGroups={noop}
|
||||||
|
viewUserStories={action('viewUserStories')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ import type {
|
||||||
StoryDataType,
|
StoryDataType,
|
||||||
StoriesStateType,
|
StoriesStateType,
|
||||||
} from '../ducks/stories';
|
} from '../ducks/stories';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { HasStories, MY_STORIES_ID } from '../../types/Stories';
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import { SendStatus } from '../../messages/MessageSendState';
|
import { SendStatus } from '../../messages/MessageSendState';
|
||||||
import { canReply } from './message';
|
import { canReply } from './message';
|
||||||
|
@ -30,6 +30,7 @@ import {
|
||||||
getMe,
|
getMe,
|
||||||
} from './conversations';
|
} from './conversations';
|
||||||
import { getDistributionListSelector } from './storyDistributionLists';
|
import { getDistributionListSelector } from './storyDistributionLists';
|
||||||
|
import { getStoriesEnabled } from './items';
|
||||||
|
|
||||||
export const getStoriesState = (state: StateType): StoriesStateType =>
|
export const getStoriesState = (state: StateType): StoriesStateType =>
|
||||||
state.stories;
|
state.stories;
|
||||||
|
@ -348,3 +349,28 @@ export const getUnreadStoriesCount = createSelector(
|
||||||
.length;
|
.length;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getHasStoriesSelector = createSelector(
|
||||||
|
getStoriesEnabled,
|
||||||
|
getStoriesState,
|
||||||
|
(isEnabled, { stories }) =>
|
||||||
|
(conversationId?: string): HasStories | undefined => {
|
||||||
|
if (!isEnabled || !conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversationStories = stories.filter(
|
||||||
|
story => story.conversationId === conversationId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!conversationStories.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversationStories.some(
|
||||||
|
story => story.readStatus === ReadStatus.Unread
|
||||||
|
)
|
||||||
|
? HasStories.Unread
|
||||||
|
: HasStories.Read;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { getAreWeASubscriber } from '../selectors/items';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getBadgesSelector } from '../selectors/badges';
|
import { getBadgesSelector } from '../selectors/badges';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
|
import { getHasStoriesSelector } from '../selectors/stories';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType): PropsDataType => {
|
const mapStateToProps = (state: StateType): PropsDataType => {
|
||||||
const { contactId, conversationId } =
|
const { contactId, conversationId } =
|
||||||
|
@ -35,12 +36,15 @@ const mapStateToProps = (state: StateType): PropsDataType => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasStories = getHasStoriesSelector(state)(conversationId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
areWeASubscriber: getAreWeASubscriber(state),
|
areWeASubscriber: getAreWeASubscriber(state),
|
||||||
areWeAdmin,
|
areWeAdmin,
|
||||||
badges: getBadgesSelector(state)(contact.badges),
|
badges: getBadgesSelector(state)(contact.badges),
|
||||||
contact,
|
contact,
|
||||||
conversation: currentConversation,
|
conversation: currentConversation,
|
||||||
|
hasStories,
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isMember,
|
isMember,
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
import type { ConversationType } from '../ducks/conversations';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
import {
|
import {
|
||||||
ConversationHeader,
|
ConversationHeader,
|
||||||
OutgoingCallButtonStyle,
|
OutgoingCallButtonStyle,
|
||||||
|
@ -12,15 +14,15 @@ import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
isMissingRequiredProfileSharing,
|
isMissingRequiredProfileSharing,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { CallMode } from '../../types/Calling';
|
import { CallMode } from '../../types/Calling';
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
|
||||||
import { getConversationCallMode } from '../ducks/conversations';
|
|
||||||
import { getActiveCall, isAnybodyElseInGroupCall } from '../ducks/calling';
|
import { getActiveCall, isAnybodyElseInGroupCall } from '../ducks/calling';
|
||||||
import { getUserACI, getIntl, getTheme } from '../selectors/user';
|
import { getConversationCallMode } from '../ducks/conversations';
|
||||||
|
import { getHasStoriesSelector } from '../selectors/stories';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { getUserACI, getIntl, getTheme } from '../selectors/user';
|
||||||
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
|
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
|
||||||
|
import { mapDispatchToProps } from '../actions';
|
||||||
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
|
@ -83,6 +85,8 @@ const mapStateToProps = (state: StateType, ownProps: OwnProps) => {
|
||||||
throw new Error('Could not find conversation');
|
throw new Error('Could not find conversation');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasStories = getHasStoriesSelector(state)(id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...pick(conversation, [
|
...pick(conversation, [
|
||||||
'acceptedMessageRequest',
|
'acceptedMessageRequest',
|
||||||
|
@ -110,6 +114,7 @@ const mapStateToProps = (state: StateType, ownProps: OwnProps) => {
|
||||||
]),
|
]),
|
||||||
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
||||||
conversationTitle: state.conversations.selectedConversationTitle,
|
conversationTitle: state.conversations.selectedConversationTitle,
|
||||||
|
hasStories,
|
||||||
isMissingMandatoryProfileSharing:
|
isMissingMandatoryProfileSharing:
|
||||||
isMissingRequiredProfileSharing(conversation),
|
isMissingRequiredProfileSharing(conversation),
|
||||||
isSMSOnly: isConversationSMSOnly(conversation),
|
isSMSOnly: isConversationSMSOnly(conversation),
|
||||||
|
@ -120,6 +125,6 @@ const mapStateToProps = (state: StateType, ownProps: OwnProps) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, {});
|
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
export const SmartConversationHeader = smart(ConversationHeader);
|
export const SmartConversationHeader = smart(ConversationHeader);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { ConversationHero } from '../../components/conversation/ConversationHero
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
|
import { getHasStoriesSelector } from '../selectors/stories';
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -27,6 +28,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
...conversation,
|
...conversation,
|
||||||
conversationType: conversation.type,
|
conversationType: conversation.type,
|
||||||
|
hasStories: getHasStoriesSelector(state)(id),
|
||||||
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
};
|
};
|
||||||
|
|
|
@ -127,3 +127,8 @@ export function getStoryDistributionListName(
|
||||||
): string {
|
): string {
|
||||||
return id === MY_STORIES_ID ? i18n('Stories__mine') : name;
|
return id === MY_STORIES_ID ? i18n('Stories__mine') : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum HasStories {
|
||||||
|
Read = 'Read',
|
||||||
|
Unread = 'Unread',
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue