Default disappearing message timeout fixes

This commit is contained in:
Fedor Indutny 2021-06-25 16:52:56 -07:00 committed by GitHub
parent c9415dcf67
commit cd28e71bc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 456 additions and 164 deletions

View file

@ -13,7 +13,7 @@ import {
} from 'react-contextmenu';
import { Emojify } from './Emojify';
import { DisappearingTimeDialog } from './DisappearingTimeDialog';
import { DisappearingTimeDialog } from '../DisappearingTimeDialog';
import { Avatar, AvatarSize } from '../Avatar';
import { InContactsIcon } from '../InContactsIcon';

View file

@ -1,29 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import { DisappearingTimeDialog } from './DisappearingTimeDialog';
import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json';
import { EXPIRE_TIMERS } from '../../test-both/util/expireTimers';
const story = storiesOf('Components/DisappearingTimeDialog', module);
const i18n = setupI18n('en', enMessages);
EXPIRE_TIMERS.forEach(({ value, label }) => {
story.add(`Initial value: ${label}`, () => {
return (
<DisappearingTimeDialog
i18n={i18n}
initialValue={value}
onSubmit={action('onSubmit')}
onClose={action('onClose')}
/>
);
});
});

View file

@ -1,124 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-restricted-syntax */
import React, { useState } from 'react';
import { ConfirmationDialog } from '../ConfirmationDialog';
import { Select } from '../Select';
import { LocalizerType } from '../../types/Util';
import { Theme } from '../../util/theme';
const CSS_MODULE = 'module-disappearing-time-dialog';
const DEFAULT_VALUE = 60;
export type PropsType = Readonly<{
i18n: LocalizerType;
theme?: Theme;
initialValue?: number;
onSubmit: (value: number) => void;
onClose: () => void;
}>;
const UNITS = ['seconds', 'minutes', 'hours', 'days', 'weeks'];
const UNIT_TO_MS = new Map<string, number>([
['seconds', 1],
['minutes', 60],
['hours', 60 * 60],
['days', 24 * 60 * 60],
['weeks', 7 * 24 * 60 * 60],
]);
const RANGES = new Map<string, [number, number]>([
['seconds', [1, 60]],
['minutes', [1, 60]],
['hours', [1, 24]],
['days', [1, 7]],
['weeks', [1, 5]],
]);
export function DisappearingTimeDialog(props: PropsType): JSX.Element {
const {
i18n,
theme,
initialValue = DEFAULT_VALUE,
onSubmit,
onClose,
} = props;
let initialUnit = 'seconds';
let initialUnitValue = 1;
for (const unit of UNITS) {
const ms = UNIT_TO_MS.get(unit) || 1;
if (initialValue < ms) {
break;
}
initialUnit = unit;
initialUnitValue = Math.floor(initialValue / ms);
}
const [unitValue, setUnitValue] = useState(initialUnitValue);
const [unit, setUnit] = useState(initialUnit);
const range = RANGES.get(unit) || [1, 1];
const values: Array<number> = [];
for (let i = range[0]; i < range[1]; i += 1) {
values.push(i);
}
return (
<ConfirmationDialog
moduleClassName={CSS_MODULE}
i18n={i18n}
theme={theme}
onClose={onClose}
title={i18n('DisappearingTimeDialog__title')}
hasXButton
actions={[
{
text: i18n('DisappearingTimeDialog__set'),
style: 'affirmative',
action() {
onSubmit(unitValue * (UNIT_TO_MS.get(unit) || 1));
},
},
]}
>
<p>{i18n('DisappearingTimeDialog__body')}</p>
<section className={`${CSS_MODULE}__time-boxes`}>
<Select
moduleClassName={`${CSS_MODULE}__time-boxes__value`}
value={unitValue}
onChange={newValue => setUnitValue(parseInt(newValue, 10))}
options={values.map(value => ({ value, text: value.toString() }))}
/>
<Select
moduleClassName={`${CSS_MODULE}__time-boxes__units`}
value={unit}
onChange={newUnit => {
setUnit(newUnit);
const ranges = RANGES.get(newUnit);
if (!ranges) {
return;
}
const [min, max] = ranges;
setUnitValue(Math.max(min, Math.min(max - 1, unitValue)));
}}
options={UNITS.map(unitName => {
return {
value: unitName,
text: i18n(`DisappearingTimeDialog__${unitName}`),
};
})}
/>
</section>
</ConfirmationDialog>
);
}

View file

@ -11,6 +11,8 @@ export type Props = {
expireTimer: number;
};
const CSS_MODULE = 'module-universal-timer-notification';
export const UniversalTimerNotification: React.FC<Props> = props => {
const { i18n, expireTimer } = props;
@ -18,11 +20,19 @@ export const UniversalTimerNotification: React.FC<Props> = props => {
return null;
}
const timeValue = expirationTimer.format(i18n, expireTimer);
return (
<div className="module-universal-timer-notification">
{i18n('UniversalTimerNotification__text', {
timeValue: expirationTimer.format(i18n, expireTimer),
})}
<div className={CSS_MODULE}>
<div className={`${CSS_MODULE}__icon-container`}>
<div className={`${CSS_MODULE}__icon`} />
<div className={`${CSS_MODULE}__icon-label`}>{timeValue}</div>
</div>
<div className={`${CSS_MODULE}__message`}>
{i18n('UniversalTimerNotification__text', {
timeValue,
})}
</div>
</div>
);
};

View file

@ -5,15 +5,12 @@ import React, { useState, ReactNode } from 'react';
import { ConversationType } from '../../../state/ducks/conversations';
import { assert } from '../../../util/assert';
import * as expirationTimer from '../../../util/expirationTimer';
import { LocalizerType } from '../../../types/Util';
import { MediaItemType } from '../../LightboxGallery';
import { missingCaseError } from '../../../util/missingCaseError';
import { Select } from '../../Select';
import { DisappearingTimeDialog } from '../DisappearingTimeDialog';
import { DisappearingTimerSelect } from '../../DisappearingTimerSelect';
import { PanelRow } from './PanelRow';
import { PanelSection } from './PanelSection';
@ -39,7 +36,6 @@ enum ModalState {
EditingGroupDescription,
EditingGroupTitle,
AddingGroupMembers,
CustomDisappearingTimeout,
}
export type StateProps = {
@ -114,15 +110,6 @@ export const ConversationDetails: React.ComponentType<Props> = ({
setAddGroupMembersRequestState,
] = useState<RequestState>(RequestState.Inactive);
const updateExpireTimer = (value: string) => {
const intValue = parseInt(value, 10);
if (intValue === -1) {
setModalState(ModalState.CustomDisappearingTimeout);
} else {
setDisappearingMessages(intValue);
}
};
if (conversation === undefined) {
throw new Error('ConversationDetails rendered without a conversation');
}
@ -218,55 +205,10 @@ export const ConversationDetails: React.ComponentType<Props> = ({
/>
);
break;
case ModalState.CustomDisappearingTimeout:
modalNode = (
<DisappearingTimeDialog
i18n={i18n}
initialValue={conversation.expireTimer}
onSubmit={value => {
setModalState(ModalState.NothingOpen);
setDisappearingMessages(value);
}}
onClose={() => setModalState(ModalState.NothingOpen)}
/>
);
break;
default:
throw missingCaseError(modalState);
}
const expireTimer: number = conversation.expireTimer || 0;
let expirationTimerOptions: ReadonlyArray<{
readonly value: number;
readonly text: string;
}> = expirationTimer.DEFAULT_DURATIONS_IN_SECONDS.map(seconds => {
const text = expirationTimer.format(i18n, seconds, {
capitalizeOff: true,
});
return {
value: seconds,
text,
};
});
const isCustomTimeSelected = !expirationTimer.DEFAULT_DURATIONS_SET.has(
expireTimer
);
// Custom time...
expirationTimerOptions = [
...expirationTimerOptions,
{
value: -1,
text: i18n(
isCustomTimeSelected
? 'selectedCustomDisappearingTimeOption'
: 'customDisappearingTimeOption'
),
},
];
return (
<div className="conversation-details-panel">
<ConversationDetailsHeader
@ -297,17 +239,12 @@ export const ConversationDetails: React.ComponentType<Props> = ({
info={i18n('ConversationDetails--disappearing-messages-info')}
label={i18n('ConversationDetails--disappearing-messages-label')}
right={
<Select
onChange={updateExpireTimer}
value={isCustomTimeSelected ? -1 : expireTimer}
options={expirationTimerOptions}
<DisappearingTimerSelect
i18n={i18n}
value={conversation.expireTimer || 0}
onChange={setDisappearingMessages}
/>
}
rightInfo={
isCustomTimeSelected
? expirationTimer.format(i18n, expireTimer)
: undefined
}
/>
) : null}
<PanelRow

View file

@ -13,7 +13,6 @@ export type Props = {
label: string | React.ReactNode;
info?: string;
right?: string | React.ReactNode;
rightInfo?: string;
actions?: React.ReactNode;
onClick?: () => void;
};
@ -28,7 +27,6 @@ export const PanelRow: React.ComponentType<Props> = ({
label,
info,
right,
rightInfo,
actions,
onClick,
}) => {
@ -39,14 +37,7 @@ export const PanelRow: React.ComponentType<Props> = ({
<div>{label}</div>
{info !== undefined ? <div className={bem('info')}>{info}</div> : null}
</div>
{right !== undefined ? (
<div className={bem('right')}>
{right}
{rightInfo !== undefined ? (
<div className={bem('right-info')}>{rightInfo}</div>
) : null}
</div>
) : null}
{right !== undefined ? <div className={bem('right')}>{right}</div> : null}
{actions !== undefined ? (
<div className={alwaysShowActions ? '' : bem('actions')}>{actions}</div>
) : null}