Merge pull request #645 from msgmaxim/group-invites
Public chat invitations
This commit is contained in:
commit
2ab0d084f1
|
@ -2229,5 +2229,14 @@
|
|||
},
|
||||
"groupNamePlaceholder": {
|
||||
"message": "Group Name"
|
||||
},
|
||||
"inviteFriends": {
|
||||
"message": "Invite Friends"
|
||||
},
|
||||
"groupInvitation": {
|
||||
"message": "Group Invitation"
|
||||
},
|
||||
"addingFriends": {
|
||||
"message": "Adding friends to"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -820,6 +820,7 @@
|
|||
<script type='text/javascript' src='js/views/device_pairing_words_dialog_view.js'></script>
|
||||
<script type='text/javascript' src='js/views/create_group_dialog_view.js'></script>
|
||||
<script type='text/javascript' src='js/views/edit_profile_dialog_view.js'></script>
|
||||
<script type='text/javascript' src='js/views/invite_friends_dialog_view.js'></script>
|
||||
|
||||
<script type='text/javascript' src='js/wall_clock_listener.js'></script>
|
||||
<script type='text/javascript' src='js/rotate_signed_prekey_listener.js'></script>
|
||||
|
|
|
@ -799,6 +799,23 @@
|
|||
appView.openConversation(groupId, {});
|
||||
};
|
||||
|
||||
window.sendGroupInvitations = (serverInfo, pubkeys) => {
|
||||
pubkeys.forEach(async pubkey => {
|
||||
const convo = await ConversationController.getOrCreateAndWait(
|
||||
pubkey,
|
||||
'private'
|
||||
);
|
||||
|
||||
if (convo) {
|
||||
convo.sendMessage('', null, null, null, {
|
||||
serverName: serverInfo.name,
|
||||
channelId: serverInfo.channelId,
|
||||
serverAddress: serverInfo.address,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Whisper.events.on('createNewGroup', async () => {
|
||||
if (appView) {
|
||||
appView.showCreateGroup();
|
||||
|
@ -811,6 +828,52 @@
|
|||
}
|
||||
});
|
||||
|
||||
Whisper.events.on('inviteFriends', async groupConvo => {
|
||||
if (appView) {
|
||||
appView.showInviteFriendsDialog(groupConvo);
|
||||
}
|
||||
});
|
||||
|
||||
Whisper.events.on(
|
||||
'publicChatInvitationAccepted',
|
||||
async (serverAddress, channelId) => {
|
||||
// To some degree this has been copy-pasted
|
||||
// form connection_to_server_dialog_view.js:
|
||||
const rawServerUrl = serverAddress
|
||||
.replace(/^https?:\/\//i, '')
|
||||
.replace(/[/\\]+$/i, '');
|
||||
const sslServerUrl = `https://${rawServerUrl}`;
|
||||
const conversationId = `publicChat:${channelId}@${rawServerUrl}`;
|
||||
|
||||
const conversationExists = ConversationController.get(conversationId);
|
||||
if (conversationExists) {
|
||||
window.log.warn('We are already a member of this public chat');
|
||||
return;
|
||||
}
|
||||
|
||||
const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(
|
||||
sslServerUrl
|
||||
);
|
||||
if (!serverAPI) {
|
||||
window.log.warn(`Could not connect to ${serverAddress}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const conversation = await ConversationController.getOrCreateAndWait(
|
||||
conversationId,
|
||||
'group'
|
||||
);
|
||||
|
||||
serverAPI.findOrCreateChannel(channelId, conversationId);
|
||||
await conversation.setPublicSource(sslServerUrl, channelId);
|
||||
await conversation.setFriendRequestStatus(
|
||||
window.friends.friendRequestStatusEnum.friends
|
||||
);
|
||||
|
||||
appView.openConversation(conversationId, {});
|
||||
}
|
||||
);
|
||||
|
||||
Whisper.events.on('leaveGroup', async groupConvo => {
|
||||
if (appView) {
|
||||
appView.showLeaveGroupDialog(groupConvo);
|
||||
|
|
|
@ -1418,7 +1418,13 @@
|
|||
};
|
||||
},
|
||||
|
||||
async sendMessage(body, attachments, quote, preview) {
|
||||
async sendMessage(
|
||||
body,
|
||||
attachments,
|
||||
quote,
|
||||
preview,
|
||||
groupInvitation = null
|
||||
) {
|
||||
this.clearTypingTimers();
|
||||
|
||||
const destination = this.id;
|
||||
|
@ -1510,6 +1516,7 @@
|
|||
}
|
||||
const attributes = {
|
||||
...messageWithSchema,
|
||||
groupInvitation,
|
||||
id: window.getGuid(),
|
||||
};
|
||||
|
||||
|
@ -1589,6 +1596,8 @@
|
|||
options.publicSendData = await this.getPublicSendData();
|
||||
}
|
||||
|
||||
options.groupInvitation = groupInvitation;
|
||||
|
||||
const groupNumbers = this.getRecipients();
|
||||
|
||||
const promise = (() => {
|
||||
|
|
|
@ -104,6 +104,8 @@
|
|||
this.propsForGroupNotification = this.getPropsForGroupNotification();
|
||||
} else if (this.isFriendRequest()) {
|
||||
this.propsForFriendRequest = this.getPropsForFriendRequest();
|
||||
} else if (this.isGroupInvitation()) {
|
||||
this.propsForGroupInvitation = this.getPropsForGroupInvitation();
|
||||
} else {
|
||||
this.propsForSearchResult = this.getPropsForSearchResult();
|
||||
this.propsForMessage = this.getPropsForMessage();
|
||||
|
@ -251,6 +253,9 @@
|
|||
if (this.isIncoming() && this.hasErrors()) {
|
||||
return i18n('incomingError');
|
||||
}
|
||||
if (this.isGroupInvitation()) {
|
||||
return `<${i18n('groupInvitation')}>`;
|
||||
}
|
||||
return this.get('body');
|
||||
},
|
||||
isVerifiedChange() {
|
||||
|
@ -262,6 +267,9 @@
|
|||
isFriendRequest() {
|
||||
return this.get('type') === 'friend-request';
|
||||
},
|
||||
isGroupInvitation() {
|
||||
return !!this.get('groupInvitation');
|
||||
},
|
||||
getNotificationText() {
|
||||
const description = this.getDescription();
|
||||
if (description) {
|
||||
|
@ -439,6 +447,27 @@
|
|||
onRetrySend,
|
||||
};
|
||||
},
|
||||
getPropsForGroupInvitation() {
|
||||
const invitation = this.get('groupInvitation');
|
||||
|
||||
let direction = this.get('direction');
|
||||
if (!direction) {
|
||||
direction = this.get('type') === 'outgoing' ? 'outgoing' : 'incoming';
|
||||
}
|
||||
|
||||
return {
|
||||
serverName: invitation.serverName,
|
||||
serverAddress: invitation.serverAddress,
|
||||
direction,
|
||||
onClick: () => {
|
||||
Whisper.events.trigger(
|
||||
'publicChatInvitationAccepted',
|
||||
invitation.serverAddress,
|
||||
invitation.channelId
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
findContact(phoneNumber) {
|
||||
return ConversationController.get(phoneNumber);
|
||||
},
|
||||
|
@ -1920,6 +1949,7 @@
|
|||
window.log.info(
|
||||
`Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversation.idForLogging()}`
|
||||
);
|
||||
|
||||
const withQuoteReference = await this.copyFromQuotedMessage(
|
||||
initialMessage
|
||||
);
|
||||
|
@ -2002,6 +2032,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (initialMessage.groupInvitation) {
|
||||
message.set({ groupInvitation: initialMessage.groupInvitation });
|
||||
}
|
||||
|
||||
const urls = window.Signal.LinkPreviews.findLinks(dataMessage.body);
|
||||
const incomingPreview = dataMessage.preview || [];
|
||||
const preview = incomingPreview.filter(
|
||||
|
@ -2227,15 +2261,6 @@
|
|||
} else if (message.get('type') !== 'outgoing') {
|
||||
// Ignore 'outgoing' messages because they are sync messages
|
||||
await sendingDeviceConversation.onFriendRequestAccepted();
|
||||
// We need to return for these types of messages because android struggles
|
||||
if (
|
||||
!message.get('body') &&
|
||||
!message.get('attachments').length &&
|
||||
!message.get('preview').length &&
|
||||
!message.get('group_update')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const id = await window.Signal.Data.saveMessage(message.attributes, {
|
||||
Message: Whisper.Message,
|
||||
|
|
|
@ -52,6 +52,12 @@ const { EditProfileDialog } = require('../../ts/components/EditProfileDialog');
|
|||
const {
|
||||
UpdateGroupDialog,
|
||||
} = require('../../ts/components/conversation/UpdateGroupDialog');
|
||||
const {
|
||||
InviteFriendsDialog,
|
||||
} = require('../../ts/components/conversation/InviteFriendsDialog');
|
||||
const {
|
||||
GroupInvitation,
|
||||
} = require('../../ts/components/conversation/GroupInvitation');
|
||||
const { ConfirmDialog } = require('../../ts/components/ConfirmDialog');
|
||||
const {
|
||||
MediaGallery,
|
||||
|
@ -232,6 +238,8 @@ exports.setup = (options = {}) => {
|
|||
EditProfileDialog,
|
||||
ConfirmDialog,
|
||||
UpdateGroupDialog,
|
||||
InviteFriendsDialog,
|
||||
GroupInvitation,
|
||||
BulkEdit,
|
||||
MediaGallery,
|
||||
Message,
|
||||
|
|
|
@ -254,5 +254,9 @@
|
|||
const dialog = new Whisper.LeaveGroupDialogView(groupConvo);
|
||||
this.el.append(dialog.el);
|
||||
},
|
||||
showInviteFriendsDialog(groupConvo) {
|
||||
const dialog = new Whisper.InviteFriendsDialogView(groupConvo);
|
||||
this.el.append(dialog.el);
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -286,6 +286,10 @@
|
|||
onLeaveGroup: () => {
|
||||
window.Whisper.events.trigger('leaveGroup', this.model);
|
||||
},
|
||||
|
||||
onInviteFriends: () => {
|
||||
window.Whisper.events.trigger('inviteFriends', this.model);
|
||||
},
|
||||
};
|
||||
};
|
||||
this.titleView = new Whisper.ReactWrapperView({
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/* global Whisper */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
||||
Whisper.InviteFriendsDialogView = Whisper.View.extend({
|
||||
className: 'loki-dialog modal',
|
||||
initialize(convo) {
|
||||
this.close = this.close.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
|
||||
const convos = window.getConversations().models;
|
||||
|
||||
const friends = convos.filter(
|
||||
d => !!d && d.isFriend() && d.isPrivate() && !d.isMe()
|
||||
);
|
||||
|
||||
this.friends = friends;
|
||||
this.chatName = convo.get('name');
|
||||
this.chatServer = convo.get('server');
|
||||
this.channelId = convo.get('channelId');
|
||||
|
||||
this.$el.focus();
|
||||
this.render();
|
||||
},
|
||||
render() {
|
||||
const view = new Whisper.ReactWrapperView({
|
||||
className: 'invite-friends-dialog',
|
||||
Component: window.Signal.Components.InviteFriendsDialog,
|
||||
props: {
|
||||
friendList: this.friends,
|
||||
onSubmit: this.submit,
|
||||
onClose: this.close,
|
||||
chatName: this.chatName,
|
||||
},
|
||||
});
|
||||
|
||||
this.$el.append(view.el);
|
||||
return this;
|
||||
},
|
||||
close() {
|
||||
this.remove();
|
||||
},
|
||||
submit(pubkeys) {
|
||||
window.sendGroupInvitations(
|
||||
{
|
||||
address: this.chatServer,
|
||||
name: this.chatName,
|
||||
channelId: this.channelId,
|
||||
},
|
||||
pubkeys
|
||||
);
|
||||
},
|
||||
});
|
||||
})();
|
|
@ -74,6 +74,11 @@
|
|||
Component: Components.FriendRequest,
|
||||
props: this.model.propsForFriendRequest,
|
||||
};
|
||||
} else if (this.model.propsForGroupInvitation) {
|
||||
return {
|
||||
Component: Components.GroupInvitation,
|
||||
props: this.model.propsForGroupInvitation,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -27,6 +27,7 @@ function Message(options) {
|
|||
this.expireTimer = options.expireTimer;
|
||||
this.profileKey = options.profileKey;
|
||||
this.profile = options.profile;
|
||||
this.groupInvitation = options.groupInvitation;
|
||||
|
||||
if (!(this.recipients instanceof Array)) {
|
||||
throw new Error('Invalid recipient list');
|
||||
|
@ -160,6 +161,16 @@ Message.prototype = {
|
|||
proto.profile = profile;
|
||||
}
|
||||
|
||||
if (this.groupInvitation) {
|
||||
proto.groupInvitation = new textsecure.protobuf.DataMessage.GroupInvitation(
|
||||
{
|
||||
serverAddress: this.groupInvitation.serverAddress,
|
||||
channelId: this.groupInvitation.channelId,
|
||||
serverName: this.groupInvitation.serverName,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.dataMessage = proto;
|
||||
return proto;
|
||||
},
|
||||
|
@ -404,7 +415,7 @@ MessageSender.prototype = {
|
|||
);
|
||||
|
||||
numbers.forEach(number => {
|
||||
// Note: if we are sending a private group message, we make our best to
|
||||
// Note: if we are sending a private group message, we do our best to
|
||||
// ensure we have signal protocol sessions with every member, but if we
|
||||
// fail, let's at least send messages to those members with which we do:
|
||||
const haveSession = _.some(
|
||||
|
@ -941,6 +952,8 @@ MessageSender.prototype = {
|
|||
? textsecure.protobuf.DataMessage.Flags.BACKGROUND_FRIEND_REQUEST
|
||||
: undefined;
|
||||
|
||||
const { groupInvitation } = options;
|
||||
|
||||
return this.sendMessage(
|
||||
{
|
||||
recipients: [number],
|
||||
|
@ -954,6 +967,7 @@ MessageSender.prototype = {
|
|||
profileKey,
|
||||
profile,
|
||||
flags,
|
||||
groupInvitation,
|
||||
},
|
||||
options
|
||||
);
|
||||
|
|
|
@ -201,6 +201,12 @@ message DataMessage {
|
|||
optional string avatar = 2;
|
||||
}
|
||||
|
||||
message GroupInvitation {
|
||||
optional string serverAddress = 1;
|
||||
optional uint32 channelId = 2;
|
||||
optional string serverName = 3;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
optional GroupContext group = 3;
|
||||
|
@ -212,6 +218,7 @@ message DataMessage {
|
|||
repeated Contact contact = 9;
|
||||
repeated Preview preview = 10;
|
||||
optional LokiProfile profile = 101; // Loki: The profile of the current user
|
||||
optional GroupInvitation groupInvitation = 102; // Loki: Invitation to a public chat
|
||||
}
|
||||
|
||||
message NullMessage {
|
||||
|
|
|
@ -204,6 +204,80 @@
|
|||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.group-invitation-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.group-invitation {
|
||||
background-color: #f4f4f0;
|
||||
display: inline-block;
|
||||
margin: 4px 16px;
|
||||
padding: 4px;
|
||||
|
||||
border: solid;
|
||||
border-width: 0.5px;
|
||||
border-radius: 4px;
|
||||
border-color: #e0e0e0;
|
||||
|
||||
align-self: flex-start;
|
||||
|
||||
box-shadow: 2px 2px lightgrey;
|
||||
|
||||
.title {
|
||||
margin: 6px;
|
||||
color: darkslategray;
|
||||
font-variant-caps: all-small-caps;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.contents {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 6px;
|
||||
|
||||
.invite-group-avatar {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.group-details {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding: 8px;
|
||||
|
||||
.group-name {
|
||||
font-weight: lighter;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.group-address {
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
|
||||
.join-btn {
|
||||
background-color: #e0e0e0;
|
||||
padding: 6px 10px;
|
||||
margin-left: 6px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 2px 2px 1px #c0c0c0;
|
||||
color: #404040;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #c7c7c7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.invitation-outgoing {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.message-selected {
|
||||
background-color: #60554060;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.invite-friends-dialog,
|
||||
.create-group-dialog {
|
||||
.content {
|
||||
max-width: 100% !important;
|
||||
|
@ -46,7 +47,9 @@
|
|||
font-size: large;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.create-group-dialog {
|
||||
.no-friends {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -124,7 +127,8 @@
|
|||
}
|
||||
|
||||
.member-list-container,
|
||||
.create-group-dialog {
|
||||
.create-group-dialog,
|
||||
.invite-friends-dialog {
|
||||
.member-item {
|
||||
padding: 4px;
|
||||
user-select: none;
|
||||
|
@ -176,7 +180,8 @@
|
|||
|
||||
.dark-theme {
|
||||
.member-list-container,
|
||||
.create-group-dialog {
|
||||
.create-group-dialog,
|
||||
.invite-friends-dialog {
|
||||
.member-item {
|
||||
&:hover:not(.member-selected) {
|
||||
background-color: $color-dark-55;
|
||||
|
|
|
@ -576,7 +576,7 @@
|
|||
<script type='text/javascript' src='../js/views/banner_view.js' data-cover></script>
|
||||
<script type='text/javascript' src='../js/views/clear_data_view.js'></script>
|
||||
<script type='text/javascript' src='../js/views/create_group_dialog_view.js'></script>
|
||||
<script type='text/javascript' src='../js/views/edit_profile_dialog_view.js'></script>
|
||||
<script type='text/javascript' src='../js/views/invite_friends_dialog_view.js'></script>
|
||||
<script type='text/javascript' src='../js/views/beta_release_disclaimer_view.js'></script>
|
||||
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ export type PropsData = {
|
|||
hasNickname?: boolean;
|
||||
isFriendItem?: boolean;
|
||||
isSecondary?: boolean;
|
||||
isGroupInvitation?: boolean;
|
||||
};
|
||||
|
||||
type PropsHousekeeping = {
|
||||
|
|
|
@ -67,6 +67,8 @@ interface Props {
|
|||
onUpdateGroup: () => void;
|
||||
onLeaveGroup: () => void;
|
||||
|
||||
onInviteFriends: () => void;
|
||||
|
||||
i18n: LocalizerType;
|
||||
}
|
||||
|
||||
|
@ -230,6 +232,7 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
onCopyPublicKey,
|
||||
onUpdateGroup,
|
||||
onLeaveGroup,
|
||||
onInviteFriends,
|
||||
} = this.props;
|
||||
|
||||
const isPrivateGroup = isGroup && !isPublic;
|
||||
|
@ -248,6 +251,9 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
<MenuItem onClick={onLeaveGroup}>{i18n('leaveGroup')}</MenuItem>
|
||||
) : null}
|
||||
{/* TODO: add delete group */}
|
||||
{isGroup && isPublic ? (
|
||||
<MenuItem onClick={onInviteFriends}>{i18n('inviteFriends')}</MenuItem>
|
||||
) : null}
|
||||
{!isMe && isClosable && !isPrivateGroup ? (
|
||||
!isPublic ? (
|
||||
<MenuItem onClick={onDeleteContact}>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
serverName: string;
|
||||
serverAddress: string;
|
||||
direction: string;
|
||||
onClick: any;
|
||||
}
|
||||
|
||||
export class GroupInvitation extends React.Component<Props> {
|
||||
public render() {
|
||||
const classes = ['group-invitation'];
|
||||
|
||||
if (this.props.direction === 'outgoing') {
|
||||
classes.push('invitation-outgoing');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'group-invitation-container'}>
|
||||
<div className={classNames(classes)}>
|
||||
<div className="title">Group invitation</div>
|
||||
<div className="contents">
|
||||
<img
|
||||
alt="group-avatar"
|
||||
src="images/loki/loki_icon.png"
|
||||
className="invite-group-avatar"
|
||||
/>
|
||||
<span className="group-details">
|
||||
<span className="group-name">{this.props.serverName}</span>
|
||||
<span className="group-address">{this.props.serverAddress}</span>
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
className="join-btn"
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
Join
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
import React from 'react';
|
||||
import { Contact, MemberList } from './MemberList';
|
||||
|
||||
interface Props {
|
||||
friendList: Array<any>;
|
||||
chatName: string;
|
||||
onSubmit: any;
|
||||
onClose: any;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
i18n: any;
|
||||
}
|
||||
}
|
||||
|
||||
interface State {
|
||||
friendList: Array<Contact>;
|
||||
}
|
||||
|
||||
export class InviteFriendsDialog extends React.Component<Props, State> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.onMemberClicked = this.onMemberClicked.bind(this);
|
||||
this.closeDialog = this.closeDialog.bind(this);
|
||||
this.onClickOK = this.onClickOK.bind(this);
|
||||
this.onKeyUp = this.onKeyUp.bind(this);
|
||||
|
||||
let friends = this.props.friendList;
|
||||
friends = friends.map(d => {
|
||||
const lokiProfile = d.getLokiProfile();
|
||||
const name = lokiProfile ? lokiProfile.displayName : 'Anonymous';
|
||||
|
||||
// TODO: should take existing members into account
|
||||
const existingMember = false;
|
||||
|
||||
return {
|
||||
id: d.id,
|
||||
authorPhoneNumber: d.id,
|
||||
authorProfileName: name,
|
||||
selected: false,
|
||||
authorName: name,
|
||||
authorColor: d.getColor(),
|
||||
checkmarked: false,
|
||||
existingMember,
|
||||
};
|
||||
});
|
||||
|
||||
this.state = {
|
||||
friendList: friends,
|
||||
};
|
||||
|
||||
window.addEventListener('keyup', this.onKeyUp);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const titleText = `${window.i18n('addingFriends')} ${this.props.chatName}`;
|
||||
const cancelText = window.i18n('cancel');
|
||||
const okText = window.i18n('ok');
|
||||
|
||||
return (
|
||||
<div className="content">
|
||||
<p className="titleText">{titleText}</p>
|
||||
<div className="friend-selection-list">
|
||||
<MemberList
|
||||
members={this.state.friendList}
|
||||
selected={{}}
|
||||
i18n={window.i18n}
|
||||
onMemberClicked={this.onMemberClicked}
|
||||
/>
|
||||
</div>
|
||||
<div className="buttons">
|
||||
<button className="cancel" tabIndex={0} onClick={this.closeDialog}>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button className="ok" tabIndex={0} onClick={this.onClickOK}>
|
||||
{okText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onClickOK() {
|
||||
const selectedFriends = this.state.friendList
|
||||
.filter(d => d.checkmarked)
|
||||
.map(d => d.id);
|
||||
|
||||
if (selectedFriends.length > 0) {
|
||||
this.props.onSubmit(selectedFriends);
|
||||
}
|
||||
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private onKeyUp(event: any) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
this.onClickOK();
|
||||
break;
|
||||
case 'Esc':
|
||||
case 'Escape':
|
||||
this.closeDialog();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
private closeDialog() {
|
||||
window.removeEventListener('keyup', this.onKeyUp);
|
||||
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
private onMemberClicked(selected: any) {
|
||||
const updatedFriends = this.state.friendList.map(member => {
|
||||
if (member.id === selected.id) {
|
||||
return { ...member, checkmarked: !member.checkmarked };
|
||||
} else {
|
||||
return member;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState(state => {
|
||||
return {
|
||||
...state,
|
||||
friendList: updatedFriends,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue