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