Default disappearing message timeout fixes
This commit is contained in:
parent
c9415dcf67
commit
cd28e71bc6
25 changed files with 456 additions and 164 deletions
|
@ -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';
|
||||
|
||||
|
|
|
@ -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')}
|
||||
/>
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue