Update call tab design based on feedback

This commit is contained in:
Jamie Kyle 2023-08-10 15:16:51 -07:00 committed by Jamie Kyle
parent ce28993c78
commit 3268d3e6eb
32 changed files with 601 additions and 289 deletions

View file

@ -6684,7 +6684,7 @@
"description": "Calls Tab > Calls List > Search Input > Placeholder" "description": "Calls Tab > Calls List > Search Input > Placeholder"
}, },
"icu:CallsList__ToggleFilterByMissedLabel": { "icu:CallsList__ToggleFilterByMissedLabel": {
"messageformat": "Toggle filter by missed", "messageformat": "Filter by missed",
"description": "Calls Tab > Calls List > Toggle search filter by missed > Accessibility label" "description": "Calls Tab > Calls List > Toggle search filter by missed > Accessibility label"
}, },
"icu:CallsList__ToggleFilterByMissed__RoleDescription": { "icu:CallsList__ToggleFilterByMissed__RoleDescription": {

View file

@ -868,6 +868,7 @@ $rtl-icon-map: (
} }
@mixin NavTabs__Scroller { @mixin NavTabs__Scroller {
padding-bottom: 8px;
@include scrollbar; @include scrollbar;
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
@include light-theme { @include light-theme {

View file

@ -6974,7 +6974,8 @@ button.module-image__border-overlay:focus {
content: ''; content: '';
display: block; display: block;
height: 0; height: 0;
margin-inline-start: -6px; /* stylelint-disable-next-line liberty/use-logical-spec */
margin-left: -6px;
margin-top: -6px; margin-top: -6px;
position: absolute; position: absolute;
width: 0; width: 0;
@ -6993,12 +6994,15 @@ button.module-image__border-overlay:focus {
&[data-placement='right'] { &[data-placement='right'] {
.module-tooltip-arrow { .module-tooltip-arrow {
inset-inline-start: 0; /* stylelint-disable-next-line liberty/use-logical-spec */
left: 0;
} }
.module-tooltip-arrow::after { .module-tooltip-arrow::after {
inset-inline-start: -6px; /* stylelint-disable-next-line liberty/use-logical-spec */
border-inline-end-color: var(--tooltip-background-color); left: -6px;
/* stylelint-disable-next-line liberty/use-logical-spec */
border-right-color: var(--tooltip-background-color);
} }
} }
@ -7015,12 +7019,15 @@ button.module-image__border-overlay:focus {
&[data-placement='left'] { &[data-placement='left'] {
.module-tooltip-arrow { .module-tooltip-arrow {
inset-inline-end: 0; /* stylelint-disable-next-line liberty/use-logical-spec */
right: 0;
} }
.module-tooltip-arrow::after { .module-tooltip-arrow::after {
inset-inline-end: -12px; /* stylelint-disable-next-line liberty/use-logical-spec */
border-inline-start-color: var(--tooltip-background-color); right: -12px;
/* stylelint-disable-next-line liberty/use-logical-spec */
border-left-color: var(--tooltip-background-color);
} }
} }
} }

View file

@ -286,4 +286,5 @@ $z-index-above-context-menu: 126;
$NavTabs__width: 80px; $NavTabs__width: 80px;
// These values are 'block' specific to coordinate with the NavSidebar__Header // These values are 'block' specific to coordinate with the NavSidebar__Header
$NavTabs__Item__blockPadding: 2px; $NavTabs__Item__blockPadding: 2px;
$NavTabs__Toggle__blockPadding: 8px;
$NavTabs__ItemButton__blockPadding: 10px; $NavTabs__ItemButton__blockPadding: 10px;

View file

@ -52,6 +52,8 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
padding-block: 80px; padding-block: 80px;
padding-inline: 24px;
user-select: none;
} }
.CallsTab__ClearCallHistoryIcon { .CallsTab__ClearCallHistoryIcon {
@ -183,8 +185,8 @@
.CallsList__LoadingAvatar { .CallsList__LoadingAvatar {
display: block; display: block;
width: 32px; width: 36px;
height: 32px; height: 36px;
border-radius: 9999px; border-radius: 9999px;
} }
@ -206,10 +208,20 @@
font-weight: bold; font-weight: bold;
} }
.CallsList__ItemCallInfo--missed { .CallsList__ItemCallInfo {
@include font-body-1;
}
// Override .ListTile__subtitle so ellipsis is correct color
.CallsList__Item--missed .ListTile__subtitle {
color: $color-accent-red; color: $color-accent-red;
} }
// Override .ListTile
.ListTile.CallsList__ItemTile {
padding-block: 12px;
}
.CallsList__Item--selected .CallsList__ItemTile { .CallsList__Item--selected .CallsList__ItemTile {
@include light-theme { @include light-theme {
background-color: $color-gray-15; background-color: $color-gray-15;

View file

@ -99,7 +99,6 @@
&__popper--single-item &__option { &__popper--single-item &__option {
padding-block: 12px; padding-block: 12px;
padding-inline: 6px;
} }
&__divider { &__divider {

View file

@ -30,6 +30,7 @@
justify-content: center; justify-content: center;
padding-bottom: 8px; padding-bottom: 8px;
padding-top: 12px; padding-top: 12px;
user-select: text;
} }
&__subtitle { &__subtitle {
@ -41,6 +42,11 @@
@include dark-theme { @include dark-theme {
color: $color-gray-25; color: $color-gray-25;
} }
&__about,
&__phone-number {
user-select: text;
}
} }
&__root--editable &__title { &__root--editable &__title {
@ -513,11 +519,6 @@
} }
} }
.ConversationDetails__CallHistoryGroup__header {
@include font-title-2;
margin-block: 24px 16px;
}
.ConversationDetails__CallHistoryGroup__List { .ConversationDetails__CallHistoryGroup__List {
list-style: none; list-style: none;
margin: 0; margin: 0;

View file

@ -20,6 +20,7 @@
&__body { &__body {
margin-top: calc(#{$header-height} + var(--title-bar-drag-area-height)); margin-top: calc(#{$header-height} + var(--title-bar-drag-area-height));
padding-inline: 24px;
} }
&__header { &__header {

View file

@ -7,6 +7,7 @@
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
display: -webkit-box; display: -webkit-box;
overflow: hidden; overflow: hidden;
user-select: text;
a { a {
@include light-theme { @include light-theme {

View file

@ -125,6 +125,11 @@
@include sr-only; @include sr-only;
} }
.NavSidebar .module-SearchInput__container {
// override .module-SearchInput__container
margin: 0;
}
.NavSidebar__Content { .NavSidebar__Content {
flex: 1 1 0%; flex: 1 1 0%;
min-height: 0; min-height: 0;

View file

@ -1,6 +1,10 @@
// Copyright 2023 Signal Messenger, LLC // Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
$NavTabs__ToggleButton__blockPadding: 4px;
$NavTabs__ItemIcon__size: 20px;
$NavTabs__ProfileAvatar__size: 28px;
// This effectively wraps the entire app // This effectively wraps the entire app
.NavTabs__Container { .NavTabs__Container {
position: relative; position: relative;
@ -14,6 +18,7 @@
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
flex-direction: column; flex-direction: column;
align-items: center;
width: $NavTabs__width; width: $NavTabs__width;
height: 100%; height: 100%;
padding-top: var(--title-bar-drag-area-height); padding-top: var(--title-bar-drag-area-height);
@ -43,6 +48,12 @@
// Handled by .NavTabs__ItemButton // Handled by .NavTabs__ItemButton
outline: none; outline: none;
} }
&.NavTabs__Toggle {
padding-block: calc(
$NavTabs__Item__blockPadding + $NavTabs__ItemButton__blockPadding -
$NavTabs__ToggleButton__blockPadding
);
}
} }
.NavTabs__ItemButton { .NavTabs__ItemButton {
@ -50,7 +61,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: $NavTabs__ItemButton__blockPadding; padding-block: $NavTabs__ItemButton__blockPadding;
border-radius: 8px; border-radius: 8px;
.NavTabs__Item:hover &, .NavTabs__Item:hover &,
.NavTabs__Item:focus-visible & { .NavTabs__Item:focus-visible & {
@ -73,6 +84,21 @@
background: $color-gray-62; background: $color-gray-62;
} }
} }
.NavTabs__Toggle & {
width: fit-content;
padding: $NavTabs__ToggleButton__blockPadding;
margin-block: 0;
margin-inline: auto;
border-radius: 4px;
}
.NavTabs__Item--Profile & {
// Normalize for the size difference of the avatar vs sidebar icons
padding-block: calc(
$NavTabs__ItemButton__blockPadding -
(($NavTabs__ProfileAvatar__size - $NavTabs__ItemIcon__size) / 2)
);
}
} }
.NavTabs__ItemContent { .NavTabs__ItemContent {
@ -107,8 +133,8 @@
.NavTabs__ItemIcon { .NavTabs__ItemIcon {
display: block; display: block;
width: 20px; width: $NavTabs__ItemIcon__size;
height: 20px; height: $NavTabs__ItemIcon__size;
} }
@mixin NavTabs__Icon($icon) { @mixin NavTabs__Icon($icon) {
@ -157,10 +183,18 @@
} }
.NavTabs__TabList { .NavTabs__TabList {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex: 1; flex: 1;
} }
.NavTabs__Misc { .NavTabs__Misc {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 8px; padding-bottom: 8px;
} }

View file

@ -5,6 +5,8 @@
&__container { &__container {
position: relative; position: relative;
flex: 1 0 0; flex: 1 0 0;
margin-inline: 16px;
margin-bottom: 8px;
} }
&__icon { &__icon {

View file

@ -83,7 +83,6 @@
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
overflow-y: overlay; overflow-y: overlay;
padding-block: 0;
padding-inline: 16px; padding-inline: 16px;
&--empty { &--empty {

View file

@ -45,7 +45,13 @@
&::before { &::before {
@include rounded-corners; @include rounded-corners;
background: inherit; background: inherit;
border: 1.5px solid $color-gray-60; border: 1.5px solid;
@include light-theme {
border-color: $color-gray-25;
}
@include dark-theme {
border-color: $color-gray-60;
}
content: ''; content: '';
display: block; display: block;
height: 20px; height: 20px;
@ -80,7 +86,12 @@
&__viewers { &__viewers {
display: flex; display: flex;
@include font-body-2; @include font-body-2;
color: $color-gray-25; @include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
} }
&__left { &__left {
@ -96,13 +107,23 @@
align-items: center; align-items: center;
width: 32px; width: 32px;
height: 32px; height: 32px;
background: $color-gray-75; @include light-theme {
background: $color-gray-15;
}
@include dark-theme {
background: $color-gray-75;
}
&::after { &::after {
@include color-svg($svg, $color-white);
content: ''; content: '';
height: 20px; height: 20px;
width: 20px; width: 20px;
@include light-theme {
@include color-svg($svg, $color-black);
}
@include dark-theme {
@include color-svg($svg, $color-white);
}
} }
} }
@ -126,10 +147,22 @@
&__delete { &__delete {
@include button-reset; @include button-reset;
@include color-svg('../images/icons/v3/trash/trash.svg', $color-gray-25);
height: 20px; height: 20px;
width: 20px; width: 20px;
visibility: hidden; visibility: hidden;
@include light-theme {
@include color-svg(
'../images/icons/v3/trash/trash.svg',
$color-gray-45
);
}
@include dark-theme {
@include color-svg(
'../images/icons/v3/trash/trash.svg',
$color-gray-25
);
}
} }
&:hover &__delete { &:hover &__delete {
@ -139,8 +172,14 @@
&__divider { &__divider {
width: 100%; width: 100%;
border: 0 solid $color-gray-65; border: 0 solid;
border-top-width: 1px; border-top-width: 1px;
@include light-theme {
border-color: $color-gray-15;
}
@include dark-theme {
border-color: $color-gray-65;
}
} }
&__input__container { &__input__container {
@ -150,8 +189,13 @@
&__visibility { &__visibility {
@include font-subtitle; @include font-subtitle;
color: $color-gray-25;
margin-top: 10px; margin-top: 10px;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
} }
&__title { &__title {
@ -161,9 +205,14 @@
&__description { &__description {
@include font-subtitle; @include font-subtitle;
color: $color-gray-25;
margin-top: 0px; margin-top: 0px;
margin-bottom: 16px; margin-bottom: 16px;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
} }
&__listHeader { &__listHeader {
@ -215,7 +264,12 @@
} }
&__checkbox-description { &__checkbox-description {
color: $color-gray-25; @include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
} }
&__conversation-list { &__conversation-list {
@ -226,11 +280,21 @@
&__disclaimer { &__disclaimer {
@include font-subtitle; @include font-subtitle;
color: $color-gray-25; @include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
&__learn-more { &__learn-more {
@include button-reset; @include button-reset;
color: $color-gray-05; @include light-theme {
color: $color-gray-90;
}
@include dark-theme {
color: $color-gray-05;
}
} }
} }
@ -242,8 +306,13 @@
&__stories-off-text { &__stories-off-text {
flex: 1; flex: 1;
color: $color-gray-25;
@include font-subtitle; @include font-subtitle;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
} }
} }
@ -271,7 +340,12 @@
&__members_help { &__members_help {
@include font-body-2; @include font-body-2;
color: $color-gray-25; @include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
} }
&__remove_group { &__remove_group {

View file

@ -167,16 +167,16 @@
} }
} }
&__icon { @mixin StoryListItem__Icon($path) {
@mixin StoryListItem__Icon($path) { @include light-theme {
@include light-theme { @include color-svg($path, $color-black);
@include color-svg($path, $color-black);
}
@include dark-theme {
@include color-svg($path, $color-white);
}
} }
@include dark-theme {
@include color-svg($path, $color-white);
}
}
&__icon {
&--chat { &--chat {
@include StoryListItem__Icon('../images/icons/v3/open/open-compact.svg'); @include StoryListItem__Icon('../images/icons/v3/open/open-compact.svg');
} }
@ -203,9 +203,8 @@
margin-inline-start: 2px; margin-inline-start: 2px;
vertical-align: middle; vertical-align: middle;
width: 16px; width: 16px;
@include color-svg( @include StoryListItem__Icon(
'../images/icons/v3/chevron/chevron-right-compact-bold.svg', '../images/icons/v3/chevron/chevron-right-compact-bold.svg'
$color-white
); );
} }
} }

View file

@ -38,6 +38,7 @@ export enum AvatarSize {
TWENTY = 20, TWENTY = 20,
TWENTY_EIGHT = 28, TWENTY_EIGHT = 28,
THIRTY_TWO = 32, THIRTY_TWO = 32,
THIRTY_SIX = 36,
FORTY_EIGHT = 48, FORTY_EIGHT = 48,
FIFTY_TWO = 52, FIFTY_TWO = 52,
EIGHTY = 80, EIGHTY = 80,

View file

@ -40,6 +40,9 @@ import { Intl } from './Intl';
import { NavSidebarSearchHeader } from './NavSidebar'; import { NavSidebarSearchHeader } from './NavSidebar';
import { SizeObserver } from '../hooks/useSizeObserver'; import { SizeObserver } from '../hooks/useSizeObserver';
import { formatCallHistoryGroup } from '../util/callDisposition'; import { formatCallHistoryGroup } from '../util/callDisposition';
import { CallsNewCallButton } from './CallsNewCall';
import { Tooltip, TooltipPlacement } from './Tooltip';
import { Theme } from '../util/theme';
function Timestamp({ function Timestamp({
i18n, i18n,
@ -100,6 +103,7 @@ const defaultPendingState: SearchState = {
}; };
type CallsListProps = Readonly<{ type CallsListProps = Readonly<{
hasActiveCall: boolean;
getCallHistoryGroupsCount: ( getCallHistoryGroupsCount: (
options: CallHistoryFilterOptions options: CallHistoryFilterOptions
) => Promise<number>; ) => Promise<number>;
@ -110,6 +114,8 @@ type CallsListProps = Readonly<{
getConversation: (id: string) => ConversationType | void; getConversation: (id: string) => ConversationType | void;
i18n: LocalizerType; i18n: LocalizerType;
selectedCallHistoryGroup: CallHistoryGroup | null; selectedCallHistoryGroup: CallHistoryGroup | null;
onOutgoingAudioCallInConversation: (conversationId: string) => void;
onOutgoingVideoCallInConversation: (conversationId: string) => void;
onSelectCallHistoryGroup: ( onSelectCallHistoryGroup: (
conversationId: string, conversationId: string,
selectedCallHistoryGroup: CallHistoryGroup selectedCallHistoryGroup: CallHistoryGroup
@ -117,15 +123,18 @@ type CallsListProps = Readonly<{
}>; }>;
function rowHeight() { function rowHeight() {
return ListTile.heightCompact; return ListTile.heightFull;
} }
export function CallsList({ export function CallsList({
hasActiveCall,
getCallHistoryGroupsCount, getCallHistoryGroupsCount,
getCallHistoryGroups, getCallHistoryGroups,
getConversation, getConversation,
i18n, i18n,
selectedCallHistoryGroup, selectedCallHistoryGroup,
onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation,
onSelectCallHistoryGroup, onSelectCallHistoryGroup,
}: CallsListProps): JSX.Element { }: CallsListProps): JSX.Element {
const infiniteLoaderRef = useRef<InfiniteLoader>(null); const infiniteLoaderRef = useRef<InfiniteLoader>(null);
@ -270,6 +279,7 @@ export function CallsList({
title={ title={
<span className="CallsList__LoadingText CallsList__LoadingText--title" /> <span className="CallsList__LoadingText CallsList__LoadingText--title" />
} }
subtitleMaxLines={1}
subtitle={ subtitle={
<span className="CallsList__LoadingText CallsList__LoadingText--subtitle" /> <span className="CallsList__LoadingText CallsList__LoadingText--subtitle" />
} }
@ -306,6 +316,7 @@ export function CallsList({
style={style} style={style}
className={classNames('CallsList__Item', { className={classNames('CallsList__Item', {
'CallsList__Item--selected': isSelected, 'CallsList__Item--selected': isSelected,
'CallsList__Item--missed': wasMissed,
})} })}
> >
<ListTile <ListTile
@ -320,17 +331,22 @@ export function CallsList({
isMe={false} isMe={false}
title={conversation.title} title={conversation.title}
sharedGroupNames={[]} sharedGroupNames={[]}
size={AvatarSize.THIRTY_TWO} size={AvatarSize.THIRTY_SIX}
badge={undefined} badge={undefined}
className="CallsList__ItemAvatar" className="CallsList__ItemAvatar"
/> />
} }
trailing={ trailing={
<span <CallsNewCallButton
className={classNames('CallsList__ItemIcon', { callType={item.type}
'CallsList__ItemIcon--Phone': item.type === CallType.Audio, hasActiveCall={hasActiveCall}
'CallsList__ItemIcon--Video': item.type !== CallType.Audio, onClick={() => {
})} if (item.type === CallType.Audio) {
onOutgoingAudioCallInConversation(conversation.id);
} else {
onOutgoingVideoCallInConversation(conversation.id);
}
}}
/> />
} }
title={ title={
@ -341,12 +357,9 @@ export function CallsList({
<UserText text={conversation.title} /> <UserText text={conversation.title} />
</span> </span>
} }
subtitleMaxLines={1}
subtitle={ subtitle={
<span <span className="CallsList__ItemCallInfo">
className={classNames('CallsList__ItemCallInfo', {
'CallsList__ItemCallInfo--missed': wasMissed,
})}
>
{item.children.length > 1 ? `(${item.children.length}) ` : ''} {item.children.length > 1 ? `(${item.children.length}) ` : ''}
{statusText} &middot;{' '} {statusText} &middot;{' '}
<Timestamp i18n={i18n} timestamp={item.timestamp} /> <Timestamp i18n={i18n} timestamp={item.timestamp} />
@ -360,10 +373,13 @@ export function CallsList({
); );
}, },
[ [
hasActiveCall,
searchState, searchState,
getConversation, getConversation,
selectedCallHistoryGroup, selectedCallHistoryGroup,
onSelectCallHistoryGroup, onSelectCallHistoryGroup,
onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation,
i18n, i18n,
] ]
); );
@ -402,21 +418,28 @@ export function CallsList({
onClear={handleSearchInputClear} onClear={handleSearchInputClear}
value={queryInput} value={queryInput}
/> />
<button <Tooltip
className={classNames('CallsList__ToggleFilterByMissed', { direction={TooltipPlacement.Bottom}
'CallsList__ToggleFilterByMissed--pressed': filteringByMissed, content={i18n('icu:CallsList__ToggleFilterByMissedLabel')}
})} theme={Theme.Dark}
type="button" delay={600}
aria-pressed={filteringByMissed}
aria-roledescription={i18n(
'icu:CallsList__ToggleFilterByMissed__RoleDescription'
)}
onClick={handleStatusToggle}
> >
<span className="CallsList__ToggleFilterByMissedLabel"> <button
{i18n('icu:CallsList__ToggleFilterByMissedLabel')} className={classNames('CallsList__ToggleFilterByMissed', {
</span> 'CallsList__ToggleFilterByMissed--pressed': filteringByMissed,
</button> })}
type="button"
aria-pressed={filteringByMissed}
aria-roledescription={i18n(
'icu:CallsList__ToggleFilterByMissed__RoleDescription'
)}
onClick={handleStatusToggle}
>
<span className="CallsList__ToggleFilterByMissedLabel">
{i18n('icu:CallsList__ToggleFilterByMissedLabel')}
</span>
</button>
</Tooltip>
</NavSidebarSearchHeader> </NavSidebarSearchHeader>
{hasEmptyResults && ( {hasEmptyResults && (

View file

@ -16,11 +16,11 @@ import { strictAssert } from '../util/assert';
import { UserText } from './UserText'; import { UserText } from './UserText';
import { Avatar, AvatarSize } from './Avatar'; import { Avatar, AvatarSize } from './Avatar';
import { Intl } from './Intl'; import { Intl } from './Intl';
import type { ActiveCallStateType } from '../state/ducks/calling';
import { SizeObserver } from '../hooks/useSizeObserver'; import { SizeObserver } from '../hooks/useSizeObserver';
import { CallType } from '../types/CallDisposition';
type CallsNewCallProps = Readonly<{ type CallsNewCallProps = Readonly<{
activeCall: ActiveCallStateType | undefined; hasActiveCall: boolean;
allConversations: ReadonlyArray<ConversationType>; allConversations: ReadonlyArray<ConversationType>;
i18n: LocalizerType; i18n: LocalizerType;
onSelectConversation: (conversationId: string) => void; onSelectConversation: (conversationId: string) => void;
@ -33,8 +33,39 @@ type Row =
| { kind: 'header'; title: string } | { kind: 'header'; title: string }
| { kind: 'conversation'; conversation: ConversationType }; | { kind: 'conversation'; conversation: ConversationType };
export function CallsNewCallButton({
callType,
hasActiveCall,
onClick,
}: {
callType: CallType;
hasActiveCall: boolean;
onClick: () => void;
}): JSX.Element {
return (
<button
type="button"
className="CallsNewCall__ItemActionButton"
aria-disabled={hasActiveCall}
onClick={event => {
event.stopPropagation();
if (!hasActiveCall) {
onClick();
}
}}
>
{callType === CallType.Audio && (
<span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Phone" />
)}
{callType !== CallType.Audio && (
<span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Video" />
)}
</button>
);
}
export function CallsNewCall({ export function CallsNewCall({
activeCall, hasActiveCall,
allConversations, allConversations,
i18n, i18n,
onSelectConversation, onSelectConversation,
@ -146,8 +177,6 @@ export function CallsNewCall({
); );
} }
const callButtonsDisabled = activeCall != null;
return ( return (
<div key={key} style={style}> <div key={key} style={style}>
<ListTile <ListTile
@ -168,33 +197,22 @@ export function CallsNewCall({
trailing={ trailing={
<div className="CallsNewCall__ItemActions"> <div className="CallsNewCall__ItemActions">
{item.conversation.type === 'direct' && ( {item.conversation.type === 'direct' && (
<button <CallsNewCallButton
type="button" callType={CallType.Audio}
className="CallsNewCall__ItemActionButton" hasActiveCall={hasActiveCall}
aria-disabled={callButtonsDisabled} onClick={() => {
onClick={event => { onOutgoingAudioCallInConversation(item.conversation.id);
event.stopPropagation();
if (!callButtonsDisabled) {
onOutgoingAudioCallInConversation(item.conversation.id);
}
}} }}
> />
<span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Phone" />
</button>
)} )}
<button <CallsNewCallButton
type="button" // It's okay if this is a group
className="CallsNewCall__ItemActionButton" callType={CallType.Video}
aria-disabled={callButtonsDisabled} hasActiveCall={hasActiveCall}
onClick={event => { onClick={() => {
event.stopPropagation(); onOutgoingVideoCallInConversation(item.conversation.id);
if (!callButtonsDisabled) {
onOutgoingVideoCallInConversation(item.conversation.id);
}
}} }}
> />
<span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Video" />
</button>
</div> </div>
} }
onClick={() => { onClick={() => {
@ -207,7 +225,7 @@ export function CallsNewCall({
[ [
rows, rows,
i18n, i18n,
activeCall, hasActiveCall,
onSelectConversation, onSelectConversation,
onOutgoingAudioCallInConversation, onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation, onOutgoingVideoCallInConversation,

View file

@ -198,18 +198,25 @@ export function CallsTab({
{sidebarView === CallsTabSidebarView.CallsListView && ( {sidebarView === CallsTabSidebarView.CallsListView && (
<CallsList <CallsList
key={CallsTabSidebarView.CallsListView} key={CallsTabSidebarView.CallsListView}
hasActiveCall={activeCall != null}
getCallHistoryGroupsCount={getCallHistoryGroupsCount} getCallHistoryGroupsCount={getCallHistoryGroupsCount}
getCallHistoryGroups={getCallHistoryGroups} getCallHistoryGroups={getCallHistoryGroups}
getConversation={getConversation} getConversation={getConversation}
i18n={i18n} i18n={i18n}
selectedCallHistoryGroup={selected?.callHistoryGroup ?? null} selectedCallHistoryGroup={selected?.callHistoryGroup ?? null}
onSelectCallHistoryGroup={handleSelectCallHistoryGroup} onSelectCallHistoryGroup={handleSelectCallHistoryGroup}
onOutgoingAudioCallInConversation={
handleOutgoingAudioCallInConversation
}
onOutgoingVideoCallInConversation={
handleOutgoingVideoCallInConversation
}
/> />
)} )}
{sidebarView === CallsTabSidebarView.NewCallView && ( {sidebarView === CallsTabSidebarView.NewCallView && (
<CallsNewCall <CallsNewCall
key={CallsTabSidebarView.NewCallView} key={CallsTabSidebarView.NewCallView}
activeCall={activeCall} hasActiveCall={activeCall != null}
allConversations={allConversations} allConversations={allConversations}
i18n={i18n} i18n={i18n}
regionCode={regionCode} regionCode={regionCode}

View file

@ -1,7 +1,7 @@
// Copyright 2019 Signal Messenger, LLC // Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { useEffect, useCallback, useMemo } from 'react'; import React, { useEffect, useCallback, useMemo, useRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
@ -26,8 +26,7 @@ import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import { usePrevious } from '../hooks/usePrevious'; import { usePrevious } from '../hooks/usePrevious';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
import type { DurationInSeconds } from '../util/durations'; import type { DurationInSeconds } from '../util/durations';
import type { WidthBreakpoint } from './_util'; import { WidthBreakpoint, getNavSidebarWidthBreakpoint } from './_util';
import { getNavSidebarWidthBreakpoint } from './_util';
import * as KeyboardLayout from '../services/keyboardLayout'; import * as KeyboardLayout from '../services/keyboardLayout';
import type { LookupConversationWithoutServiceIdActionsType } from '../util/lookupConversationWithoutServiceId'; import type { LookupConversationWithoutServiceIdActionsType } from '../util/lookupConversationWithoutServiceId';
import type { ShowConversationType } from '../state/ducks/conversations'; import type { ShowConversationType } from '../state/ducks/conversations';
@ -42,7 +41,7 @@ import type {
ReplaceAvatarActionType, ReplaceAvatarActionType,
SaveAvatarToDiskActionType, SaveAvatarToDiskActionType,
} from '../types/Avatar'; } from '../types/Avatar';
import { SizeObserver } from '../hooks/useSizeObserver'; import { useSizeObserver } from '../hooks/useSizeObserver';
import { import {
NavSidebar, NavSidebar,
NavSidebarActionButton, NavSidebarActionButton,
@ -479,7 +478,12 @@ export function LeftPane({
// It also ensures that we scroll to the top when switching views. // It also ensures that we scroll to the top when switching views.
const listKey = preRowsNode ? 1 : 0; const listKey = preRowsNode ? 1 : 0;
const widthBreakpoint = getNavSidebarWidthBreakpoint(300); const measureRef = useRef<HTMLDivElement>(null);
const measureSize = useSizeObserver(measureRef);
const widthBreakpoint = getNavSidebarWidthBreakpoint(
measureSize?.width ?? preferredWidthFromStorage
);
const commonDialogProps = { const commonDialogProps = {
i18n, i18n,
@ -548,7 +552,7 @@ export function LeftPane({
navTabsCollapsed={navTabsCollapsed} navTabsCollapsed={navTabsCollapsed}
onToggleNavTabsCollapse={toggleNavTabsCollapse} onToggleNavTabsCollapse={toggleNavTabsCollapse}
preferredLeftPaneWidth={preferredWidthFromStorage} preferredLeftPaneWidth={preferredWidthFromStorage}
requiresFullWidth={false} requiresFullWidth={modeSpecificProps.mode !== LeftPaneMode.Inbox}
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth} savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
actions={ actions={
<> <>
@ -603,91 +607,88 @@ export function LeftPane({
showChooseGroupMembers, showChooseGroupMembers,
})} })}
</div> </div>
<NavSidebarSearchHeader> {(widthBreakpoint === WidthBreakpoint.Wide ||
{helper.getSearchInput({ modeSpecificProps.mode !== LeftPaneMode.Inbox) && (
clearConversationSearch, <NavSidebarSearchHeader>
clearSearch, {helper.getSearchInput({
i18n, clearConversationSearch,
onChangeComposeSearchTerm: event => { clearSearch,
setComposeSearchTerm(event.target.value); i18n,
}, onChangeComposeSearchTerm: event => {
updateSearchTerm, setComposeSearchTerm(event.target.value);
showConversation, },
})} updateSearchTerm,
</NavSidebarSearchHeader> showConversation,
})}
</NavSidebarSearchHeader>
)}
<div className="module-left-pane__dialogs"> <div className="module-left-pane__dialogs">
{dialogs.map(({ key, dialog }) => ( {dialogs.map(({ key, dialog }) => (
<React.Fragment key={key}>{dialog}</React.Fragment> <React.Fragment key={key}>{dialog}</React.Fragment>
))} ))}
</div> </div>
{preRowsNode && <React.Fragment key={0}>{preRowsNode}</React.Fragment>} {preRowsNode && <React.Fragment key={0}>{preRowsNode}</React.Fragment>}
<SizeObserver> <div className="module-left-pane__list--measure" ref={measureRef}>
{(ref, size) => ( <div className="module-left-pane__list--wrapper">
<div className="module-left-pane__list--measure" ref={ref}> <div
<div className="module-left-pane__list--wrapper"> aria-live="polite"
<div className="module-left-pane__list"
aria-live="polite" data-supertab
className="module-left-pane__list" key={listKey}
data-supertab role="presentation"
key={listKey} tabIndex={-1}
role="presentation" >
tabIndex={-1} <ConversationList
> dimensions={measureSize ?? undefined}
<ConversationList getPreferredBadge={getPreferredBadge}
dimensions={size ?? undefined} getRow={getRow}
getPreferredBadge={getPreferredBadge} i18n={i18n}
getRow={getRow} onClickArchiveButton={showArchivedConversations}
i18n={i18n} onClickContactCheckbox={(
onClickArchiveButton={showArchivedConversations} conversationId: string,
onClickContactCheckbox={( disabledReason: undefined | ContactCheckboxDisabledReason
conversationId: string, ) => {
disabledReason: undefined | ContactCheckboxDisabledReason switch (disabledReason) {
) => { case undefined:
switch (disabledReason) { toggleConversationInChooseMembers(conversationId);
case undefined: break;
toggleConversationInChooseMembers(conversationId); case ContactCheckboxDisabledReason.AlreadyAdded:
break; case ContactCheckboxDisabledReason.MaximumContactsSelected:
case ContactCheckboxDisabledReason.AlreadyAdded: // These are no-ops.
case ContactCheckboxDisabledReason.MaximumContactsSelected: break;
// These are no-ops. default:
break; throw missingCaseError(disabledReason);
default: }
throw missingCaseError(disabledReason); }}
} showUserNotFoundModal={showUserNotFoundModal}
}} setIsFetchingUUID={setIsFetchingUUID}
showUserNotFoundModal={showUserNotFoundModal} lookupConversationWithoutServiceId={
setIsFetchingUUID={setIsFetchingUUID} lookupConversationWithoutServiceId
lookupConversationWithoutServiceId={ }
lookupConversationWithoutServiceId showConversation={showConversation}
} blockConversation={blockConversation}
showConversation={showConversation} onSelectConversation={onSelectConversation}
blockConversation={blockConversation} onOutgoingAudioCallInConversation={
onSelectConversation={onSelectConversation} onOutgoingAudioCallInConversation
onOutgoingAudioCallInConversation={ }
onOutgoingAudioCallInConversation onOutgoingVideoCallInConversation={
} onOutgoingVideoCallInConversation
onOutgoingVideoCallInConversation={ }
onOutgoingVideoCallInConversation removeConversation={
} isContactManagementEnabled ? removeConversation : undefined
removeConversation={ }
isContactManagementEnabled renderMessageSearchResult={renderMessageSearchResult}
? removeConversation rowCount={helper.getRowCount()}
: undefined scrollBehavior={scrollBehavior}
} scrollToRowIndex={rowIndexToScrollTo}
renderMessageSearchResult={renderMessageSearchResult} scrollable={isScrollable}
rowCount={helper.getRowCount()} shouldRecomputeRowHeights={shouldRecomputeRowHeights}
scrollBehavior={scrollBehavior} showChooseGroupMembers={showChooseGroupMembers}
scrollToRowIndex={rowIndexToScrollTo} theme={theme}
scrollable={isScrollable} />
shouldRecomputeRowHeights={shouldRecomputeRowHeights}
showChooseGroupMembers={showChooseGroupMembers}
theme={theme}
/>
</div>
</div>
</div> </div>
)} </div>
</SizeObserver> </div>
{footerContents && ( {footerContents && (
<div className="module-left-pane__footer">{footerContents}</div> <div className="module-left-pane__footer">{footerContents}</div>
)} )}

View file

@ -15,27 +15,38 @@ import { AvatarPopup } from './AvatarPopup';
import { handleOutsideClick } from '../util/handleOutsideClick'; import { handleOutsideClick } from '../util/handleOutsideClick';
import type { UnreadStats } from '../state/selectors/conversations'; import type { UnreadStats } from '../state/selectors/conversations';
import { NavTab } from '../state/ducks/nav'; import { NavTab } from '../state/ducks/nav';
import { Tooltip, TooltipPlacement } from './Tooltip';
import { Theme } from '../util/theme';
type NavTabProps = Readonly<{ type NavTabProps = Readonly<{
i18n: LocalizerType;
badge?: ReactNode; badge?: ReactNode;
iconClassName: string; iconClassName: string;
id: NavTab; id: NavTab;
label: string; label: string;
}>; }>;
function NavTabsItem({ badge, iconClassName, id, label }: NavTabProps) { function NavTabsItem({ i18n, badge, iconClassName, id, label }: NavTabProps) {
const isRTL = i18n.getLocaleDirection() === 'rtl';
return ( return (
<Tab id={id} data-testid={`NavTabsItem--${id}`} className="NavTabs__Item"> <Tab id={id} data-testid={`NavTabsItem--${id}`} className="NavTabs__Item">
<span className="NavTabs__ItemLabel">{label}</span> <span className="NavTabs__ItemLabel">{label}</span>
<span className="NavTabs__ItemButton"> <Tooltip
<span className="NavTabs__ItemContent"> content={label}
<span theme={Theme.Dark}
role="presentation" direction={isRTL ? TooltipPlacement.Left : TooltipPlacement.Right}
className={`NavTabs__ItemIcon ${iconClassName}`} delay={600}
/> >
{badge && <span className="NavTabs__ItemBadge">{badge}</span>} <span className="NavTabs__ItemButton">
<span className="NavTabs__ItemContent">
<span
role="presentation"
className={`NavTabs__ItemIcon ${iconClassName}`}
/>
{badge && <span className="NavTabs__ItemBadge">{badge}</span>}
</span>
</span> </span>
</span> </Tooltip>
</Tab> </Tab>
); );
} }
@ -59,23 +70,30 @@ export function NavTabsToggle({
function handleToggle() { function handleToggle() {
onToggleNavTabsCollapse(!navTabsCollapsed); onToggleNavTabsCollapse(!navTabsCollapsed);
} }
const label = navTabsCollapsed
? i18n('icu:NavTabsToggle__showTabs')
: i18n('icu:NavTabsToggle__hideTabs');
const isRTL = i18n.getLocaleDirection() === 'rtl';
return ( return (
<button <button
type="button" type="button"
className="NavTabs__Item NavTabs__Toggle" className="NavTabs__Item NavTabs__Toggle"
onClick={handleToggle} onClick={handleToggle}
> >
<span className="NavTabs__ItemButton"> <Tooltip
<span content={label}
role="presentation" theme={Theme.Dark}
className="NavTabs__ItemIcon NavTabs__ItemIcon--Menu" direction={isRTL ? TooltipPlacement.Left : TooltipPlacement.Right}
/> delay={600}
<span className="NavTabs__ItemLabel"> >
{navTabsCollapsed <span className="NavTabs__ItemButton">
? i18n('icu:NavTabsToggle__showTabs') <span
: i18n('icu:NavTabsToggle__hideTabs')} role="presentation"
className="NavTabs__ItemIcon NavTabs__ItemIcon--Menu"
/>
<span className="NavTabs__ItemLabel">{label}</span>
</span> </span>
</span> </Tooltip>
</button> </button>
); );
} }
@ -127,6 +145,8 @@ export function NavTabs({
onNavTabSelected(key as NavTab); onNavTabSelected(key as NavTab);
} }
const isRTL = i18n.getLocaleDirection() === 'rtl';
const [targetElement, setTargetElement] = useState<HTMLElement | null>(null); const [targetElement, setTargetElement] = useState<HTMLElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null); const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null); const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);
@ -202,6 +222,7 @@ export function NavTabs({
onSelectionChange={handleSelectionChange} onSelectionChange={handleSelectionChange}
> >
<NavTabsItem <NavTabsItem
i18n={i18n}
id={NavTab.Chats} id={NavTab.Chats}
label="Chats" label="Chats"
iconClassName="NavTabs__ItemIcon--Chats" iconClassName="NavTabs__ItemIcon--Chats"
@ -226,12 +247,14 @@ export function NavTabs({
} }
/> />
<NavTabsItem <NavTabsItem
i18n={i18n}
id={NavTab.Calls} id={NavTab.Calls}
label="Calls" label="Calls"
iconClassName="NavTabs__ItemIcon--Calls" iconClassName="NavTabs__ItemIcon--Calls"
/> />
{storiesEnabled && ( {storiesEnabled && (
<NavTabsItem <NavTabsItem
i18n={i18n}
id={NavTab.Stories} id={NavTab.Stories}
label="Stories" label="Stories"
iconClassName="NavTabs__ItemIcon--Stories" iconClassName="NavTabs__ItemIcon--Stories"
@ -252,49 +275,63 @@ export function NavTabs({
className="NavTabs__Item" className="NavTabs__Item"
onClick={onShowSettings} onClick={onShowSettings}
> >
<span className="NavTabs__ItemButton"> <Tooltip
<span content={i18n('icu:NavTabs__ItemLabel--Settings')}
role="presentation" theme={Theme.Dark}
className="NavTabs__ItemIcon NavTabs__ItemIcon--Settings" direction={TooltipPlacement.Right}
/> delay={600}
<span className="NavTabs__ItemLabel"> >
{i18n('icu:NavTabs__ItemLabel--Settings')} <span className="NavTabs__ItemButton">
<span
role="presentation"
className="NavTabs__ItemIcon NavTabs__ItemIcon--Settings"
/>
<span className="NavTabs__ItemLabel">
{i18n('icu:NavTabs__ItemLabel--Settings')}
</span>
</span> </span>
</span> </Tooltip>
</button> </button>
<button <button
type="button" type="button"
className="NavTabs__Item" className="NavTabs__Item NavTabs__Item--Profile"
data-supertab data-supertab
onClick={() => { onClick={() => {
setShowAvatarPopup(true); setShowAvatarPopup(true);
}} }}
aria-label={i18n('icu:NavTabs__ItemLabel--Profile')} aria-label={i18n('icu:NavTabs__ItemLabel--Profile')}
> >
<span className="NavTabs__ItemButton" ref={setTargetElement}> <Tooltip
<span className="NavTabs__ItemContent"> content={i18n('icu:NavTabs__ItemLabel--Profile')}
<Avatar theme={Theme.Dark}
acceptedMessageRequest direction={isRTL ? TooltipPlacement.Left : TooltipPlacement.Right}
avatarPath={me.avatarPath} delay={600}
badge={badge} >
className="module-main-header__avatar" <span className="NavTabs__ItemButton" ref={setTargetElement}>
color={me.color} <span className="NavTabs__ItemContent">
conversationType="direct" <Avatar
i18n={i18n} acceptedMessageRequest
isMe avatarPath={me.avatarPath}
phoneNumber={me.phoneNumber} badge={badge}
profileName={me.profileName} className="module-main-header__avatar"
theme={theme} color={me.color}
title={me.title} conversationType="direct"
// `sharedGroupNames` makes no sense for yourself, but i18n={i18n}
// `<Avatar>` needs it to determine blurring. isMe
sharedGroupNames={[]} phoneNumber={me.phoneNumber}
size={AvatarSize.TWENTY_EIGHT} profileName={me.profileName}
/> theme={theme}
{hasPendingUpdate && <div className="NavTabs__AvatarBadge" />} title={me.title}
// `sharedGroupNames` makes no sense for yourself, but
// `<Avatar>` needs it to determine blurring.
sharedGroupNames={[]}
size={AvatarSize.TWENTY_EIGHT}
/>
{hasPendingUpdate && <div className="NavTabs__AvatarBadge" />}
</span>
</span> </span>
</span> </Tooltip>
</button> </button>
{showAvatarPopup && {showAvatarPopup &&
portalElement != null && portalElement != null &&

View file

@ -10,6 +10,7 @@ import { filterAndSortConversationsByRecent } from '../util/filterAndSortConvers
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import type { ConversationWithStoriesType } from '../state/selectors/conversations'; import type { ConversationWithStoriesType } from '../state/selectors/conversations';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import { ThemeType } from '../types/Util';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { PropsType as StoriesSettingsModalPropsType } from './StoriesSettingsModal'; import type { PropsType as StoriesSettingsModalPropsType } from './StoriesSettingsModal';
import { import {
@ -34,7 +35,6 @@ import { MY_STORY_ID, getStoryDistributionListName } from '../types/Stories';
import type { RenderModalPage, ModalPropsType } from './Modal'; import type { RenderModalPage, ModalPropsType } from './Modal';
import { PagedModal, ModalPage } from './Modal'; import { PagedModal, ModalPage } from './Modal';
import { StoryDistributionListName } from './StoryDistributionListName'; import { StoryDistributionListName } from './StoryDistributionListName';
import { Theme } from '../util/theme';
import { isNotNil } from '../util/isNotNil'; import { isNotNil } from '../util/isNotNil';
import { StoryImage } from './StoryImage'; import { StoryImage } from './StoryImage';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentType } from '../types/Attachment';
@ -42,6 +42,7 @@ import { useConfirmDiscard } from '../hooks/useConfirmDiscard';
import { getStoryBackground } from '../util/getStoryBackground'; import { getStoryBackground } from '../util/getStoryBackground';
import { makeObjectUrl, revokeObjectUrl } from '../types/VisualAttachment'; import { makeObjectUrl, revokeObjectUrl } from '../types/VisualAttachment';
import { UserText } from './UserText'; import { UserText } from './UserText';
import { Theme } from '../util/theme';
export type PropsType = { export type PropsType = {
draftAttachment: AttachmentType; draftAttachment: AttachmentType;
@ -70,6 +71,7 @@ export type PropsType = {
conversationIds: Array<string> conversationIds: Array<string>
) => unknown; ) => unknown;
signalConnections: Array<ConversationType>; signalConnections: Array<ConversationType>;
theme: ThemeType;
toggleGroupsForStorySend: (cids: Array<string>) => Promise<void>; toggleGroupsForStorySend: (cids: Array<string>) => Promise<void>;
mostRecentActiveStoryTimestampByGroupOrDistributionList: Record< mostRecentActiveStoryTimestampByGroupOrDistributionList: Record<
string, string,
@ -141,6 +143,7 @@ export function SendStoryModal({
onViewersUpdated, onViewersUpdated,
setMyStoriesToAllSignalConnections, setMyStoriesToAllSignalConnections,
signalConnections, signalConnections,
theme,
toggleGroupsForStorySend, toggleGroupsForStorySend,
mostRecentActiveStoryTimestampByGroupOrDistributionList, mostRecentActiveStoryTimestampByGroupOrDistributionList,
toggleSignalConnectionsModal, toggleSignalConnectionsModal,
@ -402,6 +405,7 @@ export function SendStoryModal({
setPage={setPage} setPage={setPage}
setSelectedContacts={setSelectedContacts} setSelectedContacts={setSelectedContacts}
toggleSignalConnectionsModal={toggleSignalConnectionsModal} toggleSignalConnectionsModal={toggleSignalConnectionsModal}
theme={theme}
onBackButtonClick={() => onBackButtonClick={() =>
confirmDiscardIf(selectedContacts.length > 0, () => confirmDiscardIf(selectedContacts.length > 0, () =>
setListIdToEdit(undefined) setListIdToEdit(undefined)
@ -485,6 +489,7 @@ export function SendStoryModal({
} }
selectedContacts={selectedContacts} selectedContacts={selectedContacts}
setSelectedContacts={setSelectedContacts} setSelectedContacts={setSelectedContacts}
theme={theme}
/> />
); );
} else if (page === Page.ChooseGroups) { } else if (page === Page.ChooseGroups) {
@ -700,7 +705,7 @@ export function SendStoryModal({
placement: 'bottom', placement: 'bottom',
strategy: 'absolute', strategy: 'absolute',
}} }}
theme={Theme.Dark} theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light}
> >
<label <label
className="SendStoryModal__distribution-list__label" className="SendStoryModal__distribution-list__label"
@ -816,7 +821,7 @@ export function SendStoryModal({
placement: 'bottom', placement: 'bottom',
strategy: 'absolute', strategy: 'absolute',
}} }}
theme={Theme.Dark} theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light}
> >
<label <label
className="SendStoryModal__distribution-list__label" className="SendStoryModal__distribution-list__label"
@ -913,7 +918,7 @@ export function SendStoryModal({
placement: 'bottom', placement: 'bottom',
strategy: 'absolute', strategy: 'absolute',
}} }}
theme={Theme.Dark} theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light}
> >
{({ openMenu, onKeyDown, ref, menuNode }) => ( {({ openMenu, onKeyDown, ref, menuNode }) => (
<div> <div>
@ -947,7 +952,7 @@ export function SendStoryModal({
{!confirmDiscardModal && ( {!confirmDiscardModal && (
<PagedModal <PagedModal
modalName="SendStoryModal" modalName="SendStoryModal"
theme={Theme.Dark} theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light}
onClose={() => confirmDiscardIf(selectedContacts.length > 0, onClose)} onClose={() => confirmDiscardIf(selectedContacts.length > 0, onClose)}
> >
{modal} {modal}
@ -958,7 +963,7 @@ export function SendStoryModal({
body={i18n('icu:SendStoryModal__announcements-only')} body={i18n('icu:SendStoryModal__announcements-only')}
i18n={i18n} i18n={i18n}
onClose={() => setHasAnnouncementsOnlyAlert(false)} onClose={() => setHasAnnouncementsOnlyAlert(false)}
theme={Theme.Dark} theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light}
/> />
)} )}
{confirmRemoveGroupId && ( {confirmRemoveGroupId && (
@ -978,7 +983,7 @@ export function SendStoryModal({
onClose={() => { onClose={() => {
setConfirmRemoveGroupId(undefined); setConfirmRemoveGroupId(undefined);
}} }}
theme={Theme.Dark} theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light}
> >
{i18n('icu:SendStoryModal__confirm-remove-group')} {i18n('icu:SendStoryModal__confirm-remove-group')}
</ConfirmationDialog> </ConfirmationDialog>
@ -1000,7 +1005,7 @@ export function SendStoryModal({
onClose={() => { onClose={() => {
setConfirmDeleteList(undefined); setConfirmDeleteList(undefined);
}} }}
theme={Theme.Dark} theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light}
> >
{i18n('icu:StoriesSettings__delete-list--confirm', { {i18n('icu:StoriesSettings__delete-list--confirm', {
name: confirmDeleteList.name, name: confirmDeleteList.name,

View file

@ -7,7 +7,7 @@ import { noop } from 'lodash';
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import type { ConversationWithStoriesType } from '../state/selectors/conversations'; import type { ConversationWithStoriesType } from '../state/selectors/conversations';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType, ThemeType } from '../types/Util';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { Row } from './ConversationList'; import type { Row } from './ConversationList';
import type { StoryDistributionListWithMembersDataType } from '../types/Stories'; import type { StoryDistributionListWithMembersDataType } from '../types/Stories';
@ -27,8 +27,6 @@ import { MY_STORY_ID, getStoryDistributionListName } from '../types/Stories';
import { PagedModal, ModalPage } from './Modal'; import { PagedModal, ModalPage } from './Modal';
import { SearchInput } from './SearchInput'; import { SearchInput } from './SearchInput';
import { StoryDistributionListName } from './StoryDistributionListName'; import { StoryDistributionListName } from './StoryDistributionListName';
import { Theme } from '../util/theme';
import { ThemeType } from '../types/Util';
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations'; import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
import { isNotNil } from '../util/isNotNil'; import { isNotNil } from '../util/isNotNil';
import { import {
@ -68,6 +66,7 @@ export type PropsType = {
) => unknown; ) => unknown;
setMyStoriesToAllSignalConnections: () => unknown; setMyStoriesToAllSignalConnections: () => unknown;
storyViewReceiptsEnabled: boolean; storyViewReceiptsEnabled: boolean;
theme: ThemeType;
toggleSignalConnectionsModal: () => unknown; toggleSignalConnectionsModal: () => unknown;
setStoriesDisabled: (value: boolean) => void; setStoriesDisabled: (value: boolean) => void;
getConversationByUuid: ( getConversationByUuid: (
@ -257,6 +256,7 @@ export function StoriesSettingsModal({
setMyStoriesToAllSignalConnections, setMyStoriesToAllSignalConnections,
storyViewReceiptsEnabled, storyViewReceiptsEnabled,
toggleSignalConnectionsModal, toggleSignalConnectionsModal,
theme,
setStoriesDisabled, setStoriesDisabled,
getConversationByUuid, getConversationByUuid,
}: PropsType): JSX.Element { }: PropsType): JSX.Element {
@ -347,6 +347,7 @@ export function StoriesSettingsModal({
}} }}
selectedContacts={selectedContacts} selectedContacts={selectedContacts}
setSelectedContacts={setSelectedContacts} setSelectedContacts={setSelectedContacts}
theme={theme}
/> />
); );
} else if (listToEdit) { } else if (listToEdit) {
@ -364,6 +365,7 @@ export function StoriesSettingsModal({
setPage={setPage} setPage={setPage}
setSelectedContacts={setSelectedContacts} setSelectedContacts={setSelectedContacts}
toggleSignalConnectionsModal={toggleSignalConnectionsModal} toggleSignalConnectionsModal={toggleSignalConnectionsModal}
theme={theme}
onBackButtonClick={() => setListToEditId(undefined)} onBackButtonClick={() => setListToEditId(undefined)}
onClose={handleClose} onClose={handleClose}
/> />
@ -479,7 +481,6 @@ export function StoriesSettingsModal({
<PagedModal <PagedModal
modalName="StoriesSettingsModal" modalName="StoriesSettingsModal"
moduleClassName="StoriesSettingsModal" moduleClassName="StoriesSettingsModal"
theme={Theme.Dark}
onClose={() => onClose={() =>
confirmDiscardIf(selectedContacts.length > 0, hideStoriesSettings) confirmDiscardIf(selectedContacts.length > 0, hideStoriesSettings)
} }
@ -504,7 +505,6 @@ export function StoriesSettingsModal({
onClose={() => { onClose={() => {
setConfirmDeleteList(undefined); setConfirmDeleteList(undefined);
}} }}
theme={Theme.Dark}
> >
{i18n('icu:StoriesSettings__delete-list--confirm', { {i18n('icu:StoriesSettings__delete-list--confirm', {
name: confirmDeleteList.name, name: confirmDeleteList.name,
@ -529,7 +529,6 @@ export function StoriesSettingsModal({
onClose={() => { onClose={() => {
setConfirmRemoveGroup(null); setConfirmRemoveGroup(null);
}} }}
theme={Theme.Dark}
> >
{i18n('icu:StoriesSettings__remove_group--confirm', { {i18n('icu:StoriesSettings__remove_group--confirm', {
groupTitle: confirmRemoveGroup.title, groupTitle: confirmRemoveGroup.title,
@ -551,6 +550,7 @@ type DistributionListSettingsModalPropsType = {
}) => unknown; }) => unknown;
setPage: (page: Page) => unknown; setPage: (page: Page) => unknown;
setSelectedContacts: (contacts: Array<ConversationType>) => unknown; setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
theme: ThemeType;
onBackButtonClick: (() => void) | undefined; onBackButtonClick: (() => void) | undefined;
onClose: () => void; onClose: () => void;
} & Pick< } & Pick<
@ -574,6 +574,7 @@ export function DistributionListSettingsModal({
setMyStoriesToAllSignalConnections, setMyStoriesToAllSignalConnections,
setPage, setPage,
setSelectedContacts, setSelectedContacts,
theme,
toggleSignalConnectionsModal, toggleSignalConnectionsModal,
signalConnectionsCount, signalConnectionsCount,
}: DistributionListSettingsModalPropsType): JSX.Element { }: DistributionListSettingsModalPropsType): JSX.Element {
@ -680,7 +681,7 @@ export function DistributionListSettingsModal({
isMe isMe
sharedGroupNames={member.sharedGroupNames} sharedGroupNames={member.sharedGroupNames}
size={AvatarSize.THIRTY_TWO} size={AvatarSize.THIRTY_TWO}
theme={ThemeType.dark} theme={theme}
title={member.title} title={member.title}
/> />
<span className="StoriesSettingsModal__list__title"> <span className="StoriesSettingsModal__list__title">
@ -756,7 +757,6 @@ export function DistributionListSettingsModal({
onClose={() => { onClose={() => {
setConfirmRemoveMember(undefined); setConfirmRemoveMember(undefined);
}} }}
theme={Theme.Dark}
title={i18n('icu:StoriesSettings__remove--title', { title={i18n('icu:StoriesSettings__remove--title', {
title: confirmRemoveMember.title, title: confirmRemoveMember.title,
})} })}
@ -960,6 +960,7 @@ type EditDistributionListModalPropsType = {
selectedContacts: Array<ConversationType>; selectedContacts: Array<ConversationType>;
onClose: () => unknown; onClose: () => unknown;
setSelectedContacts: (contacts: Array<ConversationType>) => unknown; setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
theme: ThemeType;
onBackButtonClick: () => void; onBackButtonClick: () => void;
} & Pick<PropsType, 'candidateConversations' | 'getPreferredBadge' | 'i18n'>; } & Pick<PropsType, 'candidateConversations' | 'getPreferredBadge' | 'i18n'>;
@ -973,6 +974,7 @@ export function EditDistributionListModal({
onClose, onClose,
selectedContacts, selectedContacts,
setSelectedContacts, setSelectedContacts,
theme,
onBackButtonClick, onBackButtonClick,
}: EditDistributionListModalPropsType): JSX.Element { }: EditDistributionListModalPropsType): JSX.Element {
const [storyName, setStoryName] = useState(''); const [storyName, setStoryName] = useState('');
@ -1090,7 +1092,7 @@ export function EditDistributionListModal({
isMe isMe
sharedGroupNames={contact.sharedGroupNames} sharedGroupNames={contact.sharedGroupNames}
size={AvatarSize.THIRTY_TWO} size={AvatarSize.THIRTY_TWO}
theme={ThemeType.dark} theme={theme}
title={contact.title} title={contact.title}
/> />
<span className="StoriesSettingsModal__list__title"> <span className="StoriesSettingsModal__list__title">
@ -1222,7 +1224,7 @@ export function EditDistributionListModal({
showChooseGroupMembers={shouldNeverBeCalled} showChooseGroupMembers={shouldNeverBeCalled}
showConversation={shouldNeverBeCalled} showConversation={shouldNeverBeCalled}
showUserNotFoundModal={shouldNeverBeCalled} showUserNotFoundModal={shouldNeverBeCalled}
theme={ThemeType.dark} theme={theme}
/> />
</div> </div>
)} )}

View file

@ -11,7 +11,7 @@ import type {
} from '../types/Attachment'; } from '../types/Attachment';
import type { LinkPreviewSourceType } from '../types/LinkPreview'; import type { LinkPreviewSourceType } from '../types/LinkPreview';
import type { LinkPreviewType } from '../types/message/LinkPreviews'; import type { LinkPreviewType } from '../types/message/LinkPreviews';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType, ThemeType } from '../types/Util';
import type { Props as StickerButtonProps } from './stickers/StickerButton'; import type { Props as StickerButtonProps } from './stickers/StickerButton';
import type { PropsType as SendStoryModalPropsType } from './SendStoryModal'; import type { PropsType as SendStoryModalPropsType } from './SendStoryModal';
import type { StoryDistributionIdString } from '../types/StoryDistributionId'; import type { StoryDistributionIdString } from '../types/StoryDistributionId';
@ -67,6 +67,7 @@ export type PropsType = {
props: SmartCompositionTextAreaProps props: SmartCompositionTextAreaProps
) => JSX.Element; ) => JSX.Element;
sendStoryModalOpenStateChanged: (isOpen: boolean) => unknown; sendStoryModalOpenStateChanged: (isOpen: boolean) => unknown;
theme: ThemeType;
} & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> & } & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> &
Pick< Pick<
SendStoryModalPropsType, SendStoryModalPropsType,
@ -134,6 +135,7 @@ export function StoryCreator({
setMyStoriesToAllSignalConnections, setMyStoriesToAllSignalConnections,
signalConnections, signalConnections,
skinTone, skinTone,
theme,
toggleGroupsForStorySend, toggleGroupsForStorySend,
toggleSignalConnectionsModal, toggleSignalConnectionsModal,
}: PropsType): JSX.Element | null { }: PropsType): JSX.Element | null {
@ -228,6 +230,7 @@ export function StoryCreator({
mostRecentActiveStoryTimestampByGroupOrDistributionList={ mostRecentActiveStoryTimestampByGroupOrDistributionList={
mostRecentActiveStoryTimestampByGroupOrDistributionList mostRecentActiveStoryTimestampByGroupOrDistributionList
} }
theme={theme}
toggleSignalConnectionsModal={toggleSignalConnectionsModal} toggleSignalConnectionsModal={toggleSignalConnectionsModal}
/> />
)} )}

View file

@ -1,7 +1,7 @@
// Copyright 2020 Signal Messenger, LLC // Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React, { useRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { noop } from 'lodash'; import { noop } from 'lodash';
import { Manager, Reference, Popper } from 'react-popper'; import { Manager, Reference, Popper } from 'react-popper';
@ -89,8 +89,12 @@ export type PropsType = {
sticky?: boolean; sticky?: boolean;
theme?: Theme; theme?: Theme;
wrapperClassName?: string; wrapperClassName?: string;
delay?: number;
}; };
let GLOBAL_EXIT_TIMER: NodeJS.Timeout | undefined;
let GLOBAL_TOOLTIP_DISABLE_DELAY = false;
export function Tooltip({ export function Tooltip({
children, children,
className, className,
@ -100,15 +104,56 @@ export function Tooltip({
theme, theme,
popperModifiers = [], popperModifiers = [],
wrapperClassName, wrapperClassName,
delay,
}: PropsType): JSX.Element { }: PropsType): JSX.Element {
const [isHovering, setIsHovering] = React.useState(false); const timeoutRef = useRef<NodeJS.Timeout | undefined>();
const [active, setActive] = React.useState(false);
const showTooltip = isHovering || Boolean(sticky); const showTooltip = active || Boolean(sticky);
const tooltipThemeClassName = theme const tooltipThemeClassName = theme
? `module-tooltip--${themeClassName(theme)}` ? `module-tooltip--${themeClassName(theme)}`
: undefined; : undefined;
function handleHoverChanged(hovering: boolean) {
// Don't accept updates that aren't valid anymore
clearTimeout(GLOBAL_EXIT_TIMER);
clearTimeout(timeoutRef.current);
// We can skip past all of this if there's no delay
if (delay != null) {
// If we're now hovering, and delays haven't been disabled globally
// we should start the timer to show the tooltip
if (hovering && !GLOBAL_TOOLTIP_DISABLE_DELAY) {
timeoutRef.current = setTimeout(() => {
setActive(true);
// Since we have shown a tooltip we can now disable these delays
// globally.
GLOBAL_TOOLTIP_DISABLE_DELAY = true;
}, delay);
return;
}
if (!hovering) {
// If we're not hovering, we should hide the tooltip immediately
setActive(false);
// If we've disabled delays globally, we need to start a timer to undo
// that after some time has passed.
if (GLOBAL_TOOLTIP_DISABLE_DELAY) {
GLOBAL_EXIT_TIMER = setTimeout(() => {
GLOBAL_TOOLTIP_DISABLE_DELAY = false;
// We're always going to use 300 here so that a tooltip with a really
// long delay doesn't affect all of the others
}, 300);
}
return;
}
}
setActive(hovering);
}
return ( return (
<Manager> <Manager>
<Reference> <Reference>
@ -116,7 +161,7 @@ export function Tooltip({
<TooltipEventWrapper <TooltipEventWrapper
className={wrapperClassName} className={wrapperClassName}
ref={ref} ref={ref}
onHoverChanged={setIsHovering} onHoverChanged={handleHoverChanged}
> >
{children} {children}
</TooltipEventWrapper> </TooltipEventWrapper>

View file

@ -462,10 +462,7 @@ export function ConversationDetails({
</div> </div>
{callHistoryGroup && ( {callHistoryGroup && (
<PanelSection> <PanelSection title={formatDate(i18n, callHistoryGroup.timestamp)}>
<h2 className="ConversationDetails__CallHistoryGroup__header">
{formatDate(i18n, callHistoryGroup.timestamp)}
</h2>
<ol className="ConversationDetails__CallHistoryGroup__List"> <ol className="ConversationDetails__CallHistoryGroup__List">
{callHistoryGroup.children.map(child => { {callHistoryGroup.children.map(child => {
return ( return (

View file

@ -157,6 +157,7 @@ import {
callHistoryGroupSchema, callHistoryGroupSchema,
CallHistoryFilterStatus, CallHistoryFilterStatus,
callHistoryDetailsSchema, callHistoryDetailsSchema,
CallDirection,
} from '../types/CallDisposition'; } from '../types/CallDisposition';
type ConversationRow = Readonly<{ type ConversationRow = Readonly<{
@ -3347,6 +3348,7 @@ async function getCallHistory(
const MISSED = sqlConstant(DirectCallStatus.Missed); const MISSED = sqlConstant(DirectCallStatus.Missed);
const DELETED = sqlConstant(DirectCallStatus.Deleted); const DELETED = sqlConstant(DirectCallStatus.Deleted);
const INCOMING = sqlConstant(CallDirection.Incoming);
const FOUR_HOURS_IN_MS = sqlConstant(4 * 60 * 60 * 1000); const FOUR_HOURS_IN_MS = sqlConstant(4 * 60 * 60 * 1000);
function getCallHistoryGroupDataSync( function getCallHistoryGroupDataSync(
@ -3405,7 +3407,10 @@ function getCallHistoryGroupDataSync(
const filterClause = const filterClause =
status === CallHistoryFilterStatus.All status === CallHistoryFilterStatus.All
? sqlFragment`status IS NOT ${DELETED}` ? sqlFragment`status IS NOT ${DELETED}`
: sqlFragment`status IS ${MISSED} AND status IS NOT ${DELETED}`; : sqlFragment`
direction IS ${INCOMING} AND
status IS ${MISSED} AND status IS NOT ${DELETED}
`;
const offsetLimit = const offsetLimit =
limit > 0 ? sqlFragment`LIMIT ${limit} OFFSET ${offset}` : sqlFragment``; limit > 0 ? sqlFragment`LIMIT ${limit} OFFSET ${offset}` : sqlFragment``;

View file

@ -11,6 +11,8 @@ import type { ToastActionType } from './toast';
import { showToast } from './toast'; import { showToast } from './toast';
import { ToastType } from '../../types/Toast'; import { ToastType } from '../../types/Toast';
import type { CallHistoryDetails } from '../../types/CallDisposition'; import type { CallHistoryDetails } from '../../types/CallDisposition';
import * as log from '../../logging/log';
import * as Errors from '../../types/errors';
export type CallHistoryState = ReadonlyDeep<{ export type CallHistoryState = ReadonlyDeep<{
// This informs the app that underlying call history data has changed. // This informs the app that underlying call history data has changed.
@ -19,19 +21,19 @@ export type CallHistoryState = ReadonlyDeep<{
}>; }>;
const CALL_HISTORY_CACHE = 'callHistory/CACHE'; const CALL_HISTORY_CACHE = 'callHistory/CACHE';
const CALL_HISTORY_CLEAR = 'callHistory/CLEAR'; const CALL_HISTORY_RESET = 'callHistory/RESET';
export type CallHistoryCache = ReadonlyDeep<{ export type CallHistoryCache = ReadonlyDeep<{
type: typeof CALL_HISTORY_CACHE; type: typeof CALL_HISTORY_CACHE;
payload: CallHistoryDetails; payload: CallHistoryDetails;
}>; }>;
export type CallHistoryClear = ReadonlyDeep<{ export type CallHistoryReset = ReadonlyDeep<{
type: typeof CALL_HISTORY_CLEAR; type: typeof CALL_HISTORY_RESET;
}>; }>;
export type CallHistoryAction = ReadonlyDeep< export type CallHistoryAction = ReadonlyDeep<
CallHistoryCache | CallHistoryClear CallHistoryCache | CallHistoryReset
>; >;
export function getEmptyState(): CallHistoryState { export function getEmptyState(): CallHistoryState {
@ -52,12 +54,18 @@ function clearAllCallHistory(): ThunkAction<
void, void,
RootStateType, RootStateType,
unknown, unknown,
CallHistoryClear | ToastActionType CallHistoryReset | ToastActionType
> { > {
return async dispatch => { return async dispatch => {
await clearCallHistoryDataAndSync(); try {
dispatch({ type: CALL_HISTORY_CLEAR }); await clearCallHistoryDataAndSync();
dispatch(showToast({ toastType: ToastType.CallHistoryCleared })); dispatch(showToast({ toastType: ToastType.CallHistoryCleared }));
} catch (error) {
log.error('Error clearing call history', Errors.toLogFormat(error));
} finally {
// Just force a reset, even if the clear failed.
dispatch({ type: CALL_HISTORY_RESET });
}
}; };
} }
@ -75,7 +83,7 @@ export function reducer(
action: CallHistoryAction action: CallHistoryAction
): CallHistoryState { ): CallHistoryState {
switch (action.type) { switch (action.type) {
case CALL_HISTORY_CLEAR: case CALL_HISTORY_RESET:
return { ...state, edition: state.edition + 1, callHistoryByCallId: {} }; return { ...state, edition: state.edition + 1, callHistoryByCallId: {} };
case CALL_HISTORY_CACHE: case CALL_HISTORY_CACHE:
return { return {

View file

@ -1328,6 +1328,11 @@ export function getPropsForCallHistory(
}: GetPropsForCallHistoryOptions }: GetPropsForCallHistoryOptions
): CallingNotificationType { ): CallingNotificationType {
const { callId } = message; const { callId } = message;
if (callId == null && 'callHistoryDetails' in message) {
log.error(
'getPropsForCallHistory: Found callHistoryDetails, but no callId'
);
}
strictAssert(callId != null, 'getPropsForCallHistory: Missing callId'); strictAssert(callId != null, 'getPropsForCallHistory: Missing callId');
const callHistory = callHistorySelector(callId); const callHistory = callHistorySelector(callId);
strictAssert( strictAssert(

View file

@ -15,7 +15,7 @@ import {
getMe, getMe,
} from '../selectors/conversations'; } from '../selectors/conversations';
import { getDistributionListsWithMembers } from '../selectors/storyDistributionLists'; import { getDistributionListsWithMembers } from '../selectors/storyDistributionLists';
import { getIntl } from '../selectors/user'; import { getIntl, getTheme } from '../selectors/user';
import { getPreferredBadgeSelector } from '../selectors/badges'; import { getPreferredBadgeSelector } from '../selectors/badges';
import { getHasStoryViewReceiptSetting } from '../selectors/items'; import { getHasStoryViewReceiptSetting } from '../selectors/items';
import { useGlobalModalActions } from '../ducks/globalModals'; import { useGlobalModalActions } from '../ducks/globalModals';
@ -49,6 +49,7 @@ export function SmartStoriesSettingsModal(): JSX.Element | null {
const groupStories = useSelector(getGroupStories); const groupStories = useSelector(getGroupStories);
const getConversationByUuid = useSelector(getConversationByUuidSelector); const getConversationByUuid = useSelector(getConversationByUuidSelector);
const theme = useSelector(getTheme);
return ( return (
<StoriesSettingsModal <StoriesSettingsModal
@ -70,6 +71,7 @@ export function SmartStoriesSettingsModal(): JSX.Element | null {
onViewersUpdated={updateStoryViewers} onViewersUpdated={updateStoryViewers}
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections} setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
storyViewReceiptsEnabled={storyViewReceiptsEnabled} storyViewReceiptsEnabled={storyViewReceiptsEnabled}
theme={theme}
toggleSignalConnectionsModal={toggleSignalConnectionsModal} toggleSignalConnectionsModal={toggleSignalConnectionsModal}
setStoriesDisabled={setStoriesDisabled} setStoriesDisabled={setStoriesDisabled}
/> />

View file

@ -4,7 +4,7 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import type { LocalizerType } from '../../types/Util'; import { ThemeType, type LocalizerType } from '../../types/Util';
import type { StateType } from '../reducer'; import type { StateType } from '../reducer';
import { LinkPreviewSourceType } from '../../types/LinkPreview'; import { LinkPreviewSourceType } from '../../types/LinkPreview';
import { SmartCompositionTextArea } from './CompositionTextArea'; import { SmartCompositionTextArea } from './CompositionTextArea';
@ -140,6 +140,7 @@ export function SmartStoryCreator(): JSX.Element | null {
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections} setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
signalConnections={signalConnections} signalConnections={signalConnections}
skinTone={skinTone} skinTone={skinTone}
theme={ThemeType.dark}
toggleGroupsForStorySend={toggleGroupsForStorySend} toggleGroupsForStorySend={toggleGroupsForStorySend}
toggleSignalConnectionsModal={toggleSignalConnectionsModal} toggleSignalConnectionsModal={toggleSignalConnectionsModal}
/> />

View file

@ -2334,6 +2334,14 @@
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-07-30T16:57:33.618Z" "updated": "2021-07-30T16:57:33.618Z"
}, },
{
"rule": "React-useRef",
"path": "ts/components/LeftPane.tsx",
"line": " const measureRef = useRef<HTMLDivElement>(null);",
"reasonCategory": "usageTrusted",
"updated": "2023-08-09T21:48:42.602Z",
"reasonDetail": "<optional>"
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/LeftPaneSearchInput.tsx", "path": "ts/components/LeftPaneSearchInput.tsx",
@ -2395,7 +2403,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/Modal.tsx", "path": "ts/components/Modal.tsx",
"line": " const bodyRef = useRef<HTMLDivElement>(null);", "line": " const bodyRef = useRef<HTMLDivElement>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2403,7 +2411,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/Modal.tsx", "path": "ts/components/Modal.tsx",
"line": " const bodyInnerRef = useRef<HTMLDivElement>(null);", "line": " const bodyInnerRef = useRef<HTMLDivElement>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2556,7 +2564,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/TextAttachment.tsx", "path": "ts/components/TextAttachment.tsx",
"line": " const ref = useRef<HTMLDivElement>(null);", "line": " const ref = useRef<HTMLDivElement>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2588,6 +2596,14 @@
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-07-30T16:57:33.618Z" "updated": "2021-07-30T16:57:33.618Z"
}, },
{
"rule": "React-useRef",
"path": "ts/components/Tooltip.tsx",
"line": " const timeoutRef = useRef<NodeJS.Timeout | undefined>();",
"reasonCategory": "usageTrusted",
"updated": "2023-08-10T00:23:35.320Z",
"reasonDetail": "<optional>"
},
{ {
"rule": "React-createRef", "rule": "React-createRef",
"path": "ts/components/conversation/ConversationHeader.tsx", "path": "ts/components/conversation/ConversationHeader.tsx",
@ -2806,7 +2822,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx", "path": "ts/hooks/useSizeObserver.tsx",
"line": " const sizeRef = useRef<Size | null>(null);", "line": " const sizeRef = useRef<Size | null>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2814,7 +2830,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx", "path": "ts/hooks/useSizeObserver.tsx",
"line": " const onSizeChangeRef = useRef<SizeChangeHandler | void>(onSizeChange);", "line": " const onSizeChangeRef = useRef<SizeChangeHandler | void>(onSizeChange);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2822,7 +2838,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx", "path": "ts/hooks/useSizeObserver.tsx",
"line": " const ref = useRef<any>();", "line": " const ref = useRef<any>();",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2830,7 +2846,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx", "path": "ts/hooks/useSizeObserver.tsx",
"line": " * const scrollerRef = useRef()", "line": " * const scrollerRef = useRef()",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2838,7 +2854,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx", "path": "ts/hooks/useSizeObserver.tsx",
"line": " * const scrollerInnerRef = useRef()", "line": " * const scrollerInnerRef = useRef()",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2846,7 +2862,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx", "path": "ts/hooks/useSizeObserver.tsx",
"line": " const scrollRef = useRef<Scroll | null>(null);", "line": " const scrollRef = useRef<Scroll | null>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2854,7 +2870,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx", "path": "ts/hooks/useSizeObserver.tsx",
"line": " const onScrollChangeRef = useRef<ScrollChangeHandler>(onScrollChange);", "line": " const onScrollChangeRef = useRef<ScrollChangeHandler>(onScrollChange);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z", "updated": "2023-07-25T21:55:26.191Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2862,7 +2878,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/quill/formatting/menu.tsx", "path": "ts/quill/formatting/menu.tsx",
"line": " const fadeOutTimerRef = React.useRef<NodeJS.Timeout | undefined>();", "line": " const fadeOutTimerRef = React.useRef<NodeJS.Timeout | undefined>();",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-08-02T19:01:24.771Z", "updated": "2023-08-02T19:01:24.771Z",
"reasonDetail": "We need a persistent timer to know when to remove after fade-out" "reasonDetail": "We need a persistent timer to know when to remove after fade-out"
}, },
@ -2870,7 +2886,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/quill/formatting/menu.tsx", "path": "ts/quill/formatting/menu.tsx",
"line": " const hoverTimerRef = React.useRef<NodeJS.Timeout | undefined>();", "line": " const hoverTimerRef = React.useRef<NodeJS.Timeout | undefined>();",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-08-02T19:01:24.771Z", "updated": "2023-08-02T19:01:24.771Z",
"reasonDetail": "We need a persistent timer to track long-hovers" "reasonDetail": "We need a persistent timer to track long-hovers"
}, },
@ -2930,7 +2946,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallsList.tsx", "path": "ts/components/CallsList.tsx",
"line": " const infiniteLoaderRef = useRef<InfiniteLoader>(null);", "line": " const infiniteLoaderRef = useRef<InfiniteLoader>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-08-02T00:21:37.858Z", "updated": "2023-08-02T00:21:37.858Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
}, },
@ -2938,7 +2954,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallsList.tsx", "path": "ts/components/CallsList.tsx",
"line": " const listRef = useRef<List>(null);", "line": " const listRef = useRef<List>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2023-08-02T00:21:37.858Z", "updated": "2023-08-02T00:21:37.858Z",
"reasonDetail": "<optional>" "reasonDetail": "<optional>"
} }