make upload of group picture work
This commit is contained in:
parent
cef644b637
commit
6d5aed7de8
|
@ -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": {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
}
|
||||
|
||||
.edit-profile-dialog,
|
||||
.create-group-dialog,
|
||||
.user-details-dialog {
|
||||
.content {
|
||||
max-width: 100% !important;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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')}
|
||||
|
|
Loading…
Reference in New Issue