Update call link edit/add name modals
This commit is contained in:
parent
ba77ef7563
commit
b691e24d5c
20 changed files with 526 additions and 232 deletions
|
@ -7208,14 +7208,14 @@
|
||||||
"messageformat": "Call link details",
|
"messageformat": "Call link details",
|
||||||
"description": "Call Link Edit Modal > Title"
|
"description": "Call Link Edit Modal > Title"
|
||||||
},
|
},
|
||||||
"icu:CallLinkEditModal__InputLabel--Name--SrOnly": {
|
|
||||||
"messageformat": "Name",
|
|
||||||
"description": "Call Link Edit Modal > Name Input > Label (for screenreaders)"
|
|
||||||
},
|
|
||||||
"icu:CallLinkEditModal__JoinButtonLabel": {
|
"icu:CallLinkEditModal__JoinButtonLabel": {
|
||||||
"messageformat": "Join",
|
"messageformat": "Join",
|
||||||
"description": "Call Link Edit Modal > Join Button > Label"
|
"description": "Call Link Edit Modal > Join Button > Label"
|
||||||
},
|
},
|
||||||
|
"icu:CallLinkEditModal__AddCallNameLabel": {
|
||||||
|
"messageformat": "Add call name",
|
||||||
|
"description": "Call Link Edit Modal > Add Call Name Button > Label"
|
||||||
|
},
|
||||||
"icu:CallLinkEditModal__InputLabel--ApproveAllMembers": {
|
"icu:CallLinkEditModal__InputLabel--ApproveAllMembers": {
|
||||||
"messageformat": "Approve all members",
|
"messageformat": "Approve all members",
|
||||||
"description": "Call Link Edit Modal > Approve All Members Checkbox > Label"
|
"description": "Call Link Edit Modal > Approve All Members Checkbox > Label"
|
||||||
|
@ -7228,6 +7228,14 @@
|
||||||
"messageformat": "On",
|
"messageformat": "On",
|
||||||
"description": "Call Link Edit Modal > Approve All Members Checkbox > Option > On"
|
"description": "Call Link Edit Modal > Approve All Members Checkbox > Option > On"
|
||||||
},
|
},
|
||||||
|
"icu:CallLinkAddNameModal__Title": {
|
||||||
|
"messageformat": "Add call name",
|
||||||
|
"description": "Call Link Add Name Modal > Title"
|
||||||
|
},
|
||||||
|
"icu:CallLinkAddNameModal__NameLabel": {
|
||||||
|
"messageformat": "Call name",
|
||||||
|
"description": "Call Link Add Name Modal > Name Input > Label"
|
||||||
|
},
|
||||||
"icu:TypingBubble__avatar--overflow-count": {
|
"icu:TypingBubble__avatar--overflow-count": {
|
||||||
"messageformat": "{count, plural, one {# other is} other {# others are}} typing.",
|
"messageformat": "{count, plural, one {# other is} other {# others are}} typing.",
|
||||||
"description": "Group chat multiple person typing indicator when space isn't available to show every avatar, this is the count of avatars hidden."
|
"description": "Group chat multiple person typing indicator when space isn't available to show every avatar, this is the count of avatars hidden."
|
||||||
|
|
16
stylesheets/components/CallLinkAddNameModal.scss
Normal file
16
stylesheets/components/CallLinkAddNameModal.scss
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
.CallLinkAddNameModal__Row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallLinkAddNameModal__SrOnly {
|
||||||
|
@include sr-only;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overriding the default styles for the input container
|
||||||
|
.CallLinkAddNameModal__Input__container.Input__container {
|
||||||
|
flex: 1;
|
||||||
|
}
|
|
@ -1,6 +1,12 @@
|
||||||
// Copyright 2024 Signal Messenger, LLC
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
// Overriding default style
|
||||||
|
.module-Modal__body.CallLinkEditModal__body {
|
||||||
|
padding-inline: 12px 3px;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__SrOnly {
|
.CallLinkEditModal__SrOnly {
|
||||||
@include sr-only;
|
@include sr-only;
|
||||||
}
|
}
|
||||||
|
@ -13,67 +19,80 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__Header__Details {
|
.CallLinkEditModal__Header__Details {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 0;
|
||||||
min-width: 0; // fix overflow issue
|
min-width: 0; // fix overflow issue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overriding default style
|
.CallLinkEditModal__Header__Title {
|
||||||
.Input__container.CallLinkEditModal__Input--Name__container {
|
@include font-body-1-bold;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__CallLinkAndJoinButton {
|
.CallLinkEditModal__Header__CallLinkButton {
|
||||||
|
@include button-reset;
|
||||||
|
@include font-subtitle;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: $color-gray-60;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
color: $color-gray-25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallLinkEditModal__Header__CallLinkButton__Text {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallLinkEditModal__Header__Actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__CopyUrlTextButton {
|
|
||||||
@include button-reset;
|
|
||||||
border: none;
|
|
||||||
padding-block: 10px;
|
|
||||||
padding-inline: 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
// truncate with ellipsis
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
background: $color-gray-02;
|
|
||||||
color: $color-black;
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
background: $color-gray-75;
|
|
||||||
color: $color-gray-15;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.CallLinkEditModal__JoinButton {
|
.CallLinkEditModal__JoinButton {
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__ApproveAllMembers__Row {
|
.CallLinkEditModal__Row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 18px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__ApproveAllMembers__Label {
|
.CallLinkEditModal__RowButton {
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CallLinkEditModal__ActionButton {
|
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
|
width: 100%;
|
||||||
|
padding-block: 1px;
|
||||||
|
|
||||||
|
.CallLinkEditModal__Row {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
.CallLinkEditModal__Row {
|
||||||
|
@include light-theme {
|
||||||
|
background: $color-gray-02;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
background: $color-gray-75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallLinkEditModal__Row--Button {
|
||||||
@include font-body-2;
|
@include font-body-2;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-block: 8px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
|
@ -84,20 +103,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__ActionButton__Icon {
|
.CallLinkEditModal__RowLabel {
|
||||||
|
@include font-body-1;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallLinkEditModal__RowIcon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 36px;
|
width: 20px;
|
||||||
height: 36px;
|
height: 20px;
|
||||||
border-radius: 9999px;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
background: $color-gray-05;
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
background: $color-gray-65;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -110,30 +126,42 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__ActionButton__Icon--Copy {
|
@mixin CallLinkEditModal__RowIcon($iconPath) {
|
||||||
&::after {
|
&::after {
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
@include color-svg('../images/icons/v3/copy/copy.svg', $color-gray-75);
|
@include color-svg($iconPath, $color-gray-75);
|
||||||
}
|
}
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
@include color-svg('../images/icons/v3/copy/copy.svg', $color-gray-15);
|
@include color-svg($iconPath, $color-gray-15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.CallLinkEditModal__ActionButton__Icon--Share {
|
.CallLinkEditModal__RowIcon--Edit {
|
||||||
&::after {
|
@include CallLinkEditModal__RowIcon('../images/icons/v3/edit/edit.svg');
|
||||||
@include light-theme {
|
}
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v3/forward/forward.svg',
|
.CallLinkEditModal__RowIcon--Approve {
|
||||||
$color-gray-75
|
@include CallLinkEditModal__RowIcon(
|
||||||
|
'../images/icons/v3/person/person-check-compact.svg'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg(
|
.CallLinkEditModal__RowIcon--Copy {
|
||||||
'../images/icons/v3/forward/forward.svg',
|
@include CallLinkEditModal__RowIcon('../images/icons/v3/copy/copy.svg');
|
||||||
$color-gray-15
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CallLinkEditModal__RowIcon--Share {
|
||||||
|
@include CallLinkEditModal__RowIcon('../images/icons/v3/forward/forward.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CallLinkEditModal__Hr {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
background: $color-black-alpha-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overriding default style
|
||||||
|
.CallLinkEditModal__RowSelect.module-select select {
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
@import './components/CallingScreenSharingController.scss';
|
@import './components/CallingScreenSharingController.scss';
|
||||||
@import './components/CallingSelectPresentingSourcesModal.scss';
|
@import './components/CallingSelectPresentingSourcesModal.scss';
|
||||||
@import './components/CallingToast.scss';
|
@import './components/CallingToast.scss';
|
||||||
|
@import './components/CallLinkAddNameModal.scss';
|
||||||
@import './components/CallLinkDetails.scss';
|
@import './components/CallLinkDetails.scss';
|
||||||
@import './components/CallLinkEditModal.scss';
|
@import './components/CallLinkEditModal.scss';
|
||||||
@import './components/CallingRaisedHandsList.scss';
|
@import './components/CallingRaisedHandsList.scss';
|
||||||
|
|
28
ts/components/CallLinkAddNameModal.stories.tsx
Normal file
28
ts/components/CallLinkAddNameModal.stories.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { setupI18n } from '../util/setupI18n';
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
import type { CallLinkAddNameModalProps } from './CallLinkAddNameModal';
|
||||||
|
import { CallLinkAddNameModal } from './CallLinkAddNameModal';
|
||||||
|
import type { ComponentMeta } from '../storybook/types';
|
||||||
|
import { FAKE_CALL_LINK_WITH_ADMIN_KEY } from '../test-both/helpers/fakeCallLink';
|
||||||
|
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/CallLinkAddNameModal',
|
||||||
|
component: CallLinkAddNameModal,
|
||||||
|
args: {
|
||||||
|
i18n,
|
||||||
|
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||||
|
onClose: action('onClose'),
|
||||||
|
onUpdateCallLinkName: action('onUpdateCallLinkName'),
|
||||||
|
},
|
||||||
|
} satisfies ComponentMeta<CallLinkAddNameModalProps>;
|
||||||
|
|
||||||
|
export function Basic(args: CallLinkAddNameModalProps): JSX.Element {
|
||||||
|
return <CallLinkAddNameModal {...args} />;
|
||||||
|
}
|
98
ts/components/CallLinkAddNameModal.tsx
Normal file
98
ts/components/CallLinkAddNameModal.tsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { v4 as generateUuid } from 'uuid';
|
||||||
|
import { Modal } from './Modal';
|
||||||
|
import type { LocalizerType } from '../types/I18N';
|
||||||
|
import { Button, ButtonVariant } from './Button';
|
||||||
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
|
import { Input } from './Input';
|
||||||
|
import type { CallLinkType } from '../types/CallLink';
|
||||||
|
|
||||||
|
export type CallLinkAddNameModalProps = Readonly<{
|
||||||
|
i18n: LocalizerType;
|
||||||
|
callLink: CallLinkType;
|
||||||
|
onClose: () => void;
|
||||||
|
onUpdateCallLinkName: (name: string) => void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export function CallLinkAddNameModal({
|
||||||
|
i18n,
|
||||||
|
callLink,
|
||||||
|
onClose,
|
||||||
|
onUpdateCallLinkName,
|
||||||
|
}: CallLinkAddNameModalProps): JSX.Element {
|
||||||
|
const [formId] = useState(() => generateUuid());
|
||||||
|
const [nameId] = useState(() => generateUuid());
|
||||||
|
const [nameInput, setNameInput] = useState(callLink.name);
|
||||||
|
|
||||||
|
const handleNameInputChange = useCallback((nextNameInput: string) => {
|
||||||
|
setNameInput(nextNameInput);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
const nameValue = nameInput.trim();
|
||||||
|
if (nameValue === callLink.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onUpdateCallLinkName(nameValue);
|
||||||
|
onClose();
|
||||||
|
}, [nameInput, callLink, onUpdateCallLinkName, onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
modalName="CallLinkAddNameModal"
|
||||||
|
i18n={i18n}
|
||||||
|
hasXButton
|
||||||
|
noEscapeClose
|
||||||
|
noMouseClose
|
||||||
|
title={i18n('icu:CallLinkAddNameModal__Title')}
|
||||||
|
onClose={onClose}
|
||||||
|
moduleClassName="CallLinkAddNameModal"
|
||||||
|
modalFooter={
|
||||||
|
<>
|
||||||
|
<Button onClick={onClose} variant={ButtonVariant.Secondary}>
|
||||||
|
{i18n('icu:cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" form={formId} variant={ButtonVariant.Primary}>
|
||||||
|
{i18n('icu:save')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
id={formId}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="CallLinkAddNameModal__Row"
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
i18n={i18n}
|
||||||
|
badge={undefined}
|
||||||
|
conversationType="callLink"
|
||||||
|
size={AvatarSize.SIXTY_FOUR}
|
||||||
|
acceptedMessageRequest
|
||||||
|
isMe={false}
|
||||||
|
sharedGroupNames={[]}
|
||||||
|
title={
|
||||||
|
callLink.name === ''
|
||||||
|
? i18n('icu:calling__call-link-default-title')
|
||||||
|
: callLink.name
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label htmlFor={nameId} className="CallLinkAddNameModal__SrOnly">
|
||||||
|
{i18n('icu:CallLinkAddNameModal__NameLabel')}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
i18n={i18n}
|
||||||
|
id={nameId}
|
||||||
|
value={nameInput}
|
||||||
|
placeholder={i18n('icu:CallLinkAddNameModal__NameLabel')}
|
||||||
|
autoFocus
|
||||||
|
onChange={handleNameInputChange}
|
||||||
|
moduleClassName="CallLinkAddNameModal__Input"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ export default {
|
||||||
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||||
onClose: action('onClose'),
|
onClose: action('onClose'),
|
||||||
onCopyCallLink: action('onCopyCallLink'),
|
onCopyCallLink: action('onCopyCallLink'),
|
||||||
onUpdateCallLinkName: action('onUpdateCallLinkName'),
|
onOpenCallLinkAddNameModal: action('onOpenCallLinkAddNameModal'),
|
||||||
onUpdateCallLinkRestrictions: action('onUpdateCallLinkRestrictions'),
|
onUpdateCallLinkRestrictions: action('onUpdateCallLinkRestrictions'),
|
||||||
onShareCallLinkViaSignal: action('onShareCallLinkViaSignal'),
|
onShareCallLinkViaSignal: action('onShareCallLinkViaSignal'),
|
||||||
onStartCallLinkLobby: action('onStartCallLinkLobby'),
|
onStartCallLinkLobby: action('onStartCallLinkLobby'),
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright 2024 Signal Messenger, LLC
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
import { v4 as generateUuid } from 'uuid';
|
import { v4 as generateUuid } from 'uuid';
|
||||||
import { Modal } from './Modal';
|
import { Modal } from './Modal';
|
||||||
import type { LocalizerType } from '../types/I18N';
|
import type { LocalizerType } from '../types/I18N';
|
||||||
|
@ -10,19 +11,67 @@ import {
|
||||||
toCallLinkRestrictions,
|
toCallLinkRestrictions,
|
||||||
type CallLinkType,
|
type CallLinkType,
|
||||||
} from '../types/CallLink';
|
} from '../types/CallLink';
|
||||||
import { Input } from './Input';
|
|
||||||
import { Select } from './Select';
|
import { Select } from './Select';
|
||||||
import { linkCallRoute } from '../util/signalRoutes';
|
import { linkCallRoute } from '../util/signalRoutes';
|
||||||
import { Button, ButtonSize, ButtonVariant } from './Button';
|
import { Button, ButtonSize, ButtonVariant } from './Button';
|
||||||
import { Avatar, AvatarSize } from './Avatar';
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
import { formatUrlWithoutProtocol } from '../util/url';
|
|
||||||
|
const CallLinkEditModalRowIconClasses = {
|
||||||
|
Edit: 'CallLinkEditModal__RowIcon--Edit',
|
||||||
|
Approve: 'CallLinkEditModal__RowIcon--Approve',
|
||||||
|
Copy: 'CallLinkEditModal__RowIcon--Copy',
|
||||||
|
Share: 'CallLinkEditModal__RowIcon--Share',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
function RowIcon({
|
||||||
|
icon,
|
||||||
|
}: {
|
||||||
|
icon: keyof typeof CallLinkEditModalRowIconClasses;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<i
|
||||||
|
role="presentation"
|
||||||
|
className={`CallLinkEditModal__RowIcon ${CallLinkEditModalRowIconClasses[icon]}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RowText({ children }: { children: ReactNode }) {
|
||||||
|
return <div className="CallLinkEditModal__RowLabel">{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row({ children }: { children: ReactNode }) {
|
||||||
|
return <div className="CallLinkEditModal__Row">{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RowButton({
|
||||||
|
onClick,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
onClick: () => void;
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="CallLinkEditModal__RowButton"
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Hr() {
|
||||||
|
return <hr className="CallLinkEditModal__Hr" />;
|
||||||
|
}
|
||||||
|
|
||||||
export type CallLinkEditModalProps = {
|
export type CallLinkEditModalProps = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
callLink: CallLinkType;
|
callLink: CallLinkType;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onCopyCallLink: () => void;
|
onCopyCallLink: () => void;
|
||||||
onUpdateCallLinkName: (name: string) => void;
|
onOpenCallLinkAddNameModal: () => void;
|
||||||
onUpdateCallLinkRestrictions: (restrictions: CallLinkRestrictions) => void;
|
onUpdateCallLinkRestrictions: (restrictions: CallLinkRestrictions) => void;
|
||||||
onShareCallLinkViaSignal: () => void;
|
onShareCallLinkViaSignal: () => void;
|
||||||
onStartCallLinkLobby: () => void;
|
onStartCallLinkLobby: () => void;
|
||||||
|
@ -33,64 +82,32 @@ export function CallLinkEditModal({
|
||||||
callLink,
|
callLink,
|
||||||
onClose,
|
onClose,
|
||||||
onCopyCallLink,
|
onCopyCallLink,
|
||||||
onUpdateCallLinkName,
|
onOpenCallLinkAddNameModal,
|
||||||
onUpdateCallLinkRestrictions,
|
onUpdateCallLinkRestrictions,
|
||||||
onShareCallLinkViaSignal,
|
onShareCallLinkViaSignal,
|
||||||
onStartCallLinkLobby,
|
onStartCallLinkLobby,
|
||||||
}: CallLinkEditModalProps): JSX.Element {
|
}: CallLinkEditModalProps): JSX.Element {
|
||||||
const { name: savedName, restrictions: savedRestrictions } = callLink;
|
|
||||||
|
|
||||||
const [nameId] = useState(() => generateUuid());
|
|
||||||
const [restrictionsId] = useState(() => generateUuid());
|
const [restrictionsId] = useState(() => generateUuid());
|
||||||
|
|
||||||
const [nameInput, setNameInput] = useState(savedName);
|
|
||||||
const [restrictionsInput, setRestrictionsInput] = useState(savedRestrictions);
|
|
||||||
|
|
||||||
// We only want to use the default name "Signal Call" as a value if the user
|
|
||||||
// modified the input and then chose that name. Doesn't revert when saved.
|
|
||||||
const [nameTouched, setNameTouched] = useState(false);
|
|
||||||
|
|
||||||
const callLinkWebUrl = useMemo(() => {
|
const callLinkWebUrl = useMemo(() => {
|
||||||
return formatUrlWithoutProtocol(
|
return linkCallRoute.toWebUrl({ key: callLink.rootKey }).toString();
|
||||||
linkCallRoute.toWebUrl({ key: callLink.rootKey })
|
|
||||||
);
|
|
||||||
}, [callLink.rootKey]);
|
}, [callLink.rootKey]);
|
||||||
|
|
||||||
const onSaveName = useCallback(
|
|
||||||
(newName: string) => {
|
|
||||||
if (!nameTouched) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (newName === savedName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onUpdateCallLinkName(newName);
|
|
||||||
},
|
|
||||||
[nameTouched, savedName, onUpdateCallLinkName]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSaveRestrictions = useCallback(
|
|
||||||
(newRestrictions: CallLinkRestrictions) => {
|
|
||||||
if (newRestrictions === savedRestrictions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onUpdateCallLinkRestrictions(newRestrictions);
|
|
||||||
},
|
|
||||||
[savedRestrictions, onUpdateCallLinkRestrictions]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
modalName="CallLinkEditModal"
|
modalName="CallLinkEditModal"
|
||||||
moduleClassName="CallLinkEditModal"
|
moduleClassName="CallLinkEditModal"
|
||||||
title={i18n('icu:CallLinkEditModal__Title')}
|
title={i18n('icu:CallLinkEditModal__Title')}
|
||||||
hasXButton
|
noEscapeClose
|
||||||
onClose={() => {
|
noMouseClose
|
||||||
// Save the modal in case the user hits escape
|
padded={false}
|
||||||
onSaveName(nameInput);
|
modalFooter={
|
||||||
onClose();
|
<Button type="submit" variant={ButtonVariant.Primary} onClick={onClose}>
|
||||||
}}
|
{i18n('icu:done')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<div className="CallLinkEditModal__Header">
|
<div className="CallLinkEditModal__Header">
|
||||||
<Avatar
|
<Avatar
|
||||||
|
@ -101,43 +118,30 @@ export function CallLinkEditModal({
|
||||||
acceptedMessageRequest
|
acceptedMessageRequest
|
||||||
isMe={false}
|
isMe={false}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
title={callLink.name ?? i18n('icu:calling__call-link-default-title')}
|
title={
|
||||||
|
callLink.name === ''
|
||||||
|
? i18n('icu:calling__call-link-default-title')
|
||||||
|
: callLink.name
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="CallLinkEditModal__Header__Details">
|
<div className="CallLinkEditModal__Header__Details">
|
||||||
<label htmlFor={nameId} className="CallLinkEditModal__SrOnly">
|
<div className="CallLinkEditModal__Header__Title">
|
||||||
{i18n('icu:CallLinkEditModal__InputLabel--Name--SrOnly')}
|
{callLink.name === ''
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
moduleClassName="CallLinkEditModal__Input--Name"
|
|
||||||
i18n={i18n}
|
|
||||||
value={
|
|
||||||
nameInput === '' && !nameTouched
|
|
||||||
? i18n('icu:calling__call-link-default-title')
|
? i18n('icu:calling__call-link-default-title')
|
||||||
: nameInput
|
: callLink.name}
|
||||||
}
|
</div>
|
||||||
maxByteCount={120}
|
|
||||||
onChange={value => {
|
|
||||||
setNameTouched(true);
|
|
||||||
setNameInput(value);
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
onSaveName(nameInput);
|
|
||||||
}}
|
|
||||||
onEnter={() => {
|
|
||||||
onSaveName(nameInput);
|
|
||||||
}}
|
|
||||||
placeholder={i18n('icu:calling__call-link-default-title')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="CallLinkEditModal__CallLinkAndJoinButton">
|
|
||||||
<button
|
<button
|
||||||
className="CallLinkEditModal__CopyUrlTextButton"
|
className="CallLinkEditModal__Header__CallLinkButton"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCopyCallLink}
|
onClick={onCopyCallLink}
|
||||||
aria-label={i18n('icu:CallLinkDetails__CopyLink')}
|
aria-label={i18n('icu:CallLinkDetails__CopyLink')}
|
||||||
>
|
>
|
||||||
|
<div className="CallLinkEditModal__Header__CallLinkButton__Text">
|
||||||
{callLinkWebUrl}
|
{callLinkWebUrl}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="CallLinkEditModal__Header__Actions">
|
||||||
<Button
|
<Button
|
||||||
onClick={onStartCallLinkLobby}
|
onClick={onStartCallLinkLobby}
|
||||||
size={ButtonSize.Small}
|
size={ButtonSize.Small}
|
||||||
|
@ -148,67 +152,62 @@ export function CallLinkEditModal({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<Hr />
|
||||||
className="CallLinkEditModal__ApproveAllMembers__Row"
|
|
||||||
// For testing, to easily check the restrictions saved
|
<RowButton onClick={onOpenCallLinkAddNameModal}>
|
||||||
data-restrictions={savedRestrictions}
|
<Row>
|
||||||
>
|
<RowIcon icon="Edit" />
|
||||||
<label
|
<RowText>{i18n('icu:CallLinkEditModal__AddCallNameLabel')}</RowText>
|
||||||
htmlFor={restrictionsId}
|
</Row>
|
||||||
className="CallLinkEditModal__ApproveAllMembers__Label"
|
</RowButton>
|
||||||
>
|
|
||||||
|
<Row>
|
||||||
|
<RowIcon icon="Approve" />
|
||||||
|
<RowText>
|
||||||
|
<label htmlFor={restrictionsId}>
|
||||||
{i18n('icu:CallLinkEditModal__InputLabel--ApproveAllMembers')}
|
{i18n('icu:CallLinkEditModal__InputLabel--ApproveAllMembers')}
|
||||||
</label>
|
</label>
|
||||||
|
</RowText>
|
||||||
<Select
|
<Select
|
||||||
id={restrictionsId}
|
id={restrictionsId}
|
||||||
value={restrictionsInput}
|
value={String(callLink.restrictions)}
|
||||||
|
moduleClassName="CallLinkEditModal__RowSelect"
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
value: CallLinkRestrictions.None,
|
value: String(CallLinkRestrictions.None),
|
||||||
text: i18n(
|
text: i18n(
|
||||||
'icu:CallLinkEditModal__ApproveAllMembers__Option--Off'
|
'icu:CallLinkEditModal__ApproveAllMembers__Option--Off'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: CallLinkRestrictions.AdminApproval,
|
value: String(CallLinkRestrictions.AdminApproval),
|
||||||
text: i18n(
|
text: i18n(
|
||||||
'icu:CallLinkEditModal__ApproveAllMembers__Option--On'
|
'icu:CallLinkEditModal__ApproveAllMembers__Option--On'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
const newRestrictions = toCallLinkRestrictions(value);
|
onUpdateCallLinkRestrictions(toCallLinkRestrictions(value));
|
||||||
setRestrictionsInput(newRestrictions);
|
|
||||||
onSaveRestrictions(newRestrictions);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Row>
|
||||||
|
|
||||||
<button
|
<Hr />
|
||||||
type="button"
|
|
||||||
className="CallLinkEditModal__ActionButton"
|
|
||||||
onClick={onCopyCallLink}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
role="presentation"
|
|
||||||
className="CallLinkEditModal__ActionButton__Icon CallLinkEditModal__ActionButton__Icon--Copy"
|
|
||||||
/>
|
|
||||||
{i18n('icu:CallLinkDetails__CopyLink')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<RowButton onClick={onCopyCallLink}>
|
||||||
type="button"
|
<Row>
|
||||||
className="CallLinkEditModal__ActionButton"
|
<RowIcon icon="Copy" />
|
||||||
onClick={onShareCallLinkViaSignal}
|
<RowText>{i18n('icu:CallLinkDetails__CopyLink')}</RowText>
|
||||||
>
|
</Row>
|
||||||
<i
|
</RowButton>
|
||||||
role="presentation"
|
|
||||||
className="CallLinkEditModal__ActionButton__Icon CallLinkEditModal__ActionButton__Icon--Share"
|
<RowButton onClick={onShareCallLinkViaSignal}>
|
||||||
/>
|
<Row>
|
||||||
{i18n('icu:CallLinkDetails__ShareLinkViaSignal')}
|
<RowIcon icon="Share" />
|
||||||
</button>
|
<RowText>{i18n('icu:CallLinkDetails__ShareLinkViaSignal')}</RowText>
|
||||||
|
</Row>
|
||||||
|
</RowButton>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ export function EditNicknameAndNoteModal({
|
||||||
}, [givenName, familyName, note]);
|
}, [givenName, familyName, note]);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(event: MouseEvent | FormEvent) => {
|
(event: FormEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (formResult.success) {
|
if (formResult.success) {
|
||||||
onSave(formResult.data);
|
onSave(formResult.data);
|
||||||
|
@ -104,7 +104,6 @@ export function EditNicknameAndNoteModal({
|
||||||
type="submit"
|
type="submit"
|
||||||
form={formId}
|
form={formId}
|
||||||
aria-disabled={!formResult.success}
|
aria-disabled={!formResult.success}
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
>
|
||||||
{i18n('icu:save')}
|
{i18n('icu:save')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -124,7 +123,7 @@ export function EditNicknameAndNoteModal({
|
||||||
theme={undefined}
|
theme={undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit}>
|
<form id={formId} onSubmit={handleSubmit}>
|
||||||
<label
|
<label
|
||||||
htmlFor={givenNameId}
|
htmlFor={givenNameId}
|
||||||
className="EditNicknameAndNoteModal__Label"
|
className="EditNicknameAndNoteModal__Label"
|
||||||
|
|
|
@ -29,6 +29,9 @@ export type PropsType = {
|
||||||
// AddUserToAnotherGroupModal
|
// AddUserToAnotherGroupModal
|
||||||
addUserToAnotherGroupModalContactId: string | undefined;
|
addUserToAnotherGroupModalContactId: string | undefined;
|
||||||
renderAddUserToAnotherGroup: () => JSX.Element;
|
renderAddUserToAnotherGroup: () => JSX.Element;
|
||||||
|
// CallLinkAddNameModal
|
||||||
|
callLinkAddNameModalRoomId: string | null;
|
||||||
|
renderCallLinkAddNameModal: () => JSX.Element;
|
||||||
// CallLinkEditModal
|
// CallLinkEditModal
|
||||||
callLinkEditModalRoomId: string | null;
|
callLinkEditModalRoomId: string | null;
|
||||||
renderCallLinkEditModal: () => JSX.Element;
|
renderCallLinkEditModal: () => JSX.Element;
|
||||||
|
@ -105,6 +108,9 @@ export function GlobalModalContainer({
|
||||||
// AddUserToAnotherGroupModal
|
// AddUserToAnotherGroupModal
|
||||||
addUserToAnotherGroupModalContactId,
|
addUserToAnotherGroupModalContactId,
|
||||||
renderAddUserToAnotherGroup,
|
renderAddUserToAnotherGroup,
|
||||||
|
// CallLinkAddNameModal
|
||||||
|
callLinkAddNameModalRoomId,
|
||||||
|
renderCallLinkAddNameModal,
|
||||||
// CallLinkEditModal
|
// CallLinkEditModal
|
||||||
callLinkEditModalRoomId,
|
callLinkEditModalRoomId,
|
||||||
renderCallLinkEditModal,
|
renderCallLinkEditModal,
|
||||||
|
@ -194,6 +200,10 @@ export function GlobalModalContainer({
|
||||||
return renderAddUserToAnotherGroup();
|
return renderAddUserToAnotherGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (callLinkAddNameModalRoomId) {
|
||||||
|
return renderCallLinkAddNameModal();
|
||||||
|
}
|
||||||
|
|
||||||
if (callLinkEditModalRoomId) {
|
if (callLinkEditModalRoomId) {
|
||||||
return renderCallLinkEditModal();
|
return renderCallLinkEditModal();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { useRefMerger } from '../hooks/useRefMerger';
|
||||||
import { byteLength } from '../Bytes';
|
import { byteLength } from '../Bytes';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
autoFocus?: boolean;
|
||||||
countBytes?: (value: string) => number;
|
countBytes?: (value: string) => number;
|
||||||
countLength?: (value: string) => number;
|
countLength?: (value: string) => number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -63,6 +64,7 @@ export const Input = forwardRef<
|
||||||
PropsType
|
PropsType
|
||||||
>(function InputInner(
|
>(function InputInner(
|
||||||
{
|
{
|
||||||
|
autoFocus,
|
||||||
countBytes = byteLength,
|
countBytes = byteLength,
|
||||||
countLength = grapheme.count,
|
countLength = grapheme.count,
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -206,6 +208,7 @@ export const Input = forwardRef<
|
||||||
const isTextarea = expandable || forceTextarea;
|
const isTextarea = expandable || forceTextarea;
|
||||||
|
|
||||||
const inputProps = {
|
const inputProps = {
|
||||||
|
autoFocus,
|
||||||
className: classNames(
|
className: classNames(
|
||||||
getClassName('__input'),
|
getClassName('__input'),
|
||||||
icon && getClassName('__input--with-icon'),
|
icon && getClassName('__input--with-icon'),
|
||||||
|
|
|
@ -43,6 +43,7 @@ type PropsType = {
|
||||||
|
|
||||||
export type ModalPropsType = PropsType & {
|
export type ModalPropsType = PropsType & {
|
||||||
noTransform?: boolean;
|
noTransform?: boolean;
|
||||||
|
noEscapeClose?: boolean;
|
||||||
noMouseClose?: boolean;
|
noMouseClose?: boolean;
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
};
|
};
|
||||||
|
@ -57,6 +58,7 @@ export function Modal({
|
||||||
modalFooter,
|
modalFooter,
|
||||||
modalHeaderChildren,
|
modalHeaderChildren,
|
||||||
moduleClassName,
|
moduleClassName,
|
||||||
|
noEscapeClose,
|
||||||
noMouseClose,
|
noMouseClose,
|
||||||
onBackButtonClick,
|
onBackButtonClick,
|
||||||
onClose = noop,
|
onClose = noop,
|
||||||
|
@ -114,6 +116,7 @@ export function Modal({
|
||||||
<ModalHost
|
<ModalHost
|
||||||
modalName={modalName}
|
modalName={modalName}
|
||||||
moduleClassName={moduleClassName}
|
moduleClassName={moduleClassName}
|
||||||
|
noEscapeClose={noEscapeClose}
|
||||||
noMouseClose={noMouseClose}
|
noMouseClose={noMouseClose}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
onEscape={onBackButtonClick}
|
onEscape={onBackButtonClick}
|
||||||
|
|
|
@ -27,6 +27,7 @@ export type PropsType = Readonly<{
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
modalName: string;
|
modalName: string;
|
||||||
moduleClassName?: string;
|
moduleClassName?: string;
|
||||||
|
noEscapeClose?: boolean;
|
||||||
noMouseClose?: boolean;
|
noMouseClose?: boolean;
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
onEscape?: () => unknown;
|
onEscape?: () => unknown;
|
||||||
|
@ -40,6 +41,7 @@ export const ModalHost = React.memo(function ModalHostInner({
|
||||||
children,
|
children,
|
||||||
modalName,
|
modalName,
|
||||||
moduleClassName,
|
moduleClassName,
|
||||||
|
noEscapeClose,
|
||||||
noMouseClose,
|
noMouseClose,
|
||||||
onClose,
|
onClose,
|
||||||
onEscape,
|
onEscape,
|
||||||
|
@ -72,7 +74,7 @@ export const ModalHost = React.memo(function ModalHostInner({
|
||||||
};
|
};
|
||||||
}, [modalContainer]);
|
}, [modalContainer]);
|
||||||
|
|
||||||
useEscapeHandling(onEscape || onClose);
|
useEscapeHandling(noEscapeClose ? noop : onEscape || onClose);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (noMouseClose) {
|
if (noMouseClose) {
|
||||||
return noop;
|
return noop;
|
||||||
|
|
|
@ -91,6 +91,7 @@ type MigrateToGV2PropsType = ReadonlyDeep<{
|
||||||
export type GlobalModalsStateType = ReadonlyDeep<{
|
export type GlobalModalsStateType = ReadonlyDeep<{
|
||||||
addUserToAnotherGroupModalContactId?: string;
|
addUserToAnotherGroupModalContactId?: string;
|
||||||
aboutContactModalContactId?: string;
|
aboutContactModalContactId?: string;
|
||||||
|
callLinkAddNameModalRoomId: string | null;
|
||||||
callLinkEditModalRoomId: string | null;
|
callLinkEditModalRoomId: string | null;
|
||||||
contactModalState?: ContactModalStateType;
|
contactModalState?: ContactModalStateType;
|
||||||
deleteMessagesProps?: DeleteMessagesPropsType;
|
deleteMessagesProps?: DeleteMessagesPropsType;
|
||||||
|
@ -143,6 +144,8 @@ export const TOGGLE_PROFILE_EDITOR_ERROR =
|
||||||
const TOGGLE_SAFETY_NUMBER_MODAL = 'globalModals/TOGGLE_SAFETY_NUMBER_MODAL';
|
const TOGGLE_SAFETY_NUMBER_MODAL = 'globalModals/TOGGLE_SAFETY_NUMBER_MODAL';
|
||||||
const TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL =
|
const TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL =
|
||||||
'globalModals/TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL';
|
'globalModals/TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL';
|
||||||
|
const TOGGLE_CALL_LINK_ADD_NAME_MODAL =
|
||||||
|
'globalModals/TOGGLE_CALL_LINK_ADD_NAME_MODAL';
|
||||||
const TOGGLE_CALL_LINK_EDIT_MODAL = 'globalModals/TOGGLE_CALL_LINK_EDIT_MODAL';
|
const TOGGLE_CALL_LINK_EDIT_MODAL = 'globalModals/TOGGLE_CALL_LINK_EDIT_MODAL';
|
||||||
const TOGGLE_ABOUT_MODAL = 'globalModals/TOGGLE_ABOUT_MODAL';
|
const TOGGLE_ABOUT_MODAL = 'globalModals/TOGGLE_ABOUT_MODAL';
|
||||||
const TOGGLE_SIGNAL_CONNECTIONS_MODAL =
|
const TOGGLE_SIGNAL_CONNECTIONS_MODAL =
|
||||||
|
@ -244,6 +247,11 @@ type ToggleAddUserToAnotherGroupModalActionType = ReadonlyDeep<{
|
||||||
payload: string | undefined;
|
payload: string | undefined;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type ToggleCallLinkAddNameModalActionType = ReadonlyDeep<{
|
||||||
|
type: typeof TOGGLE_CALL_LINK_ADD_NAME_MODAL;
|
||||||
|
payload: string | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
type ToggleCallLinkEditModalActionType = ReadonlyDeep<{
|
type ToggleCallLinkEditModalActionType = ReadonlyDeep<{
|
||||||
type: typeof TOGGLE_CALL_LINK_EDIT_MODAL;
|
type: typeof TOGGLE_CALL_LINK_EDIT_MODAL;
|
||||||
payload: string | null;
|
payload: string | null;
|
||||||
|
@ -374,6 +382,7 @@ export type GlobalModalsActionType = ReadonlyDeep<
|
||||||
| StartMigrationToGV2ActionType
|
| StartMigrationToGV2ActionType
|
||||||
| ToggleAboutContactModalActionType
|
| ToggleAboutContactModalActionType
|
||||||
| ToggleAddUserToAnotherGroupModalActionType
|
| ToggleAddUserToAnotherGroupModalActionType
|
||||||
|
| ToggleCallLinkAddNameModalActionType
|
||||||
| ToggleCallLinkEditModalActionType
|
| ToggleCallLinkEditModalActionType
|
||||||
| ToggleConfirmationModalActionType
|
| ToggleConfirmationModalActionType
|
||||||
| ToggleDeleteMessagesModalActionType
|
| ToggleDeleteMessagesModalActionType
|
||||||
|
@ -414,6 +423,7 @@ export const actions = {
|
||||||
showWhatsNewModal,
|
showWhatsNewModal,
|
||||||
toggleAboutContactModal,
|
toggleAboutContactModal,
|
||||||
toggleAddUserToAnotherGroupModal,
|
toggleAddUserToAnotherGroupModal,
|
||||||
|
toggleCallLinkAddNameModal,
|
||||||
toggleCallLinkEditModal,
|
toggleCallLinkEditModal,
|
||||||
toggleConfirmationModal,
|
toggleConfirmationModal,
|
||||||
toggleDeleteMessagesModal,
|
toggleDeleteMessagesModal,
|
||||||
|
@ -711,6 +721,15 @@ function toggleAddUserToAnotherGroupModal(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleCallLinkAddNameModal(
|
||||||
|
roomId: string | null
|
||||||
|
): ToggleCallLinkAddNameModalActionType {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_CALL_LINK_ADD_NAME_MODAL,
|
||||||
|
payload: roomId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function toggleCallLinkEditModal(
|
function toggleCallLinkEditModal(
|
||||||
roomId: string | null
|
roomId: string | null
|
||||||
): ToggleCallLinkEditModalActionType {
|
): ToggleCallLinkEditModalActionType {
|
||||||
|
@ -935,6 +954,7 @@ function copyOverMessageAttributesIntoForwardMessages(
|
||||||
export function getEmptyState(): GlobalModalsStateType {
|
export function getEmptyState(): GlobalModalsStateType {
|
||||||
return {
|
return {
|
||||||
hasConfirmationModal: false,
|
hasConfirmationModal: false,
|
||||||
|
callLinkAddNameModalRoomId: null,
|
||||||
callLinkEditModalRoomId: null,
|
callLinkEditModalRoomId: null,
|
||||||
editNicknameAndNoteModalProps: null,
|
editNicknameAndNoteModalProps: null,
|
||||||
isProfileEditorVisible: false,
|
isProfileEditorVisible: false,
|
||||||
|
@ -1049,6 +1069,13 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === TOGGLE_CALL_LINK_ADD_NAME_MODAL) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
callLinkAddNameModalRoomId: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type === TOGGLE_CALL_LINK_EDIT_MODAL) {
|
if (action.type === TOGGLE_CALL_LINK_EDIT_MODAL) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -27,6 +27,11 @@ export const getCallLinkEditModalRoomId = createSelector(
|
||||||
({ callLinkEditModalRoomId }) => callLinkEditModalRoomId
|
({ callLinkEditModalRoomId }) => callLinkEditModalRoomId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getCallLinkAddNameModalRoomId = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ callLinkAddNameModalRoomId }) => callLinkAddNameModalRoomId
|
||||||
|
);
|
||||||
|
|
||||||
export const getContactModalState = createSelector(
|
export const getContactModalState = createSelector(
|
||||||
getGlobalModalsState,
|
getGlobalModalsState,
|
||||||
({ contactModalState }) => contactModalState
|
({ contactModalState }) => contactModalState
|
||||||
|
|
60
ts/state/smart/CallLinkAddNameModal.tsx
Normal file
60
ts/state/smart/CallLinkAddNameModal.tsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useCallingActions } from '../ducks/calling';
|
||||||
|
import { getCallLinkSelector } from '../selectors/calling';
|
||||||
|
import * as log from '../../logging/log';
|
||||||
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import { getCallLinkAddNameModalRoomId } from '../selectors/globalModals';
|
||||||
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { isCallLinksCreateEnabled } from '../../util/callLinks';
|
||||||
|
import { CallLinkAddNameModal } from '../../components/CallLinkAddNameModal';
|
||||||
|
|
||||||
|
export const SmartCallLinkAddNameModal = memo(
|
||||||
|
function SmartCallLinkAddNameModal(): JSX.Element | null {
|
||||||
|
strictAssert(isCallLinksCreateEnabled(), 'Call links creation is disabled');
|
||||||
|
|
||||||
|
const roomId = useSelector(getCallLinkAddNameModalRoomId);
|
||||||
|
strictAssert(roomId, 'Expected roomId to be set');
|
||||||
|
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const callLinkSelector = useSelector(getCallLinkSelector);
|
||||||
|
|
||||||
|
const { updateCallLinkName } = useCallingActions();
|
||||||
|
const { toggleCallLinkAddNameModal } = useGlobalModalActions();
|
||||||
|
|
||||||
|
const callLink = useMemo(() => {
|
||||||
|
return callLinkSelector(roomId);
|
||||||
|
}, [callLinkSelector, roomId]);
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
toggleCallLinkAddNameModal(null);
|
||||||
|
}, [toggleCallLinkAddNameModal]);
|
||||||
|
|
||||||
|
const handleUpdateCallLinkName = useCallback(
|
||||||
|
(newName: string) => {
|
||||||
|
updateCallLinkName(roomId, newName);
|
||||||
|
},
|
||||||
|
[roomId, updateCallLinkName]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!callLink) {
|
||||||
|
log.error(
|
||||||
|
'SmartCallLinkEditModal: No call link found for roomId',
|
||||||
|
roomId
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CallLinkAddNameModal
|
||||||
|
i18n={i18n}
|
||||||
|
callLink={callLink}
|
||||||
|
onClose={handleClose}
|
||||||
|
onUpdateCallLinkName={handleUpdateCallLinkName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
|
@ -26,13 +26,13 @@ export const SmartCallLinkEditModal = memo(
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const callLinkSelector = useSelector(getCallLinkSelector);
|
const callLinkSelector = useSelector(getCallLinkSelector);
|
||||||
|
|
||||||
|
const { updateCallLinkRestrictions, startCallLinkLobby } =
|
||||||
|
useCallingActions();
|
||||||
const {
|
const {
|
||||||
updateCallLinkName,
|
toggleCallLinkAddNameModal,
|
||||||
updateCallLinkRestrictions,
|
toggleCallLinkEditModal,
|
||||||
startCallLinkLobby,
|
showShareCallLinkViaSignal,
|
||||||
} = useCallingActions();
|
} = useGlobalModalActions();
|
||||||
const { toggleCallLinkEditModal, showShareCallLinkViaSignal } =
|
|
||||||
useGlobalModalActions();
|
|
||||||
|
|
||||||
const callLink = useMemo(() => {
|
const callLink = useMemo(() => {
|
||||||
return callLinkSelector(roomId);
|
return callLinkSelector(roomId);
|
||||||
|
@ -52,12 +52,9 @@ export const SmartCallLinkEditModal = memo(
|
||||||
drop(copyCallLink(callLinkWebUrl));
|
drop(copyCallLink(callLinkWebUrl));
|
||||||
}, [callLink]);
|
}, [callLink]);
|
||||||
|
|
||||||
const handleUpdateCallLinkName = useCallback(
|
const handleOpenCallLinkAddNameModal = useCallback(() => {
|
||||||
(newName: string) => {
|
toggleCallLinkAddNameModal(roomId);
|
||||||
updateCallLinkName(roomId, newName);
|
}, [roomId, toggleCallLinkAddNameModal]);
|
||||||
},
|
|
||||||
[roomId, updateCallLinkName]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUpdateCallLinkRestrictions = useCallback(
|
const handleUpdateCallLinkRestrictions = useCallback(
|
||||||
(newRestrictions: CallLinkRestrictions) => {
|
(newRestrictions: CallLinkRestrictions) => {
|
||||||
|
@ -91,7 +88,7 @@ export const SmartCallLinkEditModal = memo(
|
||||||
callLink={callLink}
|
callLink={callLink}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
onCopyCallLink={handleCopyCallLink}
|
onCopyCallLink={handleCopyCallLink}
|
||||||
onUpdateCallLinkName={handleUpdateCallLinkName}
|
onOpenCallLinkAddNameModal={handleOpenCallLinkAddNameModal}
|
||||||
onUpdateCallLinkRestrictions={handleUpdateCallLinkRestrictions}
|
onUpdateCallLinkRestrictions={handleUpdateCallLinkRestrictions}
|
||||||
onShareCallLinkViaSignal={handleShareCallLinkViaSignal}
|
onShareCallLinkViaSignal={handleShareCallLinkViaSignal}
|
||||||
onStartCallLinkLobby={handleStartCallLinkLobby}
|
onStartCallLinkLobby={handleStartCallLinkLobby}
|
||||||
|
|
|
@ -27,6 +27,11 @@ import { getGlobalModalsState } from '../selectors/globalModals';
|
||||||
import { SmartEditNicknameAndNoteModal } from './EditNicknameAndNoteModal';
|
import { SmartEditNicknameAndNoteModal } from './EditNicknameAndNoteModal';
|
||||||
import { SmartNotePreviewModal } from './NotePreviewModal';
|
import { SmartNotePreviewModal } from './NotePreviewModal';
|
||||||
import { SmartCallLinkEditModal } from './CallLinkEditModal';
|
import { SmartCallLinkEditModal } from './CallLinkEditModal';
|
||||||
|
import { SmartCallLinkAddNameModal } from './CallLinkAddNameModal';
|
||||||
|
|
||||||
|
function renderCallLinkAddNameModal(): JSX.Element {
|
||||||
|
return <SmartCallLinkAddNameModal />;
|
||||||
|
}
|
||||||
|
|
||||||
function renderCallLinkEditModal(): JSX.Element {
|
function renderCallLinkEditModal(): JSX.Element {
|
||||||
return <SmartCallLinkEditModal />;
|
return <SmartCallLinkEditModal />;
|
||||||
|
@ -95,6 +100,7 @@ export const SmartGlobalModalContainer = memo(
|
||||||
const {
|
const {
|
||||||
aboutContactModalContactId,
|
aboutContactModalContactId,
|
||||||
addUserToAnotherGroupModalContactId,
|
addUserToAnotherGroupModalContactId,
|
||||||
|
callLinkAddNameModalRoomId,
|
||||||
callLinkEditModalRoomId,
|
callLinkEditModalRoomId,
|
||||||
contactModalState,
|
contactModalState,
|
||||||
deleteMessagesProps,
|
deleteMessagesProps,
|
||||||
|
@ -174,6 +180,7 @@ export const SmartGlobalModalContainer = memo(
|
||||||
addUserToAnotherGroupModalContactId={
|
addUserToAnotherGroupModalContactId={
|
||||||
addUserToAnotherGroupModalContactId
|
addUserToAnotherGroupModalContactId
|
||||||
}
|
}
|
||||||
|
callLinkAddNameModalRoomId={callLinkAddNameModalRoomId}
|
||||||
callLinkEditModalRoomId={callLinkEditModalRoomId}
|
callLinkEditModalRoomId={callLinkEditModalRoomId}
|
||||||
contactModalState={contactModalState}
|
contactModalState={contactModalState}
|
||||||
editHistoryMessages={editHistoryMessages}
|
editHistoryMessages={editHistoryMessages}
|
||||||
|
@ -197,6 +204,7 @@ export const SmartGlobalModalContainer = memo(
|
||||||
isWhatsNewVisible={isWhatsNewVisible}
|
isWhatsNewVisible={isWhatsNewVisible}
|
||||||
renderAboutContactModal={renderAboutContactModal}
|
renderAboutContactModal={renderAboutContactModal}
|
||||||
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
|
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
|
||||||
|
renderCallLinkAddNameModal={renderCallLinkAddNameModal}
|
||||||
renderCallLinkEditModal={renderCallLinkEditModal}
|
renderCallLinkEditModal={renderCallLinkEditModal}
|
||||||
renderContactModal={renderContactModal}
|
renderContactModal={renderContactModal}
|
||||||
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
|
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
|
||||||
|
|
|
@ -42,23 +42,29 @@ describe('calling/callLinkAdmin', function (this: Mocha.Suite) {
|
||||||
.getByText('Create a Call Link')
|
.getByText('Create a Call Link')
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
const callLinkItem = window.locator('.CallsList__Item[data-type="Adhoc"]');
|
const editModal = window.locator('.CallLinkEditModal');
|
||||||
|
await editModal.waitFor();
|
||||||
|
|
||||||
const modal = window.locator('.CallLinkEditModal');
|
const restrictionsInput = editModal.getByLabel('Approve all members');
|
||||||
await modal.waitFor();
|
|
||||||
|
|
||||||
const row = modal.locator('.CallLinkEditModal__ApproveAllMembers__Row');
|
await expect(restrictionsInput).toHaveJSProperty('value', '0');
|
||||||
|
await restrictionsInput.selectOption({ label: 'On' });
|
||||||
|
await expect(restrictionsInput).toHaveJSProperty('value', '1');
|
||||||
|
|
||||||
await expect(row).toHaveAttribute('data-restrictions', '0');
|
await editModal.locator('button', { hasText: 'Add call name' }).click();
|
||||||
|
|
||||||
const select = modal.locator('select');
|
const addNameModal = window.locator('.CallLinkAddNameModal');
|
||||||
await select.selectOption({ label: 'On' });
|
await addNameModal.waitFor();
|
||||||
await expect(row).toHaveAttribute('data-restrictions', '1');
|
|
||||||
|
|
||||||
const nameInput = modal.locator('.CallLinkEditModal__Input--Name__input');
|
const nameInput = addNameModal.getByLabel('Call name');
|
||||||
await nameInput.fill('New Name');
|
await nameInput.fill('New Name');
|
||||||
await nameInput.blur();
|
|
||||||
|
|
||||||
await expect(callLinkItem).toContainText('New Name');
|
const saveBtn = addNameModal.getByText('Save');
|
||||||
|
await saveBtn.click();
|
||||||
|
|
||||||
|
await editModal.waitFor();
|
||||||
|
|
||||||
|
const title = editModal.locator('.CallLinkEditModal__Header__Title');
|
||||||
|
await expect(title).toContainText('New Name');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,7 +37,3 @@ export function urlPathFromComponents(
|
||||||
): string {
|
): string {
|
||||||
return `/${components.filter(Boolean).map(encodeURIComponent).join('/')}`;
|
return `/${components.filter(Boolean).map(encodeURIComponent).join('/')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatUrlWithoutProtocol(url: Readonly<URL>): string {
|
|
||||||
return `${url.hostname}${url.pathname}${url.search}${url.hash}`;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue