Update call tab design based on feedback
This commit is contained in:
parent
ce28993c78
commit
3268d3e6eb
32 changed files with 601 additions and 289 deletions
|
@ -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": {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -99,7 +99,6 @@
|
||||||
|
|
||||||
&__popper--single-item &__option {
|
&__popper--single-item &__option {
|
||||||
padding-block: 12px;
|
padding-block: 12px;
|
||||||
padding-inline: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__divider {
|
&__divider {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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} ·{' '}
|
{statusText} ·{' '}
|
||||||
<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 && (
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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``;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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>"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue