Updating message recieving

This commit is contained in:
Vincent 2020-07-21 14:43:46 +10:00
parent d823e2a758
commit 28a0d82ea2
18 changed files with 323 additions and 257 deletions

View file

@ -567,6 +567,11 @@
? `${message.get('source')}.${message.get('sourceDevice')}`
: `${message.source}.${message.sourceDevice}`;
this.clearContactTypingTimer(identifier);
const model = this.addSingleMessage(message);
MessageController.register(model.id, model);
this.trigger('change');
},
addSingleMessage(message, setToExpire = true) {
const model = this.messageCollection.add(message, { merge: true });

View file

@ -65,8 +65,6 @@
const generateProps = () => {
if (this.isExpirationTimerUpdate()) {
this.propsForTimerNotification = this.getPropsForTimerNotification();
} else if (this.isKeyChange()) {
this.propsForSafetyNumberNotification = this.getPropsForSafetyNumberNotification();
} else if (this.isVerifiedChange()) {
this.propsForVerificationNotification = this.getPropsForVerificationNotification();
} else if (this.isEndSession()) {
@ -344,19 +342,6 @@
return basicProps;
},
getPropsForSafetyNumberNotification() {
const conversation = this.getConversation();
const isGroup = conversation && !conversation.isPrivate();
const phoneNumber = this.get('key_changed');
const onVerify = () =>
this.trigger('show-identity', this.findContact(phoneNumber));
return {
isGroup,
contact: this.findAndFormatContact(phoneNumber),
onVerify,
};
},
getPropsForVerificationNotification() {
const type = this.get('verified') ? 'markVerified' : 'markNotVerified';
const isLocal = this.get('local');

View file

@ -124,9 +124,6 @@ const { Quote } = require('../../ts/components/conversation/Quote');
const {
ResetSessionNotification,
} = require('../../ts/components/conversation/ResetSessionNotification');
const {
SafetyNumberNotification,
} = require('../../ts/components/conversation/SafetyNumberNotification');
const {
StagedLinkPreview,
} = require('../../ts/components/conversation/StagedLinkPreview');
@ -323,7 +320,6 @@ exports.setup = (options = {}) => {
MessageDetail,
Quote,
ResetSessionNotification,
SafetyNumberNotification,
StagedLinkPreview,
TimerNotification,
Types: {

View file

@ -49,11 +49,6 @@
Component: Components.TimerNotification,
props: this.model.propsForTimerNotification,
};
} else if (this.model.propsForSafetyNumberNotification) {
return {
Component: Components.SafetyNumberNotification,
props: this.model.propsForSafetyNumberNotification,
};
} else if (this.model.propsForVerificationNotification) {
return {
Component: Components.VerificationNotification,

View file

@ -40,6 +40,7 @@ $composition-container-height: 60px;
.conversation-item {
display: flex;
flex-direction: column;
flex-grow: 1;
height: 100%;
outline: none;
@ -65,14 +66,6 @@ $composition-container-height: 60px;
}
}
&__content {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
outline: none;
}
&__options-pane {
position: absolute;
height: 100%;
@ -102,7 +95,7 @@ $composition-container-height: 60px;
display: flex;
left: 0px;
right: 0px;
margin: 0px $session-margin-md;
padding: 0px $session-margin-md;
align-items: center;
justify-content: space-between;
height: $main-view-header-height;
@ -138,15 +131,25 @@ $composition-container-height: 60px;
flex-direction: column;
position: relative;
background-color: $session-background;
outline: none;
.conversation-messages {
display: flex;
flex-direction: column;
flex-grow: 1;
position: absolute;
height: 100%;
width: 100%;
height: 0;
background-color: inherit;
outline: none;
&__blocking-overlay {
background-color: rgba(0, 0, 0, 0.8);
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
}
}
.conversation-info-panel {
@ -167,22 +170,6 @@ $composition-container-height: 60px;
}
}
.messages-wrapper {
display: flex;
flex-grow: 1;
flex-direction: column;
position: relative;
height: 100%;
&--blocking-overlay {
background-color: rgba(0, 0, 0, 0.8);
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
}
}
.messages-container {
display: flex;
flex-grow: 1;

View file

@ -351,7 +351,6 @@ export class ConversationHeader extends React.Component<Props> {
public renderSelectionOverlay() {
const {
onDeleteSelectedMessages,
onResetSession,
onCloseOverlay,
isPublic,
i18n,
@ -367,7 +366,7 @@ export class ConversationHeader extends React.Component<Props> {
<SessionIconButton
iconType={SessionIconType.Exit}
iconSize={SessionIconSize.Medium}
onClick={onResetSession}
onClick={onCloseOverlay}
/>
</div>
@ -394,17 +393,15 @@ export class ConversationHeader extends React.Component<Props> {
{this.renderBackButton()}
<div className="module-conversation-header__title-container">
<div className="module-conversation-header__title-flex">
{this.renderOptions(triggerId)}
{!selectionMode && this.renderOptions(triggerId)}
{this.renderTitle()}
{/* This might be redundant as we show the title in the title: */}
{/*isPrivateGroup ? this.renderMemberCount() : null*/}
</div>
</div>
{!isKickedFromGroup && this.renderExpirationLength()}
{!this.props.isRss && this.renderAvatar()}
{!this.props.isRss && !selectionMode && this.renderAvatar()}
{this.renderMenu(triggerId)}
{!selectionMode && this.renderMenu(triggerId)}
</div>
{selectionMode && this.renderSelectionOverlay()}

View file

@ -1186,7 +1186,7 @@ export class Message extends React.PureComponent<Props, State> {
// User clicked on message body
const target = event.target as HTMLDivElement;
if (target.className === 'text-selectable') {
if (!multiSelectMode && target.className === 'text-selectable') {
return;
}
@ -1196,9 +1196,8 @@ export class Message extends React.PureComponent<Props, State> {
}}
>
{this.renderError(isIncoming)}
{isRss || isKickedFromGroup
? null
: this.renderMenu(!isIncoming, triggerId)}
{enableContextMenu ? this.renderMenu(!isIncoming, triggerId) : null}
<div
className={classNames(
'module-message__container',

View file

@ -1,25 +0,0 @@
### In group conversation
```js
<util.ConversationContext theme={util.theme}>
<SafetyNumberNotification
i18n={util.i18n}
isGroup={true}
contact={{ phoneNumber: '(202) 500-1000', profileName: 'Mr. Fire' }}
onVerify={() => console.log('onVerify')}
/>
</util.ConversationContext>
```
### In one-on-one conversation
```js
<util.ConversationContext theme={util.theme}>
<SafetyNumberNotification
i18n={util.i18n}
isGroup={false}
contact={{ phoneNumber: '(202) 500-1000', profileName: 'Mr. Fire' }}
onVerify={() => console.log('onVerify')}
/>
</util.ConversationContext>
```

View file

@ -1,61 +0,0 @@
import React from 'react';
// import classNames from 'classnames';
import { ContactName } from './ContactName';
import { Intl } from '../Intl';
import { LocalizerType } from '../../types/Util';
interface Contact {
phoneNumber: string;
profileName?: string;
name?: string;
}
interface Props {
isGroup: boolean;
contact: Contact;
i18n: LocalizerType;
onVerify: () => void;
}
export class SafetyNumberNotification extends React.Component<Props> {
public render() {
const { contact, isGroup, i18n, onVerify } = this.props;
const changeKey = isGroup
? 'safetyNumberChangedGroup'
: 'safetyNumberChanged';
return (
<div className="module-safety-number-notification">
<div className="module-safety-number-notification__icon" />
<div className="module-safety-number-notification__text">
<Intl
id={changeKey}
components={[
<span
key="external-1"
className="module-safety-number-notification__contact"
>
<ContactName
i18n={i18n}
name={contact.name}
profileName={contact.profileName}
phoneNumber={contact.phoneNumber}
module="module-verification-notification__contact"
/>
</span>,
]}
i18n={i18n}
/>
</div>
<div
role="button"
onClick={onVerify}
className="module-verification-notification__button"
>
{i18n('verifyNewNumber')}
</div>
</div>
);
}
}

View file

@ -13,6 +13,7 @@ import { ConversationModel } from '../../../js/models/conversations';
import { SessionSpinner } from './SessionSpinner';
import classNames from 'classnames';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
import { Constants } from '../../session';
interface Props {
conversation: ConversationModel;
@ -70,7 +71,9 @@ export class SessionKeyVerification extends React.Component<Props, State> {
);
}
const verificationIconColor = isVerified ? '#00f782' : '#ff453a';
const verificationIconColor = isVerified
? Constants.UI.COLORS.GREEN
: Constants.UI.COLORS.DANGER;
const verificationButtonColor = isVerified
? SessionButtonColor.Warning
: SessionButtonColor.Success;
@ -112,7 +115,7 @@ export class SessionKeyVerification extends React.Component<Props, State> {
<span>
<SessionIcon
iconType={SessionIconType.Lock}
iconSize={SessionIconSize.Large}
iconSize={SessionIconSize.Huge}
iconColor={verificationIconColor}
/>
{window.i18n(

View file

@ -3,6 +3,7 @@ import classNames from 'classnames';
import { Avatar } from '../Avatar';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
import { Constants } from '../../session';
export interface ContactType {
id: string;
@ -79,7 +80,7 @@ export class SessionMemberListItem extends React.Component<Props, State> {
<SessionIcon
iconType={SessionIconType.Check}
iconSize={SessionIconSize.Medium}
iconColor={'#00f782'}
iconColor={Constants.UI.COLORS.GREEN}
/>
</span>
</div>

View file

@ -7,6 +7,7 @@ import {
SessionButtonColor,
SessionButtonType,
} from './SessionButton';
import { Constants } from '../../session';
interface State {
error: string;
@ -82,7 +83,7 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
<SessionIcon
iconType={SessionIconType.Lock}
iconSize={35}
iconColor="#00f782"
iconColor={Constants.UI.COLORS.GREEN}
/>
);
const errorSection = !this.state.clearDataView && (

View file

@ -1,5 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import { Constants } from '../../session';
interface Props {
// Value ranges from 0 to 100
@ -54,11 +55,8 @@ export class SessionProgress extends React.PureComponent<Props, State> {
// 2. Transition duration scales with the
// distance it needs to travel
// FIXME VINCE - globalise all JS color references
const sessionBrandColor = '#00f782';
const sessionDangerAlt = '#ff4538';
const successColor = sessionBrandColor;
const failureColor = sessionDangerAlt;
const successColor = Constants.UI.COLORS.GREEN;
const failureColor = Constants.UI.COLORS.DANGER_ALT;
const backgroundColor = sendStatus === -1 ? failureColor : successColor;
const shiftDurationMs =
@ -78,7 +76,7 @@ export class SessionProgress extends React.PureComponent<Props, State> {
// 'transition-property': 'transform, opacity',
'transition-duration': `${shiftDurationMs}ms`,
// 'transition-duration': `${shiftDurationMs}ms, ${showDurationMs}ms`,
'transition-delay': `0ms`,
'transition-delay': '0ms',
// 'transition-delay': `0ms, ${showOffsetMs}ms`,
'transition-timing-funtion': 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
//'transition-timing-funtion':'cubic-bezier(0.25, 0.46, 0.45, 0.94), linear',

View file

@ -205,7 +205,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
<SessionIconButton
iconType={SessionIconType.Send}
iconSize={SessionIconSize.Large}
iconColor={'#FFFFFF'}
iconColor={Constants.UI.COLORS.WHITE}
iconRotation={90}
onClick={this.onSendMessage}
/>

View file

@ -19,6 +19,7 @@ import { ResetSessionNotification } from '../../conversation/ResetSessionNotific
import { Constants, getMessageQueue } from '../../../session';
import { MessageQueue } from '../../../session/sending';
import { SessionKeyVerification } from '../SessionKeyVerification';
import _ from 'lodash';
interface State {
conversationKey: string;
@ -113,6 +114,7 @@ export class SessionConversation extends React.Component<any, State> {
this.onMessageSending = this.onMessageSending.bind(this);
this.onMessageSuccess = this.onMessageSuccess.bind(this);
this.onMessageFailure = this.onMessageFailure.bind(this);
this.deleteSelectedMessages = this.deleteSelectedMessages.bind(this);
this.messagesEndRef = React.createRef();
this.messageContainerRef = React.createRef();
@ -126,7 +128,7 @@ export class SessionConversation extends React.Component<any, State> {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public componentDidMount() {
this.getMessages()
this.loadInitialMessages()
.then(() => {
// Pause thread to wait for rendering to complete
setTimeout(() => {
@ -158,6 +160,14 @@ export class SessionConversation extends React.Component<any, State> {
if (timestamp > this.state.messageFetchTimestamp) {
await this.getMessages();
}
// console.log('[vince] this.props.conversations:', this.props.conversations);
console.log(`[vince] Conversation changed from redux`);
const conversationModel = window.ConversationController.get(this.state.conversationKey);
const messages = conversationModel.messageCollection;
console.log('[vince] messages:', messages);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -172,7 +182,7 @@ export class SessionConversation extends React.Component<any, State> {
showOptionsPane,
showScrollButton,
} = this.state;
const loading = !doneInitialScroll || messages.length === 0;
const loading = !doneInitialScroll;
const selectionMode = !!this.state.selectedMessages.length;
const conversation = this.props.conversations.conversationLookup[
@ -195,18 +205,9 @@ export class SessionConversation extends React.Component<any, State> {
return (
<>
<div
className={classNames(
'conversation-item__content',
selectionMode && 'selection-mode'
)}
tabIndex={0}
onKeyDown={this.onKeyDown}
role="navigation"
>
<div className="conversation-header">{this.renderHeader()}</div>
<div className="conversation-header">{this.renderHeader()}</div>
{/* <SessionProgress
{/* <SessionProgress
visible={this.state.messageProgressVisible}
value={this.state.sendingProgress}
prevValue={this.state.prevSendingProgress}
@ -214,54 +215,59 @@ export class SessionConversation extends React.Component<any, State> {
resetProgress={this.resetSendingProgress}
/> */}
<div className="conversation-content">
<div
className={classNames(
'conversation-info-panel',
this.state.infoViewState && 'show'
)}
>
{showSafetyNumber && (
<SessionKeyVerification conversation={conversationModel} />
)}
{showMessageDetails && <>&nbsp</>}
</div>
<div className="conversation-messages">
<div className="messages-wrapper">
{loading && <div className="messages-container__loading" />}
<div
className="messages-container"
onScroll={this.handleScroll}
ref={this.messageContainerRef}
>
{this.renderMessages()}
<div ref={this.messagesEndRef} />
</div>
<SessionScrollButton
show={showScrollButton}
onClick={this.scrollToBottom}
/>
{showRecordingView && (
<div className="messages-wrapper--blocking-overlay" />
)}
</div>
{!isRss && (
<SessionCompositionBox
sendMessage={sendMessageFn}
dropZoneFiles={this.state.dropZoneFiles}
onMessageSending={this.onMessageSending}
onMessageSuccess={this.onMessageSuccess}
onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
/>
)}
</div>
<div
className={classNames(
'conversation-content',
selectionMode && 'selection-mode'
)}
tabIndex={0}
onKeyDown={this.onKeyDown}
role="navigation"
>
<div
className={classNames(
'conversation-info-panel',
this.state.infoViewState && 'show'
)}
>
{showSafetyNumber && (
<SessionKeyVerification conversation={conversationModel} />
)}
{showMessageDetails && <>&nbsp</>}
</div>
<div className="conversation-messages">
{loading && <div className="messages-container__loading" />}
<div
className="messages-container"
onScroll={this.handleScroll}
ref={this.messageContainerRef}
>
{this.renderMessages()}
<div ref={this.messagesEndRef} />
</div>
<SessionScrollButton
show={showScrollButton}
onClick={this.scrollToBottom}
/>
{showRecordingView && (
<div className="conversation-messages__blocking-overlay" />
)}
</div>
{!isRss && (
<SessionCompositionBox
sendMessage={sendMessageFn}
dropZoneFiles={this.state.dropZoneFiles}
onMessageSending={this.onMessageSending}
onMessageSuccess={this.onMessageSuccess}
onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
/>
)}
</div>
{shouldRenderGroupSettings && (
@ -281,6 +287,7 @@ export class SessionConversation extends React.Component<any, State> {
public renderMessages() {
const { messages } = this.state;
const multiSelectMode = Boolean(this.state.selectedMessages.length);
// FIXME VINCE: IF MESSAGE IS THE TOP OF UNREAD, THEN INSERT AN UNREAD BANNER
return (
@ -293,10 +300,6 @@ export class SessionConversation extends React.Component<any, State> {
i18n: window.i18n,
...message.propsForTimerNotification,
};
const friendRequestProps = message.propsForFriendRequest && {
i18n: window.i18n,
...message.propsForFriendRequest,
};
const resetSessionProps = message.propsForResetSessionNotification && {
i18n: window.i18n,
...message.propsForResetSessionNotification,
@ -309,12 +312,17 @@ export class SessionConversation extends React.Component<any, State> {
// firstMessageOfSeries tells us to render the avatar only for the first message
// in a series of messages from the same user
item = messageProps
? this.renderMessage(messageProps, message.firstMessageOfSeries)
? this.renderMessage(
messageProps,
message.firstMessageOfSeries,
multiSelectMode
)
: item;
item = quoteProps
? this.renderMessage(
timerProps,
message.firstMessageOfSeries,
multiSelectMode,
quoteProps
)
: item;
@ -335,13 +343,13 @@ export class SessionConversation extends React.Component<any, State> {
public renderHeader() {
const headerProps = this.getHeaderProps();
return <ConversationHeader {...headerProps} />;
}
public renderMessage(
messageProps: any,
firstMessageOfSeries: boolean,
multiSelectMode: boolean,
quoteProps?: any
) {
const selected =
@ -351,6 +359,7 @@ export class SessionConversation extends React.Component<any, State> {
messageProps.i18n = window.i18n;
messageProps.selected = selected;
messageProps.firstMessageOfSeries = firstMessageOfSeries;
messageProps.multiSelectMode = multiSelectMode;
messageProps.onSelectMessage = (messageId: string) => {
this.selectMessage(messageId);
};
@ -364,11 +373,38 @@ export class SessionConversation extends React.Component<any, State> {
// ~~~~~~~~~~~~~~ GETTER METHODS ~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public async loadInitialMessages() {
// Grabs the initial set of messages and adds them to our conversation model.
// After the inital fetch, all new messages are automatically added from onNewMessage
// in the conversation model.
// The only time we need to call getMessages() is to grab more messages on scroll.
const { conversationKey } = this.state;
const conversationModel = window.ConversationController.get(conversationKey);
const messageSet = await window.Signal.Data.getMessagesByConversation(
conversationKey,
{ limit: Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT, MessageCollection: window.Whisper.MessageCollection }
);
const messageModels = messageSet.models;
const messages = messageModels.map((message: any) => message.id);
this.setState({ messages }, () => {
if (this.state.isScrolledToBottom) {
this.updateReadMessages();
}
});
// Add new messages to conversation collection
conversationModel.messageCollection = messageSet;
}
public async getMessages(
numMessages?: number,
fetchInterval = Constants.CONVERSATION.MESSAGE_FETCH_INTERVAL
) {
const { conversationKey, messageFetchTimestamp } = this.state;
const conversationModel = window.ConversationController.get(conversationKey);
const timestamp = getTimestamp();
// If we have pulled messages in the last interval, don't bother rescanning
@ -393,19 +429,19 @@ export class SessionConversation extends React.Component<any, State> {
);
// Set first member of series here.
const messageModels = messageSet.models;
const messages = [];
let previousSender;
for (let i = 0; i < messageModels.length; i++) {
// Handle firstMessageOfSeries for conditional avatar rendering
let firstMessageOfSeries = true;
if (i > 0 && previousSender === messageModels[i].authorPhoneNumber) {
firstMessageOfSeries = false;
}
// const messageModels = messageSet.models;
// const messages = [];
// let previousSender;
// for (let i = 0; i < messageModels.length; i++) {
// // Handle firstMessageOfSeries for conditional avatar rendering
// let firstMessageOfSeries = true;
// if (i > 0 && previousSender === messageModels[i].authorPhoneNumber) {
// firstMessageOfSeries = false;
// }
messages.push({ ...messageModels[i], firstMessageOfSeries });
previousSender = messageModels[i].authorPhoneNumber;
}
// messages.push({ ...messageModels[i], firstMessageOfSeries });
// previousSender = messageModels[i].authorPhoneNumber;
// }
const previousTopMessage = this.state.messages[0]?.id;
const newTopMessage = messages[0]?.id;
@ -416,6 +452,12 @@ export class SessionConversation extends React.Component<any, State> {
}
});
// Add new messages to conversation collection
// const newMessages = _.xor(messages, previousMessageSet);
// newMessages.forEach(message => conversationModel.addSingleMessage(message));
// console.log('[vince] conversationModel.messageCollection:', conversationModel.messageCollection);
return { newTopMessage, previousTopMessage };
}
@ -463,9 +505,14 @@ export class SessionConversation extends React.Component<any, State> {
onSetDisappearingMessages: (seconds: any) =>
conversation.updateExpirationTimer(seconds),
onDeleteMessages: () => conversation.destroyMessages(),
onDeleteSelectedMessages: () => conversation.deleteSelectedMessages(),
onCloseOverlay: () => conversation.resetMessageSelection(),
onDeleteMessages: () => null,
onDeleteSelectedMessages: async () => {
await this.deleteSelectedMessages();
},
onCloseOverlay: () => {
this.setState({ selectedMessages: [] });
conversation.resetMessageSelection();
},
onDeleteContact: () => conversation.deleteContact(),
onResetSession: () => {
conversation.endSession();
@ -715,6 +762,97 @@ export class SessionConversation extends React.Component<any, State> {
return null;
}
public async deleteSelectedMessages(onSuccess?: any) {
// Get message objects
const messageObjects = this.state.messages.filter(message => this.state.selectedMessages.find(
selectedMessage => selectedMessage === message.id
));
// Get message model for each message
const messages = messageObjects.map(message => message?.collection?.models[0]);
const { conversationKey } = this.state;
const conversationModel = window.ConversationController.get(
conversationKey
);
const multiple = messages.length > 1;
const isPublic = conversationModel.isPublic();
// In future, we may be able to unsend private messages also
// isServerDeletable also defined in ConversationHeader.tsx for
// future reference
const isServerDeletable = isPublic;
const warningMessage = (() => {
if (isPublic) {
return multiple
? window.i18n('deleteMultiplePublicWarning')
: window.i18n('deletePublicWarning');
}
return multiple ? window.i18n('deleteMultipleWarning') : window.i18n('deleteWarning');
})();
const doDelete = async () => {
let toDeleteLocally;
console.log('[vince] conversationKey:', conversationKey);
console.log('[vince] conversationModel:', conversationModel);
console.log('[vince] messages:', messages);
// VINCE TOOD: MARK TO-DELETE MESSAGES AS READ
if (isPublic) {
toDeleteLocally = await conversationModel.deletePublicMessages(messages);
if (toDeleteLocally.length === 0) {
// Message failed to delete from server, show error?
return;
}
} else {
messages.forEach(m => conversationModel.messageCollection.remove(m.id));
toDeleteLocally = messages;
}
await Promise.all(
toDeleteLocally.map(async (message: any) => {
await window.Signal.Data.removeMessage(message.id, {
Message: window.Whisper.Message,
});
message.trigger('unload');
})
);
if (onSuccess) {
onSuccess();
}
};
// Only show a warning when at least one messages was successfully
// saved in on the server
if (!messages.some(m => !m.hasErrors())) {
await doDelete();
return;
}
// If removable from server, we "Unsend" - otherwise "Delete"
const pluralSuffix = multiple ? 's' : '';
const title = window.i18n(
isPublic
? `unsendMessage${pluralSuffix}`
: `deleteMessage${pluralSuffix}`
);
const okText = window.i18n(isServerDeletable ? 'unsend' : 'delete');
window.confirmationDialog({
title,
message: warningMessage,
okText,
okTheme: 'danger',
resolve: doDelete,
centeredText: true,
});
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~ SCROLLING METHODS ~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -1,6 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import { Picker } from 'emoji-mart';
import { Constants } from '../../../session';
interface Props {
onEmojiClicked: (emoji: any) => void;
@ -31,7 +32,7 @@ export class SessionEmojiPanel extends React.Component<Props, State> {
set={'twitter'}
sheetSize={32}
darkMode={true}
color={'#00F782'}
color={Constants.UI.COLORS.GREEN}
showPreview={true}
title={''}
onSelect={onEmojiClicked}

View file

@ -115,8 +115,8 @@ export class SessionRecording extends React.Component<Props, State> {
barRadius: 15,
barWidth: 4,
barPadding: 3,
barColorInit: '#AFAFAF',
barColorPlay: '#FFFFFF',
barColorInit: Constants.UI.COLORS.WHITE_PALE,
barColorPlay: Constants.UI.COLORS.WHITE,
maxBarHeight: 30,
minBarHeight: 3,
},
@ -194,8 +194,7 @@ export class SessionRecording extends React.Component<Props, State> {
<SessionIconButton
iconType={SessionIconType.Pause}
iconSize={SessionIconSize.Medium}
// FIXME VINCE: Globalise constants for JS Session Colors
iconColor={'#FF4538'}
iconColor={Constants.UI.COLORS.DANGER_ALT}
onClick={actionPauseFn}
/>
)}
@ -203,8 +202,7 @@ export class SessionRecording extends React.Component<Props, State> {
<SessionIconButton
iconType={SessionIconType.Pause}
iconSize={SessionIconSize.Medium}
// FIXME VINCE: Globalise constants for JS Session Colors
iconColor={'#FFFFFF'}
iconColor={Constants.UI.COLORS.WHITE}
onClick={actionPauseFn}
/>
)}
@ -249,7 +247,7 @@ export class SessionRecording extends React.Component<Props, State> {
<SessionIconButton
iconType={SessionIconType.Send}
iconSize={SessionIconSize.Large}
iconColor={'#FFFFFF'}
iconColor={Constants.UI.COLORS.WHITE}
iconRotation={90}
onClick={this.onSendVoiceMessage}
/>
@ -546,19 +544,21 @@ export class SessionRecording extends React.Component<Props, State> {
const canvasContext = canvas && canvas.getContext('2d');
for (var i = 0; i < volumeArray.length; i++) {
for (let i = 0; i < volumeArray.length; i++) {
const barHeight = Math.ceil(volumeArray[i]);
const offset_x = Math.ceil(i * (barWidth + barPadding));
const offset_y = Math.ceil(height / 2 - barHeight / 2);
const offsetX = Math.ceil(i * (barWidth + barPadding));
const offsetY = Math.ceil(height / 2 - barHeight / 2);
// FIXME VINCE - Globalise JS references to colors
canvasContext && (canvasContext.fillStyle = barColorInit);
canvasContext &&
this.drawRoundedRect(canvasContext, offset_x, offset_y, barHeight);
if (canvasContext) {
canvasContext.fillStyle = barColorInit;
this.drawRoundedRect(canvasContext, offsetX, offsetY, barHeight);
}
}
};
this.state.isRecording && requestAnimationFrame(drawRecordingCanvas);
if (this.state.isRecording) {
requestAnimationFrame(drawRecordingCanvas);
}
};
// Init listeners for visualisation
@ -663,7 +663,6 @@ export class SessionRecording extends React.Component<Props, State> {
const offsetX = Math.ceil(i * (barWidth + barPadding));
const offsetY = Math.ceil(height / 2 - barHeight / 2);
// FIXME VINCE - Globalise JS references to colors
canvasContext.fillStyle = barColorInit;
this.drawRoundedRect(canvasContext, offsetX, offsetY, barHeight);

View file

@ -31,4 +31,51 @@ export const UI = {
// Pixels (scroll) from the top of the top of message container
// at which more messages should be loaded
MESSAGE_CONTAINER_BUFFER_OFFSET_PX: 30,
COLORS: {
// COMMON
WHITE: '#FFFFFF',
WHITE_PALE: '#AFAFAF',
LIGHT_GREY: '#A0A0A0',
DARK_GREY: '#353535',
BLACK: '#000000',
GREEN: '#00F782',
GREEN_ALT_1: '#00F480',
GREEN_ALT_2: '#00FD73',
GREEN_ALT_3: '#00F782',
BACKGROUND: '#121212',
// SHADES
SHADE_1: '#0C0C0C',
SHADE_1_ALT: '#0F1011',
SHADE_2: '#161616',
SHADE_3: '#191818',
SHADE_4: '#1B1B1B',
SHADE_5: '#222325',
SHADE_6: '#232323',
SHADE_6_ALT: '#2C2C2C',
SHADE_7: '#2E2E2E',
SHADE_8: '#2F2F2F',
SHADE_9: '#313131',
SHADE_10: '#3E3E3E',
SHADE_11: '#3F3F3F',
SHADE_12: '#3F4146',
SHADE_13: '#474646',
SHADE_14: '#535865',
SHADE_15: '#5B6C72',
SHADE_16: '#979797',
SHADE_17: '#A0A0A0',
SHADE_18: '#141414',
// SEMANTIC COLORS
INFO: '#3F3F3F',
SUCCESS: '#35D388',
ERROR: '#EDD422',
WARNING: '#A0A0A0',
WARNING_ALT: '#FF9D00',
DANGER: '#FF453A',
DANGER_ALT: '#FF4538',
PRIMARY: '#474646',
SECONDARY: '#232323',
},
};