GroupMembers updates

This commit is contained in:
MyNameIsBatman 2021-09-06 02:53:24 -03:00
parent 8b6f08b989
commit 3cd6d56b3e
14 changed files with 117 additions and 38 deletions

View File

@ -1,6 +1,7 @@
import { CreateLinkEvent } from '..'; import { CreateLinkEvent } from '..';
export function GetGroupMembers(groupId: number): void export function GetGroupMembers(groupId: number, levelId?: number): void
{ {
CreateLinkEvent(`groups/members/${groupId}`); if(!levelId) CreateLinkEvent(`groups/members/${groupId}`);
else CreateLinkEvent(`groups/members/${groupId}/${levelId}`);
} }

View File

@ -2,4 +2,5 @@ export * from './core';
export * from './groups'; export * from './groups';
export * from './navigator'; export * from './navigator';
export * from './nitro'; export * from './nitro';
export * from './user';
export * from './utils'; export * from './utils';

View File

@ -0,0 +1,7 @@
import { UserProfileComposer } from '@nitrots/nitro-renderer';
import { SendMessageHook } from '../../hooks';
export function GetUserProfile(userId: number): void
{
SendMessageHook(new UserProfileComposer(userId));
}

1
src/api/user/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './GetUserProfile';

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 173 B

View File

@ -621,6 +621,12 @@
height: 14px; height: 14px;
} }
&.icon-group-accept-member {
background: url('../images/groups/icons/group_icon_accept_member.png');
width: 13px;
height: 14px;
}
&.icon-group-small-owner { &.icon-group-small-owner {
background: url('../images/groups/icons/group_icon_small_owner.png'); background: url('../images/groups/icons/group_icon_small_owner.png');
width: 13px; width: 13px;

View File

@ -114,8 +114,8 @@ export const GroupsMessageHandler: FC<{}> = props =>
parser.badgeParts.forEach((part, id) => parser.badgeParts.forEach((part, id) =>
{ {
groupBadgeParts.push(new GroupBadgePart( groupBadgeParts.push(new GroupBadgePart(
part.isBase ? GroupBadgePart.BASE : part.key >= 100 ? GroupBadgePart.SYMBOL_ALT : GroupBadgePart.SYMBOL, part.isBase ? GroupBadgePart.BASE : GroupBadgePart.SYMBOL,
part.key >= 100 ? part.key - 100 : part.key, part.key,
part.color, part.color,
part.position part.position
)); ));

View File

@ -2,7 +2,6 @@ export class GroupBadgePart
{ {
public static BASE: string = 'b'; public static BASE: string = 'b';
public static SYMBOL: string = 's'; public static SYMBOL: string = 's';
public static SYMBOL_ALT: string = 't';
public type: string; public type: string;
public key: number; public key: number;
@ -26,6 +25,6 @@ export class GroupBadgePart
public static getCode(type: string, key: number, color: number, position: number): string public static getCode(type: string, key: number, color: number, position: number): string
{ {
return (type === GroupBadgePart.BASE ? type : key >= 100 ? GroupBadgePart.SYMBOL_ALT : GroupBadgePart.SYMBOL) + (key < 10 ? '0' : '') + (type === GroupBadgePart.BASE ? key : key >= 100 ? key - 100 : key) + (color < 10 ? '0' : '') + color + position; return type + (key < 10 ? '0' : '') + key + (color < 10 ? '0' : '') + color + position;
} }
} }

View File

@ -24,8 +24,11 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
const leaveGroup = useCallback(() => const leaveGroup = useCallback(() =>
{ {
SendMessageHook(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); if(window.confirm(LocalizeText('group.leaveconfirm.desc')))
if(onClose) onClose(); {
SendMessageHook(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId));
if(onClose) onClose();
}
}, [ groupInformation, onClose ]); }, [ groupInformation, onClose ]);
const isRealOwner = useCallback(() => const isRealOwner = useCallback(() =>
@ -80,6 +83,9 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
case 'members': case 'members':
GetGroupMembers(groupInformation.id); GetGroupMembers(groupInformation.id);
break; break;
case 'members_pending':
GetGroupMembers(groupInformation.id, 2);
break;
case 'manage': case 'manage':
GetGroupManager(groupInformation.id); GetGroupManager(groupInformation.id);
break; break;
@ -104,8 +110,8 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
<div className="mt-3 cursor-pointer" onClick={ () => handleAction('members') }> <div className="mt-3 cursor-pointer" onClick={ () => handleAction('members') }>
{ LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()]) } { LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()]) }
</div> </div>
{ groupInformation.pendingRequestsCount > 0 && <div className="cursor-pointer"> { groupInformation.pendingRequestsCount > 0 && <div className="cursor-pointer" onClick={ () => handleAction('members_pending') }>
{ LocalizeText('group.pendingmembercount', ['totalMembers'], [groupInformation.pendingRequestsCount.toString()]) } { LocalizeText('group.pendingmembercount', ['amount'], [groupInformation.pendingRequestsCount.toString()]) }
</div> } </div> }
{ groupInformation.isOwner && <div className="cursor-pointer" onClick={ () => handleAction('manage') }> { groupInformation.isOwner && <div className="cursor-pointer" onClick={ () => handleAction('manage') }>
{ LocalizeText('group.manage') } { LocalizeText('group.manage') }

View File

@ -16,12 +16,12 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
width: 40px; width: 40px;
height: 40px; height: 50px;
.avatar-image { .avatar-image {
position: absolute; position: absolute;
left: -25px; left: -25px;
top: -25px; top: -20px;
} }
} }
} }

View File

@ -1,7 +1,7 @@
import { GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembersParser, GroupRank } from '@nitrots/nitro-renderer'; import { GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer } from '@nitrots/nitro-renderer';
import classNames from 'classnames'; import classNames from 'classnames';
import { FC, KeyboardEvent, useCallback, useEffect, useState } from 'react'; import { FC, KeyboardEvent, useCallback, useEffect, useState } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../../api'; import { GetSessionDataManager, GetUserProfile, LocalizeText } from '../../../../api';
import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; import { CreateMessageHook, SendMessageHook } from '../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView'; import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
@ -16,23 +16,40 @@ export const GroupMembersView: FC<GroupMembersViewProps> = props =>
const [ searchQuery, setSearchQuery ] = useState<string>(''); const [ searchQuery, setSearchQuery ] = useState<string>('');
const [ searchLevelId, setSearchLevelId ] = useState<number>(3); const [ searchLevelId, setSearchLevelId ] = useState<number>(3);
const [ totalPages, setTotalPages ] = useState<number>(0); const [ totalPages, setTotalPages ] = useState<number>(0);
const [ removingMemberName, setRemovingMemberName ] = useState<string>(null);
const searchMembers = useCallback((pageId: number) => const searchMembers = useCallback((pageId: number, newLevelId?: number) =>
{ {
if(!groupId) return; if(!groupId) return;
SendMessageHook(new GroupMembersComposer(groupId, pageId, searchQuery, searchLevelId)); SendMessageHook(new GroupMembersComposer(groupId, pageId, searchQuery, newLevelId !== null ? newLevelId : searchLevelId));
}, [ groupId, searchQuery, searchLevelId ]); }, [ groupId, searchQuery, searchLevelId ]);
const onGroupMembersEvent = useCallback((event: GroupMembersEvent) => const onGroupMembersEvent = useCallback((event: GroupMembersEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();
setPageData(null);
setPageData(parser); setPageData(parser);
setSearchLevelId(parser.level);
setTotalPages(Math.ceil(parser.totalMembersCount / parser.pageSize)); setTotalPages(Math.ceil(parser.totalMembersCount / parser.pageSize));
}, []); }, []);
const onGroupConfirmMemberRemoveEvent = useCallback((event: GroupConfirmMemberRemoveEvent) =>
{
const parser = event.getParser();
if(window.confirm(LocalizeText(parser.furnitureCount > 0 ? 'group.kickconfirm.desc' : 'group.kickconfirm_nofurni.desc', ['user', 'amount'], [removingMemberName, parser.furnitureCount.toString()])))
{
SendMessageHook(new GroupRemoveMemberComposer(pageData.groupId, parser.userId));
searchMembers(pageData.pageIndex);
}
setRemovingMemberName(null);
}, [ pageData, removingMemberName, searchMembers ]);
CreateMessageHook(GroupMembersEvent, onGroupMembersEvent); CreateMessageHook(GroupMembersEvent, onGroupMembersEvent);
CreateMessageHook(GroupConfirmMemberRemoveEvent, onGroupConfirmMemberRemoveEvent);
useEffect(() => useEffect(() =>
{ {
@ -43,13 +60,15 @@ export const GroupMembersView: FC<GroupMembersViewProps> = props =>
if(!groupId) return; if(!groupId) return;
if(levelId !== null) setSearchLevelId(levelId); if(levelId !== null) setSearchLevelId(levelId);
searchMembers(0);
searchMembers(0, levelId);
}, [ groupId, levelId ]); }, [ groupId, levelId ]);
useEffect(() => const selectSearchLevelId = useCallback((level: number) =>
{ {
searchMembers(0); setSearchLevelId(level);
}, [ searchLevelId ]); searchMembers(0, level);
}, [ searchMembers ]);
const previousPage = useCallback(() => const previousPage = useCallback(() =>
{ {
@ -82,6 +101,45 @@ export const GroupMembersView: FC<GroupMembersViewProps> = props =>
return ''; return '';
}, [ pageData ]); }, [ pageData ]);
const toggleAdmin = useCallback((member: GroupMemberParser) =>
{
if(member.rank === GroupRank.OWNER) return;
if(member.rank !== GroupRank.ADMIN)
{
SendMessageHook(new GroupAdminGiveComposer(pageData.groupId, member.id));
}
else
{
SendMessageHook(new GroupAdminTakeComposer(pageData.groupId, member.id));
}
searchMembers(pageData.pageIndex);
}, [ pageData ]);
const acceptMembership = useCallback((member) =>
{
if(member.rank === GroupRank.REQUESTED)
{
SendMessageHook(new GroupMembershipAcceptComposer(pageData.groupId, member.id));
searchMembers(pageData.pageIndex);
}
}, [ pageData ]);
const removeMemberOrDeclineMembership = useCallback((member) =>
{
if(member.rank === GroupRank.REQUESTED)
{
SendMessageHook(new GroupMembershipDeclineComposer(pageData.groupId, member.id));
searchMembers(pageData.pageIndex);
}
else
{
setRemovingMemberName(member.name);
SendMessageHook(new GroupConfirmRemoveMemberComposer(pageData.groupId, member.id));
}
}, [ pageData ]);
if(!pageData) return null; if(!pageData) return null;
return ( return (
@ -94,7 +152,7 @@ export const GroupMembersView: FC<GroupMembersViewProps> = props =>
</div> </div>
<div className="w-100"> <div className="w-100">
<input type="text" className="form-control form-control-sm w-100 mb-1" placeholder={ LocalizeText('group.members.searchinfo') } value={ searchQuery } onChange={ (e) => setSearchQuery(e.target.value) } onBlur={ () => searchMembers(pageData.pageIndex) } onKeyDown={ onKeyDown } /> <input type="text" className="form-control form-control-sm w-100 mb-1" placeholder={ LocalizeText('group.members.searchinfo') } value={ searchQuery } onChange={ (e) => setSearchQuery(e.target.value) } onBlur={ () => searchMembers(pageData.pageIndex) } onKeyDown={ onKeyDown } />
<select className="form-select form-select-sm w-100" value={ searchLevelId } onChange={ (e) => setSearchLevelId(Number(e.target.value)) }> <select className="form-select form-select-sm w-100" value={ searchLevelId } onChange={ (e) => selectSearchLevelId(Number(e.target.value)) }>
<option value="0">{ LocalizeText('group.members.search.all') }</option> <option value="0">{ LocalizeText('group.members.search.all') }</option>
<option value="1">{ LocalizeText('group.members.search.admins') }</option> <option value="1">{ LocalizeText('group.members.search.admins') }</option>
<option value="2">{ LocalizeText('group.members.search.pending') }</option> <option value="2">{ LocalizeText('group.members.search.pending') }</option>
@ -107,19 +165,22 @@ export const GroupMembersView: FC<GroupMembersViewProps> = props =>
return ( return (
<div key={ index } className={ 'col pb-1' + classNames({ ' pe-1': index % 2 === 0 }) }> <div key={ index } className={ 'col pb-1' + classNames({ ' pe-1': index % 2 === 0 }) }>
<div className="list-member bg-white rounded d-flex text-black"> <div className="list-member bg-white rounded d-flex text-black">
<div className="avatar-head flex-shrink-0"> <div className="avatar-head flex-shrink-0 cursor-pointer" onClick={ () => { GetUserProfile(member.id) } }>
<AvatarImageView figure={ member.figure } headOnly={ true } direction={ 2 } /> <AvatarImageView figure={ member.figure } headOnly={ true } direction={ 2 } />
</div> </div>
<div className="p-1 w-100"> <div className="p-1 w-100 d-flex flex-column justify-content-center">
<div className="fw-bold small">{ member.name }</div> <div className="fw-bold small cursor-pointer" onClick={ () => { GetUserProfile(member.id) } }>{ member.name }</div>
<div className="text-muted fst-italic small">{ LocalizeText('group.members.since', ['date'], [member.joinedAt]) }</div> { member.rank !== GroupRank.REQUESTED && <div className="text-muted fst-italic small">{ LocalizeText('group.members.since', ['date'], [member.joinedAt]) }</div> }
</div> </div>
<div className="d-flex flex-column pe-2 align-items-center justify-content-center"> <div className="d-flex flex-column pe-2 align-items-center justify-content-center">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<i className={ 'icon icon-group-small-' + classNames({ 'owner': member.rank === GroupRank.OWNER, 'admin': member.rank === GroupRank.ADMIN, 'not-admin': member.rank === GroupRank.MEMBER, 'cursor-pointer': pageData.admin }) } title={ LocalizeText(getRankDescription(member)) } /> <i className={ 'icon icon-group-small-' + classNames({ 'owner': member.rank === GroupRank.OWNER, 'admin': member.rank === GroupRank.ADMIN, 'not-admin': member.rank === GroupRank.MEMBER, 'cursor-pointer': pageData.admin }) } title={ LocalizeText(getRankDescription(member)) } onClick={ () => toggleAdmin(member) } />
</div> </div>
{ member.rank === GroupRank.REQUESTED && <div className="d-flex align-items-center">
<i className="icon cursor-pointer icon-group-accept-member" title={ LocalizeText('group.members.accept') } onClick={ () => acceptMembership(member) } />
</div> }
{ member.rank !== GroupRank.OWNER && pageData.admin && member.id !== GetSessionDataManager().userId &&<div className="d-flex align-items-center mt-1"> { member.rank !== GroupRank.OWNER && pageData.admin && member.id !== GetSessionDataManager().userId &&<div className="d-flex align-items-center mt-1">
<i className="icon cursor-pointer icon-group-remove-member" title={ LocalizeText(member.rank === GroupRank.REQUESTED ? 'group.members.reject' : 'group.members.kick') } /> <i className="icon cursor-pointer icon-group-remove-member" title={ LocalizeText(member.rank === GroupRank.REQUESTED ? 'group.members.reject' : 'group.members.kick') } onClick={ () => removeMemberOrDeclineMembership(member) } />
</div> } </div> }
</div> </div>
</div> </div>

View File

@ -65,8 +65,11 @@ export const GroupRoomInformationView: FC<{}> = props =>
const tryLeaveGroup = useCallback(() => const tryLeaveGroup = useCallback(() =>
{ {
SendMessageHook(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); if(window.confirm(LocalizeText('group.leaveconfirm.desc')))
SendMessageHook(new GroupInformationComposer(groupInformation.id, false)); {
SendMessageHook(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId));
SendMessageHook(new GroupInformationComposer(groupInformation.id, false));
}
}, [ groupInformation ]); }, [ groupInformation ]);
const getButtonText = useCallback(() => const getButtonText = useCallback(() =>

View File

@ -1,18 +1,12 @@
import { UserProfileComposer } from '@nitrots/nitro-renderer'; import { FC } from 'react';
import { FC, useCallback } from 'react'; import { GetUserProfile } from '../../../api';
import { SendMessageHook } from '../../../hooks';
import { UserProfileIconViewProps } from './UserProfileIconView.types'; import { UserProfileIconViewProps } from './UserProfileIconView.types';
export const UserProfileIconView: FC<UserProfileIconViewProps> = props => export const UserProfileIconView: FC<UserProfileIconViewProps> = props =>
{ {
const { userId = 0, userName = null } = props; const { userId = 0, userName = null } = props;
const visitProfile = useCallback(() =>
{
if(userId) SendMessageHook(new UserProfileComposer(userId));
}, [ userId ]);
return (<> return (<>
<i className="icon icon-user-profile me-1 cursor-pointer" onClick={ visitProfile } /> <i className="icon icon-user-profile me-1 cursor-pointer" onClick={ () => GetUserProfile(userId) } />
</>); </>);
} }