make upload of group picture work

This commit is contained in:
Audric Ackermann 2020-02-19 11:27:37 +11:00
parent cef644b637
commit 6d5aed7de8
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4
11 changed files with 243 additions and 115 deletions

View File

@ -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": {

View File

@ -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,

View File

@ -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?

View File

@ -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(),

View File

@ -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();

View File

@ -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,

View File

@ -11,6 +11,7 @@
}
.edit-profile-dialog,
.create-group-dialog,
.user-details-dialog {
.content {
max-width: 100% !important;

View File

@ -34,7 +34,6 @@ export class UpdateGroupMembersDialog extends React.Component<Props, State> {
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<Props, State> {
};
});
}
private onGroupNameChanged(event: any) {
event.persist();
this.setState(state => {
return {
...state,
groupName: event.target.value,
};
});
}
}

View File

@ -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<String>;
// avatar stuff
avatarPath: string;
}
interface State {
groupName: string;
errorDisplayed: boolean;
errorMessage: string;
avatar: string;
}
export class UpdateGroupNameDialog extends React.Component<Props, State> {
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<Props, State> {
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<Props, State> {
<p className={errorMessageClasses}>{errorMsg}</p>
<div className="spacer-md" />
<div className={noAvatarClasses}>
<div className="avatar-center-inner">
{this.renderAvatar()}
<div className="upload-btn-background">
<input
type="file"
ref={this.inputEl}
className="input-file"
placeholder="input file"
name="name"
onChange={this.onFileSelected}
/>
<div
role="button"
className={'module-message__buttons__upload'}
onClick={() => {
const el = this.inputEl.current;
if (el) {
el.click();
}
}}
/>
</div>
</div>
</div>
<div className="spacer-md" />
<input
type="text"
className="profile-name-input"
@ -145,4 +191,28 @@ export class UpdateGroupNameDialog extends React.Component<Props, State> {
};
});
}
private renderAvatar() {
const avatarPath = this.state.avatar;
const color = '#00ff00';
return (
<Avatar
avatarPath={avatarPath}
color={color}
conversationType="group"
i18n={this.props.i18n}
size={80}
/>
);
}
private onFileSelected() {
const file = this.inputEl.current.files[0];
const url = window.URL.createObjectURL(file);
this.setState({
avatar: url,
});
}
}

View File

@ -399,13 +399,18 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
groupMembers: Array<ContactType>
) {
// 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<Props, State> {
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',
});

View File

@ -20,6 +20,7 @@ interface Props {
timerOptions: Array<TimerOption>;
isPublic: boolean;
isAdmin: boolean;
amMod: boolean;
onGoBack: () => void;
onInviteFriends: () => void;
@ -211,6 +212,7 @@ export class SessionGroupSettings extends React.Component<Props, any> {
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<Props, any> {
};
});
const showUpdateGroupNameButton = isPublic ? amMod : isAdmin;
const showUpdateGroupMembersButton = !isPublic && isAdmin;
return (
<div className="group-settings">
{this.renderHeader()}
@ -245,25 +250,23 @@ export class SessionGroupSettings extends React.Component<Props, any> {
className="description"
placeholder={window.i18n('description')}
/>
{!isPublic && (
<>
{isAdmin && (
<div
className="group-settings-item"
role="button"
onClick={this.props.onUpdateGroupName}
>
{window.i18n('editGroupName')}
</div>
)}
<div
className="group-settings-item"
role="button"
onClick={this.props.onUpdateGroupMembers}
>
{window.i18n('showMembers')}
</div>
</>
{showUpdateGroupNameButton && (
<div
className="group-settings-item"
role="button"
onClick={this.props.onUpdateGroupName}
>
{window.i18n('editGroupName')}
</div>
)}
{showUpdateGroupMembersButton && (
<div
className="group-settings-item"
role="button"
onClick={this.props.onUpdateGroupMembers}
>
{window.i18n('showMembers')}
</div>
)}
{/*<div className="group-settings-item">
{window.i18n('notifications')}