move InboxView to react

pull/1381/head
Audric Ackermann 5 years ago
parent 2ae7a6dfe5
commit 918eeae275
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -35,24 +35,6 @@
</script> </script>
<script type='text/x-tmpl-mustache' id='two-column'>
<div class='gutter'>
<div class='network-status-container'></div>
<div class='left-pane-placeholder'></div>
</div>
<div id='main-view'>
<div class='conversation placeholder'>
<div class='conversation-header'></div>
<div class='container'>
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='expired_alert'> <script type='text/x-tmpl-mustache' id='expired_alert'>
<a target='_blank' href='https://getsession.org/'> <a target='_blank' href='https://getsession.org/'>
<button class='upgrade'>{{ upgrade }}</button> <button class='upgrade'>{{ upgrade }}</button>
@ -222,6 +204,7 @@
<script type='text/javascript' src='js/views/nickname_dialog_view.js'></script> <script type='text/javascript' src='js/views/nickname_dialog_view.js'></script>
<script type='text/javascript' src='js/views/password_dialog_view.js'></script> <script type='text/javascript' src='js/views/password_dialog_view.js'></script>
<script type='text/javascript' src='js/views/seed_dialog_view.js'></script> <script type='text/javascript' src='js/views/seed_dialog_view.js'></script>
<script type='text/javascript' src='js/views/session_inbox_view.js'></script>
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script> <script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
<script type='text/javascript' src='js/views/banner_view.js'></script> <script type='text/javascript' src='js/views/banner_view.js'></script>
<script type='text/javascript' src='js/views/session_registration_view.js'></script> <script type='text/javascript' src='js/views/session_registration_view.js'></script>

@ -34,23 +34,6 @@
</div> </div>
</script> </script>
<script type='text/x-tmpl-mustache' id='two-column'>
<div class='gutter'>
<div class='network-status-container'></div>
<div class='left-pane-placeholder'></div>
</div>
<div id='main-view'>
<div class='conversation placeholder'>
<div class='conversation-header'></div>
<div class='container'>
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='expired_alert'> <script type='text/x-tmpl-mustache' id='expired_alert'>
@ -224,6 +207,7 @@
<script type='text/javascript' src='js/views/nickname_dialog_view.js'></script> <script type='text/javascript' src='js/views/nickname_dialog_view.js'></script>
<script type='text/javascript' src='js/views/password_dialog_view.js'></script> <script type='text/javascript' src='js/views/password_dialog_view.js'></script>
<script type='text/javascript' src='js/views/seed_dialog_view.js'></script> <script type='text/javascript' src='js/views/seed_dialog_view.js'></script>
<script type='text/javascript' src='js/views/session_inbox_view.js'></script>
<script type='text/javascript' src='js/views/identicon_svg_view.js'></script> <script type='text/javascript' src='js/views/identicon_svg_view.js'></script>
<script type='text/javascript' src='js/views/banner_view.js'></script> <script type='text/javascript' src='js/views/banner_view.js'></script>
<script type='text/javascript' src='js/views/session_registration_view.js'></script> <script type='text/javascript' src='js/views/session_registration_view.js'></script>

@ -86,7 +86,7 @@
// const applicableConversationChanges = // const applicableConversationChanges =
// 'change:color change:name change:number change:profileName change:profileAvatar'; // 'change:color change:name change:number change:profileName change:profileAvatar';
// FIXME AUDRIC // FIXME AUDRIC
const conversation = this.getConversation(); // const conversation = this.getConversation();
// const fromContact = this.getIncomingContact(); // const fromContact = this.getIncomingContact();
// this.listenTo(conversation, applicableConversationChanges, generateProps); // this.listenTo(conversation, applicableConversationChanges, generateProps);

@ -1,6 +1,5 @@
// The idea with this file is to make it webpackable for the style guide // The idea with this file is to make it webpackable for the style guide
const { bindActionCreators } = require('redux');
const Crypto = require('./crypto'); const Crypto = require('./crypto');
const Data = require('./data'); const Data = require('./data');
const Database = require('./database'); const Database = require('./database');
@ -46,14 +45,11 @@ const {
const { const {
SessionConversation, SessionConversation,
} = require('../../ts/components/session/conversation/SessionConversation'); } = require('../../ts/components/session/conversation/SessionConversation');
const {
SettingsView,
} = require('../../ts/components/session/settings/SessionSettings');
const { SessionModal } = require('../../ts/components/session/SessionModal'); const { SessionModal } = require('../../ts/components/session/SessionModal');
const { const {
SessionSeedModal, SessionSeedModal,
} = require('../../ts/components/session/SessionSeedModal'); } = require('../../ts/components/session/SessionSeedModal');
const { SessionInboxView} = require('../../ts/components/session/SessionInboxView')
const { const {
SessionPasswordModal, SessionPasswordModal,
} = require('../../ts/components/session/SessionPasswordModal'); } = require('../../ts/components/session/SessionPasswordModal');
@ -107,11 +103,6 @@ const {
} = require('../../ts/components/conversation/TypingBubble'); } = require('../../ts/components/conversation/TypingBubble');
// State // State
const { createLeftPane } = require('../../ts/state/roots/createLeftPane');
const {
createSessionConversation,
} = require('../../ts/state/roots/createSessionConversation');
const { createStore } = require('../../ts/state/createStore');
const conversationsDuck = require('../../ts/state/ducks/conversations'); const conversationsDuck = require('../../ts/state/ducks/conversations');
const userDuck = require('../../ts/state/ducks/user'); const userDuck = require('../../ts/state/ducks/user');
const messagesDuck = require('../../ts/state/ducks/messages'); const messagesDuck = require('../../ts/state/ducks/messages');
@ -251,7 +242,6 @@ exports.setup = (options = {}) => {
ContactDetail, ContactDetail,
ContactListItem, ContactListItem,
ContactName, ContactName,
SettingsView,
EmbeddedContact, EmbeddedContact,
Emojify, Emojify,
Lightbox, Lightbox,
@ -262,6 +252,7 @@ exports.setup = (options = {}) => {
UserDetailsDialog, UserDetailsDialog,
DevicePairingDialog, DevicePairingDialog,
SessionRegistrationView, SessionRegistrationView,
SessionInboxView,
ConfirmDialog, ConfirmDialog,
UpdateGroupNameDialog, UpdateGroupNameDialog,
UpdateGroupMembersDialog, UpdateGroupMembersDialog,
@ -287,19 +278,12 @@ exports.setup = (options = {}) => {
TypingBubble, TypingBubble,
}; };
const Roots = {
createLeftPane,
createSessionConversation,
};
const Ducks = { const Ducks = {
conversations: conversationsDuck, conversations: conversationsDuck,
user: userDuck, user: userDuck,
messages: messagesDuck, messages: messagesDuck,
}; };
const State = { const State = {
bindActionCreators,
createStore,
Roots,
Ducks, Ducks,
}; };

@ -662,10 +662,6 @@
this.updateHeader(); this.updateHeader();
}, },
async openConversation(number) {
window.Whisper.events.trigger('showConversation', number);
},
listenBack(view) { listenBack(view) {
this.panels = this.panels || []; this.panels = this.panels || [];
if (this.panels.length > 0) { if (this.panels.length > 0) {

@ -23,7 +23,7 @@
className: 'app-loading-screen', className: 'app-loading-screen',
}); });
Whisper.InboxView = Whisper.View.extend({ Whisper.InboxViewWhisper = Whisper.View.extend({
templateName: 'two-column', templateName: 'two-column',
className: 'inbox index', className: 'inbox index',
initialize(options = {}) { initialize(options = {}) {
@ -61,182 +61,14 @@
} }
}); });
this.openSettings = this.openSettings.bind(this);
this.openSessionConversation = this.openSessionConversation.bind(this);
// FIXME: Fix this for new react views // FIXME: Fix this for new react views
this.setupLeftPane(); this.setupLeftPane();
}, },
open(conversation) {
this.setupSessionConversation(conversation.id);
conversation.trigger('opened');
},
close(conversation) {
const $el = $(`#conversation-${conversation.cid}`);
if ($el && $el.length > 0) {
$el.remove();
}
},
setupSessionConversation() {
// Here we set up a full redux store with initial state for our Conversation Root
this.sessionConversationView = new Whisper.ReactWrapperView({
JSX: Signal.State.Roots.createSessionConversation(window.inboxStore),
className: 'conversation-item',
});
// Add sessionConversation to the DOM
$('#main-view').html('');
$('#main-view').append(this.sessionConversationView.el);
},
async setupLeftPane() {
// Here we set up a full redux store with initial state for our LeftPane Root
const convoCollection = getConversations();
const conversations = convoCollection.map(
conversation => conversation.cachedProps
);
const filledConversations = conversations.map(async conv => {
const messages = await window.getMessagesByKey(conv.id);
return { ...conv, messages };
});
const fullFilledConversations = await Promise.all(filledConversations);
const initialState = {
conversations: {
conversationLookup: Signal.Util.makeLookup(
fullFilledConversations,
'id'
),
},
user: {
regionCode: window.storage.get('regionCode'),
ourNumber:
window.storage.get('primaryDevicePubKey') ||
textsecure.storage.user.getNumber(),
isSecondaryDevice: !!window.storage.get('isSecondaryDevice'),
i18n: window.i18n,
},
section: {
focusedSection: 1,
},
};
this.store = Signal.State.createStore(initialState);
window.inboxStore = this.store;
this.leftPaneView = new Whisper.ReactWrapperView({
JSX: Signal.State.Roots.createLeftPane(this.store),
className: 'left-pane-wrapper',
});
// Enables our redux store to be updated by backbone events in the outside world
const {
conversationAdded,
conversationChanged,
conversationRemoved,
removeAllConversations,
messageExpired,
openConversationExternal,
} = Signal.State.bindActionCreators(
Signal.State.Ducks.conversations.actions,
this.store.dispatch
);
const { userChanged } = Signal.State.bindActionCreators(
Signal.State.Ducks.user.actions,
this.store.dispatch
);
const { messageChanged } = Signal.State.bindActionCreators(
Signal.State.Ducks.messages.actions,
this.store.dispatch
);
this.openConversationAction = openConversationExternal;
this.fetchHandleMessageSentData = this.fetchHandleMessageSentData.bind(
this
);
this.handleMessageSentFailure = this.handleMessageSentFailure.bind(this);
this.handleMessageSentSuccess = this.handleMessageSentSuccess.bind(this);
this.listenTo(convoCollection, 'remove', conversation => {
const { id } = conversation || {};
conversationRemoved(id);
});
this.listenTo(convoCollection, 'add', conversation => {
const { id, cachedProps } = conversation || {};
conversationAdded(id, cachedProps);
});
this.listenTo(convoCollection, 'change', conversation => {
const { id, cachedProps } = conversation || {};
conversationChanged(id, cachedProps);
});
this.listenTo(convoCollection, 'reset', removeAllConversations);
window.libsession
.getMessageQueue()
.events.addListener('success', this.handleMessageSentSuccess);
window.libsession
.getMessageQueue()
.events.addListener('fail', this.handleMessageSentFailure);
Whisper.events.on('messageExpired', messageExpired);
Whisper.events.on('messageChanged', messageChanged);
Whisper.events.on('userChanged', userChanged);
// Finally, add it to the DOM
this.$('.left-pane-placeholder').append(this.leftPaneView.el);
},
async fetchHandleMessageSentData(m) {
// nobody is listening to this freshly fetched message .trigger calls
const tmpMsg = await window.Signal.Data.getMessageById(m.identifier, {
Message: Whisper.Message,
});
if (!tmpMsg) {
return null;
}
// find the corresponding conversation of this message
const conv = window.ConversationController.get(
tmpMsg.get('conversationId')
);
if (!conv) {
return null;
}
// then, find in this conversation the very same message
// const msg = conv.messageCollection.models.find(
// convMsg => convMsg.id === tmpMsg.id
// );
const msg = window.MessageController._get()[m.identifier];
if (!msg || !msg.message) {
return null;
}
return { msg: msg.message };
},
async handleMessageSentSuccess(sentMessage, wrappedEnvelope) {
const fetchedData = await this.fetchHandleMessageSentData(sentMessage);
if (!fetchedData) {
return;
}
const { msg } = fetchedData;
msg.handleMessageSentSuccess(sentMessage, wrappedEnvelope);
},
async handleMessageSentFailure(sentMessage, error) {
const fetchedData = await this.fetchHandleMessageSentData(sentMessage);
if (!fetchedData) {
return;
}
const { msg } = fetchedData;
await msg.handleMessageSentFailure(sentMessage, error);
},
startConnectionListener() { startConnectionListener() {
this.interval = setInterval(() => { this.interval = setInterval(() => {
@ -271,10 +103,6 @@
view.remove(); view.remove();
} }
}, },
onProgress() {},
reloadBackgroundPage() {
window.location.reload();
},
async openConversation(id, messageId) { async openConversation(id, messageId) {
// If we call this to create a new conversation, it can only be private // If we call this to create a new conversation, it can only be private
// (group conversations are created elsewhere) // (group conversations are created elsewhere)

@ -1,26 +0,0 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.SessionConversationView = Whisper.View.extend({
initialize(options) {
this.props = {
...options,
};
},
render() {
this.conversationView = new Whisper.ReactWrapperView({
className: 'session-conversation-wrapper',
Component: window.Signal.Components.SessionConversation,
props: this.props,
});
this.$el.prepend(this.conversationView.el);
},
});
})();

@ -0,0 +1,28 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.InboxView = Whisper.View.extend({
initialize() {
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'inbox index',
Component: window.Signal.Components.SessionInboxView,
});
this.$el.append(this.dialogView.el);
return this;
},
close() {
this.remove();
},
});
})();

@ -1,28 +0,0 @@
/* global i18n, Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.SessionSettingsView = Whisper.View.extend({
initialize() {
this.render();
},
render() {
this.settingsView = new Whisper.ReactWrapperView({
className: 'session-settings',
Component: window.Signal.Components.SettingsView,
props: {
i18n,
},
});
this.$el.append(this.settingsView.el);
},
close() {
this.remove();
},
});
})();

@ -1,10 +1,12 @@
.new-conversation, .inbox {
.inbox,
.gutter {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
.inbox.index {
display: flex;
}
.edit-profile-dialog, .edit-profile-dialog,
.create-group-dialog, .create-group-dialog,
.user-details-dialog { .user-details-dialog {
@ -74,13 +76,6 @@
} }
.gutter { .gutter {
display: flex;
flex-direction: column;
float: left;
width: 300px;
user-select: none;
position: relative;
.tool-bar { .tool-bar {
margin-top: 8px; margin-top: 8px;
color: $color-dark-05; color: $color-dark-05;
@ -197,7 +192,9 @@ h4.section-toggle,
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;
} }
.conversation.placeholder {
height: 100vh;
}
.left-pane-wrapper { .left-pane-wrapper {
flex: 1; flex: 1;
} }

@ -168,7 +168,6 @@ $session-search-input-height: 34px;
$main-view-header-height: 63px; $main-view-header-height: 63px;
$session-conversation-header-height: 60px; $session-conversation-header-height: 60px;
$session-left-pane-width: 300px; $session-left-pane-width: 300px;
$session-left-pane-sections-container-width: 80px;
// Various Components // Various Components
$session-icon-size-sm: 15px; $session-icon-size-sm: 15px;

@ -112,11 +112,10 @@
} }
} }
.session-conversation-wrapper { .session-conversation {
position: absolute; flex-grow: 1;
width: 100%; display: flex;
height: 100%; flex-direction: column;
background-color: $session-shade-2;
} }
.conversation-content { .conversation-content {

@ -2,7 +2,6 @@ $session-compose-margin: 20px;
.gutter { .gutter {
width: 380px !important; width: 380px !important;
padding-inline-end: 5px !important;
transition: $session-transition-duration; transition: $session-transition-duration;
@include themify($themes) { @include themify($themes) {
@ -98,7 +97,8 @@ $session-compose-margin: 20px;
.module-left-pane { .module-left-pane {
width: $session-left-pane-width; width: $session-left-pane-width;
position: relative; position: relative;
height: -webkit-fill-available; height: 100vh;
flex-shrink: 0;
&-session { &-session {
display: flex; display: flex;
@ -106,8 +106,8 @@ $session-compose-margin: 20px;
} }
&__sections-container { &__sections-container {
height: -webkit-fill-available; height: 100vh;
width: $session-left-pane-sections-container-width; flex-shrink: 0;
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
overflow: auto; overflow: auto;

@ -24,22 +24,6 @@
</div> </div>
</script> </script>
<script type="text/x-tmpl-mustache" id="two-column">
<div class="gutter">
<div class="network-status-container"></div>
<div class="left-pane-placeholder"></div>
</div>
<div class="conversation placeholder">
<div class="conversation-header"></div>
<div class="container">
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
</div>
</script>
<script type="text/x-tmpl-mustache" id="banner"> <script type="text/x-tmpl-mustache" id="banner">
<div class="body"> <div class="body">
<span class="icon warning"></span> <span class="icon warning"></span>
@ -254,6 +238,7 @@
<script type="text/javascript" src="../js/views/nickname_dialog_view.js"></script> <script type="text/javascript" src="../js/views/nickname_dialog_view.js"></script>
<script type="text/javascript" src="../js/views/password_dialog_view.js"></script> <script type="text/javascript" src="../js/views/password_dialog_view.js"></script>
<script type="text/javascript" src="../js/views/seed_dialog_view.js"></script> <script type="text/javascript" src="../js/views/seed_dialog_view.js"></script>
<script type='text/javascript' src='../js/views/session_inbox_viw.js'></script>
<script type="text/javascript" src="../js/views/identicon_svg_view.js"></script> <script type="text/javascript" src="../js/views/identicon_svg_view.js"></script>
<script type="text/javascript" src="../js/views/banner_view.js"></script> <script type="text/javascript" src="../js/views/banner_view.js"></script>
<script type="text/javascript" src="../js/views/session_registration_view.js"></script> <script type="text/javascript" src="../js/views/session_registration_view.js"></script>

@ -14,6 +14,7 @@ import { LeftPaneSettingSection } from './session/LeftPaneSettingSection';
import { SessionIconType } from './session/icon'; import { SessionIconType } from './session/icon';
import { SessionTheme } from '../state/ducks/SessionTheme'; import { SessionTheme } from '../state/ducks/SessionTheme';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
import { SessionSettingCategory } from './session/settings/SessionSettings';
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 // from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
export type RowRendererParamsType = { export type RowRendererParamsType = {
@ -36,7 +37,10 @@ interface Props {
focusedSection: SectionType; focusedSection: SectionType;
focusSection: (section: SectionType) => void; focusSection: (section: SectionType) => void;
openConversationInternal: (id: string, messageId?: string) => void; openConversationExternal: (id: string, messageId?: string) => void;
showSessionSettingsCategory: (category: SessionSettingCategory) => void;
showSessionViewConversation: () => void;
settingsCategory?: SessionSettingCategory;
updateSearchTerm: (searchTerm: string) => void; updateSearchTerm: (searchTerm: string) => void;
search: (query: string, options: SearchOptions) => void; search: (query: string, options: SearchOptions) => void;
clearSearch: () => void; clearSearch: () => void;
@ -75,6 +79,11 @@ export class LeftPane extends React.Component<Props> {
public handleSectionSelected(section: SectionType) { public handleSectionSelected(section: SectionType) {
this.props.clearSearch(); this.props.clearSearch();
this.props.focusSection(section); this.props.focusSection(section);
if (section === SectionType.Settings) {
this.props.showSessionSettingsCategory(SessionSettingCategory.Appearance);
} else {
this.props.showSessionViewConversation();
}
} }
public render(): JSX.Element { public render(): JSX.Element {
@ -108,7 +117,7 @@ export class LeftPane extends React.Component<Props> {
private renderMessageSection() { private renderMessageSection() {
const { const {
openConversationInternal, openConversationExternal,
conversations, conversations,
contacts, contacts,
searchResults, searchResults,
@ -129,7 +138,7 @@ export class LeftPane extends React.Component<Props> {
return ( return (
<LeftPaneMessageSection <LeftPaneMessageSection
contacts={contacts} contacts={contacts}
openConversationInternal={openConversationInternal} openConversationExternal={openConversationExternal}
conversations={filteredConversations} conversations={filteredConversations}
searchResults={searchResults} searchResults={searchResults}
searchTerm={searchTerm} searchTerm={searchTerm}
@ -142,13 +151,13 @@ export class LeftPane extends React.Component<Props> {
} }
private renderContactSection() { private renderContactSection() {
const { openConversationInternal } = this.props; const { openConversationExternal } = this.props;
const directContacts = this.getDirectContactsOnly(); const directContacts = this.getDirectContactsOnly();
return ( return (
<LeftPaneContactSection <LeftPaneContactSection
openConversationInternal={openConversationInternal} openConversationExternal={openConversationExternal}
directContacts={directContacts} directContacts={directContacts}
/> />
); );
@ -159,8 +168,16 @@ export class LeftPane extends React.Component<Props> {
} }
private renderSettingSection() { private renderSettingSection() {
const { isSecondaryDevice } = this.props; const { isSecondaryDevice, showSessionSettingsCategory, settingsCategory } = this.props;
const category = settingsCategory || SessionSettingCategory.Appearance;
return <LeftPaneSettingSection isSecondaryDevice={isSecondaryDevice} />; return (
<LeftPaneSettingSection
isSecondaryDevice={isSecondaryDevice}
showSessionSettingsCategory={showSessionSettingsCategory}
settingsCategory={category}
/>
);
} }
} }

@ -1,19 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import {
SessionSettingCategory,
SettingsView,
} from './session/settings/SessionSettings';
import { createLegacyGroup, createMediumGroup } from '../session/medium_group'; import { createLegacyGroup, createMediumGroup } from '../session/medium_group';
export const MainViewController = {
createClosedGroup,
renderMessageView,
renderSettingsView,
};
import { ContactType } from './session/SessionMemberListItem'; import { ContactType } from './session/SessionMemberListItem';
import { ToastUtils } from '../session/utils'; import { ToastUtils } from '../session/utils';
@ -98,28 +85,6 @@ async function createClosedGroup(
return true; return true;
} }
// ///////////////////////////////////// export const MainViewController = {
// ///////////// Rendering ///////////// createClosedGroup,
// ///////////////////////////////////// };
function renderMessageView() {
if (document.getElementById('main-view')) {
ReactDOM.render(<MessageView />, document.getElementById('main-view'));
}
}
function renderSettingsView(category: SessionSettingCategory) {
// tslint:disable-next-line: no-backbone-get-set-outside-model
const isSecondaryDevice = !!window.textsecure.storage.get(
'isSecondaryDevice'
);
if (document.getElementById('main-view')) {
ReactDOM.render(
<SettingsView
category={category}
isSecondaryDevice={isSecondaryDevice}
/>,
document.getElementById('main-view')
);
}
}

@ -14,15 +14,12 @@ import {
SessionClosableOverlay, SessionClosableOverlay,
SessionClosableOverlayType, SessionClosableOverlayType,
} from './SessionClosableOverlay'; } from './SessionClosableOverlay';
import { MainViewController } from '../MainViewController';
import { ToastUtils } from '../../session/utils'; import { ToastUtils } from '../../session/utils';
import { toast } from 'react-toastify';
import { SessionToast } from './SessionToast';
export interface Props { export interface Props {
directContacts: Array<ConversationType>; directContacts: Array<ConversationType>;
openConversationInternal: (id: string, messageId?: string) => void; openConversationExternal: (id: string, messageId?: string) => void;
} }
interface State { interface State {
@ -49,15 +46,9 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
} }
public componentDidMount() { public componentDidMount() {
MainViewController.renderMessageView();
window.Whisper.events.on('calculatingPoW', this.closeOverlay); window.Whisper.events.on('calculatingPoW', this.closeOverlay);
} }
public componentDidUpdate() {
MainViewController.renderMessageView();
}
public componentWillUnmount() { public componentWillUnmount() {
this.setState({ addContactRecipientID: '' }); this.setState({ addContactRecipientID: '' });
window.Whisper.events.off('calculatingPoW', this.closeOverlay); window.Whisper.events.off('calculatingPoW', this.closeOverlay);
@ -101,7 +92,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
style={style} style={style}
{...item} {...item}
i18n={window.i18n} i18n={window.i18n}
onClick={this.props.openConversationInternal} onClick={this.props.openConversationExternal}
/> />
); );
}; };

@ -44,7 +44,7 @@ export interface Props {
updateSearchTerm: (searchTerm: string) => void; updateSearchTerm: (searchTerm: string) => void;
search: (query: string, options: SearchOptions) => void; search: (query: string, options: SearchOptions) => void;
openConversationInternal: (id: string, messageId?: string) => void; openConversationExternal: (id: string, messageId?: string) => void;
clearSearch: () => void; clearSearch: () => void;
} }
@ -99,14 +99,9 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
} }
public componentDidMount() { public componentDidMount() {
MainViewController.renderMessageView();
window.Whisper.events.on('calculatingPoW', this.closeOverlay); window.Whisper.events.on('calculatingPoW', this.closeOverlay);
} }
public componentDidUpdate() {
MainViewController.renderMessageView();
}
public componentWillUnmount() { public componentWillUnmount() {
this.updateSearch(''); this.updateSearch('');
window.Whisper.events.off('calculatingPoW', this.closeOverlay); window.Whisper.events.off('calculatingPoW', this.closeOverlay);
@ -117,7 +112,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
key, key,
style, style,
}: RowRendererParamsType): JSX.Element => { }: RowRendererParamsType): JSX.Element => {
const { conversations, openConversationInternal } = this.props; const { conversations, openConversationExternal } = this.props;
if (!conversations) { if (!conversations) {
throw new Error('renderRow: Tried to render without conversations'); throw new Error('renderRow: Tried to render without conversations');
@ -130,7 +125,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
key={key} key={key}
style={style} style={style}
{...conversation} {...conversation}
onClick={openConversationInternal} onClick={openConversationExternal}
i18n={window.i18n} i18n={window.i18n}
/> />
); );
@ -139,7 +134,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
public renderList(): JSX.Element | Array<JSX.Element | null> { public renderList(): JSX.Element | Array<JSX.Element | null> {
const { const {
conversations, conversations,
openConversationInternal, openConversationExternal,
searchResults, searchResults,
} = this.props; } = this.props;
const contacts = searchResults?.contacts || []; const contacts = searchResults?.contacts || [];
@ -149,7 +144,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
<SearchResults <SearchResults
{...searchResults} {...searchResults}
contacts={contacts} contacts={contacts}
openConversation={openConversationInternal} openConversation={openConversationExternal}
i18n={window.i18n} i18n={window.i18n}
/> />
); );
@ -384,7 +379,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
} }
private handleMessageButtonClick() { private handleMessageButtonClick() {
const { openConversationInternal } = this.props; const { openConversationExternal } = this.props;
if (!this.state.valuePasted && !this.props.searchTerm) { if (!this.state.valuePasted && !this.props.searchTerm) {
ToastUtils.pushToastError( ToastUtils.pushToastError(
@ -399,7 +394,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
const error = validateNumber(pubkey); const error = validateNumber(pubkey);
if (!error) { if (!error) {
openConversationInternal(pubkey); openConversationExternal(pubkey);
} else { } else {
ToastUtils.pushToastError('invalidPubKey', error); ToastUtils.pushToastError('invalidPubKey', error);
} }

@ -3,8 +3,6 @@ import classNames from 'classnames';
import { LeftPane } from '../LeftPane'; import { LeftPane } from '../LeftPane';
import { MainViewController } from '../MainViewController';
import { import {
SessionButton, SessionButton,
SessionButtonColor, SessionButtonColor,
@ -17,10 +15,11 @@ import { SessionSettingCategory } from './settings/SessionSettings';
interface Props { interface Props {
isSecondaryDevice: boolean; isSecondaryDevice: boolean;
settingsCategory: SessionSettingCategory;
showSessionSettingsCategory: (category: SessionSettingCategory) => void;
} }
export interface State { export interface State {
settingCategory: SessionSettingCategory;
searchQuery: string; searchQuery: string;
} }
@ -29,7 +28,6 @@ export class LeftPaneSettingSection extends React.Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
settingCategory: SessionSettingCategory.Appearance,
searchQuery: '', searchQuery: '',
}; };
@ -37,14 +35,6 @@ export class LeftPaneSettingSection extends React.Component<Props, State> {
this.onDeleteAccount = this.onDeleteAccount.bind(this); this.onDeleteAccount = this.onDeleteAccount.bind(this);
} }
public componentDidMount() {
MainViewController.renderSettingsView(this.state.settingCategory);
}
public componentDidUpdate() {
MainViewController.renderSettingsView(this.state.settingCategory);
}
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div className="left-pane-setting-section"> <div className="left-pane-setting-section">
@ -68,12 +58,13 @@ export class LeftPaneSettingSection extends React.Component<Props, State> {
} }
public renderRow(item: any): JSX.Element { public renderRow(item: any): JSX.Element {
const {settingsCategory} = this.props;
return ( return (
<div <div
key={item.id} key={item.id}
className={classNames( className={classNames(
'left-pane-setting-category-list-item', 'left-pane-setting-category-list-item',
item.id === this.state.settingCategory ? 'active' : '' item.id === settingsCategory ? 'active' : ''
)} )}
role="link" role="link"
onClick={() => { onClick={() => {
@ -85,7 +76,7 @@ export class LeftPaneSettingSection extends React.Component<Props, State> {
</div> </div>
<div> <div>
{item.id === this.state.settingCategory && ( {item.id === settingsCategory && (
<SessionIcon <SessionIcon
iconSize={SessionIconSize.Medium} iconSize={SessionIconSize.Medium}
iconType={SessionIconType.Chevron} iconType={SessionIconType.Chevron}
@ -236,8 +227,6 @@ export class LeftPaneSettingSection extends React.Component<Props, State> {
} }
public setCategory(category: SessionSettingCategory) { public setCategory(category: SessionSettingCategory) {
this.setState({ this.props.showSessionSettingsCategory(category);
settingCategory: category,
});
} }
} }

@ -0,0 +1,273 @@
import React from 'react';
import { Provider } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getMessageQueue } from '../../session';
import { createStore } from '../../state/createStore';
import { StateType } from '../../state/reducer';
import { SmartLeftPane } from '../../state/smart/LeftPane';
import { SmartSessionConversation } from '../../state/smart/SessionConversation';
import { SessionSettingCategory, SettingsView } from './settings/SessionSettings';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
const FilteredLeftPane = SmartLeftPane as any;
const FilteredSessionConversation = SmartSessionConversation as any;
type Props = {
focusedSection: number;
};
type State = {
isInitialLoadComplete: boolean;
settingsCategory?: SessionSettingCategory;
};
// tslint:disable: react-a11y-img-has-alt
export class SessionInboxView extends React.Component<Props, State> {
private store: any;
constructor(props: any) {
super(props);
this.state = {
isInitialLoadComplete: false,
settingsCategory: undefined,
};
// Inbox
const inboxCollection = window.getInboxCollection();
this.fetchHandleMessageSentData = this.fetchHandleMessageSentData.bind(
this
);
this.handleMessageSentFailure = this.handleMessageSentFailure.bind(this);
this.handleMessageSentSuccess = this.handleMessageSentSuccess.bind(this);
this.showSessionSettingsCategory = this.showSessionSettingsCategory.bind(this);
this.showSessionViewConversation = this.showSessionViewConversation.bind(this);
void this.setupLeftPane();
// ConversationCollection
// this.listenTo(inboxCollection, 'messageError', () => {
// if (this.networkStatusView) {
// this.networkStatusView.render();
// }
// });
// this.networkStatusView = new Whisper.NetworkStatusView();
// this.$el
// .find('.network-status-container')
// .append(this.networkStatusView.render().el);
// extension.expired(expired => {
// if (expired) {
// const banner = new Whisper.ExpiredAlertBanner().render();
// banner.$el.prependTo(this.$el);
// this.$el.addClass('expired');
// }
// });
}
public render() {
if (!this.state.isInitialLoadComplete) {
return <></>;
}
const isSettingsView = this.state.settingsCategory !== undefined;
return (
<Provider store={this.store}>
<div className="gutter">
<div className="network-status-container"/>
{this.renderLeftPane()}
</div>
{isSettingsView ? this.renderSettings() : this.renderSessionConversation()}
</Provider>
); }
private renderLeftPane() {
return (
<FilteredLeftPane
showSessionSettingsCategory={this.showSessionSettingsCategory}
showSessionViewConversation={this.showSessionViewConversation}
settingsCategory={this.state.settingsCategory}
/>
);
}
private renderSettings() {
const isSecondaryDevice = !!window.textsecure.storage.get('isSecondaryDevice');
const category = this.state.settingsCategory || SessionSettingCategory.Appearance;
return (
<SettingsView
isSecondaryDevice={isSecondaryDevice}
category={category}
/>
);
}
private renderSessionConversation() {
return (
<div className="session-conversation">
<FilteredSessionConversation />
</div>
);
}
private async fetchHandleMessageSentData(m: any) {
// nobody is listening to this freshly fetched message .trigger calls
const tmpMsg = await window.Signal.Data.getMessageById(m.identifier, {
Message: window.Whisper.Message,
});
if (!tmpMsg) {
return null;
}
// find the corresponding conversation of this message
const conv = window.ConversationController.get(
tmpMsg.get('conversationId')
);
if (!conv) {
return null;
}
// then, find in this conversation the very same message
// const msg = conv.messageCollection.models.find(
// convMsg => convMsg.id === tmpMsg.id
// );
const msg = window.MessageController._get()[m.identifier];
if (!msg || !msg.message) {
return null;
}
return { msg: msg.message };
}
private async handleMessageSentSuccess(sentMessage: any, wrappedEnvelope: any) {
const fetchedData = await this.fetchHandleMessageSentData(sentMessage);
if (!fetchedData) {
return;
}
const { msg } = fetchedData;
msg.handleMessageSentSuccess(sentMessage, wrappedEnvelope);
}
private async handleMessageSentFailure(sentMessage: any, error: any) {
const fetchedData = await this.fetchHandleMessageSentData(sentMessage);
if (!fetchedData) {
return;
}
const { msg } = fetchedData;
await msg.handleMessageSentFailure(sentMessage, error);
}
private async setupLeftPane() {
// Here we set up a full redux store with initial state for our LeftPane Root
const convoCollection = window.getConversations();
const conversations = convoCollection.map(
(conversation: any) => conversation.cachedProps
);
const filledConversations = conversations.map(async (conv: any) => {
const messages = await window.getMessagesByKey(conv.id);
return { ...conv, messages };
});
const fullFilledConversations = await Promise.all(filledConversations);
const initialState = {
conversations: {
conversationLookup: window.Signal.Util.makeLookup(
fullFilledConversations,
'id'
),
},
user: {
regionCode: window.storage.get('regionCode'),
ourNumber:
window.storage.get('primaryDevicePubKey') ||
window.textsecure.storage.user.getNumber(),
isSecondaryDevice: !!window.storage.get('isSecondaryDevice'),
i18n: window.i18n,
},
section: {
focusedSection: 1,
},
};
this.store = createStore(initialState);
window.inboxStore = this.store;
// Enables our redux store to be updated by backbone events in the outside world
const {
conversationAdded,
conversationChanged,
conversationRemoved,
removeAllConversations,
messageExpired,
openConversationExternal,
} = bindActionCreators(
window.Signal.State.Ducks.conversations.actions,
this.store.dispatch
);
const { userChanged } = bindActionCreators(
window.Signal.State.Ducks.user.actions,
this.store.dispatch
);
const { messageChanged } = bindActionCreators(
window.Signal.State.Ducks.messages.actions,
this.store.dispatch
);
// this.openConversationAction = openConversationExternal;
this.fetchHandleMessageSentData = this.fetchHandleMessageSentData.bind(
this
);
this.handleMessageSentFailure = this.handleMessageSentFailure.bind(this);
this.handleMessageSentSuccess = this.handleMessageSentSuccess.bind(this);
// this.listenTo(convoCollection, 'remove', conversation => {
// const { id } = conversation || {};
// conversationRemoved(id);
// });
// this.listenTo(convoCollection, 'add', conversation => {
// const { id, cachedProps } = conversation || {};
// conversationAdded(id, cachedProps);
// });
// this.listenTo(convoCollection, 'change', conversation => {
// const { id, cachedProps } = conversation || {};
// conversationChanged(id, cachedProps);
// });
// this.listenTo(convoCollection, 'reset', removeAllConversations);
getMessageQueue()
.events.addListener('success', this.handleMessageSentSuccess);
getMessageQueue()
.events.addListener('fail', this.handleMessageSentFailure);
window.Whisper.events.on('messageExpired', messageExpired);
window.Whisper.events.on('messageChanged', messageChanged);
window.Whisper.events.on('userChanged', userChanged);
// Finally, add it to the DOM
// this.$('.left-pane-placeholder').append(this.leftPaneView.el);
this.setState({ isInitialLoadComplete: true });
}
private showSessionSettingsCategory(category: SessionSettingCategory) {
this.setState({ settingsCategory: category });
}
private showSessionViewConversation() {
this.setState({ settingsCategory: undefined });
}
}

@ -83,11 +83,11 @@ export class SessionConversation extends React.Component<Props, State> {
const { conversationKey } = this.props; const { conversationKey } = this.props;
const conversationModel = window.ConversationController.getOrThrow( const conversationModel = window.ConversationController.get(
conversationKey conversationKey
); );
const unreadCount = conversationModel.get('unreadCount'); const unreadCount = conversationModel?.get('unreadCount') || 0;
this.state = { this.state = {
messageProgressVisible: false, messageProgressVisible: false,
sendingProgress: 0, sendingProgress: 0,
@ -151,8 +151,11 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~~~ LIFECYCLES ~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~ LIFECYCLES ~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public async componentWillMount() {
await this.loadInitialMessages(); public componentDidUpdate(prevProps: Props, prevState: State) {
if (this.props.conversationKey !== prevProps.conversationKey) {
void this.loadInitialMessages();
}
} }
public componentWillUnmount() { public componentWillUnmount() {
@ -304,15 +307,17 @@ export class SessionConversation extends React.Component<Props, State> {
public async loadInitialMessages() { public async loadInitialMessages() {
const { conversationKey } = this.props; const { conversationKey } = this.props;
const conversationModel = window.ConversationController.getOrThrow( const conversationModel = window.ConversationController.get(
conversationKey conversationKey
); );
if (!conversationModel) {
return;
}
const unreadCount = await conversationModel.getUnreadCount(); const unreadCount = await conversationModel.getUnreadCount();
const messagesToFetch = Math.max( const messagesToFetch = Math.max(
Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT, Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT,
unreadCount unreadCount
); );
this.props.actions.fetchMessagesForConversation({ this.props.actions.fetchMessagesForConversation({
conversationKey, conversationKey,
count: messagesToFetch, count: messagesToFetch,

@ -10,8 +10,6 @@ import {
import { BlockedNumberController, UserUtil } from '../../../util'; import { BlockedNumberController, UserUtil } from '../../../util';
import { MultiDeviceProtocol } from '../../../session/protocols'; import { MultiDeviceProtocol } from '../../../session/protocols';
import { PubKey } from '../../../session/types'; import { PubKey } from '../../../session/types';
import { SessionToast, SessionToastType } from '../SessionToast';
import { toast } from 'react-toastify';
import { ToastUtils } from '../../../session/utils'; import { ToastUtils } from '../../../session/utils';
export enum SessionSettingCategory { export enum SessionSettingCategory {

@ -1,7 +1,6 @@
import { omit } from 'lodash'; import { omit } from 'lodash';
import { trigger } from '../../shims/events'; import { trigger } from '../../shims/events';
import { NoopActionType } from './noop';
// State // State
@ -127,7 +126,6 @@ export const actions = {
conversationRemoved, conversationRemoved,
removeAllConversations, removeAllConversations,
messageExpired, messageExpired,
openConversationInternal,
openConversationExternal, openConversationExternal,
}; };
@ -183,20 +181,6 @@ function messageExpired(
}; };
} }
// Note: we need two actions here to simplify. Operations outside of the left pane can
// trigger an 'openConversation' so we go through Whisper.events for all conversation
// selection.
function openConversationInternal(
id: string,
messageId?: string
): NoopActionType {
trigger('showConversation', id, messageId);
return {
type: 'NOOP',
payload: null,
};
}
function openConversationExternal( function openConversationExternal(
id: string, id: string,
messageId?: string messageId?: string
@ -286,6 +270,9 @@ export function reducer(
if (action.type === 'SELECTED_CONVERSATION_CHANGED') { if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
const { payload } = action; const { payload } = action;
const { id } = payload; const { id } = payload;
if (state.selectedConversation !== id) {
window.owsDesktopApp.appView.openConversation(id, {});
}
return { return {
...state, ...state,

@ -1,4 +0,0 @@
export type NoopActionType = {
type: 'NOOP';
payload: null;
};

@ -1,16 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import { SmartLeftPane } from '../smart/LeftPane';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
const FilteredLeftPane = SmartLeftPane as any;
export const createLeftPane = (store: Store) => (
<Provider store={store as any}>
<FilteredLeftPane />
</Provider>
);

@ -1,16 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import { SmartSessionConversation } from '../smart/SessionConversation';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
const FilteredSessionConversation = SmartSessionConversation as any;
export const createSessionConversation = (store: Store) => (
<Provider store={store as any}>
<FilteredSessionConversation />
</Provider>
);

6
ts/window.d.ts vendored

@ -16,6 +16,7 @@ import {} from 'styled-components/cssprop';
import { ConversationControllerType } from '../js/ConversationController'; import { ConversationControllerType } from '../js/ConversationController';
import { any } from 'underscore'; import { any } from 'underscore';
import { Store } from 'redux';
/* /*
We declare window stuff here instead of global.d.ts because we are importing other declarations. We declare window stuff here instead of global.d.ts because we are importing other declarations.
If you import anything in global.d.ts, the type system won't work correctly. If you import anything in global.d.ts, the type system won't work correctly.
@ -95,7 +96,7 @@ declare global {
versionInfo: any; versionInfo: any;
getStoragePubKey: any; getStoragePubKey: any;
pubkeyPattern: any; pubkeyPattern: any;
getConversations: any; getConversations: () => ConversationCollection;
getGuid: any; getGuid: any;
ContactBuffer: any; ContactBuffer: any;
GroupBuffer: any; GroupBuffer: any;
@ -111,5 +112,8 @@ declare global {
) => Promise<{ pubKey: ArrayBufferLike; privKey: ArrayBufferLike }>; ) => Promise<{ pubKey: ArrayBufferLike; privKey: ArrayBufferLike }>;
setClockParams: any; setClockParams: any;
clientClockSynced: number | undefined; clientClockSynced: number | undefined;
getInboxCollection: any;
getMessagesByKey: any;
inboxStore: Store;
} }
} }

Loading…
Cancel
Save