Hide call buttons when on call
This commit is contained in:
parent
a7854c6083
commit
decc93532b
18 changed files with 622 additions and 366 deletions
|
@ -1,9 +1,6 @@
|
|||
/* global
|
||||
_,
|
||||
Backbone,
|
||||
i18n,
|
||||
MessageController,
|
||||
moment,
|
||||
Whisper
|
||||
*/
|
||||
|
||||
|
@ -98,59 +95,4 @@
|
|||
},
|
||||
update: debouncedCheckExpiringMessages,
|
||||
};
|
||||
|
||||
const TimerOption = Backbone.Model.extend({
|
||||
getName() {
|
||||
return (
|
||||
i18n(['timerOption', this.get('time'), this.get('unit')].join('_')) ||
|
||||
moment.duration(this.get('time'), this.get('unit')).humanize()
|
||||
);
|
||||
},
|
||||
getAbbreviated() {
|
||||
return i18n(
|
||||
['timerOption', this.get('time'), this.get('unit'), 'abbreviated'].join(
|
||||
'_'
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
Whisper.ExpirationTimerOptions = new (Backbone.Collection.extend({
|
||||
model: TimerOption,
|
||||
getName(seconds = 0) {
|
||||
const o = this.findWhere({ seconds });
|
||||
if (o) {
|
||||
return o.getName();
|
||||
}
|
||||
return [seconds, 'seconds'].join(' ');
|
||||
},
|
||||
getAbbreviated(seconds = 0) {
|
||||
const o = this.findWhere({ seconds });
|
||||
if (o) {
|
||||
return o.getAbbreviated();
|
||||
}
|
||||
return [seconds, 's'].join('');
|
||||
},
|
||||
}))(
|
||||
[
|
||||
[0, 'seconds'],
|
||||
[5, 'seconds'],
|
||||
[10, 'seconds'],
|
||||
[30, 'seconds'],
|
||||
[1, 'minute'],
|
||||
[5, 'minutes'],
|
||||
[30, 'minutes'],
|
||||
[1, 'hour'],
|
||||
[6, 'hours'],
|
||||
[12, 'hours'],
|
||||
[1, 'day'],
|
||||
[1, 'week'],
|
||||
].map(o => {
|
||||
const duration = moment.duration(o[0], o[1]); // 5, 'seconds'
|
||||
return {
|
||||
time: o[0],
|
||||
unit: o[1],
|
||||
seconds: duration.asSeconds(),
|
||||
};
|
||||
})
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -33,9 +33,6 @@ const {
|
|||
ContactDetail,
|
||||
} = require('../../ts/components/conversation/ContactDetail');
|
||||
const { ContactListItem } = require('../../ts/components/ContactListItem');
|
||||
const {
|
||||
ConversationHeader,
|
||||
} = require('../../ts/components/conversation/ConversationHeader');
|
||||
const { Emojify } = require('../../ts/components/conversation/Emojify');
|
||||
const { ErrorModal } = require('../../ts/components/ErrorModal');
|
||||
const { Lightbox } = require('../../ts/components/Lightbox');
|
||||
|
@ -63,6 +60,9 @@ const { createTimeline } = require('../../ts/state/roots/createTimeline');
|
|||
const {
|
||||
createCompositionArea,
|
||||
} = require('../../ts/state/roots/createCompositionArea');
|
||||
const {
|
||||
createConversationHeader,
|
||||
} = require('../../ts/state/roots/createConversationHeader');
|
||||
const { createCallManager } = require('../../ts/state/roots/createCallManager');
|
||||
const { createLeftPane } = require('../../ts/state/roots/createLeftPane');
|
||||
const {
|
||||
|
@ -295,7 +295,6 @@ exports.setup = (options = {}) => {
|
|||
ConfirmationModal,
|
||||
ContactDetail,
|
||||
ContactListItem,
|
||||
ConversationHeader,
|
||||
Emojify,
|
||||
ErrorModal,
|
||||
getCallingNotificationText,
|
||||
|
@ -315,6 +314,7 @@ exports.setup = (options = {}) => {
|
|||
const Roots = {
|
||||
createCallManager,
|
||||
createCompositionArea,
|
||||
createConversationHeader,
|
||||
createLeftPane,
|
||||
createSafetyNumberViewer,
|
||||
createShortcutGuideModal,
|
||||
|
|
|
@ -124,7 +124,7 @@ type WhatIsThis = typeof window.WhatIsThis;
|
|||
};
|
||||
|
||||
// Keyboard/mouse mode
|
||||
let interactionMode = 'mouse';
|
||||
let interactionMode: 'mouse' | 'keyboard' = 'mouse';
|
||||
$(document.body).addClass('mouse-mode');
|
||||
|
||||
window.enterKeyboardMode = () => {
|
||||
|
@ -664,8 +664,8 @@ type WhatIsThis = typeof window.WhatIsThis;
|
|||
conversationLookup: window.Signal.Util.makeLookup(conversations, 'id'),
|
||||
messagesByConversation: {},
|
||||
messagesLookup: {},
|
||||
selectedConversation: null,
|
||||
selectedMessage: null,
|
||||
selectedConversation: undefined,
|
||||
selectedMessage: undefined,
|
||||
selectedMessageCounter: 0,
|
||||
showArchived: false,
|
||||
},
|
||||
|
@ -822,7 +822,7 @@ type WhatIsThis = typeof window.WhatIsThis;
|
|||
const conversationsToSearch = getConversationsToSearch();
|
||||
|
||||
const increment = direction === 'up' ? -1 : 1;
|
||||
let startIndex;
|
||||
let startIndex: WhatIsThis;
|
||||
|
||||
if (conversationId) {
|
||||
const index = conversationsToSearch.findIndex(
|
||||
|
@ -844,7 +844,7 @@ type WhatIsThis = typeof window.WhatIsThis;
|
|||
if (!unreadOnly) {
|
||||
return target.id;
|
||||
}
|
||||
if (target.unreadCount > 0) {
|
||||
if ((target.unreadCount || 0) > 0) {
|
||||
return target.id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import React, { ComponentProps } from 'react';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import {
|
||||
ConversationHeader,
|
||||
PropsActionsType,
|
||||
PropsHousekeepingType,
|
||||
PropsType,
|
||||
} from './ConversationHeader';
|
||||
import { ConversationHeader } from './ConversationHeader';
|
||||
import { gifUrl } from '../../storybook/Fixtures';
|
||||
|
||||
const book = storiesOf('Components/Conversation/ConversationHeader', module);
|
||||
|
@ -21,11 +16,17 @@ type ConversationHeaderStory = {
|
|||
description: string;
|
||||
items: Array<{
|
||||
title: string;
|
||||
props: PropsType;
|
||||
props: ComponentProps<typeof ConversationHeader>;
|
||||
}>;
|
||||
};
|
||||
|
||||
const actionProps: PropsActionsType = {
|
||||
const commonProps = {
|
||||
showBackButton: false,
|
||||
showCallButtons: true,
|
||||
markedUnread: false,
|
||||
|
||||
i18n,
|
||||
|
||||
onSetDisappearingMessages: action('onSetDisappearingMessages'),
|
||||
onDeleteMessages: action('onDeleteMessages'),
|
||||
onResetSession: action('onResetSession'),
|
||||
|
@ -49,10 +50,6 @@ const actionProps: PropsActionsType = {
|
|||
onSetPin: action('onSetPin'),
|
||||
};
|
||||
|
||||
const housekeepingProps: PropsHousekeepingType = {
|
||||
i18n,
|
||||
};
|
||||
|
||||
const stories: Array<ConversationHeaderStory> = [
|
||||
{
|
||||
title: '1:1 conversation',
|
||||
|
@ -62,6 +59,7 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
{
|
||||
title: 'With name and profile, verified',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'red',
|
||||
isVerified: true,
|
||||
avatarPath: gifUrl,
|
||||
|
@ -72,13 +70,12 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
id: '1',
|
||||
profileName: '🔥Flames🔥',
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With name, not verified, no avatar',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'blue',
|
||||
isVerified: false,
|
||||
title: 'Someone 🔥 Somewhere',
|
||||
|
@ -87,13 +84,12 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
type: 'direct',
|
||||
id: '2',
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With name, not verified, descenders',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'blue',
|
||||
isVerified: false,
|
||||
title: 'Joyrey 🔥 Leppey',
|
||||
|
@ -102,13 +98,12 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
type: 'direct',
|
||||
id: '2',
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Profile, no name',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'teal',
|
||||
isVerified: false,
|
||||
phoneNumber: '(202) 555-0003',
|
||||
|
@ -117,25 +112,23 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
title: '🔥Flames🔥',
|
||||
profileName: '🔥Flames🔥',
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'No name, no profile, no color',
|
||||
props: {
|
||||
...commonProps,
|
||||
title: '(202) 555-0011',
|
||||
phoneNumber: '(202) 555-0011',
|
||||
type: 'direct',
|
||||
id: '11',
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With back button',
|
||||
props: {
|
||||
...commonProps,
|
||||
showBackButton: true,
|
||||
color: 'deep_orange',
|
||||
phoneNumber: '(202) 555-0004',
|
||||
|
@ -143,46 +136,32 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
type: 'direct',
|
||||
id: '4',
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Disappearing messages set',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'indigo',
|
||||
title: '(202) 555-0005',
|
||||
phoneNumber: '(202) 555-0005',
|
||||
type: 'direct',
|
||||
id: '5',
|
||||
expirationSettingName: '10 seconds',
|
||||
timerOptions: [
|
||||
{
|
||||
name: 'off',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
],
|
||||
expireTimer: 10,
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Muting Conversation',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'ultramarine',
|
||||
title: '(202) 555-0006',
|
||||
phoneNumber: '(202) 555-0006',
|
||||
type: 'direct',
|
||||
id: '6',
|
||||
muteExpirationLabel: '10/18/3000, 11:11 AM',
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
muteExpiresAt: new Date('3000-10-18T11:11:11Z').valueOf(),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -195,52 +174,30 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
{
|
||||
title: 'Basic',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'signal-blue',
|
||||
title: 'Typescript support group',
|
||||
name: 'Typescript support group',
|
||||
phoneNumber: '',
|
||||
id: '1',
|
||||
type: 'group',
|
||||
expirationSettingName: '10 seconds',
|
||||
timerOptions: [
|
||||
{
|
||||
name: 'off',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
],
|
||||
expireTimer: 10,
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'In a group you left - no disappearing messages',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'signal-blue',
|
||||
title: 'Typescript support group',
|
||||
name: 'Typescript support group',
|
||||
phoneNumber: '',
|
||||
id: '2',
|
||||
type: 'group',
|
||||
disableTimerChanges: true,
|
||||
expirationSettingName: '10 seconds',
|
||||
timerOptions: [
|
||||
{
|
||||
name: 'off',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
],
|
||||
left: true,
|
||||
expireTimer: 10,
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -252,6 +209,7 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
{
|
||||
title: 'In chat with yourself',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'blue',
|
||||
title: '(202) 555-0007',
|
||||
phoneNumber: '(202) 555-0007',
|
||||
|
@ -259,8 +217,6 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
type: 'direct',
|
||||
isMe: true,
|
||||
acceptedMessageRequest: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -272,6 +228,7 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
{
|
||||
title: '1:1 conversation',
|
||||
props: {
|
||||
...commonProps,
|
||||
color: 'blue',
|
||||
title: '(202) 555-0007',
|
||||
phoneNumber: '(202) 555-0007',
|
||||
|
@ -279,8 +236,6 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
type: 'direct',
|
||||
isMe: false,
|
||||
acceptedMessageRequest: false,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
ContextMenu,
|
||||
|
@ -14,11 +15,11 @@ import { InContactsIcon } from '../InContactsIcon';
|
|||
import { LocalizerType } from '../../types/Util';
|
||||
import { ColorType } from '../../types/Colors';
|
||||
import { getMuteOptions } from '../../util/getMuteOptions';
|
||||
|
||||
interface TimerOption {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
import {
|
||||
ExpirationTimerOptions,
|
||||
TimerOption,
|
||||
} from '../../util/ExpirationTimerOptions';
|
||||
import { isMuted } from '../../util/isMuted';
|
||||
|
||||
export interface PropsDataType {
|
||||
id: string;
|
||||
|
@ -36,13 +37,16 @@ export interface PropsDataType {
|
|||
isMe?: boolean;
|
||||
isArchived?: boolean;
|
||||
isPinned?: boolean;
|
||||
isMissingMandatoryProfileSharing?: boolean;
|
||||
left?: boolean;
|
||||
markedUnread?: boolean;
|
||||
|
||||
disableTimerChanges?: boolean;
|
||||
expirationSettingName?: string;
|
||||
muteExpirationLabel?: string;
|
||||
canChangeTimer?: boolean;
|
||||
expireTimer?: number;
|
||||
muteExpiresAt?: number;
|
||||
|
||||
showBackButton?: boolean;
|
||||
timerOptions?: Array<TimerOption>;
|
||||
showCallButtons?: boolean;
|
||||
}
|
||||
|
||||
export interface PropsActionsType {
|
||||
|
@ -186,8 +190,11 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
}
|
||||
|
||||
public renderExpirationLength(): JSX.Element | null {
|
||||
const { expirationSettingName, showBackButton } = this.props;
|
||||
const { i18n, expireTimer, showBackButton } = this.props;
|
||||
|
||||
const expirationSettingName = expireTimer
|
||||
? ExpirationTimerOptions.getName(i18n, expireTimer)
|
||||
: undefined;
|
||||
if (!expirationSettingName) {
|
||||
return null;
|
||||
}
|
||||
|
@ -249,45 +256,21 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
public renderOutgoingAudioCallButton(): JSX.Element | null {
|
||||
private renderOutgoingCallButtons(): JSX.Element | null {
|
||||
const {
|
||||
i18n,
|
||||
isMe,
|
||||
onOutgoingAudioCallInConversation,
|
||||
onOutgoingVideoCallInConversation,
|
||||
showCallButtons,
|
||||
showBackButton,
|
||||
type,
|
||||
} = this.props;
|
||||
|
||||
if (type === 'group' || isMe) {
|
||||
if (!showCallButtons) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOutgoingAudioCallInConversation}
|
||||
className={classNames(
|
||||
'module-conversation-header__audio-calling-button',
|
||||
showBackButton
|
||||
? null
|
||||
: 'module-conversation-header__audio-calling-button--show'
|
||||
)}
|
||||
disabled={showBackButton}
|
||||
aria-label={i18n('makeOutgoingCall')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public renderOutgoingVideoCallButton(): JSX.Element | null {
|
||||
const { i18n, isMe, type } = this.props;
|
||||
|
||||
if (type === 'group' || isMe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { onOutgoingVideoCallInConversation, showBackButton } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOutgoingVideoCallInConversation}
|
||||
|
@ -300,20 +283,35 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
disabled={showBackButton}
|
||||
aria-label={i18n('makeOutgoingVideoCall')}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOutgoingAudioCallInConversation}
|
||||
className={classNames(
|
||||
'module-conversation-header__audio-calling-button',
|
||||
showBackButton
|
||||
? null
|
||||
: 'module-conversation-header__audio-calling-button--show'
|
||||
)}
|
||||
disabled={showBackButton}
|
||||
aria-label={i18n('makeOutgoingCall')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public renderMenu(triggerId: string): JSX.Element {
|
||||
const {
|
||||
disableTimerChanges,
|
||||
i18n,
|
||||
acceptedMessageRequest,
|
||||
canChangeTimer,
|
||||
isArchived,
|
||||
isMe,
|
||||
isPinned,
|
||||
type,
|
||||
isArchived,
|
||||
markedUnread,
|
||||
muteExpirationLabel,
|
||||
muteExpiresAt,
|
||||
isMissingMandatoryProfileSharing,
|
||||
left,
|
||||
onDeleteMessages,
|
||||
onResetSession,
|
||||
onSetDisappearingMessages,
|
||||
|
@ -325,11 +323,15 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
onMarkUnread,
|
||||
onSetPin,
|
||||
onMoveToInbox,
|
||||
timerOptions,
|
||||
} = this.props;
|
||||
|
||||
const muteOptions = [];
|
||||
if (muteExpirationLabel) {
|
||||
if (isMuted(muteExpiresAt)) {
|
||||
const expires = moment(muteExpiresAt);
|
||||
const muteExpirationLabel = moment().isSame(expires, 'day')
|
||||
? expires.format('hh:mm A')
|
||||
: expires.format('M/D/YY, hh:mm A');
|
||||
|
||||
muteOptions.push(
|
||||
...[
|
||||
{
|
||||
|
@ -352,18 +354,25 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
const muteTitle = i18n('muteNotificationsTitle') as any;
|
||||
const isGroup = type === 'group';
|
||||
|
||||
const disableTimerChanges = Boolean(
|
||||
!canChangeTimer ||
|
||||
!acceptedMessageRequest ||
|
||||
left ||
|
||||
isMissingMandatoryProfileSharing
|
||||
);
|
||||
|
||||
return (
|
||||
<ContextMenu id={triggerId}>
|
||||
{disableTimerChanges ? null : (
|
||||
<SubMenu title={disappearingTitle}>
|
||||
{(timerOptions || []).map(item => (
|
||||
{ExpirationTimerOptions.map((item: typeof TimerOption) => (
|
||||
<MenuItem
|
||||
key={item.value}
|
||||
key={item.get('seconds')}
|
||||
onClick={() => {
|
||||
onSetDisappearingMessages(item.value);
|
||||
onSetDisappearingMessages(item.get('seconds'));
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
{item.getName(i18n)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</SubMenu>
|
||||
|
@ -435,8 +444,7 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
</div>
|
||||
{this.renderExpirationLength()}
|
||||
{this.renderSearchButton()}
|
||||
{this.renderOutgoingVideoCallButton()}
|
||||
{this.renderOutgoingAudioCallButton()}
|
||||
{this.renderOutgoingCallButtons()}
|
||||
{this.renderMoreButton(triggerId)}
|
||||
{this.renderMenu(triggerId)}
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '../state/ducks/conversations';
|
||||
import { ColorType } from '../types/Colors';
|
||||
import { MessageModel } from './messages';
|
||||
import { isMuted } from '../util/isMuted';
|
||||
import { sniffImageMimeType } from '../util/sniffImageMimeType';
|
||||
import { MIMEType, IMAGE_WEBP } from '../types/MIME';
|
||||
import {
|
||||
|
@ -1118,6 +1119,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
areWePending: Boolean(
|
||||
ourConversationId && this.isMemberPending(ourConversationId)
|
||||
),
|
||||
canChangeTimer: this.canChangeTimer(),
|
||||
avatarPath: this.getAvatarPath()!,
|
||||
color,
|
||||
draftPreview,
|
||||
|
@ -1137,11 +1139,13 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
deletedForEveryone: this.get('lastMessageDeletedForEveryone')!,
|
||||
},
|
||||
lastUpdated: this.get('timestamp')!,
|
||||
left: Boolean(this.get('left')),
|
||||
markedUnread: this.get('markedUnread')!,
|
||||
membersCount: this.isPrivate()
|
||||
? undefined
|
||||
: (this.get('membersV2')! || this.get('members')! || []).length,
|
||||
messageRequestsEnabled,
|
||||
expireTimer: this.get('expireTimer'),
|
||||
muteExpiresAt: this.get('muteExpiresAt')!,
|
||||
name: this.get('name')!,
|
||||
phoneNumber: this.getNumber()!,
|
||||
|
@ -3957,7 +3961,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
return getAbsoluteAttachmentPath(avatar.path);
|
||||
}
|
||||
|
||||
canChangeTimer(): boolean {
|
||||
private canChangeTimer(): boolean {
|
||||
if (this.isPrivate()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -4027,10 +4031,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
}
|
||||
|
||||
isMuted(): boolean {
|
||||
return (
|
||||
Boolean(this.get('muteExpiresAt')) &&
|
||||
Date.now() < this.get('muteExpiresAt')
|
||||
);
|
||||
return isMuted(this.get('muteExpiresAt'));
|
||||
}
|
||||
|
||||
getMuteTimeoutId(): string {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '../state/ducks/conversations';
|
||||
import { PropsData } from '../components/conversation/Message';
|
||||
import { CallbackResultType } from '../textsecure/SendMessage';
|
||||
import { ExpirationTimerOptions } from '../util/ExpirationTimerOptions';
|
||||
import { BodyRangesType } from '../types/Util';
|
||||
import { PropsDataType as GroupsV2Props } from '../components/conversation/GroupV2Change';
|
||||
import {
|
||||
|
@ -504,7 +505,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
|
||||
const { expireTimer, fromSync, source, sourceUuid } = timerUpdate;
|
||||
const timespan = window.Whisper.ExpirationTimerOptions.getName(
|
||||
const timespan = ExpirationTimerOptions.getName(
|
||||
window.i18n,
|
||||
expireTimer || 0
|
||||
);
|
||||
const disabled = !expireTimer;
|
||||
|
@ -1300,9 +1302,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
return {
|
||||
text: window.i18n('timerSetTo', [
|
||||
window.Whisper.ExpirationTimerOptions.getAbbreviated(
|
||||
expireTimer || 0
|
||||
),
|
||||
ExpirationTimerOptions.getAbbreviated(window.i18n, expireTimer || 0),
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -96,6 +96,21 @@ export type SetRendererCanvasType = {
|
|||
element: React.RefObject<HTMLCanvasElement> | undefined;
|
||||
};
|
||||
|
||||
// Helpers
|
||||
|
||||
export function isCallActive({
|
||||
callDetails,
|
||||
callState,
|
||||
}: CallingStateType): boolean {
|
||||
return Boolean(
|
||||
callDetails &&
|
||||
((!callDetails.isIncoming &&
|
||||
(callState === CallState.Prering || callState === CallState.Ringing)) ||
|
||||
callState === CallState.Accepted ||
|
||||
callState === CallState.Reconnecting)
|
||||
);
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
const ACCEPT_CALL = 'calling/ACCEPT_CALL';
|
||||
|
@ -522,7 +537,7 @@ export type ActionsType = typeof actions;
|
|||
|
||||
// Reducer
|
||||
|
||||
function getEmptyState(): CallingStateType {
|
||||
export function getEmptyState(): CallingStateType {
|
||||
return {
|
||||
availableCameras: [],
|
||||
availableMicrophones: [],
|
||||
|
|
|
@ -43,7 +43,9 @@ export type ConversationType = {
|
|||
profileName?: string;
|
||||
avatarPath?: string;
|
||||
areWePending?: boolean;
|
||||
canChangeTimer?: boolean;
|
||||
color?: ColorType;
|
||||
isAccepted?: boolean;
|
||||
isArchived?: boolean;
|
||||
isBlocked?: boolean;
|
||||
isPinned?: boolean;
|
||||
|
@ -51,6 +53,7 @@ export type ConversationType = {
|
|||
activeAt?: number;
|
||||
timestamp?: number;
|
||||
inboxPosition?: number;
|
||||
left?: boolean;
|
||||
lastMessage?: {
|
||||
status: LastMessageStatus;
|
||||
text: string;
|
||||
|
@ -59,6 +62,7 @@ export type ConversationType = {
|
|||
markedUnread: boolean;
|
||||
phoneNumber?: string;
|
||||
membersCount?: number;
|
||||
expireTimer?: number;
|
||||
muteExpiresAt?: number;
|
||||
type: ConversationTypeType;
|
||||
isMe?: boolean;
|
||||
|
@ -168,6 +172,7 @@ export type ConversationsStateType = {
|
|||
selectedConversation?: string;
|
||||
selectedMessage?: string;
|
||||
selectedMessageCounter: number;
|
||||
selectedConversationPanelDepth: number;
|
||||
showArchived: boolean;
|
||||
|
||||
// Note: it's very important that both of these locations are always kept up to date
|
||||
|
@ -271,6 +276,10 @@ export type SetIsNearBottomActionType = {
|
|||
isNearBottom: boolean;
|
||||
};
|
||||
};
|
||||
export type SetSelectedConversationPanelDepthActionType = {
|
||||
type: 'SET_SELECTED_CONVERSATION_PANEL_DEPTH';
|
||||
payload: { panelDepth: number };
|
||||
};
|
||||
export type ScrollToMessageActionType = {
|
||||
type: 'SCROLL_TO_MESSAGE';
|
||||
payload: {
|
||||
|
@ -328,6 +337,7 @@ export type ConversationActionType =
|
|||
| ClearSelectedMessageActionType
|
||||
| ClearUnreadMetricsActionType
|
||||
| ScrollToMessageActionType
|
||||
| SetSelectedConversationPanelDepthActionType
|
||||
| SelectedConversationChangedActionType
|
||||
| MessageDeletedActionType
|
||||
| SelectedConversationChangedActionType
|
||||
|
@ -350,6 +360,7 @@ export const actions = {
|
|||
setMessagesLoading,
|
||||
setLoadCountdownStart,
|
||||
setIsNearBottom,
|
||||
setSelectedConversationPanelDepth,
|
||||
clearChangedMessages,
|
||||
clearSelectedMessage,
|
||||
clearUnreadMetrics,
|
||||
|
@ -516,6 +527,14 @@ function setIsNearBottom(
|
|||
},
|
||||
};
|
||||
}
|
||||
function setSelectedConversationPanelDepth(
|
||||
panelDepth: number
|
||||
): SetSelectedConversationPanelDepthActionType {
|
||||
return {
|
||||
type: 'SET_SELECTED_CONVERSATION_PANEL_DEPTH',
|
||||
payload: { panelDepth },
|
||||
};
|
||||
}
|
||||
function clearChangedMessages(
|
||||
conversationId: string
|
||||
): ClearChangedMessagesActionType {
|
||||
|
@ -606,6 +625,7 @@ function getEmptyState(): ConversationsStateType {
|
|||
messagesLookup: {},
|
||||
selectedMessageCounter: 0,
|
||||
showArchived: false,
|
||||
selectedConversationPanelDepth: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -761,6 +781,7 @@ export function reducer(
|
|||
return {
|
||||
...state,
|
||||
selectedConversation,
|
||||
selectedConversationPanelDepth: 0,
|
||||
messagesLookup: omit(state.messagesLookup, messageIds),
|
||||
messagesByConversation: omit(state.messagesByConversation, [id]),
|
||||
};
|
||||
|
@ -768,6 +789,12 @@ export function reducer(
|
|||
if (action.type === 'CONVERSATIONS_REMOVE_ALL') {
|
||||
return getEmptyState();
|
||||
}
|
||||
if (action.type === 'SET_SELECTED_CONVERSATION_PANEL_DEPTH') {
|
||||
return {
|
||||
...state,
|
||||
selectedConversationPanelDepth: action.payload.panelDepth,
|
||||
};
|
||||
}
|
||||
if (action.type === 'MESSAGE_SELECTED') {
|
||||
const { messageId, conversationId } = action.payload;
|
||||
|
||||
|
|
14
ts/state/roots/createConversationHeader.tsx
Normal file
14
ts/state/roots/createConversationHeader.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import { Store } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { SmartConversationHeader, OwnProps } from '../smart/ConversationHeader';
|
||||
|
||||
export const createConversationHeader = (
|
||||
store: Store,
|
||||
props: OwnProps
|
||||
): React.ReactElement => (
|
||||
<Provider store={store}>
|
||||
<SmartConversationHeader {...props} />
|
||||
</Provider>
|
||||
);
|
68
ts/state/smart/ConversationHeader.tsx
Normal file
68
ts/state/smart/ConversationHeader.tsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { pick } from 'lodash';
|
||||
import { ConversationHeader } from '../../components/conversation/ConversationHeader';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { StateType } from '../reducer';
|
||||
import { isCallActive } from '../ducks/calling';
|
||||
import { getIntl } from '../selectors/user';
|
||||
|
||||
export interface OwnProps {
|
||||
id: string;
|
||||
|
||||
onDeleteMessages: () => void;
|
||||
onGoBack: () => void;
|
||||
onOutgoingAudioCallInConversation: () => void;
|
||||
onOutgoingVideoCallInConversation: () => void;
|
||||
onResetSession: () => void;
|
||||
onSearchInConversation: () => void;
|
||||
onSetDisappearingMessages: (seconds: number) => void;
|
||||
onSetMuteNotifications: (seconds: number) => void;
|
||||
onSetPin: (value: boolean) => void;
|
||||
onShowAllMedia: () => void;
|
||||
onShowGroupMembers: () => void;
|
||||
|
||||
onArchive: () => void;
|
||||
onMarkUnread: () => void;
|
||||
onMoveToInbox: () => void;
|
||||
onShowSafetyNumber: () => void;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StateType, ownProps: OwnProps) => {
|
||||
const conversation = getConversationSelector(state)(ownProps.id);
|
||||
if (!conversation) {
|
||||
throw new Error('Could not find conversation');
|
||||
}
|
||||
|
||||
return {
|
||||
...pick(conversation, [
|
||||
'acceptedMessageRequest',
|
||||
'avatarPath',
|
||||
'canChangeTimer',
|
||||
'color',
|
||||
'expireTimer',
|
||||
'isArchived',
|
||||
'isMe',
|
||||
'isMissingMandatoryProfileSharing',
|
||||
'isPinned',
|
||||
'isVerified',
|
||||
'left',
|
||||
'markedUnread',
|
||||
'muteExpiresAt',
|
||||
'name',
|
||||
'phoneNumber',
|
||||
'profileName',
|
||||
'title',
|
||||
'type',
|
||||
]),
|
||||
i18n: getIntl(state),
|
||||
showBackButton: state.conversations.selectedConversationPanelDepth > 0,
|
||||
showCallButtons:
|
||||
conversation.type === 'direct' &&
|
||||
!conversation.isMe &&
|
||||
!isCallActive(state.calling),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, {});
|
||||
|
||||
export const SmartConversationHeader = smart(ConversationHeader);
|
107
ts/test/state/ducks/calling_test.ts
Normal file
107
ts/test/state/ducks/calling_test.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { assert } from 'chai';
|
||||
import {
|
||||
CallDetailsType,
|
||||
getEmptyState,
|
||||
isCallActive,
|
||||
} from '../../../state/ducks/calling';
|
||||
import { CallState } from '../../../types/Calling';
|
||||
|
||||
describe('calling duck', () => {
|
||||
describe('helpers', () => {
|
||||
describe('isCallActive', () => {
|
||||
const fakeCallDetails: CallDetailsType = {
|
||||
id: 'fake-call',
|
||||
title: 'Fake Call',
|
||||
callId: 123,
|
||||
isIncoming: false,
|
||||
isVideoCall: false,
|
||||
};
|
||||
|
||||
it('returns false if there are no call details', () => {
|
||||
assert.isFalse(isCallActive(getEmptyState()));
|
||||
});
|
||||
|
||||
it('returns false if an incoming call is in a pre-reing state', () => {
|
||||
assert.isFalse(
|
||||
isCallActive({
|
||||
...getEmptyState(),
|
||||
callDetails: {
|
||||
...fakeCallDetails,
|
||||
isIncoming: true,
|
||||
},
|
||||
callState: CallState.Prering,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if an outgoing call is in a pre-reing state', () => {
|
||||
assert.isTrue(
|
||||
isCallActive({
|
||||
...getEmptyState(),
|
||||
callDetails: {
|
||||
...fakeCallDetails,
|
||||
isIncoming: false,
|
||||
},
|
||||
callState: CallState.Prering,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if an incoming call is ringing', () => {
|
||||
assert.isFalse(
|
||||
isCallActive({
|
||||
...getEmptyState(),
|
||||
callDetails: {
|
||||
...fakeCallDetails,
|
||||
isIncoming: true,
|
||||
},
|
||||
callState: CallState.Ringing,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if an outgoing call is ringing', () => {
|
||||
assert.isTrue(
|
||||
isCallActive({
|
||||
...getEmptyState(),
|
||||
callDetails: {
|
||||
...fakeCallDetails,
|
||||
isIncoming: false,
|
||||
},
|
||||
callState: CallState.Ringing,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if a call is in an accepted state', () => {
|
||||
assert.isTrue(
|
||||
isCallActive({
|
||||
...getEmptyState(),
|
||||
callDetails: fakeCallDetails,
|
||||
callState: CallState.Accepted,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if a call is in a reconnecting state', () => {
|
||||
assert.isTrue(
|
||||
isCallActive({
|
||||
...getEmptyState(),
|
||||
callDetails: fakeCallDetails,
|
||||
callState: CallState.Reconnecting,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if a call is in an ended state', () => {
|
||||
assert.isFalse(
|
||||
isCallActive({
|
||||
...getEmptyState(),
|
||||
callDetails: fakeCallDetails,
|
||||
callState: CallState.Ended,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
19
ts/test/util/isMuted_test.ts
Normal file
19
ts/test/util/isMuted_test.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { isMuted } from '../../util/isMuted';
|
||||
|
||||
describe('isMuted', () => {
|
||||
it('returns false if passed undefined', () => {
|
||||
assert.isFalse(isMuted(undefined));
|
||||
});
|
||||
|
||||
it('returns false if passed a date in the past', () => {
|
||||
assert.isFalse(isMuted(0));
|
||||
assert.isFalse(isMuted(Date.now() - 123));
|
||||
});
|
||||
|
||||
it('returns false if passed a date in the future', () => {
|
||||
assert.isTrue(isMuted(Date.now() + 123));
|
||||
assert.isTrue(isMuted(Date.now() + 123456));
|
||||
});
|
||||
});
|
74
ts/util/ExpirationTimerOptions.ts
Normal file
74
ts/util/ExpirationTimerOptions.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import * as Backbone from 'backbone';
|
||||
import * as moment from 'moment';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
type ExpirationTime = [
|
||||
number,
|
||||
(
|
||||
| 'second'
|
||||
| 'seconds'
|
||||
| 'minute'
|
||||
| 'minutes'
|
||||
| 'hour'
|
||||
| 'hours'
|
||||
| 'day'
|
||||
| 'week'
|
||||
)
|
||||
];
|
||||
const EXPIRATION_TIMES: Array<ExpirationTime> = [
|
||||
[0, 'seconds'],
|
||||
[5, 'seconds'],
|
||||
[10, 'seconds'],
|
||||
[30, 'seconds'],
|
||||
[1, 'minute'],
|
||||
[5, 'minutes'],
|
||||
[30, 'minutes'],
|
||||
[1, 'hour'],
|
||||
[6, 'hours'],
|
||||
[12, 'hours'],
|
||||
[1, 'day'],
|
||||
[1, 'week'],
|
||||
];
|
||||
|
||||
export const TimerOption = Backbone.Model.extend({
|
||||
getName(i18n: LocalizerType) {
|
||||
return (
|
||||
i18n(['timerOption', this.get('time'), this.get('unit')].join('_')) ||
|
||||
moment.duration(this.get('time'), this.get('unit')).humanize()
|
||||
);
|
||||
},
|
||||
getAbbreviated(i18n: LocalizerType) {
|
||||
return i18n(
|
||||
['timerOption', this.get('time'), this.get('unit'), 'abbreviated'].join(
|
||||
'_'
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const ExpirationTimerOptions = new (Backbone.Collection.extend({
|
||||
model: TimerOption,
|
||||
getName(i18n: LocalizerType, seconds = 0) {
|
||||
const o = this.findWhere({ seconds });
|
||||
if (o) {
|
||||
return o.getName(i18n);
|
||||
}
|
||||
return [seconds, 'seconds'].join(' ');
|
||||
},
|
||||
getAbbreviated(i18n: LocalizerType, seconds = 0) {
|
||||
const o = this.findWhere({ seconds });
|
||||
if (o) {
|
||||
return o.getAbbreviated(i18n);
|
||||
}
|
||||
return [seconds, 's'].join('');
|
||||
},
|
||||
}))(
|
||||
EXPIRATION_TIMES.map(o => {
|
||||
const duration = moment.duration(o[0], o[1]); // 5, 'seconds'
|
||||
return {
|
||||
time: o[0],
|
||||
unit: o[1],
|
||||
seconds: duration.asSeconds(),
|
||||
};
|
||||
})
|
||||
);
|
3
ts/util/isMuted.ts
Normal file
3
ts/util/isMuted.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function isMuted(muteExpiresAt: undefined | number): boolean {
|
||||
return Boolean(muteExpiresAt && Date.now() < muteExpiresAt);
|
||||
}
|
|
@ -14694,7 +14694,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/ConversationHeader.js",
|
||||
"line": " this.menuTriggerRef = react_1.default.createRef();",
|
||||
"lineNumber": 16,
|
||||
"lineNumber": 19,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-28T16:12:19.904Z",
|
||||
"reasonDetail": "Used to reference popup menu"
|
||||
|
@ -14703,7 +14703,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/ConversationHeader.tsx",
|
||||
"line": " this.menuTriggerRef = React.createRef();",
|
||||
"lineNumber": 86,
|
||||
"lineNumber": 90,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-05-20T20:10:43.540Z",
|
||||
"reasonDetail": "Used to reference popup menu"
|
||||
|
|
|
@ -435,31 +435,12 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
},
|
||||
|
||||
setupHeader() {
|
||||
const getHeaderProps = (_unknown?: unknown) => {
|
||||
const expireTimer = this.model.get('expireTimer');
|
||||
const expirationSettingName = expireTimer
|
||||
? Whisper.ExpirationTimerOptions.getName(expireTimer || 0)
|
||||
: null;
|
||||
|
||||
return {
|
||||
...this.model.format(),
|
||||
|
||||
leftGroup: this.model.get('left'),
|
||||
|
||||
disableTimerChanges:
|
||||
this.model.isMissingRequiredProfileSharing() ||
|
||||
this.model.get('left') ||
|
||||
!this.model.getAccepted() ||
|
||||
!this.model.canChangeTimer(),
|
||||
showBackButton: Boolean(this.panels && this.panels.length),
|
||||
|
||||
expirationSettingName,
|
||||
timerOptions: Whisper.ExpirationTimerOptions.map((item: any) => ({
|
||||
name: item.getName(),
|
||||
value: item.get('seconds'),
|
||||
})),
|
||||
|
||||
muteExpirationLabel: this.getMuteExpirationLabel(),
|
||||
this.titleView = new Whisper.ReactWrapperView({
|
||||
className: 'title-wrapper',
|
||||
JSX: window.Signal.State.Roots.createConversationHeader(
|
||||
window.reduxStore,
|
||||
{
|
||||
id: this.model.id,
|
||||
|
||||
onSetDisappearingMessages: (seconds: number) =>
|
||||
this.setDisappearingMessages(seconds),
|
||||
|
@ -535,7 +516,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
},
|
||||
onShowGroupMembers: async () => {
|
||||
await this.showMembers();
|
||||
this.updateHeader();
|
||||
},
|
||||
onGoBack: () => {
|
||||
this.resetPanel();
|
||||
|
@ -566,15 +546,9 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
document.body
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
this.titleView = new Whisper.ReactWrapperView({
|
||||
className: 'title-wrapper',
|
||||
Component: window.Signal.Components.ConversationHeader,
|
||||
props: getHeaderProps(this.model),
|
||||
}
|
||||
),
|
||||
});
|
||||
this.updateHeader = () => this.titleView.update(getHeaderProps());
|
||||
this.listenTo(this.model, 'change', this.updateHeader);
|
||||
this.$('.conversation-header').append(this.titleView.el);
|
||||
},
|
||||
|
||||
|
@ -1268,6 +1242,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
const panel = this.panels[i];
|
||||
panel.remove();
|
||||
}
|
||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(0);
|
||||
}
|
||||
|
||||
this.remove();
|
||||
|
@ -2209,7 +2184,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
this.listenTo(this.model.messageCollection, 'remove', update);
|
||||
|
||||
this.listenBack(view);
|
||||
this.updateHeader();
|
||||
},
|
||||
|
||||
focusMessageField() {
|
||||
|
@ -2319,7 +2293,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
model: conversation,
|
||||
});
|
||||
this.listenBack(view);
|
||||
this.updateHeader();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2638,7 +2611,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
// We could listen to all involved contacts, but we'll call that overkill
|
||||
|
||||
this.listenBack(view);
|
||||
this.updateHeader();
|
||||
view.render();
|
||||
},
|
||||
|
||||
|
@ -2652,7 +2624,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
});
|
||||
|
||||
this.listenBack(view);
|
||||
this.updateHeader();
|
||||
view.render();
|
||||
},
|
||||
|
||||
|
@ -2675,7 +2646,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
});
|
||||
|
||||
this.listenBack(view);
|
||||
this.updateHeader();
|
||||
},
|
||||
|
||||
async openConversation(number: any) {
|
||||
|
@ -2694,6 +2664,10 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
view.$el.one('animationend', () => {
|
||||
view.$el.addClass('panel--static');
|
||||
});
|
||||
|
||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
||||
this.panels.length
|
||||
);
|
||||
},
|
||||
resetPanel() {
|
||||
if (!this.panels || !this.panels.length) {
|
||||
|
@ -2714,7 +2688,6 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
if (this.panels.length > 0) {
|
||||
this.panels[0].$el.fadeIn(250);
|
||||
}
|
||||
this.updateHeader();
|
||||
|
||||
view.$el.addClass('panel--remove').one('transitionend', () => {
|
||||
view.remove();
|
||||
|
@ -2724,6 +2697,10 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
});
|
||||
|
||||
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
||||
this.panels.length
|
||||
);
|
||||
},
|
||||
|
||||
endSession() {
|
||||
|
|
64
ts/window.d.ts
vendored
64
ts/window.d.ts
vendored
|
@ -3,6 +3,7 @@
|
|||
import * as Backbone from 'backbone';
|
||||
import * as Underscore from 'underscore';
|
||||
import { Ref } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as LinkPreviews from '../js/modules/link_previews.d';
|
||||
import * as Util from './util';
|
||||
import {
|
||||
|
@ -27,6 +28,28 @@ import { CallHistoryDetailsType } from './types/Calling';
|
|||
import { ColorType } from './types/Colors';
|
||||
import { ConversationController } from './ConversationController';
|
||||
import { ReduxActions } from './state/types';
|
||||
import { createStore } from './state/createStore';
|
||||
import { createCallManager } from './state/roots/createCallManager';
|
||||
import { createCompositionArea } from './state/roots/createCompositionArea';
|
||||
import { createConversationHeader } from './state/roots/createConversationHeader';
|
||||
import { createLeftPane } from './state/roots/createLeftPane';
|
||||
import { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
|
||||
import { createShortcutGuideModal } from './state/roots/createShortcutGuideModal';
|
||||
import { createStickerManager } from './state/roots/createStickerManager';
|
||||
import { createStickerPreviewModal } from './state/roots/createStickerPreviewModal';
|
||||
import { createTimeline } from './state/roots/createTimeline';
|
||||
import * as callingDuck from './state/ducks/calling';
|
||||
import * as conversationsDuck from './state/ducks/conversations';
|
||||
import * as emojisDuck from './state/ducks/emojis';
|
||||
import * as expirationDuck from './state/ducks/expiration';
|
||||
import * as itemsDuck from './state/ducks/items';
|
||||
import * as networkDuck from './state/ducks/network';
|
||||
import * as updatesDuck from './state/ducks/updates';
|
||||
import * as userDuck from './state/ducks/user';
|
||||
import * as searchDuck from './state/ducks/search';
|
||||
import * as stickersDuck from './state/ducks/stickers';
|
||||
import * as conversationsSelectors from './state/selectors/conversations';
|
||||
import * as searchSelectors from './state/selectors/search';
|
||||
import { SendOptionsType } from './textsecure/SendMessage';
|
||||
import AccountManager from './textsecure/AccountManager';
|
||||
import Data from './sql/Client';
|
||||
|
@ -81,7 +104,7 @@ declare global {
|
|||
getGuid: () => string;
|
||||
getInboxCollection: () => ConversationModelCollectionType;
|
||||
getIncomingCallNotification: () => Promise<boolean>;
|
||||
getInteractionMode: () => string;
|
||||
getInteractionMode: () => 'mouse' | 'keyboard';
|
||||
getMediaCameraPermissions: () => Promise<boolean>;
|
||||
getMediaPermissions: () => Promise<boolean>;
|
||||
getServerPublicParams: () => string;
|
||||
|
@ -367,7 +390,6 @@ declare global {
|
|||
AttachmentList: any;
|
||||
CaptionEditor: any;
|
||||
ContactDetail: any;
|
||||
ConversationHeader: any;
|
||||
ErrorModal: typeof ErrorModal;
|
||||
Lightbox: any;
|
||||
LightboxGallery: any;
|
||||
|
@ -394,7 +416,37 @@ declare global {
|
|||
doesDatabaseExist: WhatIsThis;
|
||||
};
|
||||
Views: WhatIsThis;
|
||||
State: WhatIsThis;
|
||||
State: {
|
||||
bindActionCreators: typeof bindActionCreators;
|
||||
createStore: typeof createStore;
|
||||
Roots: {
|
||||
createCallManager: typeof createCallManager;
|
||||
createCompositionArea: typeof createCompositionArea;
|
||||
createConversationHeader: typeof createConversationHeader;
|
||||
createLeftPane: typeof createLeftPane;
|
||||
createSafetyNumberViewer: typeof createSafetyNumberViewer;
|
||||
createShortcutGuideModal: typeof createShortcutGuideModal;
|
||||
createStickerManager: typeof createStickerManager;
|
||||
createStickerPreviewModal: typeof createStickerPreviewModal;
|
||||
createTimeline: typeof createTimeline;
|
||||
};
|
||||
Ducks: {
|
||||
calling: typeof callingDuck;
|
||||
conversations: typeof conversationsDuck;
|
||||
emojis: typeof emojisDuck;
|
||||
expiration: typeof expirationDuck;
|
||||
items: typeof itemsDuck;
|
||||
network: typeof networkDuck;
|
||||
updates: typeof updatesDuck;
|
||||
user: typeof userDuck;
|
||||
search: typeof searchDuck;
|
||||
stickers: typeof stickersDuck;
|
||||
};
|
||||
Selectors: {
|
||||
conversations: typeof conversationsSelectors;
|
||||
search: typeof searchSelectors;
|
||||
};
|
||||
};
|
||||
Logs: WhatIsThis;
|
||||
conversationControllerStart: WhatIsThis;
|
||||
Emojis: {
|
||||
|
@ -555,12 +607,6 @@ export type WhisperType = {
|
|||
KeyVerificationPanelView: any;
|
||||
SafetyNumberChangeDialogView: any;
|
||||
|
||||
ExpirationTimerOptions: {
|
||||
map: any;
|
||||
getName: (number: number) => string;
|
||||
getAbbreviated: (number: number) => string;
|
||||
};
|
||||
|
||||
Notifications: {
|
||||
removeBy: (filter: Partial<unknown>) => void;
|
||||
add: (notification: unknown) => void;
|
||||
|
|
Loading…
Reference in a new issue