Add UI elements for searching and selecting members in a group chat
parent
3152637cdc
commit
1496a368e9
@ -0,0 +1,65 @@
|
|||||||
|
/* global _, Whisper, */
|
||||||
|
|
||||||
|
// eslint-disable-next-line func-names
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.Whisper = window.Whisper || {};
|
||||||
|
|
||||||
|
Whisper.MemberListView = Whisper.View.extend({
|
||||||
|
initialize(options) {
|
||||||
|
this.member_list = [];
|
||||||
|
this.selected_idx = 0;
|
||||||
|
this.onClicked = options.onClicked;
|
||||||
|
|
||||||
|
this.listenTo(this.model, 'change', this.render);
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
if (this.memberView) {
|
||||||
|
this.memberView.remove();
|
||||||
|
this.memberView = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.memberView = new Whisper.ReactWrapperView({
|
||||||
|
className: 'member-list',
|
||||||
|
Component: window.Signal.Components.MemberList,
|
||||||
|
props: {
|
||||||
|
members: this.member_list,
|
||||||
|
selected: this.selectedMember(),
|
||||||
|
onMemberClicked: this.handleMemberClicked.bind(this),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.append(this.memberView.el);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
handleMemberClicked(member) {
|
||||||
|
this.onClicked(member);
|
||||||
|
},
|
||||||
|
update_members(members) {
|
||||||
|
if (!_.isEqual(this.member_list, members)) {
|
||||||
|
// Whenever the list is updated, we reset the selection
|
||||||
|
this.selected_idx = 0;
|
||||||
|
this.member_list = members;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
members_shown() {
|
||||||
|
return this.member_list.length !== 0;
|
||||||
|
},
|
||||||
|
selectUp() {
|
||||||
|
this.selected_idx = Math.max(this.selected_idx - 1, 0);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
selectDown() {
|
||||||
|
this.selected_idx = Math.min(
|
||||||
|
this.selected_idx + 1,
|
||||||
|
this.member_list.length - 1
|
||||||
|
);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
selectedMember() {
|
||||||
|
return this.member_list[this.selected_idx];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
@ -0,0 +1,49 @@
|
|||||||
|
.member-list-container {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
max-height: 240px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
.member-item {
|
||||||
|
padding: 4px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover:not(.member-selected) {
|
||||||
|
background-color: $color-light-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: $color-light-10;
|
||||||
|
|
||||||
|
.name-part {
|
||||||
|
font-weight: 300;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pubkey-part {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-selected {
|
||||||
|
background-color: $color-light-35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
.member-list-container {
|
||||||
|
.member-item {
|
||||||
|
&:hover:not(.member-selected) {
|
||||||
|
background-color: $color-dark-55;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: $color-dark-70;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-selected {
|
||||||
|
background-color: $color-dark-60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Avatar } from '../Avatar';
|
||||||
|
|
||||||
|
interface MemberItemProps {
|
||||||
|
member: any;
|
||||||
|
selected: Boolean;
|
||||||
|
onClicked: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MemberItem extends React.Component<MemberItemProps> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const name = this.props.member.authorProfileName;
|
||||||
|
const pubkey = this.props.member.authorPhoneNumber;
|
||||||
|
const selected = this.props.selected;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
className={classNames(
|
||||||
|
'member-item',
|
||||||
|
selected ? 'member-selected' : null
|
||||||
|
)}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
>
|
||||||
|
{this.renderAvatar()}
|
||||||
|
<span className="name-part">{name}</span>
|
||||||
|
<span className="pubkey-part">{pubkey}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClick() {
|
||||||
|
this.props.onClicked(this.props.member);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAvatar() {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
avatarPath={this.props.member.authorAvatarPath}
|
||||||
|
color={this.props.member.authorColor}
|
||||||
|
conversationType="direct"
|
||||||
|
i18n={this.props.member.i18n}
|
||||||
|
name={this.props.member.authorName}
|
||||||
|
phoneNumber={this.props.member.authorPhoneNumber}
|
||||||
|
profileName={this.props.member.authorProfileName}
|
||||||
|
size={28}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MemberListProps {
|
||||||
|
members: [any];
|
||||||
|
selected: any;
|
||||||
|
onMemberClicked: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MemberList extends React.Component<MemberListProps> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleMemberClicked = this.handleMemberClicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { members } = this.props;
|
||||||
|
|
||||||
|
const itemList = members.map(item => {
|
||||||
|
const selected = item === this.props.selected;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MemberItem
|
||||||
|
key={item.id}
|
||||||
|
member={item}
|
||||||
|
selected={selected}
|
||||||
|
onClicked={this.handleMemberClicked}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>{itemList}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMemberClicked(member: any) {
|
||||||
|
this.props.onMemberClicked(member);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue