Prevent deletion of active call links and style join button

This commit is contained in:
ayumi-signal 2024-09-09 15:09:57 -07:00 committed by GitHub
parent 5835e9033d
commit dec06209e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 198 additions and 65 deletions

View file

@ -7357,6 +7357,10 @@
"messageformat": "Approve all members",
"description": "Call History > Call Link Details > Approve All Members > Label"
},
"icu:CallLinkDetails__SettingTooltip--disabled-for-active-call": {
"messageformat": "This setting can't be changed while the call is active",
"description": "Call History > Call Link Details > Approve All Members > Tooltip when disabled due to active call"
},
"icu:CallLinkDetails__CopyLink": {
"messageformat": "Copy link",
"description": "Call History > Call Link Details > Copy Link Button"
@ -7385,6 +7389,10 @@
"messageformat": "Delete",
"description": "Call History > Call Link Details > Delete Link Modal > Delete Button"
},
"icu:CallLinkDetails__DeleteLinkTooltip--disabled-for-active-call": {
"messageformat": "This link can't be deleted while the call is active",
"description": "Call History > Call Link Details > Delete Link Button > Tooltip when disabled due to active call"
},
"icu:CallLinkEditModal__Title": {
"messageformat": "Call link details",
"description": "Call Link Edit Modal > Title"

View file

@ -974,3 +974,50 @@ $rtl-icon-map: (
}
}
}
@mixin button-active-call {
$background: $color-accent-green;
@include font-body-2-bold;
@include rounded-corners;
display: flex;
width: auto;
align-items: center;
background-color: $background;
color: $color-white;
outline: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none;
&:before {
$icon-size: 16px;
@include color-svg(
'../images/icons/v3/video/video-compact-fill.svg',
$color-white
);
content: '';
display: block;
height: $icon-size;
margin-inline-end: 4px;
min-width: $icon-size;
width: $icon-size;
}
&:not(:disabled) {
&:hover {
@include any-theme {
background-color: darken($background, 16%);
}
}
&:focus {
@include keyboard-mode {
background-color: darken($background, 16%);
}
}
}
}

View file

@ -46,6 +46,10 @@
font-weight: 600;
}
.CallLinkDetails__HeaderButton--active-call {
@include button-active-call;
}
.CallLinkDetails__DeleteLink {
// Override the default icon color
.ConversationDetails-icon__icon--trash::after {
@ -59,3 +63,19 @@
color: $color-accent-red;
}
}
.CallLinkDetails__DeleteLink--disabled-for-active-call {
.ConversationDetails-icon__icon--trash::after {
@include any-theme {
background-color: $color-gray-45;
}
}
.ConversationDetails-panel-row__label {
color: $color-gray-45;
}
}
.CallLinkDetails__ApproveAllMembersDisabledTooltip,
.CallLinkDetails__DeleteLinkTooltip {
@include tooltip;
}

View file

@ -356,53 +356,10 @@
}
.CallsNewCall__ItemActionButton--join-call {
$background: $color-accent-green;
@include font-body-2-bold;
@include rounded-corners;
display: flex;
width: auto;
@include button-active-call;
height: 26px;
padding-block: 4px;
padding-inline: 10px;
align-items: center;
background-color: $background;
color: $color-white;
outline: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none;
&:before {
$icon-size: 16px;
@include color-svg(
'../images/icons/v3/video/video-compact-fill.svg',
$color-white
);
content: '';
display: block;
height: $icon-size;
margin-inline-end: 4px;
min-width: $icon-size;
width: $icon-size;
}
&:not(:disabled) {
&:hover {
@include any-theme {
background-color: darken($background, 16%);
}
}
&:focus {
@include keyboard-mode {
background-color: darken($background, 16%);
}
}
}
}
.CallsNewCall__ItemActionButton--join-call-disabled {

View file

@ -23,7 +23,9 @@ export default {
i18n,
callHistoryGroup: getFakeCallLinkHistoryGroup(),
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
hasActiveCall: false,
isAnybodyInCall: false,
isInCall: false,
isInAnotherCall: false,
onDeleteCallLink: action('onDeleteCallLink'),
onOpenCallLinkAddNameModal: action('onOpenCallLinkAddNameModal'),
onStartCallLinkLobby: action('onStartCallLinkLobby'),
@ -36,10 +38,39 @@ export function Admin(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} />;
}
export function AdminAndCallActive(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} isAnybodyInCall />;
}
export function AdminAndInCall(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} isAnybodyInCall isInCall />;
}
export function NonAdmin(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} callLink={FAKE_CALL_LINK} />;
}
export function InAnotherCall(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} callLink={FAKE_CALL_LINK} hasActiveCall />;
export function NonAdminAndCallActive(args: CallLinkDetailsProps): JSX.Element {
return (
<CallLinkDetails {...args} callLink={FAKE_CALL_LINK} isAnybodyInCall />
);
}
export function InAnotherCall(args: CallLinkDetailsProps): JSX.Element {
return (
<CallLinkDetails {...args} callLink={FAKE_CALL_LINK} isInAnotherCall />
);
}
export function InAnotherCallAndCallActive(
args: CallLinkDetailsProps
): JSX.Element {
return (
<CallLinkDetails
{...args}
callLink={FAKE_CALL_LINK}
isAnybodyInCall
isInAnotherCall
/>
);
}

View file

@ -1,6 +1,7 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import classNames from 'classnames';
import type { CallHistoryGroup } from '../types/CallDisposition';
import type { LocalizerType } from '../types/I18N';
import { CallHistoryGroupPanelSection } from './conversation/conversation-details/CallHistoryGroupPanelSection';
@ -21,6 +22,8 @@ import { isCallLinkAdmin } from '../types/CallLink';
import { CallLinkRestrictionsSelect } from './CallLinkRestrictionsSelect';
import { ConfirmationDialog } from './ConfirmationDialog';
import { InAnotherCallTooltip } from './conversation/InAnotherCallTooltip';
import { offsetDistanceModifier } from '../util/popperUtil';
import { Tooltip, TooltipPlacement } from './Tooltip';
function toUrlWithoutProtocol(url: URL): string {
return `${url.hostname}${url.pathname}${url.search}${url.hash}`;
@ -29,7 +32,9 @@ function toUrlWithoutProtocol(url: URL): string {
export type CallLinkDetailsProps = Readonly<{
callHistoryGroup: CallHistoryGroup;
callLink: CallLinkType;
hasActiveCall: boolean;
isAnybodyInCall: boolean;
isInCall: boolean;
isInAnotherCall: boolean;
i18n: LocalizerType;
onDeleteCallLink: () => void;
onOpenCallLinkAddNameModal: () => void;
@ -42,7 +47,9 @@ export function CallLinkDetails({
callHistoryGroup,
callLink,
i18n,
hasActiveCall,
isAnybodyInCall,
isInCall,
isInAnotherCall,
onDeleteCallLink,
onOpenCallLinkAddNameModal,
onStartCallLinkLobby,
@ -56,15 +63,32 @@ export function CallLinkDetails({
});
const joinButton = (
<Button
className="CallLinkDetails__HeaderButton"
variant={ButtonVariant.SecondaryAffirmative}
discouraged={hasActiveCall}
className={classNames({
CallLinkDetails__HeaderButton: true,
'CallLinkDetails__HeaderButton--active-call': isAnybodyInCall,
})}
variant={
isAnybodyInCall
? ButtonVariant.Calling
: ButtonVariant.SecondaryAffirmative
}
discouraged={isInAnotherCall}
size={ButtonSize.Small}
onClick={onStartCallLinkLobby}
>
{i18n('icu:CallLinkDetails__Join')}
{isInCall
? i18n('icu:CallsNewCallButton--return')
: i18n('icu:CallLinkDetails__Join')}
</Button>
);
const callLinkRestrictionsSelect = (
<CallLinkRestrictionsSelect
disabled={isAnybodyInCall}
i18n={i18n}
value={callLink.restrictions}
onChange={onUpdateCallLinkRestrictions}
/>
);
return (
<div className="CallLinkDetails__Container">
@ -92,7 +116,7 @@ export function CallLinkDetails({
</p>
</div>
<div className="CallLinkDetails__HeaderActions">
{hasActiveCall ? (
{isInAnotherCall ? (
<InAnotherCallTooltip i18n={i18n}>
{joinButton}
</InAnotherCallTooltip>
@ -130,11 +154,20 @@ export function CallLinkDetails({
}
label={i18n('icu:CallLinkDetails__ApproveAllMembersLabel')}
right={
<CallLinkRestrictionsSelect
i18n={i18n}
value={callLink.restrictions}
onChange={onUpdateCallLinkRestrictions}
/>
isAnybodyInCall ? (
<Tooltip
className="CallLinkDetails__ApproveAllMembersDisabledTooltip"
content={i18n(
'icu:CallLinkDetails__SettingTooltip--disabled-for-active-call'
)}
direction={TooltipPlacement.Top}
popperModifiers={[offsetDistanceModifier(5)]}
>
{callLinkRestrictionsSelect}
</Tooltip>
) : (
callLinkRestrictionsSelect
)
}
/>
</PanelSection>
@ -166,14 +199,34 @@ export function CallLinkDetails({
{isCallLinkAdmin(callLink) && (
<PanelSection>
<PanelRow
className="CallLinkDetails__DeleteLink"
className={classNames({
CallLinkDetails__DeleteLink: true,
'CallLinkDetails__DeleteLink--disabled-for-active-call':
isAnybodyInCall,
})}
disabled={isAnybodyInCall}
icon={
<ConversationDetailsIcon
ariaLabel={i18n('icu:CallLinkDetails__DeleteLink')}
icon={IconType.trash}
/>
}
label={i18n('icu:CallLinkDetails__DeleteLink')}
label={
isAnybodyInCall ? (
<Tooltip
className="CallLinkDetails__DeleteLinkTooltip"
content={i18n(
'icu:CallLinkDetails__DeleteLinkTooltip--disabled-for-active-call'
)}
direction={TooltipPlacement.Top}
popperModifiers={[offsetDistanceModifier(5)]}
>
{i18n('icu:CallLinkDetails__DeleteLink')}
</Tooltip>
) : (
i18n('icu:CallLinkDetails__DeleteLink')
)
}
onClick={() => {
setIsDeleteCallLinkModalOpen(true);
}}

View file

@ -9,6 +9,7 @@ import type { LocalizerType } from '../types/I18N';
import { Select } from './Select';
export type CallLinkRestrictionsSelectProps = Readonly<{
disabled?: boolean;
i18n: LocalizerType;
id?: string;
value: CallLinkRestrictions;
@ -16,6 +17,7 @@ export type CallLinkRestrictionsSelectProps = Readonly<{
}>;
export function CallLinkRestrictionsSelect({
disabled,
i18n,
id,
value,
@ -23,6 +25,7 @@ export function CallLinkRestrictionsSelect({
}: CallLinkRestrictionsSelectProps): JSX.Element {
return (
<Select
disabled={disabled}
id={id}
value={String(value)}
moduleClassName="CallLinkRestrictionsSelect"

View file

@ -5,12 +5,17 @@ import { useSelector } from 'react-redux';
import type { CallHistoryGroup } from '../../types/CallDisposition';
import { getIntl } from '../selectors/user';
import { CallLinkDetails } from '../../components/CallLinkDetails';
import { getActiveCallState, getCallLinkSelector } from '../selectors/calling';
import {
getActiveCallState,
getAdhocCallSelector,
getCallLinkSelector,
} from '../selectors/calling';
import { useGlobalModalActions } from '../ducks/globalModals';
import { useCallingActions } from '../ducks/calling';
import * as log from '../../logging/log';
import { strictAssert } from '../../util/assert';
import type { CallLinkRestrictions } from '../../types/CallLink';
import { isAnybodyInGroupCall } from '../ducks/callingHelpers';
export type SmartCallLinkDetailsProps = Readonly<{
roomId: string;
@ -60,9 +65,16 @@ export const SmartCallLinkDetails = memo(function SmartCallLinkDetails({
[roomId, updateCallLinkRestrictions]
);
const adhocCallSelector = useSelector(getAdhocCallSelector);
const adhocCall = adhocCallSelector(roomId);
const hasActiveCall = isAnybodyInGroupCall(adhocCall?.peekInfo);
const activeCall = useSelector(getActiveCallState);
const hasActiveCall = Boolean(
activeCall && callLink && activeCall?.conversationId !== callLink?.roomId
const isInAnotherCall = Boolean(
activeCall && callLink && activeCall.conversationId !== callLink.roomId
);
const isInCall = Boolean(
activeCall && callLink && activeCall.conversationId === callLink.roomId
);
if (callLink == null) {
@ -74,7 +86,9 @@ export const SmartCallLinkDetails = memo(function SmartCallLinkDetails({
<CallLinkDetails
callHistoryGroup={callHistoryGroup}
callLink={callLink}
hasActiveCall={hasActiveCall}
isAnybodyInCall={hasActiveCall}
isInCall={isInCall}
isInAnotherCall={isInAnotherCall}
i18n={i18n}
onDeleteCallLink={handleDeleteCallLink}
onOpenCallLinkAddNameModal={handleOpenCallLinkAddNameModal}