From 6d5aed7de8cdb59db850e6bfc344c03604970a7a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 11:27:37 +1100 Subject: [PATCH 1/8] make upload of group picture work --- _locales/en/messages.json | 2 +- js/background.js | 48 ++++++++- js/modules/loki_app_dot_net_api.js | 34 +++++- js/views/conversation_view.js | 3 + js/views/create_group_dialog_view.js | 102 +++++++----------- js/views/invite_friends_dialog_view.js | 5 +- stylesheets/_index.scss | 1 + .../conversation/UpdateGroupMembersDialog.tsx | 12 --- .../conversation/UpdateGroupNameDialog.tsx | 86 +++++++++++++-- .../session/LeftPaneChannelSection.tsx | 24 +++-- .../session/SessionGroupSettings.tsx | 41 +++---- 11 files changed, 243 insertions(+), 115 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5ee80af32..03ff620bb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2205,7 +2205,7 @@ "description": "Button action that the user can click to edit their profile" }, "editGroupName": { - "message": "Edit group name", + "message": "Edit group name or picture", "description": "Button action that the user can click to edit a group name" }, "createGroupDialogTitle": { diff --git a/js/background.js b/js/background.js index 859909b61..5478fbffc 100644 --- a/js/background.js +++ b/js/background.js @@ -702,7 +702,7 @@ } }); - window.doUpdateGroup = async (groupId, groupName, members) => { + window.doUpdateGroup = async (groupId, groupName, members, avatar) => { const ourKey = textsecure.storage.user.getNumber(); const ev = new Event('message'); @@ -729,6 +729,44 @@ if (convo.isPublic()) { const API = await convo.getPublicSendData(); + + if (avatar) { + // I hate duplicating this... + const readFile = attachment => + new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = e => { + const data = e.target.result; + resolve({ + ...attachment, + data, + size: data.byteLength, + }); + }; + fileReader.onerror = reject; + fileReader.onabort = reject; + fileReader.readAsArrayBuffer(attachment.file); + }); + const attachment = await readFile({ file: avatar }); + // const tempUrl = window.URL.createObjectURL(avatar); + + // Get file onto public chat server + const fileObj = await API.serverAPI.putAttachment(attachment.data); + if (fileObj === null) { + // problem + log.warn('File upload failed'); + return; + } + + // lets not allow ANY URLs, lets force it to be local to public chat server + const relativeFileUrl = fileObj.url.replace( + API.serverAPI.baseServerUrl, + '' + ); + // write it to the channel + const changeRes = await API.setChannelAvatar(relativeFileUrl); + } + if (await API.setChannelName(groupName)) { // queue update from server // and let that set the conversation @@ -741,7 +779,11 @@ return; } - const avatar = ''; + const nullAvatar = ''; + if (avatar) { + // would get to download this file on each client in the group + // and reference the local file + } const options = {}; const recipients = _.union(convo.get('members'), members); @@ -750,7 +792,7 @@ convo.updateGroup({ groupId, groupName, - avatar, + nullAvatar, recipients, members, options, diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index c9664abef..ddb201663 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -877,6 +877,7 @@ class LokiAppDotNetServerAPI { }; } + // for avatar async uploadData(data) { const endpoint = 'files'; const options = { @@ -901,6 +902,7 @@ class LokiAppDotNetServerAPI { }; } + // for files putAttachment(attachmentBin) { const formData = new FormData(); const buffer = Buffer.from(attachmentBin); @@ -1246,7 +1248,37 @@ class LokiPublicChannelAPI { this.conversation.setGroupName(note.value.name); } if (note.value && note.value.avatar) { - this.conversation.setProfileAvatar(note.value.avatar); + const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar; + console.log('setting', avatarAbsUrl); + const { + upgradeMessageSchema, + writeNewAttachmentData, + deleteAttachmentData, + } = window.Signal.Migrations; + // do we already have this image? no, then + + // download a copy and save it + const imageData = await nodeFetch(avatarAbsUrl); + function toArrayBuffer(buf) { + var ab = new ArrayBuffer(buf.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buf.length; ++i) { + view[i] = buf[i]; + } + return ab; + } + const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( + this.conversation.attributes, + toArrayBuffer(imageData), + { + writeNewAttachmentData, + deleteAttachmentData, + } + ); + console.log('newAttributes.avatar', newAttributes.avatar); + // update group + this.conversation.set(newAttributes); + //this.conversation.setProfileAvatar(newAttributes.avatar); } // is it mutable? // who are the moderators? diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 0550fa63e..9f28d86c7 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -287,6 +287,9 @@ isAdmin: this.model.get('groupAdmins').includes(ourPK), isRss: this.model.isRss(), memberCount: members.length, + amMod: this.model.isModerator( + window.storage.get('primaryDevicePubKey') + ), timerOptions: Whisper.ExpirationTimerOptions.map(item => ({ name: item.getName(), diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index befbf3b3b..193fd1ed0 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -59,31 +59,12 @@ this.close = this.close.bind(this); this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); + this.groupId = groupConvo.id; const ourPK = textsecure.storage.user.getNumber(); this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); - const convos = window.getConversations().models.filter(d => !!d); - - let existingMembers = groupConvo.get('members') || []; - - // Show a contact if they are our friend or if they are a member - const friendsAndMembers = convos.filter( - d => - (d.isFriend() || existingMembers.includes(d.id)) && - d.isPrivate() && - !d.isMe() - ); - this.friendsAndMembers = _.uniq(friendsAndMembers, true, d => d.id); - - // at least make sure it's an array - if (!Array.isArray(existingMembers)) { - existingMembers = []; - } - - this.existingMembers = existingMembers; - // public chat settings overrides if (this.isPublic) { // fix the title @@ -98,6 +79,24 @@ // zero out friendList for now this.friendsAndMembers = []; this.existingMembers = []; + } else { + const convos = window.getConversations().models.filter(d => !!d); + + this.existingMembers = groupConvo.get('members') || []; + // Show a contact if they are our friend or if they are a member + this.friendsAndMembers = convos.filter( + d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() + ); + this.friendsAndMembers = _.uniq( + this.friendsAndMembers, + true, + d => d.id + ); + + // at least make sure it's an array + if (!Array.isArray(this.existingMembers)) { + this.existingMembers = []; + } } this.$el.focus(); @@ -109,24 +108,22 @@ Component: window.Signal.Components.UpdateGroupNameDialog, props: { titleText: this.titleText, - groupName: this.groupName, - okText: this.okText, isPublic: this.isPublic, - cancelText: this.cancelText, - existingMembers: this.existingMembers, + groupName: this.groupName, + okText: i18n('ok'), + cancelText: i18n('cancel'), isAdmin: this.isAdmin, - onClose: this.close, + i18n, onSubmit: this.onSubmit, + onClose: this.close, }, }); this.$el.append(this.dialogView.el); return this; }, - onSubmit(newGroupName, members) { - const groupId = this.conversation.get('id'); - - window.doUpdateGroup(groupId, newGroupName, members); + onSubmit(groupName, avatar) { + window.doUpdateGroup(this.groupId, groupName, this.members, avatar); }, close() { this.remove(); @@ -136,40 +133,16 @@ Whisper.UpdateGroupMembersDialogView = Whisper.View.extend({ className: 'loki-dialog modal', initialize(groupConvo) { + const ourPK = textsecure.storage.user.getNumber(); this.groupName = groupConvo.get('name'); - - this.conversation = groupConvo; - this.titleText = i18n('updateGroupDialogTitle'); - this.okText = i18n('ok'); - this.cancelText = i18n('cancel'); this.close = this.close.bind(this); this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); + this.groupId = groupConvo.id; + this.avatarPath = groupConvo.getAvatarPath(); + this.members = groupConvo.get('members') || []; - const ourPK = textsecure.storage.user.getNumber(); - - this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); - - const convos = window.getConversations().models.filter(d => !!d); - - let existingMembers = groupConvo.get('members') || []; - - // Show a contact if they are our friend or if they are a member - const friendsAndMembers = convos.filter( - d => existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() - ); - this.friendsAndMembers = _.uniq(friendsAndMembers, true, d => d.id); - - // at least make sure it's an array - if (!Array.isArray(existingMembers)) { - existingMembers = []; - } - - this.existingMembers = existingMembers; - - // public chat settings overrides if (this.isPublic) { - // fix the title this.titleText = `${i18n('updatePublicGroupDialogTitle')}: ${ this.groupName }`; @@ -178,9 +151,9 @@ this.isAdmin = groupConvo.isModerator( window.storage.get('primaryDevicePubKey') ); - // zero out friendList for now - this.friendsAndMembers = []; - this.existingMembers = []; + } else { + this.titleText = i18n('updateGroupDialogTitle'); + this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); } this.$el.focus(); @@ -201,6 +174,7 @@ isAdmin: this.isAdmin, onClose: this.close, onSubmit: this.onSubmit, + groupId: this.groupId, }, }); @@ -210,9 +184,13 @@ onSubmit(groupName, newMembers) { const ourPK = textsecure.storage.user.getNumber(); const allMembers = window.Lodash.concat(newMembers, [ourPK]); - const groupId = this.conversation.get('id'); - window.doUpdateGroup(groupId, groupName, allMembers); + window.doUpdateGroup( + this.groupId, + groupName, + allMembers, + this.avatarPath + ); }, close() { this.remove(); diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js index 1cc7c0ec0..ddf8d5745 100644 --- a/js/views/invite_friends_dialog_view.js +++ b/js/views/invite_friends_dialog_view.js @@ -74,7 +74,10 @@ newMembers.length + existingMembers.length > window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { - const msg = window.i18n('maxGroupMembersError', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT); + const msg = window.i18n( + 'maxGroupMembersError', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ); window.pushToast({ title: msg, diff --git a/stylesheets/_index.scss b/stylesheets/_index.scss index f5156f5dc..672319a6a 100644 --- a/stylesheets/_index.scss +++ b/stylesheets/_index.scss @@ -11,6 +11,7 @@ } .edit-profile-dialog, +.create-group-dialog, .user-details-dialog { .content { max-width: 100% !important; diff --git a/ts/components/conversation/UpdateGroupMembersDialog.tsx b/ts/components/conversation/UpdateGroupMembersDialog.tsx index 27de62c49..7c3f77775 100644 --- a/ts/components/conversation/UpdateGroupMembersDialog.tsx +++ b/ts/components/conversation/UpdateGroupMembersDialog.tsx @@ -34,7 +34,6 @@ export class UpdateGroupMembersDialog extends React.Component { this.onClickOK = this.onClickOK.bind(this); this.onKeyUp = this.onKeyUp.bind(this); this.closeDialog = this.closeDialog.bind(this); - this.onGroupNameChanged = this.onGroupNameChanged.bind(this); let friends = this.props.friendList; friends = friends.map(d => { @@ -209,15 +208,4 @@ export class UpdateGroupMembersDialog extends React.Component { }; }); } - - private onGroupNameChanged(event: any) { - event.persist(); - - this.setState(state => { - return { - ...state, - groupName: event.target.value, - }; - }); - } } diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index ba44a2497..bc4a1a95d 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -3,9 +3,11 @@ import classNames from 'classnames'; import { SessionModal } from '../session/SessionModal'; import { SessionButton } from '../session/SessionButton'; +import { Avatar } from '../Avatar'; interface Props { titleText: string; + isPublic: boolean; groupName: string; okText: string; cancelText: string; @@ -13,30 +15,35 @@ interface Props { i18n: any; onSubmit: any; onClose: any; - existingMembers: Array; + // avatar stuff + avatarPath: string; } interface State { groupName: string; errorDisplayed: boolean; errorMessage: string; + avatar: string; } export class UpdateGroupNameDialog extends React.Component { + private readonly inputEl: any; + constructor(props: any) { super(props); this.onClickOK = this.onClickOK.bind(this); this.onKeyUp = this.onKeyUp.bind(this); this.closeDialog = this.closeDialog.bind(this); - this.onGroupNameChanged = this.onGroupNameChanged.bind(this); + this.onFileSelected = this.onFileSelected.bind(this); this.state = { groupName: this.props.groupName, errorDisplayed: false, errorMessage: 'placeholder', + avatar: this.props.avatarPath, }; - + this.inputEl = React.createRef(); window.addEventListener('keyup', this.onKeyUp); } @@ -47,18 +54,30 @@ export class UpdateGroupNameDialog extends React.Component { return; } - this.props.onSubmit(this.state.groupName, this.props.existingMembers); + const avatar = + this.inputEl && + this.inputEl.current && + this.inputEl.current.files && + this.inputEl.current.files.length > 0 + ? this.inputEl.current.files[0] + : this.props.avatarPath; // otherwise use the current avatar + + this.props.onSubmit(this.props.groupName, avatar); this.closeDialog(); } public render() { - const okText = this.props.okText; - const cancelText = this.props.cancelText; + const { isPublic, okText, cancelText } = this.props; - let titleText; + const titleText = `${this.props.titleText}`; + let noAvatarClasses; - titleText = `${this.props.titleText}`; + if (isPublic) { + noAvatarClasses = classNames('avatar-center'); + } else { + noAvatarClasses = classNames('hidden'); + } const errorMsg = this.state.errorMessage; const errorMessageClasses = classNames( @@ -77,6 +96,33 @@ export class UpdateGroupNameDialog extends React.Component {

{errorMsg}

+
+
+ {this.renderAvatar()} +
+ +
{ + const el = this.inputEl.current; + if (el) { + el.click(); + } + }} + /> +
+
+
+
+ { }; }); } + + private renderAvatar() { + const avatarPath = this.state.avatar; + const color = '#00ff00'; + + return ( + + ); + } + + private onFileSelected() { + const file = this.inputEl.current.files[0]; + const url = window.URL.createObjectURL(file); + + this.setState({ + avatar: url, + }); + } } diff --git a/ts/components/session/LeftPaneChannelSection.tsx b/ts/components/session/LeftPaneChannelSection.tsx index 569d9bec1..fd543ebef 100644 --- a/ts/components/session/LeftPaneChannelSection.tsx +++ b/ts/components/session/LeftPaneChannelSection.tsx @@ -399,13 +399,18 @@ export class LeftPaneChannelSection extends React.Component { groupMembers: Array ) { // Validate groupName and groupMembers length - if (groupName.length === 0 || - groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH) { - window.pushToast({ - title: window.i18n('invalidGroupName', window.CONSTANTS.MAX_GROUP_NAME_LENGTH), - type: 'error', - id: 'invalidGroupName', - }); + if ( + groupName.length === 0 || + groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ) { + window.pushToast({ + title: window.i18n( + 'invalidGroupName', + window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ), + type: 'error', + id: 'invalidGroupName', + }); return; } @@ -416,7 +421,10 @@ export class LeftPaneChannelSection extends React.Component { groupMembers.length >= window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { window.pushToast({ - title: window.i18n('invalidGroupSize', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT), + title: window.i18n( + 'invalidGroupSize', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ), type: 'error', id: 'invalidGroupSize', }); diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index 8f831c577..cff0c7245 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -20,6 +20,7 @@ interface Props { timerOptions: Array; isPublic: boolean; isAdmin: boolean; + amMod: boolean; onGoBack: () => void; onInviteFriends: () => void; @@ -211,6 +212,7 @@ export class SessionGroupSettings extends React.Component { onLeaveGroup, isPublic, isAdmin, + amMod, } = this.props; const { documents, media, onItemClick } = this.state; const showMemberCount = !!(memberCount && memberCount > 0); @@ -228,6 +230,9 @@ export class SessionGroupSettings extends React.Component { }; }); + const showUpdateGroupNameButton = isPublic ? amMod : isAdmin; + const showUpdateGroupMembersButton = !isPublic && isAdmin; + return (
{this.renderHeader()} @@ -245,25 +250,23 @@ export class SessionGroupSettings extends React.Component { className="description" placeholder={window.i18n('description')} /> - {!isPublic && ( - <> - {isAdmin && ( -
- {window.i18n('editGroupName')} -
- )} -
- {window.i18n('showMembers')} -
- + {showUpdateGroupNameButton && ( +
+ {window.i18n('editGroupName')} +
+ )} + {showUpdateGroupMembersButton && ( +
+ {window.i18n('showMembers')} +
)} {/*
{window.i18n('notifications')} From 60ed8f297236f24693716207c3589dca462ac83b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 13:41:08 +1100 Subject: [PATCH 2/8] make download of group avatar work --- js/modules/loki_app_dot_net_api.js | 14 ++++++++------ .../conversation/UpdateGroupNameDialog.tsx | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index ddb201663..320e16192 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1249,9 +1249,7 @@ class LokiPublicChannelAPI { } if (note.value && note.value.avatar) { const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar; - console.log('setting', avatarAbsUrl); const { - upgradeMessageSchema, writeNewAttachmentData, deleteAttachmentData, } = window.Signal.Migrations; @@ -1267,18 +1265,22 @@ class LokiPublicChannelAPI { } return ab; } + const buffer = await imageData.buffer(); const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( this.conversation.attributes, - toArrayBuffer(imageData), + toArrayBuffer(buffer), { writeNewAttachmentData, deleteAttachmentData, } ); - console.log('newAttributes.avatar', newAttributes.avatar); // update group - this.conversation.set(newAttributes); - //this.conversation.setProfileAvatar(newAttributes.avatar); + this.conversation.set('avatar', newAttributes.avatar); + + await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { + Conversation: Whisper.Conversation, + }); + this.conversation.trigger('change'); } // is it mutable? // who are the moderators? diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index bc4a1a95d..7e923a848 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -36,6 +36,7 @@ export class UpdateGroupNameDialog extends React.Component { this.onKeyUp = this.onKeyUp.bind(this); this.closeDialog = this.closeDialog.bind(this); this.onFileSelected = this.onFileSelected.bind(this); + this.onGroupNameChanged = this.onGroupNameChanged.bind(this); this.state = { groupName: this.props.groupName, From b756332f89a76fdc7b7e7149c636406e5c5967a1 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 14:00:20 +1100 Subject: [PATCH 3/8] fix group member dialog and refresh right after group avatar update --- js/modules/loki_app_dot_net_api.js | 11 +++---- js/views/create_group_dialog_view.js | 43 ++++++++++++++-------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 320e16192..6a92618fe 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1276,11 +1276,6 @@ class LokiPublicChannelAPI { ); // update group this.conversation.set('avatar', newAttributes.avatar); - - await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { - Conversation: Whisper.Conversation, - }); - this.conversation.trigger('change'); } // is it mutable? // who are the moderators? @@ -1290,6 +1285,12 @@ class LokiPublicChannelAPI { if (data.counts && Number.isInteger(data.counts.subscribers)) { this.conversation.setSubscriberCount(data.counts.subscribers); } + + await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { + Conversation: Whisper.Conversation, + }); + await this.pollForChannelOnce(); + this.conversation.trigger('change'); } // get moderation actions diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 193fd1ed0..639711ee3 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -60,6 +60,7 @@ this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); this.groupId = groupConvo.id; + this.members = groupConvo.get('members') || []; const ourPK = textsecure.storage.user.getNumber(); @@ -76,27 +77,6 @@ this.isAdmin = groupConvo.isModerator( window.storage.get('primaryDevicePubKey') ); - // zero out friendList for now - this.friendsAndMembers = []; - this.existingMembers = []; - } else { - const convos = window.getConversations().models.filter(d => !!d); - - this.existingMembers = groupConvo.get('members') || []; - // Show a contact if they are our friend or if they are a member - this.friendsAndMembers = convos.filter( - d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() - ); - this.friendsAndMembers = _.uniq( - this.friendsAndMembers, - true, - d => d.id - ); - - // at least make sure it's an array - if (!Array.isArray(this.existingMembers)) { - this.existingMembers = []; - } } this.$el.focus(); @@ -140,7 +120,6 @@ this.isPublic = groupConvo.isPublic(); this.groupId = groupConvo.id; this.avatarPath = groupConvo.getAvatarPath(); - this.members = groupConvo.get('members') || []; if (this.isPublic) { this.titleText = `${i18n('updatePublicGroupDialogTitle')}: ${ @@ -151,9 +130,29 @@ this.isAdmin = groupConvo.isModerator( window.storage.get('primaryDevicePubKey') ); + // zero out friendList for now + this.friendsAndMembers = []; + this.existingMembers = []; } else { this.titleText = i18n('updateGroupDialogTitle'); this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); + const convos = window.getConversations().models.filter(d => !!d); + + this.existingMembers = groupConvo.get('members') || []; + // Show a contact if they are our friend or if they are a member + this.friendsAndMembers = convos.filter( + d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() + ); + this.friendsAndMembers = _.uniq( + this.friendsAndMembers, + true, + d => d.id + ); + + // at least make sure it's an array + if (!Array.isArray(this.existingMembers)) { + this.existingMembers = []; + } } this.$el.focus(); From 1428cfe1dd11151592e7bfbad8202f23dfeac21e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 15:17:20 +1100 Subject: [PATCH 4/8] fix download profile image open groups --- js/modules/loki_app_dot_net_api.js | 11 +++++++---- js/views/create_group_dialog_view.js | 4 ++-- .../conversation/UpdateGroupNameDialog.tsx | 15 +++++---------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 6a92618fe..75b2ebb43 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1257,14 +1257,18 @@ class LokiPublicChannelAPI { // download a copy and save it const imageData = await nodeFetch(avatarAbsUrl); + // eslint-disable-next-line no-inner-declarations function toArrayBuffer(buf) { - var ab = new ArrayBuffer(buf.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buf.length; ++i) { + const ab = new ArrayBuffer(buf.length); + const view = new Uint8Array(ab); + // eslint-disable-next-line no-plusplus + for (let i = 0; i < buf.length; i++) { view[i] = buf[i]; } return ab; } + // eslint-enable-next-line no-inner-declarations + const buffer = await imageData.buffer(); const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( this.conversation.attributes, @@ -1290,7 +1294,6 @@ class LokiPublicChannelAPI { Conversation: Whisper.Conversation, }); await this.pollForChannelOnce(); - this.conversation.trigger('change'); } // get moderation actions diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 639711ee3..8a5cb9e30 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -54,13 +54,12 @@ this.conversation = groupConvo; this.titleText = i18n('updateGroupDialogTitle'); - this.okText = i18n('ok'); - this.cancelText = i18n('cancel'); this.close = this.close.bind(this); this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); this.groupId = groupConvo.id; this.members = groupConvo.get('members') || []; + this.avatarPath = groupConvo.getAvatarPath(); const ourPK = textsecure.storage.user.getNumber(); @@ -96,6 +95,7 @@ i18n, onSubmit: this.onSubmit, onClose: this.close, + avatarPath: this.avatarPath, }, }); diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index 7e923a848..53fcf648e 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -61,24 +61,21 @@ export class UpdateGroupNameDialog extends React.Component { this.inputEl.current.files && this.inputEl.current.files.length > 0 ? this.inputEl.current.files[0] - : this.props.avatarPath; // otherwise use the current avatar + : null; // otherwise use the current avatar - this.props.onSubmit(this.props.groupName, avatar); + this.props.onSubmit(this.state.groupName, avatar); this.closeDialog(); } public render() { - const { isPublic, okText, cancelText } = this.props; + const { okText, cancelText } = this.props; const titleText = `${this.props.titleText}`; let noAvatarClasses; - if (isPublic) { - noAvatarClasses = classNames('avatar-center'); - } else { - noAvatarClasses = classNames('hidden'); - } + noAvatarClasses = classNames('avatar-center'); + const errorMsg = this.state.errorMessage; const errorMessageClasses = classNames( @@ -195,12 +192,10 @@ export class UpdateGroupNameDialog extends React.Component { private renderAvatar() { const avatarPath = this.state.avatar; - const color = '#00ff00'; return ( Date: Wed, 19 Feb 2020 16:21:52 +1100 Subject: [PATCH 5/8] disable profile image upload for closed group --- _locales/en/messages.json | 8 ++- js/background.js | 4 +- .../conversation/UpdateGroupNameDialog.tsx | 72 +++++++++---------- .../session/SessionGroupSettings.tsx | 2 +- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 03ff620bb..a7e6d2766 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2204,9 +2204,13 @@ "message": "Edit Profile", "description": "Button action that the user can click to edit their profile" }, - "editGroupName": { + "editGroupNameOrPicture": { "message": "Edit group name or picture", - "description": "Button action that the user can click to edit a group name" + "description": "Button action that the user can click to edit a group name (open)" + }, + "editGroupName": { + "message": "Edit group name", + "description": "Button action that the user can click to edit a group name (closed)" }, "createGroupDialogTitle": { "message": "Creating a Closed Group", diff --git a/js/background.js b/js/background.js index 5478fbffc..bd3ced3d5 100644 --- a/js/background.js +++ b/js/background.js @@ -754,7 +754,7 @@ const fileObj = await API.serverAPI.putAttachment(attachment.data); if (fileObj === null) { // problem - log.warn('File upload failed'); + window.warn('File upload failed'); return; } @@ -764,7 +764,7 @@ '' ); // write it to the channel - const changeRes = await API.setChannelAvatar(relativeFileUrl); + await API.setChannelAvatar(relativeFileUrl); } if (await API.setChannelName(groupName)) { diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index 53fcf648e..1f0d033ff 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -72,10 +72,6 @@ export class UpdateGroupNameDialog extends React.Component { const { okText, cancelText } = this.props; const titleText = `${this.props.titleText}`; - let noAvatarClasses; - - noAvatarClasses = classNames('avatar-center'); - const errorMsg = this.state.errorMessage; const errorMessageClasses = classNames( @@ -93,32 +89,7 @@ export class UpdateGroupNameDialog extends React.Component {

{errorMsg}

- -
-
- {this.renderAvatar()} -
- -
{ - const el = this.inputEl.current; - if (el) { - el.click(); - } - }} - /> -
-
-
+ {this.renderAvatar()}
{ private renderAvatar() { const avatarPath = this.state.avatar; + const isPublic = this.props.isPublic; + + if (!isPublic) { + return undefined; + } return ( - +
+
+ +
+ +
{ + const el = this.inputEl.current; + if (el) { + el.click(); + } + }} + /> +
+
+
); } diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index cff0c7245..c5f50bef7 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -256,7 +256,7 @@ export class SessionGroupSettings extends React.Component { role="button" onClick={this.props.onUpdateGroupName} > - {window.i18n('editGroupName')} + {isPublic ? window.i18n('editGroupNameOrPicture') : window.i18n('editGroupName')}
)} {showUpdateGroupMembersButton && ( From ad682b588a2255bef6c645df0d3e21fbed2d5074 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 16:35:38 +1100 Subject: [PATCH 6/8] make group image upload look same as edit profile upload --- stylesheets/_session.scss | 46 +++++++++---------- .../conversation/UpdateGroupNameDialog.tsx | 40 ++++++++-------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index caae396ac..a3b282caa 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -1020,29 +1020,6 @@ label { } } - .image-upload-section { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - cursor: pointer; - width: 80px; - height: 80px; - border-radius: 100%; - background-color: rgba($session-color-black, 0.72); - box-shadow: 0px 0px 3px 0.5px rgba(0, 0, 0, 0.75); - opacity: 0; - transition: $session-transition-duration; - - &:after { - content: 'Edit'; - } - - &:hover { - opacity: 1; - } - } - .session-id-section { display: flex; align-items: center; @@ -1099,6 +1076,29 @@ label { } } +.image-upload-section { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + cursor: pointer; + width: 80px; + height: 80px; + border-radius: 100%; + background-color: rgba($session-color-black, 0.72); + box-shadow: 0px 0px 3px 0.5px rgba(0, 0, 0, 0.75); + opacity: 0; + transition: $session-transition-duration; + + &:after { + content: 'Edit'; + } + + &:hover { + opacity: 1; + } +} + .qr-image { display: flex; justify-content: center; diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index 1f0d033ff..c6e9aa8f2 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -178,27 +178,25 @@ export class UpdateGroupNameDialog extends React.Component { i18n={this.props.i18n} size={80} /> -
- -
{ - const el = this.inputEl.current; - if (el) { - el.click(); - } - }} - /> -
-
+
{ + const el = this.inputEl.current; + if (el) { + el.click(); + } + }} + /> + +
); } From e0ff1755acf6f5435bcbc1308026c5ea325caa27 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 20 Feb 2020 09:24:07 +1100 Subject: [PATCH 7/8] lint --- _locales/en/messages.json | 6 ++++-- js/modules/loki_app_dot_net_api.js | 10 +++++++--- ts/components/conversation/UpdateGroupNameDialog.tsx | 2 +- ts/components/session/SessionGroupSettings.tsx | 4 +++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index a7e6d2766..6d93306bb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2206,11 +2206,13 @@ }, "editGroupNameOrPicture": { "message": "Edit group name or picture", - "description": "Button action that the user can click to edit a group name (open)" + "description": + "Button action that the user can click to edit a group name (open)" }, "editGroupName": { "message": "Edit group name", - "description": "Button action that the user can click to edit a group name (closed)" + "description": + "Button action that the user can click to edit a group name (closed)" }, "createGroupDialogTitle": { "message": "Creating a Closed Group", diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 75b2ebb43..ff63a3980 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1290,9 +1290,13 @@ class LokiPublicChannelAPI { this.conversation.setSubscriberCount(data.counts.subscribers); } - await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { - Conversation: Whisper.Conversation, - }); + await window.Signal.Data.updateConversation( + this.conversation.id, + this.conversation.attributes, + { + Conversation: Whisper.Conversation, + } + ); await this.pollForChannelOnce(); } diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index c6e9aa8f2..facdef321 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -197,7 +197,7 @@ export class UpdateGroupNameDialog extends React.Component { onChange={this.onFileSelected} />
-
+
); } diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index c5f50bef7..123f17d23 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -256,7 +256,9 @@ export class SessionGroupSettings extends React.Component { role="button" onClick={this.props.onUpdateGroupName} > - {isPublic ? window.i18n('editGroupNameOrPicture') : window.i18n('editGroupName')} + {isPublic + ? window.i18n('editGroupNameOrPicture') + : window.i18n('editGroupName')}
)} {showUpdateGroupMembersButton && ( From 845fc349647569e525b470b12213f348fa6142e0 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 26 Feb 2020 16:36:13 -0800 Subject: [PATCH 8/8] Fix my typo --- js/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/background.js b/js/background.js index bd3ced3d5..5c36209fa 100644 --- a/js/background.js +++ b/js/background.js @@ -792,7 +792,7 @@ convo.updateGroup({ groupId, groupName, - nullAvatar, + avatar: nullAvatar, recipients, members, options,