fix: added type for all accessibility ids with react.d.ts

pull/2963/head
Audric Ackermann 1 year ago
parent 0a4e3041de
commit 5867c5af7f

@ -317,84 +317,6 @@ label {
}
}
.session-dropdown {
position: relative;
width: 100%;
&__label {
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
padding: 0px var(--margins-md);
font-size: var(--font-size-md);
background-color: var(--right-panel-item-background-color);
color: var(--right-panel-item-text-color);
&:hover {
background: var(--right-panel-item-background-hover-color);
}
}
&__list-container {
z-index: 99;
display: block;
position: absolute;
top: 50px;
left: 0px;
right: 0px;
list-style: none;
padding: 0px;
margin: 0px;
max-height: 40vh;
overflow-y: auto;
}
&__item {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
height: 35px;
padding: 0 var(--margins-md);
background: var(--right-panel-item-background-color);
color: var(--right-panel-item-text-color);
font-size: 0.8rem;
width: -webkit-fill-available;
transition: var(--default-duration);
&:first-child {
border-top: 1px solid var(--border-color);
}
&:last-child {
border-top: 1px solid var(--border-color);
}
.session-icon {
margin-inline-start: 6px;
}
.item-content {
margin-inline-start: 6px;
}
&.active,
&:hover {
background: var(--right-panel-item-background-hover-color);
}
&.danger {
color: var(--danger-color);
}
}
&:hover {
background: var(--right-panel-item-background-hover-color);
}
}
.image-upload-section {
display: flex;
align-items: center;

@ -95,7 +95,7 @@ type MemberListItemProps = {
isAdmin?: boolean; // if true, we add a small crown on top of their avatar
onSelect?: (pubkey: string) => void;
onUnselect?: (pubkey: string) => void;
dataTestId?: string;
dataTestId?: React.SessionDataTestId;
displayGroupStatus?: boolean;
groupPk?: string;
};
@ -162,7 +162,10 @@ const GroupStatusText = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: Gro
return null;
}
return (
<StyledGroupStatusText isFailure={groupPromotionFailed || groupInviteFailed}>
<StyledGroupStatusText
data-testid={'group_member_status_text'}
isFailure={groupPromotionFailed || groupInviteFailed}
>
{statusText}
</StyledGroupStatusText>
);
@ -194,7 +197,7 @@ const ResendInviteButton = ({
}) => {
return (
<SessionButton
dataTestId="resend-invite-button"
dataTestId={'resend_invite_button'}
buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid}
text={window.i18n('resend')}
@ -214,7 +217,7 @@ const ResendPromoteButton = ({
}) => {
return (
<SessionButton
dataTestId="resend-promote-button"
dataTestId={'resend_promote_button'}
buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid}
buttonColor={SessionButtonColor.Danger}
@ -268,7 +271,7 @@ export const MemberListItem = ({
margin="0 var(--margins-md)"
alignItems="flex-start"
>
<StyledName>{memberName}</StyledName>
<StyledName data-testid={'group_member_name'}>{memberName}</StyledName>
<GroupStatusContainer
pubkey={pubkey}
displayGroupStatus={displayGroupStatus}

@ -1,6 +1,6 @@
import classNames from 'classnames';
import { isEqual } from 'lodash';
import React, { useState } from 'react';
import React, { SessionDataTestId, useState } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
@ -32,7 +32,8 @@ type Props = {
size: AvatarSize;
base64Data?: string; // if this is not empty, it will be used to render the avatar with base64 encoded data
onAvatarClick?: () => void;
dataTestId?: string;
dataTestId?: SessionDataTestId;
imageDataTestId?: SessionDataTestId;
};
const Identicon = (props: Pick<Props, 'forcedName' | 'pubkey' | 'size'>) => {
@ -110,8 +111,16 @@ const AvatarImage = (
};
const AvatarInner = (props: Props) => {
const { base64Data, size, pubkey, forcedAvatarPath, forcedName, dataTestId, onAvatarClick } =
props;
const {
base64Data,
size,
pubkey,
forcedAvatarPath,
forcedName,
dataTestId,
imageDataTestId,
onAvatarClick,
} = props;
const [imageBroken, setImageBroken] = useState(false);
const isSelectingMessages = useSelector(isMessageSelectionMode);
@ -163,7 +172,7 @@ const AvatarInner = (props: Props) => {
imageBroken={imageBroken}
name={forcedName || name}
handleImageError={handleImageError}
dataTestId={dataTestId ? `img-${dataTestId}` : undefined}
dataTestId={imageDataTestId}
/>
) : (
<NoImage

@ -1,3 +1,4 @@
import { SessionDataTestId } from 'react';
import styled from 'styled-components';
import { HTMLDirection } from '../../util/i18n';
@ -5,7 +6,7 @@ export interface FlexProps {
children?: any;
className?: string;
container?: boolean;
dataTestId?: string;
dataTestId?: SessionDataTestId;
// Container Props
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
justifyContent?:

@ -1,5 +1,5 @@
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import React, { ReactNode } from 'react';
import styled from 'styled-components';
export enum SessionButtonType {
@ -112,7 +112,7 @@ type Props = {
onClick: any;
children?: ReactNode;
margin?: string;
dataTestId?: string;
dataTestId?: React.SessionDataTestId;
};
export const SessionButton = (props: Props) => {

@ -1,63 +0,0 @@
import React, { useState } from 'react';
import { SessionIcon, SessionIconType } from '../icon';
import { SessionDropdownItem, SessionDropDownItemType } from './SessionDropdownItem';
// THIS IS DROPDOWN ACCORDION STYLE OPTIONS SELECTOR ELEMENT, NOT A CONTEXTMENU
type Props = {
label: string;
onClick?: any;
expanded?: boolean;
options: Array<{
content: string;
id?: string;
icon?: SessionIconType | null;
type?: SessionDropDownItemType;
active?: boolean;
onClick?: any;
}>;
dataTestId?: string;
};
export const SessionDropdown = (props: Props) => {
const { label, options, dataTestId } = props;
const [expanded, setExpanded] = useState(!!props.expanded);
const chevronOrientation = expanded ? 180 : 0;
return (
<div className="session-dropdown" data-testid={dataTestId}>
<div
className="session-dropdown__label"
onClick={() => {
setExpanded(!expanded);
}}
role="button"
>
{label}
<SessionIcon iconType="chevron" iconSize="small" iconRotation={chevronOrientation} />
</div>
{expanded && (
<div className="session-dropdown__list-container">
{options.map((item: any) => {
return (
<SessionDropdownItem
key={item.content}
dataTestId={`dropdownitem-${item.content.replace(' ', '-')}`}
content={item.content}
icon={item.icon}
type={item.type}
active={item.active}
onClick={() => {
setExpanded(false);
item.onClick();
}}
/>
);
})}
</div>
)}
</div>
);
};

@ -1,44 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import { SessionIcon, SessionIconType } from '../icon';
export enum SessionDropDownItemType {
Default = 'default',
Danger = 'danger',
}
type Props = {
content: string;
type: SessionDropDownItemType;
icon: SessionIconType | null;
active: boolean;
onClick: any;
dataTestId?: string;
};
export const SessionDropdownItem = (props: Props) => {
const clickHandler = (e: any) => {
if (props.onClick) {
e.stopPropagation();
props.onClick();
}
};
const { content, type, icon, active, dataTestId } = props;
return (
<div
className={classNames(
'session-dropdown__item',
active ? 'active' : '',
type || SessionDropDownItemType.Default
)}
role="button"
onClick={clickHandler}
data-testid={dataTestId}
>
{icon ? <SessionIcon iconType={icon} iconSize="small" /> : ''}
<div className="item-content">{content}</div>
</div>
);
};

@ -1,5 +1,5 @@
import React, { ChangeEvent, KeyboardEvent, useRef } from 'react';
import classNames from 'classnames';
import React, { ChangeEvent, KeyboardEvent, SessionDataTestId, useRef } from 'react';
import { useFocusMount } from '../../hooks/useFocusMount';
type Props = {
@ -11,7 +11,7 @@ type Props = {
onPressEnter?: any;
maxLength?: number;
isGroup?: boolean;
dataTestId?: string;
dataTestId?: SessionDataTestId;
};
export const SessionIdEditable = (props: Props) => {

@ -1,9 +1,9 @@
import React, { useState } from 'react';
import React, { SessionDataTestId, useState } from 'react';
import classNames from 'classnames';
import { SessionIconButton } from '../icon';
import { Noop } from '../../types/Util';
import { useHTMLDirection } from '../../util/i18n';
import { SessionIconButton } from '../icon';
type Props = {
label?: string;
@ -17,7 +17,7 @@ type Props = {
onEnterPressed?: any;
autoFocus?: boolean;
ref?: any;
inputDataTestId?: string;
inputDataTestId?: SessionDataTestId;
};
const LabelItem = (props: { inputValue: string; label?: string }) => {

@ -1,4 +1,4 @@
import React, { ChangeEvent } from 'react';
import React, { ChangeEvent, SessionDataTestId } from 'react';
import styled, { CSSProperties } from 'styled-components';
import { Flex } from './Flex';
@ -56,6 +56,8 @@ type SessionRadioProps = {
disabled?: boolean;
radioPosition?: 'left' | 'right';
style?: CSSProperties;
labelDatatestId?: SessionDataTestId;
inputDatatestId?: SessionDataTestId;
};
export const SessionRadio = (props: SessionRadioProps) => {
@ -69,6 +71,8 @@ export const SessionRadio = (props: SessionRadioProps) => {
disabled = false,
radioPosition = 'left',
style,
labelDatatestId,
inputDatatestId,
} = props;
const clickHandler = (e: ChangeEvent<any>) => {
@ -99,7 +103,7 @@ export const SessionRadio = (props: SessionRadioProps) => {
filledSize={filledSize * 2}
outlineOffset={outlineOffset}
disabled={disabled}
data-testid={`input-${value.replaceAll(' ', '-')}`} // data-testid cannot have spaces
data-testid={inputDatatestId}
/>
<StyledLabel
role="button"
@ -109,7 +113,7 @@ export const SessionRadio = (props: SessionRadioProps) => {
beforeMargins={beforeMargins}
aria-label={label}
disabled={disabled}
data-testid={`label-${value}`}
data-testid={labelDatatestId}
>
{label}
</StyledLabel>

@ -1,10 +1,15 @@
import React, { useState } from 'react';
import React, { SessionDataTestId, useState } from 'react';
import useMount from 'react-use/lib/useMount';
import styled, { CSSProperties } from 'styled-components';
import { SessionRadio } from './SessionRadio';
export type SessionRadioItems = Array<{ value: string; label: string }>;
export type SessionRadioItems = Array<{
value: string;
label: string;
inputDatatestId: SessionDataTestId;
labelDatatestId: SessionDataTestId;
}>;
interface Props {
initialItem: string;
@ -50,6 +55,8 @@ export const SessionRadioGroup = (props: Props) => {
label={item.label}
active={itemIsActive}
value={item.value}
inputDatatestId={item.inputDatatestId}
labelDatatestId={item.labelDatatestId}
inputName={group}
onClick={(value: string) => {
setActiveItem(value);

@ -48,7 +48,7 @@ type Props = {
active: boolean;
onClick: () => void;
confirmationDialogParams?: any | undefined;
dataTestId?: string;
dataTestId?: React.SessionDataTestId;
};
export const SessionToggle = (props: Props) => {

@ -1,10 +1,10 @@
import React from 'react';
import React, { SessionDataTestId } from 'react';
import styled from 'styled-components';
type Props = {
size: 'small' | 'normal';
direction?: string;
dataTestId?: string;
dataTestId?: SessionDataTestId;
};
// Module: Spinner

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import React, { ReactNode, SessionDataTestId } from 'react';
import styled, { CSSProperties } from 'styled-components';
import { Flex } from '../basic/Flex';
@ -87,7 +87,7 @@ export type PanelButtonProps = {
disabled?: boolean;
children: ReactNode;
onClick: (...args: Array<any>) => void;
dataTestId: string;
dataTestId: SessionDataTestId;
style?: CSSProperties;
};

@ -1,18 +1,21 @@
import React from 'react';
import classNames from 'classnames';
import React from 'react';
import { CSSProperties } from 'styled-components';
import { Emojify } from './Emojify';
import {
useNicknameOrProfileNameOrShortenedPubkey,
useIsPrivate,
useNicknameOrProfileNameOrShortenedPubkey,
} from '../../hooks/useParamSelector';
import { Emojify } from './Emojify';
type Props = {
pubkey: string;
name?: string | null;
profileName?: string | null;
module?: string;
module?:
| 'module-conversation__user'
| 'module-message-search-result__header__name'
| 'module-message__author';
boldProfileName?: boolean;
compact?: boolean;
shouldShowPubkey: boolean;
@ -38,7 +41,7 @@ export const ContactName = (props: Props) => {
<span
className={classNames(prefix, compact && 'compact')}
dir="auto"
data-testid={`${prefix}__profile-name`}
data-testid={`${prefix}__profile-name` as const}
style={{ textOverflow: 'inherit' }}
>
{shouldShowProfile ? (

@ -1,4 +1,4 @@
import React from 'react';
import React, { SessionDataTestId } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import {
@ -42,7 +42,7 @@ const TextInner = styled.div`
max-width: 390px;
`;
function TextNotification({ html, dataTestId }: { html: string; dataTestId: string }) {
function TextNotification({ html, dataTestId }: { html: string; dataTestId: SessionDataTestId }) {
return (
<Container data-testid={dataTestId}>
<TextInner>

@ -1,5 +1,5 @@
import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import React, { SessionDataTestId, useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { replyToMessage } from '../../../../interactions/conversationInteractions';
@ -30,7 +30,7 @@ type Props = {
messageId: string;
ctxMenuID: string;
isDetailView?: boolean;
dataTestId: string;
dataTestId: SessionDataTestId;
enableReactions: boolean;
};

@ -1,5 +1,5 @@
import { ipcRenderer } from 'electron';
import React, { useCallback } from 'react';
import React, { SessionDataTestId, useCallback } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useMessageExpirationPropsById } from '../../../../hooks/useParamSelector';
@ -14,7 +14,7 @@ import { ExpireTimer } from '../../ExpireTimer';
type Props = {
isDetailView: boolean;
messageId: string;
dataTestId?: string | undefined;
dataTestId: SessionDataTestId;
};
/**

@ -2,6 +2,7 @@ import { debounce, noop } from 'lodash';
import React, {
AriaRole,
MouseEventHandler,
SessionDataTestId,
useCallback,
useContext,
useLayoutEffect,
@ -39,7 +40,7 @@ export type ReadableMessageProps = {
onClick?: MouseEventHandler<HTMLElement>;
onDoubleClickCapture?: MouseEventHandler<HTMLElement>;
role?: AriaRole;
dataTestId: string;
dataTestId: SessionDataTestId;
onContextMenu?: (e: React.MouseEvent<HTMLElement>) => void;
isControlMessage?: boolean;
};

@ -1,20 +1,19 @@
import React from 'react';
import React, { SessionDataTestId } from 'react';
import { DisappearingMessageConversationModeType } from '../../../../../session/disappearing_messages/types';
import { PanelButtonGroup, PanelLabel } from '../../../../buttons/PanelButton';
import { PanelRadioButton } from '../../../../buttons/PanelRadioButton';
function loadDataTestId(mode: DisappearingMessageConversationModeType) {
const dataTestId = 'disappear-%-option';
function toDataTestId(mode: DisappearingMessageConversationModeType): SessionDataTestId {
switch (mode) {
case 'legacy':
return dataTestId.replace('%', 'legacy');
return 'disappear-legacy-option';
case 'deleteAfterRead':
return dataTestId.replace('%', 'after-read');
return 'disappear-after-read-option';
case 'deleteAfterSend':
return dataTestId.replace('%', 'after-send');
return 'disappear-after-send-option';
case 'off':
default:
return dataTestId.replace('%', 'off');
return 'disappear-off-option';
}
}
@ -67,7 +66,7 @@ export const DisappearingModes = (props: DisappearingModesProps) => {
setSelected(mode);
}}
disabled={options[mode]}
dataTestId={loadDataTestId(mode)}
dataTestId={toDataTestId(mode)}
/>
);
})}

@ -34,7 +34,7 @@ export const TimeOptions = (props: TimerOptionsProps) => {
setSelected(option.value);
}}
disabled={disabled}
dataTestId={`time-option-${option.name.replace(' ', '-')}`} // we want "time-option-1-minute", etc as accessibility id
dataTestId={`time-option-${option.value}`} // we want "time-option-3600", etc as accessibility id
/>
);
})}

@ -154,8 +154,8 @@ async function deleteEverythingAndNetworkData() {
}
}
const DEVICE_ONLY = 'device_only';
const DEVICE_AND_NETWORK = 'device_and_network';
const DEVICE_ONLY = 'device_only' as const;
const DEVICE_AND_NETWORK = 'device_and_network' as const;
type DeleteModes = typeof DEVICE_ONLY | typeof DEVICE_AND_NETWORK;
const DescriptionBeforeAskingConfirmation = (props: {
@ -163,6 +163,22 @@ const DescriptionBeforeAskingConfirmation = (props: {
setDeleteMode: (deleteMode: DeleteModes) => void;
}) => {
const { deleteMode, setDeleteMode } = props;
const items = [
{
label: window.i18n('deviceOnly'),
value: DEVICE_ONLY,
},
{
label: window.i18n('entireAccount'),
value: DEVICE_AND_NETWORK,
},
].map(m => ({
...m,
inputDatatestId: `input-${m.value}` as const,
labelDatatestId: `label-${m.value}` as const,
}));
return (
<>
<span className="session-confirm-main-message">{window.i18n('deleteAccountWarning')}</span>
@ -179,10 +195,7 @@ const DescriptionBeforeAskingConfirmation = (props: {
setDeleteMode(value);
}
}}
items={[
{ label: window.i18n('deviceOnly'), value: DEVICE_ONLY },
{ label: window.i18n('entireAccount'), value: 'device_and_network' },
]}
items={items}
/>
</>
);

@ -1,5 +1,5 @@
import { shell } from 'electron';
import React from 'react';
import React, { SessionDataTestId } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import useHover from 'react-use/lib/useHover';
@ -25,7 +25,7 @@ export type StatusLightType = {
glowStartDelay: number;
glowDuration: number;
color?: string;
dataTestId?: string;
dataTestId?: SessionDataTestId;
};
const StyledCountry = styled.div`
@ -143,7 +143,7 @@ const OnionPathModalInner = () => {
export type OnionNodeStatusLightType = {
glowStartDelay: number;
glowDuration: number;
dataTestId?: string;
dataTestId?: SessionDataTestId;
};
/**

@ -1,4 +1,4 @@
import React from 'react';
import React, { SessionDataTestId } from 'react';
import styled, { css, keyframes } from 'styled-components';
import { icons, SessionIconSize, SessionIconType } from '.';
@ -15,7 +15,7 @@ export type SessionIconProps = {
glowStartDelay?: number;
noScale?: boolean;
backgroundColor?: string;
dataTestId?: string;
dataTestId?: SessionDataTestId;
};
const getIconDimensionFromIconSize = (iconSize: SessionIconSize | number) => {
@ -146,7 +146,7 @@ const SessionSvg = (props: {
borderRadius?: string;
backgroundColor?: string;
iconPadding?: string;
dataTestId?: string;
dataTestId?: SessionDataTestId;
}) => {
const colorSvg = props.iconColor ? props.iconColor : '--button-icon-stroke-color';
const pathArray = props.path instanceof Array ? props.path : [props.path];

@ -1,6 +1,6 @@
import React, { KeyboardEvent } from 'react';
import classNames from 'classnames';
import _ from 'lodash';
import React, { KeyboardEvent, SessionDataTestId } from 'react';
import styled from 'styled-components';
import { SessionIcon, SessionIconProps } from '.';
@ -12,8 +12,8 @@ interface SProps extends SessionIconProps {
isSelected?: boolean;
isHidden?: boolean;
margin?: string;
dataTestId?: string;
dataTestIdIcon?: string;
dataTestId?: SessionDataTestId;
dataTestIdIcon?: SessionDataTestId;
id?: string;
style?: object;
tabIndex?: number;

@ -91,6 +91,7 @@ const Section = (props: { type: SectionType }) => {
onAvatarClick={handleClick}
pubkey={ourNumber}
dataTestId="leftpane-primary-avatar"
imageDataTestId={`img-leftpane-primary-avatar`}
/>
);
}

@ -1,4 +1,4 @@
import React from 'react';
import React, { SessionDataTestId } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
@ -45,73 +45,75 @@ const StyledSettingsListItem = styled.div<{ active: boolean }>`
const getCategories = () => {
return [
{
id: SessionSettingCategory.Privacy,
id: 'privacy' as const,
title: window.i18n('privacySettingsTitle'),
},
{
id: SessionSettingCategory.Notifications,
id: 'notifications' as const,
title: window.i18n('notificationsSettingsTitle'),
},
{
id: SessionSettingCategory.Conversations,
id: 'conversations' as const,
title: window.i18n('conversationsSettingsTitle'),
},
{
id: SessionSettingCategory.MessageRequests,
id: 'messageRequests' as const,
title: window.i18n('openMessageRequestInbox'),
},
{
id: SessionSettingCategory.Appearance,
id: 'appearance' as const,
title: window.i18n('appearanceSettingsTitle'),
},
{
id: SessionSettingCategory.Permissions,
id: 'permissions' as const,
title: window.i18n('permissionsSettingsTitle'),
},
{
id: SessionSettingCategory.Help,
id: 'help' as const,
title: window.i18n('helpSettingsTitle'),
},
{
id: SessionSettingCategory.RecoveryPhrase,
id: 'recoveryPhrase' as const,
title: window.i18n('recoveryPhrase'),
},
{
id: SessionSettingCategory.ClearData,
id: 'ClearData' as const,
title: window.i18n('clearDataSettingsTitle'),
},
];
].map(m => ({ ...m, dataTestId: `${m.id}-settings-menu-item` as const }));
};
const LeftPaneSettingsCategoryRow = (props: {
item: { id: SessionSettingCategory; title: string };
item: {
id: SessionSettingCategory;
title: string;
dataTestId: SessionDataTestId;
};
}) => {
const { item } = props;
const { id, title } = item;
const dispatch = useDispatch();
const focusedSettingsSection = useSelector(getFocusedSettingsSection);
const dataTestId = `${title.toLowerCase().replace(' ', '-')}-settings-menu-item`;
const isClearData = id === SessionSettingCategory.ClearData;
const isClearData = id === 'ClearData';
return (
<StyledSettingsListItem
data-testid={dataTestId}
data-testid={item.dataTestId}
key={id}
active={id === focusedSettingsSection}
role="link"
onClick={() => {
switch (id) {
case SessionSettingCategory.MessageRequests:
case 'messageRequests':
dispatch(showLeftPaneSection(SectionType.Message));
dispatch(setLeftOverlayMode('message-requests'));
dispatch(resetConversationExternal());
break;
case SessionSettingCategory.RecoveryPhrase:
case 'recoveryPhrase':
dispatch(recoveryPhraseModal({}));
break;
case SessionSettingCategory.ClearData:
case 'ClearData':
dispatch(updateDeleteAccountModal({}));
break;
default:

@ -7,16 +7,11 @@ import { SettingsKey } from '../../data/settings-key';
import { isAudioNotificationSupported } from '../../types/Settings';
import { Notifications } from '../../util/notifications';
import { SessionButton } from '../basic/SessionButton';
import { SessionRadioGroup } from '../basic/SessionRadioGroup';
import { SessionRadioGroup, SessionRadioItems } from '../basic/SessionRadioGroup';
import { SpacerLG } from '../basic/Text';
import { SessionSettingsItemWrapper, SessionToggleWithDescription } from './SessionSettingListItem';
enum NOTIFICATION {
MESSAGE = 'message',
NAME = 'name',
COUNT = 'count',
OFF = 'off',
}
const NotificationType = { message: 'message', name: 'name', count: 'count', off: 'off' } as const;
const StyledButtonContainer = styled.div`
display: flex;
@ -33,28 +28,26 @@ export const SessionNotificationGroupSettings = (props: { hasPassword: boolean |
}
const initialNotificationEnabled =
window.getSettingValue(SettingsKey.settingsNotification) || NOTIFICATION.MESSAGE;
window.getSettingValue(SettingsKey.settingsNotification) || NotificationType.message;
const initialAudioNotificationEnabled =
window.getSettingValue(SettingsKey.settingsAudioNotification) || false;
const notificationsAreEnabled =
initialNotificationEnabled && initialNotificationEnabled !== NOTIFICATION.OFF;
initialNotificationEnabled && initialNotificationEnabled !== NotificationType.off;
const items = [
{
label: window.i18n('nameAndMessage'),
value: NOTIFICATION.MESSAGE,
},
{
label: window.i18n('nameOnly'),
value: NOTIFICATION.NAME,
},
{
label: window.i18n('noNameOrMessage'),
value: NOTIFICATION.COUNT,
},
];
const options = [
{ label: window.i18n('nameAndMessage'), value: NotificationType.message },
{ label: window.i18n('nameOnly'), value: NotificationType.name },
{ label: window.i18n('noNameOrMessage'), value: NotificationType.count },
] as const;
const items: SessionRadioItems = options.map(m => ({
label: m.label,
value: m.value,
inputDatatestId: `input-${m.value}`,
labelDatatestId: `label-${m.value}`,
}));
const onClickPreview = () => {
if (!notificationsAreEnabled) {
@ -79,7 +72,7 @@ export const SessionNotificationGroupSettings = (props: { hasPassword: boolean |
onClickToggle={async () => {
await window.setSettingValue(
SettingsKey.settingsNotification,
notificationsAreEnabled ? NOTIFICATION.OFF : NOTIFICATION.MESSAGE
notificationsAreEnabled ? 'off' : 'message'
);
forceUpdate();
}}

@ -1,6 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { Noop } from '../../types/Util';
import {
SessionButton,
SessionButtonColor,
@ -10,7 +11,6 @@ import {
import { SessionToggle } from '../basic/SessionToggle';
import { SessionConfirmDialogProps } from '../dialog/SessionConfirm';
import { SessionIconButton } from '../icon';
import { Noop } from '../../types/Util';
type ButtonSettingsProps = {
title?: string;
@ -19,7 +19,7 @@ type ButtonSettingsProps = {
buttonType?: SessionButtonType;
buttonShape?: SessionButtonShape;
buttonText: string;
dataTestId?: string;
dataTestId?: React.SessionDataTestId;
onClick: () => void;
};
@ -130,7 +130,7 @@ export const SessionToggleWithDescription = (props: {
onClickToggle: () => void;
confirmationDialogParams?: SessionConfirmDialogProps;
childrenDescription?: React.ReactNode; // if set, those elements will be appended next to description field (only used for typing message settings as of now)
dataTestId?: string;
dataTestId?: React.SessionDataTestId;
}) => {
const {
title,

@ -1,6 +1,6 @@
import autoBind from 'auto-bind';
import { shell } from 'electron';
import React from 'react';
import autoBind from 'auto-bind';
import styled from 'styled-components';
import { SettingsHeader } from './SessionSettingsHeader';
@ -9,15 +9,15 @@ import { SessionIconButton } from '../icon';
import { SessionNotificationGroupSettings } from './SessionNotificationGroupSettings';
import { CategoryConversations } from './section/CategoryConversations';
import { SettingsCategoryPrivacy } from './section/CategoryPrivacy';
import { SettingsCategoryAppearance } from './section/CategoryAppearance';
import { Data } from '../../data/data';
import { SettingsCategoryPermissions } from './section/CategoryPermissions';
import { SettingsCategoryHelp } from './section/CategoryHelp';
import { sessionPassword } from '../../state/ducks/modalDialog';
import { PasswordAction } from '../dialog/SessionPasswordDialog';
import { SectionType, showLeftPaneSection } from '../../state/ducks/section';
import { PasswordAction } from '../dialog/SessionPasswordDialog';
import { SettingsCategoryAppearance } from './section/CategoryAppearance';
import { CategoryConversations } from './section/CategoryConversations';
import { SettingsCategoryHelp } from './section/CategoryHelp';
import { SettingsCategoryPermissions } from './section/CategoryPermissions';
import { SettingsCategoryPrivacy } from './section/CategoryPrivacy';
export function displayPasswordModal(
passwordAction: PasswordAction,
@ -41,17 +41,16 @@ export function getCallMediaPermissionsSettings() {
return window.getSettingValue('call-media-permissions');
}
export enum SessionSettingCategory {
Privacy = 'privacy',
Notifications = 'notifications',
Conversations = 'conversations',
MessageRequests = 'messageRequests',
Appearance = 'appearance',
Permissions = 'permissions',
Help = 'help',
RecoveryPhrase = 'recoveryPhrase',
ClearData = 'ClearData',
}
export type SessionSettingCategory =
| 'privacy'
| 'notifications'
| 'conversations'
| 'messageRequests'
| 'appearance'
| 'permissions'
| 'help'
| 'recoveryPhrase'
| 'ClearData';
export interface SettingsViewProps {
category: SessionSettingCategory;
@ -120,25 +119,25 @@ const SettingInCategory = (props: {
}
switch (category) {
// special case for blocked user
case SessionSettingCategory.Conversations:
case 'conversations':
return <CategoryConversations />;
case SessionSettingCategory.Appearance:
case 'appearance':
return <SettingsCategoryAppearance hasPassword={hasPassword} />;
case SessionSettingCategory.Notifications:
case 'notifications':
return <SessionNotificationGroupSettings hasPassword={hasPassword} />;
case SessionSettingCategory.Privacy:
case 'privacy':
return (
<SettingsCategoryPrivacy onPasswordUpdated={onPasswordUpdated} hasPassword={hasPassword} />
);
case SessionSettingCategory.Help:
case 'help':
return <SettingsCategoryHelp hasPassword={hasPassword} />;
case SessionSettingCategory.Permissions:
case 'permissions':
return <SettingsCategoryPermissions hasPassword={hasPassword} />;
// these three down there have no options, they are just a button
case SessionSettingCategory.ClearData:
case SessionSettingCategory.MessageRequests:
case SessionSettingCategory.RecoveryPhrase:
case 'ClearData':
case 'messageRequests':
case 'recoveryPhrase':
default:
return null;
}

@ -1,7 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { assertUnreachable } from '../../types/sqlSharedTypes';
import { SessionSettingCategory, SettingsViewProps } from './SessionSettings';
import { SettingsViewProps } from './SessionSettings';
type Props = Pick<SettingsViewProps, 'category'>;
@ -26,27 +26,27 @@ export const SettingsHeader = (props: Props) => {
let categoryTitle: string | null = null;
switch (category) {
case SessionSettingCategory.Appearance:
case 'appearance':
categoryTitle = window.i18n('appearanceSettingsTitle');
break;
case SessionSettingCategory.Conversations:
case 'conversations':
categoryTitle = window.i18n('conversationsSettingsTitle');
break;
case SessionSettingCategory.Notifications:
case 'notifications':
categoryTitle = window.i18n('notificationsSettingsTitle');
break;
case SessionSettingCategory.Help:
case 'help':
categoryTitle = window.i18n('helpSettingsTitle');
break;
case SessionSettingCategory.Permissions:
case 'permissions':
categoryTitle = window.i18n('permissionsSettingsTitle');
break;
case SessionSettingCategory.Privacy:
case 'privacy':
categoryTitle = window.i18n('privacySettingsTitle');
break;
case SessionSettingCategory.ClearData:
case SessionSettingCategory.MessageRequests:
case SessionSettingCategory.RecoveryPhrase:
case 'ClearData':
case 'messageRequests':
case 'recoveryPhrase':
throw new Error(`no header for should be tried to be rendered for "${category}"`);
default:

@ -5,14 +5,14 @@ import useUpdate from 'react-use/lib/useUpdate';
import { SettingsKey } from '../../../data/settings-key';
import { ToastUtils } from '../../../session/utils';
import { toggleAudioAutoplay } from '../../../state/ducks/userConfig';
import { useHasEnterSendEnabled } from '../../../state/selectors/settings';
import { getAudioAutoplay } from '../../../state/selectors/userConfig';
import { SessionRadioGroup } from '../../basic/SessionRadioGroup';
import { SessionRadioGroup, SessionRadioItems } from '../../basic/SessionRadioGroup';
import { BlockedContactsList } from '../BlockedList';
import {
SessionSettingsItemWrapper,
SessionToggleWithDescription,
} from '../SessionSettingListItem';
import { useHasEnterSendEnabled } from '../../../state/selectors/settings';
async function toggleCommunitiesPruning() {
try {
@ -88,14 +88,18 @@ const EnterKeyFunctionSetting = () => {
const initialSetting = useHasEnterSendEnabled();
const selectedWithSettingTrue = 'enterForNewLine';
const items = [
const items: SessionRadioItems = [
{
label: window.i18n('enterSendNewMessageDescription'),
value: 'enterForSend',
inputDatatestId: 'input-enterForSend',
labelDatatestId: 'label-enterForSend',
},
{
label: window.i18n('enterNewLineDescription'),
value: selectedWithSettingTrue,
inputDatatestId: `input-${selectedWithSettingTrue}`,
labelDatatestId: `label-${selectedWithSettingTrue}`,
},
];

@ -391,8 +391,18 @@ export async function deleteMessagesById(messageIds: Array<string>, conversation
: window.i18n('deleteMessageQuestion'),
radioOptions: !isMe
? [
{ label: window.i18n('deleteJustForMe'), value: 'deleteJustForMe' },
{ label: window.i18n('deleteForEveryone'), value: 'deleteForEveryone' },
{
label: window.i18n('deleteJustForMe'),
value: 'deleteJustForMe',
inputDatatestId: 'input-deleteJustForMe',
labelDatatestId: 'label-deleteJustForMe',
},
{
label: window.i18n('deleteForEveryone'),
value: 'deleteForEveryone',
inputDatatestId: 'input-deleteForEveryone',
labelDatatestId: 'label-deleteForEveryone',
},
]
: undefined,
okText: window.i18n('delete'),

202
ts/react.d.ts vendored

@ -0,0 +1,202 @@
import 'react';
/**
* WARNING: if you change something here, you will most likely break some integration tests.
* So be sure to check with QA first.
*/
declare module 'react' {
type SessionDataTestId =
| 'group_member_status_text'
| 'group_member_name'
| 'loading-spinner'
| 'session-toast'
| 'loading-animation'
| 'your-session-id'
| 'chooser-new-community'
| 'chooser-new-group'
| 'chooser-new-conversation-button'
| 'new-conversation-button'
| 'module-conversation__user__profile-name'
| 'message-request-banner'
| 'leftpane-section-container'
| 'group-name-input'
| 'recovery-phrase-seed-modal'
| 'password-input-reconfirm'
| 'conversation-header-subtitle'
| 'password-input'
| 'nickname-input'
| 'image-upload-click'
| 'profile-name-input'
| 'your-profile-name'
| 'edit-profile-dialog'
| 'image-upload-section'
| 'right-panel-group-name'
| 'control-message'
| 'header-conversation-name'
| 'disappear-messages-type-and-time'
| 'message-input'
| 'messages-container'
| 'decline-and-block-message-request'
| 'session-dropdown'
| 'path-light-container'
| 'add-user-button'
| 'back-button-conversation-options'
| 'send-message-button'
| 'scroll-to-bottom-button'
| 'end-call'
| 'modal-close-button'
| 'end-voice-message'
| 'back-button-message-details'
| 'edit-profile-icon'
| 'microphone-button'
| 'call-button'
| 'attachments-button'
// generic button types
| 'emoji-button'
| 'reveal-blocked-user-settings'
// left pane section types
| 'theme-section'
| 'settings-section'
| 'message-section'
| 'privacy-section'
// settings menu item types
| 'messageRequests-settings-menu-item' // needs to be tweaked
| 'recoveryPhrase-settings-menu-item' // needs to be tweaked
| 'privacy-settings-menu-item' // needs to be tweaked
| 'notifications-settings-menu-item' // needs to be tweaked
| 'conversations-settings-menu-item' // needs to be tweaked
| 'appearance-settings-menu-item' // needs to be tweaked
| 'help-settings-menu-item' // needs to be tweaked
| 'permissions-settings-menu-item' // needs to be tweaked
| 'ClearData-settings-menu-item' // TODO AUDRIC needs to be tweaked
// timer options
| 'time-option-0'
| 'time-option-5'
| 'time-option-10'
| 'time-option-30'
| 'time-option-60'
| 'time-option-300'
| 'time-option-1800'
| 'time-option-3600'
| 'time-option-21600'
| 'time-option-43200'
| 'time-option-86400'
| 'time-option-604800'
| 'time-option-1209600'
// generic readably message (not control message)
| 'message-content'
// control message types
| 'message-request-response-message'
| 'interaction-notification'
| 'data-extraction-notification'
| 'group-update-message'
| 'disappear-control-message'
// subtle control message types
| 'group-request-explanation'
| 'conversation-request-explanation'
| 'group-invite-control-message'
| 'empty-conversation-notification'
// call notification types
| 'call-notification-missed-call'
| 'call-notification-started-call'
| 'call-notification-answered-a-call'
// disappear options
| 'disappear-after-send-option'
| 'disappear-after-read-option'
| 'disappear-legacy-option'
| 'disappear-off-option'
// settings toggle and buttons
| 'remove-password-settings-button'
| 'change-password-settings-button'
| 'enable-read-receipts'
| 'set-password-button'
| 'enable-read-receipts'
| 'enable-calls'
| 'enable-microphone'
| 'enable-follow-system-theme'
| 'unblock-button-settings-screen'
| 'save-attachment-from-details'
| 'resend-msg-from-details'
| 'reply-to-msg-from-details'
| 'leave-group-button'
| 'disappearing-messages'
| 'group-members'
| 'remove-moderators'
| 'add-moderators'
| 'edit-group-name'
// SessionRadioGroup & SessionRadio
| 'password-input-confirm'
| 'msg-status'
| 'input-device_and_network'
| 'label-device_and_network'
| 'input-device_only'
| 'label-device_only'
| 'input-deleteForEveryone'
| 'label-deleteForEveryone'
| 'input-deleteJustForMe'
| 'label-deleteJustForMe'
| 'input-enterForSend'
| 'label-enterForSend'
| 'input-enterForNewLine'
| 'label-enterForNewLine'
| 'input-message'
| 'label-message'
| 'input-name'
| 'label-name'
| 'input-count'
| 'label-count'
// to sort
| 'restore-using-recovery'
| 'link-device'
| 'continue-session-button'
| 'next-new-conversation-button'
| 'reveal-recovery-phrase'
| 'resend_invite_button'
| 'session-confirm-cancel-button'
| 'session-confirm-ok-button'
| 'confirm-nickname'
| 'path-light-svg'
| 'group_member_status_text'
| 'group_member_name'
| 'resend_promote_button'
| 'next-button'
| 'save-button-profile-update'
| 'save-button-profile-update'
| 'copy-button-profile-update'
| 'disappear-set-button'
| 'decline-message-request'
| 'accept-message-request'
| 'mentions-popup-row'
| 'session-id-signup'
| 'three-dot-loading-animation'
| 'recovery-phrase-input'
| 'display-name-input'
| 'new-session-conversation'
| 'new-closed-group-name'
| 'leftpane-primary-avatar'
| 'img-leftpane-primary-avatar'
| 'conversation-options-avatar'
// modules profile name
| 'module-conversation__user__profile-name'
| 'module-message-search-result__header__name__profile-name'
| 'module-message__author__profile-name'
| 'module-contact-name__profile-name'
| 'delete-from-details';
interface HTMLAttributes {
'data-testid'?: SessionDataTestId;
}
}

@ -2,35 +2,43 @@ import moment from 'moment';
import { isDevProd } from '../../shared/env_vars';
import { LocalizerKeys } from '../../types/LocalizerKeys';
type TimerOptionsEntry = { name: string; value: number };
type TimerOptionsEntry = { name: string; value: TimerOptionSeconds };
export type TimerOptionsArray = Array<TimerOptionsEntry>;
type TimerOptionSeconds =
| 0
| 5
| 10
| 30
| 60
| 300
| 1800
| 3600
| 21600
| 43200
| 86400
| 604800
| 1209600;
const timerOptionsDurations: Array<{
time: number;
unit: moment.DurationInputArg2;
seconds: number;
seconds: TimerOptionSeconds;
}> = [
{ time: 0, unit: 'seconds' as moment.DurationInputArg2 },
{ time: 5, unit: 'seconds' as moment.DurationInputArg2 },
{ time: 10, unit: 'seconds' as moment.DurationInputArg2 },
{ time: 30, unit: 'seconds' as moment.DurationInputArg2 },
{ time: 1, unit: 'minute' as moment.DurationInputArg2 },
{ time: 5, unit: 'minutes' as moment.DurationInputArg2 },
{ time: 30, unit: 'minutes' as moment.DurationInputArg2 },
{ time: 1, unit: 'hour' as moment.DurationInputArg2 },
{ time: 6, unit: 'hours' as moment.DurationInputArg2 },
{ time: 12, unit: 'hours' as moment.DurationInputArg2 },
{ time: 1, unit: 'day' as moment.DurationInputArg2 },
{ time: 1, unit: 'week' as moment.DurationInputArg2 },
{ time: 2, unit: 'weeks' as moment.DurationInputArg2 },
].map(o => {
const duration = moment.duration(o.time, o.unit); // 5, 'seconds'
return {
time: o.time,
unit: o.unit,
seconds: duration.asSeconds(),
};
});
{ time: 0, unit: 'seconds' as moment.DurationInputArg2, seconds: 0 },
{ time: 5, unit: 'seconds' as moment.DurationInputArg2, seconds: 5 },
{ time: 10, unit: 'seconds' as moment.DurationInputArg2, seconds: 10 },
{ time: 30, unit: 'seconds' as moment.DurationInputArg2, seconds: 30 },
{ time: 1, unit: 'minute' as moment.DurationInputArg2, seconds: 60 },
{ time: 5, unit: 'minutes' as moment.DurationInputArg2, seconds: 300 },
{ time: 30, unit: 'minutes' as moment.DurationInputArg2, seconds: 1800 },
{ time: 1, unit: 'hour' as moment.DurationInputArg2, seconds: 3600 },
{ time: 6, unit: 'hours' as moment.DurationInputArg2, seconds: 21600 },
{ time: 12, unit: 'hours' as moment.DurationInputArg2, seconds: 43200 },
{ time: 1, unit: 'day' as moment.DurationInputArg2, seconds: 86400 },
{ time: 1, unit: 'week' as moment.DurationInputArg2, seconds: 604800 },
{ time: 2, unit: 'weeks' as moment.DurationInputArg2, seconds: 1209600 },
];
function getTimerOptionName(time: number, unit: moment.DurationInputArg2) {
return (
@ -62,7 +70,7 @@ function getAbbreviated(seconds = 0) {
return [seconds, 's'].join('');
}
const VALUES: Array<number> = timerOptionsDurations.map(t => {
const VALUES = timerOptionsDurations.map(t => {
return t.seconds;
});
@ -110,10 +118,10 @@ const DELETE_LEGACY = VALUES.filter(option => {
}).filter(filterOutDebugValues);
const DEFAULT_OPTIONS = {
DELETE_AFTER_READ: 43200, // 12 hours
DELETE_AFTER_SEND: 86400, // 1 day
DELETE_AFTER_READ: 43200 as TimerOptionSeconds, // 12 hours
DELETE_AFTER_SEND: 86400 as TimerOptionSeconds, // 1 day
// TODO legacy messages support will be removed in a future release
LEGACY: 86400, // 1 day
LEGACY: 86400 as TimerOptionSeconds, // 1 day
};
export const TimerOptions = {

@ -2,7 +2,6 @@ import React from 'react';
import { toast } from 'react-toastify';
import { SessionToast, SessionToastType } from '../../components/basic/SessionToast';
import { SessionIconType } from '../../components/icon';
import { SessionSettingCategory } from '../../components/settings/SessionSettings';
import { SectionType, showLeftPaneSection, showSettingsSection } from '../../state/ducks/section';
// if you push a toast manually with toast...() be sure to set the type attribute of the SessionToast component
@ -127,7 +126,7 @@ export function pushedMissedCall(conversationName: string) {
const openPermissionsSettings = () => {
window.inboxStore?.dispatch(showLeftPaneSection(SectionType.Settings));
window.inboxStore?.dispatch(showSettingsSection(SessionSettingCategory.Permissions));
window.inboxStore?.dispatch(showSettingsSection('permissions'));
};
export function pushedMissedCallCauseOfPermission(conversationName: string) {

@ -0,0 +1,8 @@
/**
* Returns a string with all spaces replaced to '-'.
* A datatestid cannot have spaces on desktop, so we use this to format them accross the app.
*
*/
export function strToDataTestId(input: string) {
return input.replaceAll(' ', '-');
}

@ -384,7 +384,6 @@ const refreshGroupDetailsFromWrapper = createAsyncThunk(
const destroyGroupDetails = createAsyncThunk(
'group/destroyGroupDetails',
async ({ groupPk }: { groupPk: GroupPubkeyType }) => {
debugger;
const us = UserUtils.getOurPubKeyStrFromCache();
const weAreAdmin = await checkWeAreAdmin(groupPk);
const allMembers = await MetaGroupWrapperActions.memberGetAll(groupPk);

@ -173,7 +173,7 @@ export const reducer = (
return {
...state,
focusedSection: payload,
focusedSettingsSection: SessionSettingCategory.Privacy,
focusedSettingsSection: 'privacy',
};
case FOCUS_SETTINGS_SECTION:
return {

Loading…
Cancel
Save