New feature flag with ability to migrate GV1 groups
This commit is contained in:
parent
089a6fb5a2
commit
2b8ae412e0
26 changed files with 608 additions and 189 deletions
|
@ -69,6 +69,8 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
overrideProps.messageRequestsEnabled || false
|
||||
),
|
||||
title: '',
|
||||
// GroupV1 Disabled Actions
|
||||
onStartGroupMigration: action('onStartGroupMigration'),
|
||||
});
|
||||
|
||||
story.add('Default', () => {
|
||||
|
|
|
@ -18,6 +18,10 @@ import {
|
|||
MessageRequestActions,
|
||||
Props as MessageRequestActionsProps,
|
||||
} from './conversation/MessageRequestActions';
|
||||
import {
|
||||
GroupV1DisabledActions,
|
||||
PropsType as GroupV1DisabledActionsPropsType,
|
||||
} from './conversation/GroupV1DisabledActions';
|
||||
import { MandatoryProfileSharingActions } from './conversation/MandatoryProfileSharingActions';
|
||||
import { countStickers } from './stickers/lib';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
@ -27,6 +31,7 @@ export type OwnProps = {
|
|||
readonly i18n: LocalizerType;
|
||||
readonly areWePending?: boolean;
|
||||
readonly groupVersion?: 1 | 2;
|
||||
readonly isGroupV1AndDisabled?: boolean;
|
||||
readonly isMissingMandatoryProfileSharing?: boolean;
|
||||
readonly messageRequestsEnabled?: boolean;
|
||||
readonly acceptedMessageRequest?: boolean;
|
||||
|
@ -77,6 +82,7 @@ export type Props = Pick<
|
|||
| 'clearShowPickerHint'
|
||||
> &
|
||||
MessageRequestActionsProps &
|
||||
Pick<GroupV1DisabledActionsPropsType, 'onStartGroupMigration'> &
|
||||
OwnProps;
|
||||
|
||||
const emptyElement = (el: HTMLElement) => {
|
||||
|
@ -135,6 +141,9 @@ export const CompositionArea = ({
|
|||
phoneNumber,
|
||||
profileName,
|
||||
title,
|
||||
// GroupV1 Disabled Actions
|
||||
isGroupV1AndDisabled,
|
||||
onStartGroupMigration,
|
||||
}: Props): JSX.Element => {
|
||||
const [disabled, setDisabled] = React.useState(false);
|
||||
const [showMic, setShowMic] = React.useState(!draftText);
|
||||
|
@ -381,6 +390,16 @@ export const CompositionArea = ({
|
|||
);
|
||||
}
|
||||
|
||||
// If this is a V1 group, now disabled entirely, we show UI to help them upgrade
|
||||
if (isGroupV1AndDisabled) {
|
||||
return (
|
||||
<GroupV1DisabledActions
|
||||
i18n={i18n}
|
||||
onStartGroupMigration={onStartGroupMigration}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="module-composition-area">
|
||||
<div className="module-composition-area__toggle-large">
|
||||
|
|
|
@ -570,6 +570,13 @@ export const CompositionInput: React.ComponentType<Props> = props => {
|
|||
[]
|
||||
);
|
||||
|
||||
// The onClick handler below is only to make it easier for mouse users to focus the
|
||||
// message box. In 'large' mode, the actual Quill text box can be one line while the
|
||||
// visual text box is much larger. Clicking that should allow you to start typing,
|
||||
// hence the click handler.
|
||||
// eslint-disable-next-line max-len
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
|
||||
|
||||
return (
|
||||
<Manager>
|
||||
<Reference>
|
||||
|
@ -577,6 +584,7 @@ export const CompositionInput: React.ComponentType<Props> = props => {
|
|||
<div className="module-composition-input__input" ref={ref}>
|
||||
<div
|
||||
ref={scrollerRef}
|
||||
onClick={focus}
|
||||
className={classNames(
|
||||
'module-composition-input__input__scroller',
|
||||
large
|
||||
|
|
|
@ -43,7 +43,6 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
),
|
||||
i18n,
|
||||
invitedMembers: overrideProps.invitedMembers || [contact2],
|
||||
learnMore: action('learnMore'),
|
||||
migrate: action('migrate'),
|
||||
onClose: action('onClose'),
|
||||
});
|
||||
|
|
|
@ -19,7 +19,6 @@ export type DataPropsType = {
|
|||
readonly droppedMembers: Array<ConversationType>;
|
||||
readonly hasMigrated: boolean;
|
||||
readonly invitedMembers: Array<ConversationType>;
|
||||
readonly learnMore: CallbackType;
|
||||
readonly migrate: CallbackType;
|
||||
readonly onClose: CallbackType;
|
||||
};
|
||||
|
@ -42,7 +41,6 @@ export const GroupV1MigrationDialog = React.memo((props: PropsType) => {
|
|||
hasMigrated,
|
||||
i18n,
|
||||
invitedMembers,
|
||||
learnMore,
|
||||
migrate,
|
||||
onClose,
|
||||
} = props;
|
||||
|
@ -85,7 +83,7 @@ export const GroupV1MigrationDialog = React.memo((props: PropsType) => {
|
|||
)}
|
||||
{renderMembers(droppedMembers, droppedMembersKey, i18n)}
|
||||
</div>
|
||||
{renderButtons(hasMigrated, onClose, learnMore, migrate, i18n)}
|
||||
{renderButtons(hasMigrated, onClose, migrate, i18n)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -93,7 +91,6 @@ export const GroupV1MigrationDialog = React.memo((props: PropsType) => {
|
|||
function renderButtons(
|
||||
hasMigrated: boolean,
|
||||
onClose: CallbackType,
|
||||
learnMore: CallbackType,
|
||||
migrate: CallbackType,
|
||||
i18n: LocalizerType
|
||||
) {
|
||||
|
@ -125,9 +122,9 @@ function renderButtons(
|
|||
'module-group-v2-migration-dialog__button--secondary'
|
||||
)}
|
||||
type="button"
|
||||
onClick={learnMore}
|
||||
onClick={onClose}
|
||||
>
|
||||
{i18n('GroupV1--Migration--learn-more')}
|
||||
{i18n('cancel')}
|
||||
</button>
|
||||
<button
|
||||
className="module-group-v2-migration-dialog__button"
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import {
|
||||
GroupV1DisabledActions,
|
||||
PropsType as GroupV1DisabledActionsPropsType,
|
||||
} from './GroupV1DisabledActions';
|
||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const createProps = (): GroupV1DisabledActionsPropsType => ({
|
||||
i18n,
|
||||
onStartGroupMigration: action('onStartGroupMigration'),
|
||||
});
|
||||
|
||||
const stories = storiesOf(
|
||||
'Components/Conversation/GroupV1DisabledActions',
|
||||
module
|
||||
);
|
||||
|
||||
stories.add('Default', () => {
|
||||
return <GroupV1DisabledActions {...createProps()} />;
|
||||
});
|
49
ts/components/conversation/GroupV1DisabledActions.tsx
Normal file
49
ts/components/conversation/GroupV1DisabledActions.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import { Intl } from '../Intl';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
export type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
onStartGroupMigration: () => unknown;
|
||||
};
|
||||
|
||||
export const GroupV1DisabledActions = ({
|
||||
i18n,
|
||||
onStartGroupMigration,
|
||||
}: PropsType): JSX.Element => {
|
||||
return (
|
||||
<div className="module-group-v1-disabled-actions">
|
||||
<p className="module-group-v1-disabled-actions__message">
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="GroupV1--Migration--disabled"
|
||||
components={{
|
||||
learnMore: (
|
||||
<a
|
||||
href="https://support.signal.org/hc/articles/360007319331"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="module-group-v1-disabled-actions__message__learn-more"
|
||||
>
|
||||
{i18n('MessageRequests--learn-more')}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<div className="module-group-v1-disabled-actions__buttons">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onStartGroupMigration}
|
||||
tabIndex={0}
|
||||
className="module-group-v1-disabled-actions__buttons__button"
|
||||
>
|
||||
{i18n('MessageRequests--continue')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -55,9 +55,6 @@ export function GroupV1Migration(props: PropsType): React.ReactElement {
|
|||
hasMigrated
|
||||
i18n={i18n}
|
||||
invitedMembers={invitedMembers}
|
||||
learnMore={() =>
|
||||
window.log.warn('GroupV1Migration: Modal called learnMore()')
|
||||
}
|
||||
migrate={() =>
|
||||
window.log.warn('GroupV1Migration: Modal called migrate()')
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions';
|
|||
|
||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import { Props, Timeline } from './Timeline';
|
||||
import { PropsType, Timeline } from './Timeline';
|
||||
import { TimelineItem, TimelineItemType } from './TimelineItem';
|
||||
import { LastSeenIndicator } from './LastSeenIndicator';
|
||||
import { TimelineLoadingRow } from './TimelineLoadingRow';
|
||||
|
@ -278,7 +278,7 @@ const renderTypingBubble = () => (
|
|||
/>
|
||||
);
|
||||
|
||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
i18n,
|
||||
|
||||
haveNewest: boolean('haveNewest', overrideProps.haveNewest !== false),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { debounce, get, isNumber } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import React, { CSSProperties } from 'react';
|
||||
import {
|
||||
AutoSizer,
|
||||
|
@ -44,6 +45,8 @@ type PropsHousekeepingType = {
|
|||
id: string;
|
||||
unreadCount?: number;
|
||||
typingContact?: unknown;
|
||||
isGroupV1AndDisabled?: boolean;
|
||||
|
||||
selectedMessageId?: string;
|
||||
|
||||
i18n: LocalizerType;
|
||||
|
@ -82,7 +85,9 @@ type PropsActionsType = {
|
|||
} & MessageActionsType &
|
||||
SafetyNumberActionsType;
|
||||
|
||||
export type Props = PropsDataType & PropsHousekeepingType & PropsActionsType;
|
||||
export type PropsType = PropsDataType &
|
||||
PropsHousekeepingType &
|
||||
PropsActionsType;
|
||||
|
||||
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
|
||||
type RowRendererParamsType = {
|
||||
|
@ -120,7 +125,7 @@ type VisibleRowsType = {
|
|||
};
|
||||
};
|
||||
|
||||
type State = {
|
||||
type StateType = {
|
||||
atBottom: boolean;
|
||||
atTop: boolean;
|
||||
oneTimeScrollRow?: number;
|
||||
|
@ -133,7 +138,7 @@ type State = {
|
|||
areUnreadBelowCurrentPosition: boolean;
|
||||
};
|
||||
|
||||
export class Timeline extends React.PureComponent<Props, State> {
|
||||
export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||
public cellSizeCache = new CellMeasurerCache({
|
||||
defaultHeight: 64,
|
||||
fixedWidth: true,
|
||||
|
@ -153,7 +158,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
|
||||
public loadCountdownTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
|
||||
const { scrollToIndex } = this.props;
|
||||
|
@ -170,7 +175,10 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps(props: Props, state: State): State {
|
||||
public static getDerivedStateFromProps(
|
||||
props: PropsType,
|
||||
state: StateType
|
||||
): StateType {
|
||||
if (
|
||||
isNumber(props.scrollToIndex) &&
|
||||
(props.scrollToIndex !== state.prevPropScrollToIndex ||
|
||||
|
@ -646,7 +654,10 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
return itemsCount + extraRows;
|
||||
}
|
||||
|
||||
public fromRowToItemIndex(row: number, props?: Props): number | undefined {
|
||||
public fromRowToItemIndex(
|
||||
row: number,
|
||||
props?: PropsType
|
||||
): number | undefined {
|
||||
const { items } = props || this.props;
|
||||
|
||||
// We will always render either the hero row or the loading row
|
||||
|
@ -666,7 +677,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
return index;
|
||||
}
|
||||
|
||||
public getLastSeenIndicatorRow(props?: Props): number | undefined {
|
||||
public getLastSeenIndicatorRow(props?: PropsType): number | undefined {
|
||||
const { oldestUnreadIndex } = props || this.props;
|
||||
if (!isNumber(oldestUnreadIndex)) {
|
||||
return;
|
||||
|
@ -785,7 +796,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
window.unregisterForActive(this.updateWithVisibleRows);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props): void {
|
||||
public componentDidUpdate(prevProps: PropsType): void {
|
||||
const {
|
||||
id,
|
||||
clearChangedMessages,
|
||||
|
@ -1052,7 +1063,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
public render(): JSX.Element | null {
|
||||
const { i18n, id, items } = this.props;
|
||||
const { i18n, id, items, isGroupV1AndDisabled } = this.props;
|
||||
const {
|
||||
shouldShowScrollDownButton,
|
||||
areUnreadBelowCurrentPosition,
|
||||
|
@ -1067,7 +1078,10 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="module-timeline"
|
||||
className={classNames(
|
||||
'module-timeline',
|
||||
isGroupV1AndDisabled ? 'module-timeline--disabled' : null
|
||||
)}
|
||||
role="presentation"
|
||||
tabIndex={-1}
|
||||
onBlur={this.handleBlur}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue