Closed groups UI

pull/790/head
Vincent 5 years ago
parent de4edf9252
commit c5e0fe8503

@ -1675,3 +1675,59 @@ input {
}
}
}
.group-member-list {
&__container {
padding: 2px 0px;
width: 100%;
max-height: 400px;
overflow-y: auto;
box-shadow:
inset 0px 14px 7px -15px $session-color-dark-grey,
inset 0px -14px 7px -15px $session-color-dark-grey;
}
&__selection {
height: 100%;
display: flex;
flex-direction: column;
}
}
.session-member-item {
font-family: "SF Pro Text";
padding: 0px $session-margin-sm;
height: 50px;
display: flex;
justify-content: space-between;
transition: $session-transition-duration;
&.selected {
background-color: $session-shade-4;
}
&__checkmark {
opacity: 0;
transition: $session-transition-duration;
&.selected{
opacity: 1;
}
}
&__info, &__checkmark{
display: flex;
align-items: center;
}
&__name {
font-weight: bold;
margin-left: $session-margin-md;
}
&__pubkey {
margin-left: 5px;
opacity: 0.8;
}
}

@ -177,8 +177,8 @@ $session-compose-margin: 20px;
&__header {
display: flex;
flex-direction: row;
margin: 15px 7px 14px 0px;
height: 33px;
padding: 15px 7px 14px 0px;
height: 63px;
@at-root .light-theme #{&} {
background-color: $session-color-white;

@ -39,6 +39,7 @@ export class CreateGroupDialog extends React.Component<Props, State> {
this.onGroupNameChanged = this.onGroupNameChanged.bind(this);
let friends = this.props.friendList;
friends = friends.map(d => {
const lokiProfile = d.getLokiProfile();
const name = lokiProfile ? lokiProfile.displayName : 'Anonymous';
@ -95,6 +96,8 @@ export class CreateGroupDialog extends React.Component<Props, State> {
return (
<SessionModal title={titleText} onClose={() => null} onOk={() => null}>
<div className="spacer-lg"></div>
<p className={errorMessageClasses}>{this.state.errorMessage}</p>
<input
type="text"
@ -116,6 +119,9 @@ export class CreateGroupDialog extends React.Component<Props, State> {
onMemberClicked={this.onMemberClicked}
/>
</div>
<div className="spacer-lg"></div>
<div className="buttons">
<button className="cancel" tabIndex={0} onClick={this.closeDialog}>
{cancelText}

@ -31,6 +31,10 @@ export class InviteFriendsDialog extends React.Component<Props, State> {
this.onKeyUp = this.onKeyUp.bind(this);
let friends = this.props.friendList;
console.log("Contacts from invitefriendsDialog before filter::");
console.log(friends);
friends = friends.map(d => {
const lokiProfile = d.getLokiProfile();
const name = lokiProfile ? lokiProfile.displayName : 'Anonymous';
@ -50,6 +54,9 @@ export class InviteFriendsDialog extends React.Component<Props, State> {
};
});
console.log("Ideal friends list from inviteDialog");
console.log(friends);
this.state = {
friendList: friends,
};
@ -70,6 +77,8 @@ export class InviteFriendsDialog extends React.Component<Props, State> {
onOk={() => null}
onClose={this.closeDialog}
>
<div className="spacer-lg"></div>
<div className="friend-selection-list">
<MemberList
members={this.state.friendList}
@ -86,6 +95,8 @@ export class InviteFriendsDialog extends React.Component<Props, State> {
</>
)}
<div className="spacer-lg"></div>
<div className="session-modal__button-group">
<SessionButton text={cancelText} onClick={this.closeDialog} />
<SessionButton

@ -33,6 +33,7 @@ class MemberItem extends React.Component<MemberItemProps> {
const pubkey = this.props.member.authorPhoneNumber;
const selected = this.props.selected;
const existingMember = this.props.existingMember;
const shortPubkey = window.shortenPubkey(pubkey);
let markType: 'none' | 'kicked' | 'added' | 'existing' = 'none';
@ -79,7 +80,7 @@ class MemberItem extends React.Component<MemberItemProps> {
>
{this.renderAvatar()}
<span className="name-part">{name}</span>
<span className="pubkey-part">{pubkey}</span>
<span className="pubkey-part">{shortPubkey}</span>
<span className={classNames(markClasses)}>{mark}</span>
</div>
);

@ -19,13 +19,12 @@ import {
import { SearchOptions } from '../../types/Search';
import { debounce } from 'lodash';
import { cleanSearchTerm } from '../../util/cleanSearchTerm';
import { ConversationType } from '../../state/ducks/conversations';
import { SessionSearchInput } from './SessionSearchInput';
import { SessionClosableOverlay } from './SessionClosableOverlay';
import { MainViewController } from '../MainViewController';
import { ContactType } from './SessionMemberListItem'
export interface Props {
friends: Array<ConversationType>;
searchTerm: string;
isSecondaryDevice: boolean;
@ -282,7 +281,7 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
}
private renderClosableOverlay(groupType: SessionGroupType) {
const { searchTerm, friends } = this.props;
const { searchTerm } = this.props;
const { loading } = this.state;
const openGroupElement = (
@ -299,11 +298,12 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
const closedGroupElement = (
<SessionClosableOverlay
friends={friends}
overlayMode={SessionGroupType.Closed}
onChangeSessionID={this.handleOnPasteUrl}
onCloseClick={() => this.handleToggleOverlay(undefined)}
onButtonClick={this.handleCreateClosedGroupButtonClick}
onButtonClick={(groupName: string, groupMembers: Array<ContactType>) =>
this.onCreateClosedGroup(groupName, groupMembers)
}
searchTerm={searchTerm}
updateSearch={this.updateSearchBound}
showSpinner={loading}
@ -385,8 +385,8 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
return true;
}
private handleCreateClosedGroupButtonClick() {
alert("creating closed group!");
private onCreateClosedGroup(groupName: string, groupMembers: Array<ContactType>) {
console.log(`Creating group with name: ${groupName}`);
return true;
}

@ -3,7 +3,7 @@ import React from 'react';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { SessionIdEditable } from './SessionIdEditable';
import { UserSearchDropdown } from './UserSearchDropdown';
import { MemberList } from '../conversation/MemberList';
import { ContactType, SessionMemberListItem } from './SessionMemberListItem';
import { ConversationType } from '../../state/ducks/conversations';
import {
SessionButton,
@ -18,20 +18,31 @@ interface Props {
onChangeSessionID: any;
onCloseClick: any;
onButtonClick: any;
friends?: Array<ConversationType>;
contacts?: Array<ConversationType>;
searchTerm?: string;
searchResults?: any;
updateSearch?: any;
showSpinner?: boolean;
}
export class SessionClosableOverlay extends React.Component<Props> {
interface State {
groupName: string;
selectedMembers: Array<ContactType>;
}
export class SessionClosableOverlay extends React.Component<Props, State> {
private readonly inputRef: React.RefObject<SessionIdEditable>;
public constructor(props: Props) {
super(props);
this.state = {
groupName: '',
selectedMembers: [],
};
this.inputRef = React.createRef();
}
public componentDidMount() {
@ -40,6 +51,39 @@ export class SessionClosableOverlay extends React.Component<Props> {
}
}
public getContacts() {
const conversations = window.getConversations();
let conversationList = conversations;
if (conversationList !== undefined) {
conversationList = conversationList.filter((conv: any) => {
return !conv.isRss() && !conv.isPublic() && conv.attributes.lastMessage
});
}
let friends = conversationList.map((d: any) => {
const lokiProfile = d.getLokiProfile();
const name = lokiProfile ? lokiProfile.displayName : 'Anonymous';
// TODO: should take existing members into account
const existingMember = false;
return {
id: d.id,
authorPhoneNumber: d.id,
authorProfileName: name,
selected: false,
authorName: name,
authorColor: d.getColor(),
checkmarked: false,
existingMember,
};
});
return friends;
}
public render(): JSX.Element {
const {
overlayMode,
@ -55,7 +99,7 @@ export class SessionClosableOverlay extends React.Component<Props> {
const isAddContactView = overlayMode === 'contact';
const isMessageView = overlayMode === 'message';
// const isOpenGroupView = overlayMode === SessionGroupType.Open;
const isOpenGroupView = overlayMode === SessionGroupType.Open;
const isClosedGroupView = overlayMode === SessionGroupType.Closed;
let title;
@ -96,10 +140,8 @@ export class SessionClosableOverlay extends React.Component<Props> {
break;
}
const { groupName, selectedMembers } = this.state;
const ourSessionID = window.textsecure.storage.user.getNumber();
const friends = window.getFriendsFromContacts(this.props.friends);
console.log(this.props.friends);
console.log(window.getFriendsFromContacts(this.props.friends));
return (
<div className="module-left-pane-overlay">
@ -110,29 +152,49 @@ export class SessionClosableOverlay extends React.Component<Props> {
onClick={onCloseClick}
/>
</div>
<div className="spacer-md"></div>
<h2>{title}</h2>
<h3>{subtitle}</h3>
<div className="module-left-pane-overlay-border-container">
<hr className="white" />
<hr className="green" />
</div>
<SessionIdEditable
ref={this.inputRef}
editable={true}
placeholder={placeholder}
onChange={onChangeSessionID}
/>
{ (isOpenGroupView || isClosedGroupView) ?
(
<SessionIdEditable
ref={this.inputRef}
editable={true}
placeholder={placeholder}
value={this.state.groupName}
onChange={this.onGroupNameChanged}
/>
) : (
<SessionIdEditable
ref={this.inputRef}
editable={true}
placeholder={placeholder}
onChange={onChangeSessionID}
/>
)
}
{showSpinner && <SessionSpinner />}
{isClosedGroupView && (
<div className="friend-selection-list">
<MemberList
members={friends}
selected={{}}
i18n={window.i18n}
onMemberClicked={() => null }//this.onMemberClicked}
/>
</div>
<>
<div className="spacer-lg"></div>
<div className="group-member-list__container">
<div className="group-member-list__selection">
{this.renderMemberList()}
</div>
</div>
<div className="spacer-lg"></div>
</>
)}
<div className="session-description-long">{descriptionLong}</div>
@ -165,9 +227,51 @@ export class SessionClosableOverlay extends React.Component<Props> {
buttonColor={SessionButtonColor.Green}
buttonType={SessionButtonType.BrandOutline}
text={buttonText}
onClick={onButtonClick}
onClick={() => onButtonClick(groupName, selectedMembers)}
/>
</div>
);
}
private renderMemberList() {
const members = this.getContacts();
const memberList = members.map((member: ContactType) => (
<SessionMemberListItem
member={member}
isSelected={false}
onSelect={(member: ContactType) => this.handleSelectMember(member)}
onUnselect={(member: ContactType) => this.handleUnselectMember(member)}
/>
)
);
return memberList;
}
private handleSelectMember(member: ContactType){
if (this.state.selectedMembers.includes(member)){
return;
}
this.setState({
selectedMembers: [...this.state.selectedMembers, member]
});
}
private handleUnselectMember(member: ContactType){
this.setState({
selectedMembers: this.state.selectedMembers.filter(selectedMember => {
return selectedMember !== member;
})
});
}
private onGroupNameChanged(event: any) {
event.persist;
this.setState({
groupName: event.target.value,
});
}
}

@ -2,6 +2,7 @@ import React from 'react';
interface Props {
placeholder?: string;
value?: string;
text?: string;
editable?: boolean;
onChange?: any;
@ -25,7 +26,7 @@ export class SessionIdEditable extends React.PureComponent<Props> {
}
public render() {
const { placeholder, editable, text } = this.props;
const { placeholder, editable, text, value} = this.props;
return (
<div className="session-id-editable">
@ -37,7 +38,7 @@ export class SessionIdEditable extends React.PureComponent<Props> {
spellCheck={false}
onKeyDown={this.handleKeyDown}
onChange={this.handleChange}
value={text}
value={value || text}
/>
</div>
);

@ -0,0 +1,123 @@
import React from 'react';
import classNames from 'classnames';
import { Avatar } from '../Avatar';
import { SessionIcon, SessionIconType, SessionIconSize } from './icon';
export interface ContactType {
id: string;
selected: boolean;
authorProfileName: string;
authorPhoneNumber: string;
authorName: string;
authorColor: any;
authorAvatarPath: string;
checkmarked: boolean;
existingMember: boolean;
}
interface Props {
member: ContactType;
isSelected: boolean;
onSelect?: any;
onUnselect?: any;
}
interface State {
isSelected: boolean;
}
export class SessionMemberListItem extends React.Component<Props, State> {
public static defaultProps = {
isSelected: false,
};
constructor(props: any) {
super(props);
this.state = {
isSelected: this.props.isSelected,
};
this.handleSelectionAction = this.handleSelectionAction.bind(this);
this.selectMember = this.selectMember.bind(this);
this.unselectMember = this.unselectMember.bind(this);
this.renderAvatar = this.renderAvatar.bind(this);
}
public render() {
const { isSelected } = this.state;
const name = this.props.member.authorProfileName;
const pubkey = this.props.member.authorPhoneNumber;
const shortPubkey = window.shortenPubkey(pubkey);
return (
<div
className={classNames('session-member-item', isSelected && 'selected')}
onClick={this.handleSelectionAction}
>
<div className="session-member-item__info">
<span className="session-member-item__avatar">{this.renderAvatar()}</span>
<span className="session-member-item__name">{name}</span>
<span className="session-member-item__pubkey">{shortPubkey}</span>
</div>
<span className={classNames('session-member-item__checkmark', isSelected && 'selected')}>
<SessionIcon
iconType={SessionIconType.Check}
iconSize={SessionIconSize.Medium}
iconColor={"#00f782"}
/>
</span>
</div>
);
}
private renderAvatar() {
return (
<Avatar
avatarPath={this.props.member.authorAvatarPath}
color={this.props.member.authorColor}
conversationType="direct"
i18n={window.i18n}
name={this.props.member.authorName}
phoneNumber={this.props.member.authorPhoneNumber}
profileName={this.props.member.authorProfileName}
size={28}
/>
);
}
private handleSelectionAction() {
if (this.state.isSelected) {
this.unselectMember();
return;
}
this.selectMember();
}
private selectMember() {
this.setState({
isSelected: true,
});
if (this.props.onSelect){
this.props.onSelect(this.props.member);
}
}
private unselectMember() {
this.setState({
isSelected: false,
});
if (this.props.onUnselect){
this.props.onUnselect(this.props.member);
}
}
}

2
ts/global.d.ts vendored

@ -6,6 +6,7 @@ interface Window {
deleteAllData: any;
clearLocalData: any;
getAccountManager: any;
getConversations: any;
getFriendsFromContacts: any;
mnemonic: any;
clipboard: any;
@ -13,6 +14,7 @@ interface Window {
passwordUtil: any;
userConfig: any;
shortenPubkey: any;
dcodeIO: any;
libsignal: any;

Loading…
Cancel
Save