diff --git a/src/components/dialogs/LoginDialog/index.jsx b/src/components/dialogs/LoginDialog/index.jsx index 9a37ddd5..78338dc7 100644 --- a/src/components/dialogs/LoginDialog/index.jsx +++ b/src/components/dialogs/LoginDialog/index.jsx @@ -10,8 +10,6 @@ import keyCodes from 'app/utils/keyCodes'; import { pageSession } from 'app/redux/UserSaga' export function showLoginDialog(username, onClose, authType = 'active', saveLogin = false, hint = '') { - let dm, oldZ = '' - DialogManager.showDialog({ component: LoginDialog, adaptive: true, @@ -21,16 +19,9 @@ export function showLoginDialog(username, onClose, authType = 'active', saveLogi hint, }, onClose: (data) => { - if (dm) dm.style.zIndex = oldZ if (onClose) onClose(data) }, }); - - setTimeout(() => { - dm = document.getElementsByClassName('DialogManager')[0] - oldZ = dm ? dm.style.zIndex : '' - if (dm) dm.style.zIndex = 1000 - }, 1) } export default class LoginDialog extends React.PureComponent { diff --git a/src/components/elements/common/DialogManager/index.scss b/src/components/elements/common/DialogManager/index.scss index 42b70609..7714a003 100644 --- a/src/components/elements/common/DialogManager/index.scss +++ b/src/components/elements/common/DialogManager/index.scss @@ -5,7 +5,7 @@ left: 0; right: 0; bottom: 0; - z-index: 500; + z-index: 1000; &__window { position: absolute; diff --git a/src/components/elements/groups/GroupMember.jsx b/src/components/elements/groups/GroupMember.jsx index 869015d0..f4359187 100644 --- a/src/components/elements/groups/GroupMember.jsx +++ b/src/components/elements/groups/GroupMember.jsx @@ -5,7 +5,7 @@ import cn from 'classnames' import Icon from 'app/components/elements/Icon' import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper' import Userpic from 'app/components/elements/Userpic' -import { getMemberType } from 'app/utils/groups' +import { getRoleInGroup } from 'app/utils/groups' class GroupMember extends React.Component { // shouldComponentUpdate(nextProps) { @@ -54,10 +54,13 @@ class GroupMember extends React.Component { render() { const { member, username, currentGroup } = this.props const { account, member_type, joined } = member - const { creatingNew, member_list } = currentGroup + const { creatingNew, } = currentGroup - const amOwner = currentGroup.owner === username - const amModer = amOwner || (member_list && getMemberType(member_list, username) === 'moder') + let { amOwner, amModer } = getRoleInGroup(currentGroup, username) + if (creatingNew) { + amOwner = true + amModer = true + } const isMe = username === account const isOwner = currentGroup.owner === account @@ -86,7 +89,7 @@ class GroupMember extends React.Component { if ((!isMe || isBanned) && amModer) { banBtn = this.groupMember(e, member, 'banned')} /> + onClick={e => this.groupMember(e, member, isBanned ? 'member' : 'banned')} /> } } else { deleteBtn = this.groupMember(e, member, 'member')} />} - {(amOwner || isModer) && this.groupMember(e, member, 'moder')} />} {banBtn} diff --git a/src/components/modules/MessagesTopCenter.jsx b/src/components/modules/MessagesTopCenter.jsx index 2dca6302..329e2b2f 100644 --- a/src/components/modules/MessagesTopCenter.jsx +++ b/src/components/modules/MessagesTopCenter.jsx @@ -28,6 +28,21 @@ class MessagesTopCenter extends React.Component { } openDropdown = (e) => { + e.preventDefault() + let isInside = false + let node = e.target + while (node.parentNode) { + if (node.className.includes('msgs-group-dropdown')) { + isInside = true + return + } + node = node.parentNode + } + if (isInside) return + this.dropdown.current.click() + } + + closeDropdown = (e) => { e.preventDefault() this.dropdown.current.click() } @@ -36,7 +51,8 @@ class MessagesTopCenter extends React.Component { e.preventDefault() const { the_group } = this.props if (!the_group) return - this.props.showGroupMembers({ group: the_group }) + const { name } = the_group + this.props.showGroupMembers({ group: name }) } editGroup = (e) => { @@ -136,36 +152,29 @@ class MessagesTopCenter extends React.Component { if (btnType) { const onBtnClick = async (e) => { e.preventDefault() - this.openDropdown(e) + setTimeout(() => { + this.closeDropdown(e) + }, 500) if (btnType === 'retire') { + let retireWarning + if (privacy !== 'public_group') { + retireWarning =
{tt('msgs_group_dropdown.joining_back_will_require_approval')}
+ } + const res = await DialogManager.dangerConfirm(
- {tt('msgs_group_dropdown.are_you_sure_retire') + ' ' + title + '?'}
, + {tt('msgs_group_dropdown.are_you_sure_retire') + ' ' + title + '?'}{retireWarning}, 'GOLOS Messenger') if (!res) return } - const member_type = btnType === 'join' ? 'pending' : 'retired' + const groupPublic = privacy === 'public_group' + const member_type = btnType === 'join' ? (groupPublic ? 'member' : 'pending') : 'retired' this.props.groupMember({ requester: username, group: name, member: username, member_type, onSuccess: () => { - let ml = [] - if (btnType === 'join') { - ml.push({ - account: username, - member_type: 'pending' - }) - } else { - ml = member_list.map(mem => { - if (mem.account === username) { - mem.member_type = member_type - } - return mem - }) - } - this.props.updateMemberList(ml) }, onError: (err, errStr) => { alert(errStr) @@ -197,9 +206,7 @@ class MessagesTopCenter extends React.Component { title={is_encrypted ? tt('msgs_group_dropdown.encrypted') : tt('msgs_group_dropdown.not_encrypted')} name={is_encrypted ? 'ionicons/lock-closed-outline' : 'ionicons/lock-open-outline'} /> - return
{ - e.stopPropagation() - }}> + return
{title} {lock} @@ -211,7 +218,7 @@ class MessagesTopCenter extends React.Component { : null} {btn} @@ -362,14 +369,11 @@ export default withRouter(connect( })); }, showGroupMembers({ group }) { - dispatch(user.actions.showGroupMembers({ group })) + dispatch(user.actions.showGroupMembers({ group: ['the_group', group] })) }, showGroupSettings({ group }) { dispatch(user.actions.showGroupSettings({ group })) }, - updateMemberList(member_list) { - dispatch(g.actions.updateMemberList({ member_list })) - }, deleteGroup: ({ owner, name, password, onSuccess, onError }) => { const opData = { diff --git a/src/components/modules/MessagesTopCenter.scss b/src/components/modules/MessagesTopCenter.scss index c657929f..f7ac07d6 100644 --- a/src/components/modules/MessagesTopCenter.scss +++ b/src/components/modules/MessagesTopCenter.scss @@ -53,6 +53,8 @@ padding-left: 0.75rem; padding-bottom: 0.75rem; padding-right: 0.75rem; + min-width: 200px; + .button { margin-right: 0px !important; &.margin { diff --git a/src/components/modules/groups/GroupMembers.jsx b/src/components/modules/groups/GroupMembers.jsx index 7480c333..875ddd1a 100644 --- a/src/components/modules/groups/GroupMembers.jsx +++ b/src/components/modules/groups/GroupMembers.jsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux' import { Field, ErrorMessage, } from 'formik' import tt from 'counterpart' import { validateAccountName } from 'golos-lib-js/lib/utils' +import cn from 'classnames' import g from 'app/redux/GlobalReducer' import transaction from 'app/redux/TransactionReducer' @@ -10,17 +11,19 @@ import AccountName from 'app/components/elements/common/AccountName' import Input from 'app/components/elements/common/Input'; import GroupMember from 'app/components/elements/groups/GroupMember' import LoadingIndicator from 'app/components/elements/LoadingIndicator' -import { getMemberType, getGroupMeta, getGroupTitle } from 'app/utils/groups' +import { getRoleInGroup, getGroupMeta, getGroupTitle } from 'app/utils/groups' export async function validateMembersStep(values, errors) { // nothing yet... } class GroupMembers extends React.Component { - state = {} - constructor(props) { super(props) + this.state = { + showModers: false, + showPendings: !!props.showPendings, + } } componentDidMount() { @@ -39,13 +42,20 @@ class GroupMembers extends React.Component { return members.get('loading') } - init = () => { + init = (force = false) => { const { initialized } = this.state - if (!initialized) { + if (!initialized || force) { const { currentGroup } = this.props if (currentGroup) { const group = currentGroup - this.props.fetchGroupMembers(group) + + const memberTypes = ['moder'] + const { showPendings, showBanneds, showModers } = this.state + if (!showModers) memberTypes.push('member') + if (showPendings) memberTypes.push('pending') + if (showBanneds) memberTypes.push('banned') + + this.props.fetchGroupMembers(group, memberTypes) this.setState({ initialized: true }) @@ -53,6 +63,32 @@ class GroupMembers extends React.Component { } } + toggleModers = (e) => { + this.setState({ + showModers: !this.state.showModers + }, () => { + this.init(true) + }) + } + + togglePendings = (e) => { + const { checked } = e.target + this.setState({ + showPendings: checked + }, () => { + this.init(true) + }) + } + + toggleBanneds = (e) => { + const { checked } = e.target + this.setState({ + showBanneds: checked + }, () => { + this.init(true) + }) + } + onAddAccount = (e) => { try { const { value } = e.target @@ -71,7 +107,6 @@ class GroupMembers extends React.Component { member, member_type, onSuccess: () => { - this.props.updateGroupMember(group, member, member_type) }, onError: (err, errStr) => { alert(errStr) @@ -83,6 +118,22 @@ class GroupMembers extends React.Component { } } + _renderMemberTypeSwitch = () => { + const { currentGroup, } = this.props + const { moders, members, } = currentGroup + const { showModers } = this.state + const disabled = !moders + return +
+ {tt('group_members_jsx.all') + ' (' + (moders + members) + ')'} +
+ +
+ {tt('group_members_jsx.moders') + ' (' + moders + ')'} +
+
+ } + render() { const { currentGroup, group, username } = this.props const loading = this.isLoading() @@ -90,6 +141,13 @@ class GroupMembers extends React.Component { if (members) members = members.get('data') if (members) members = members.toJS() + const { creatingNew } = currentGroup + let { amOwner, amModer } = getRoleInGroup(currentGroup, username) + if (creatingNew) { + amOwner = true + amModer = true + } + let mems if (loading) { mems =
@@ -121,13 +179,8 @@ class GroupMembers extends React.Component { filterAccs.add(m.account) } - // TODO: but we should check it in diff cases - const { owner, member_list } = currentGroup - const amOwner = currentGroup.owner === username - const amModer = amOwner || (member_list && getMemberType(member_list, username) === 'moder') - mems =
-
+ {amModer ?
-
+
: null}
{mems} @@ -145,7 +198,6 @@ class GroupMembers extends React.Component { } let header - const { creatingNew } = currentGroup if (creatingNew) { header =
@@ -153,30 +205,38 @@ class GroupMembers extends React.Component {
} else { - const { name, json_metadata } = currentGroup + const { name, json_metadata, pendings, banneds, } = currentGroup const meta = getGroupMeta(json_metadata) let title = getGroupTitle(meta, name) title = tt('group_members_jsx.title') + title + tt('group_members_jsx.title2') + + const { showPendings, showBanneds } = this.state + header =

{title}

-
+ {amModer ?
-
-
+
:
+
+ {this._renderMemberTypeSwitch()} +
+
}
} @@ -194,12 +254,24 @@ export default connect( const username = currentUser && currentUser.get('username') const { newGroup } = ownProps - let currentGroup + let currentGroup, showPendings if (newGroup) { currentGroup = newGroup } else { - currentGroup = state.user.get('current_group') - if (currentGroup) currentGroup = currentGroup.toJS() + const options = state.user.get('group_members_modal') + if (options) { + currentGroup = options.get('group') + showPendings = options.get('show_pendings') + } + if (currentGroup) { + const [ path, name ] = currentGroup + if (path === 'the_group') { + currentGroup = state.global.get('the_group') + } else { + currentGroup = state.global.get('my_groups').find(g => g.get('name') === name) + } + if (currentGroup) currentGroup = currentGroup.toJS() + } } const group = currentGroup && state.global.getIn(['groups', currentGroup.name]) return { @@ -207,12 +279,13 @@ export default connect( username, currentGroup, group, + showPendings, } }, dispatch => ({ - fetchGroupMembers: (group) => { + fetchGroupMembers: (group, memberTypes) => { dispatch(g.actions.fetchGroupMembers({ - group: group.name, creatingNew: !!group.creatingNew })) + group: group.name, creatingNew: !!group.creatingNew, memberTypes, })) }, updateGroupMember: (group, member, member_type) => { dispatch(g.actions.updateGroupMember({ diff --git a/src/components/modules/groups/GroupMembers.scss b/src/components/modules/groups/GroupMembers.scss index 90a2b4e6..4f81660e 100644 --- a/src/components/modules/groups/GroupMembers.scss +++ b/src/components/modules/groups/GroupMembers.scss @@ -5,6 +5,35 @@ vertical-align: top; } @include themify($themes) { + .label { + padding: 0.5rem; + transition: all .1s ease-in; + user-select: none; + + &:not(.disabled) { + cursor: pointer; + } + &:not(.checked) { + background: #f4f4f8; + color: #333333; + } + &:hover:not(.disabled) { + background: #0078C4; + color: #fefefe; + } + + &.moders { + &:hover:not(.disabled) { + background: lime; + color: #333333; + } + &.checked { + background: lime; + color: #333333; + } + } + } + .member-btns { float: right; padding-right: 1rem; @@ -13,7 +42,7 @@ cursor: pointer; padding-top: 0.35rem; transition: all .1s ease-in; - &.selected { + &.selected:not(.ban) { cursor: auto; } } diff --git a/src/components/modules/groups/MyGroups.jsx b/src/components/modules/groups/MyGroups.jsx index 53eb6174..42761739 100644 --- a/src/components/modules/groups/MyGroups.jsx +++ b/src/components/modules/groups/MyGroups.jsx @@ -5,6 +5,7 @@ import { Map } from 'immutable' import { api, formatter } from 'golos-lib-js' import tt from 'counterpart' +import DialogManager from 'app/components/elements/common/DialogManager' import g from 'app/redux/GlobalReducer' import transaction from 'app/redux/TransactionReducer' import user from 'app/redux/UserReducer' @@ -13,7 +14,7 @@ import DropdownMenu from 'app/components/elements/DropdownMenu' import Icon from 'app/components/elements/Icon' import LoadingIndicator from 'app/components/elements/LoadingIndicator' import { showLoginDialog } from 'app/components/dialogs/LoginDialog' -import { getGroupLogo, getGroupMeta } from 'app/utils/groups' +import { getGroupLogo, getGroupMeta, getRoleInGroup } from 'app/utils/groups' class MyGroups extends React.Component { constructor(props) { @@ -69,14 +70,44 @@ class MyGroups extends React.Component { })) } + retireCancel = async (e, group, title, isPending) => { + e.preventDefault() + e.stopPropagation() + const { username } = this.props + + let retireWarning + if (!isPending && group.privacy !== 'public_group') { + retireWarning =
{tt('msgs_group_dropdown.joining_back_will_require_approval')}
+ } + const res = await DialogManager.dangerConfirm(
+ {(isPending ? tt('my_groups_jsx.are_you_sure_cancel') : tt('msgs_group_dropdown.are_you_sure_retire')) + + ' ' + title + '?'} + {retireWarning}
, + 'GOLOS Messenger') + if (!res) return + + this.props.groupMember({ + requester: username, group: group.name, + member: username, + member_type: 'retired', + onSuccess: () => { + this.refetch() + }, + onError: (err, errStr) => { + alert(errStr) + } + }) + } + showGroupSettings = (e, group) => { e.preventDefault() this.props.showGroupSettings({ group }) } - showGroupMembers = (e, group) => { + showGroupMembers = (e, group, show_pendings) => { e.preventDefault() - this.props.showGroupMembers({ group }) + const { name } = group + this.props.showGroupMembers({ group: name, show_pendings }) } onGoGroup = (e) => { @@ -85,7 +116,7 @@ class MyGroups extends React.Component { } _renderGroup = (group) => { - const { name, json_metadata } = group + const { name, json_metadata, pendings } = group const meta = getGroupMeta(json_metadata) @@ -97,9 +128,22 @@ class MyGroups extends React.Component { const kebabItems = [] - kebabItems.push({ link: '#', onClick: e => { - this.deleteGroup(e, group, titleShr) - }, value: tt('g.delete') }) + const { username } = this.props + const { amOwner, amModer, amPending, amMember } = getRoleInGroup(group, username) + + if (amOwner) { + kebabItems.push({ link: '#', onClick: e => { + this.showGroupSettings(e, group) + }, value: tt('my_groups_jsx.edit') }) + kebabItems.push({ link: '#', onClick: e => { + this.deleteGroup(e, group, titleShr) + }, value: tt('g.delete') }) + } + if (amMember || (amModer && !amOwner)) { + kebabItems.push({ link: '#', onClick: e => { + this.retireCancel(e, group, titleShr, amPending) + }, value: tt('msgs_group_dropdown.retire') }) + } return @@ -109,19 +153,33 @@ class MyGroups extends React.Component { { e.preventDefault() + e.stopPropagation() }}> + {amPending ? : null} + {(amModer && pendings) ? : null} - + : null*/} {kebabItems.length ? : null} @@ -145,7 +203,7 @@ class MyGroups extends React.Component { {tt('my_groups_jsx.empty')} {tt('my_groups_jsx.empty2')} - {tt('my_groups_jsx.create')} + {tt('my_groups_jsx.create')}.
} else { @@ -175,6 +233,7 @@ class MyGroups extends React.Component {
{button} {groups} + {hasGroups ?
: null}
} } @@ -182,12 +241,12 @@ class MyGroups extends React.Component { export default connect( (state, ownProps) => { const currentUser = state.user.getIn(['current']) - const currentAccount = currentUser && state.global.getIn(['accounts', currentUser.get('username')]) + const username = currentUser && currentUser.get('username') const my_groups = state.global.get('my_groups') return { ...ownProps, currentUser, - currentAccount, + username, my_groups, } }, @@ -203,8 +262,8 @@ export default connect( showGroupSettings({ group }) { dispatch(user.actions.showGroupSettings({ group })) }, - showGroupMembers({ group }) { - dispatch(user.actions.showGroupMembers({ group })) + showGroupMembers({ group, show_pendings }) { + dispatch(user.actions.showGroupMembers({ group: ['my_groups', group], show_pendings })) }, deleteGroup: ({ owner, name, password, onSuccess, onError }) => { @@ -231,6 +290,35 @@ export default connect( if (onError) onError(err, errStr) }, })); - } + }, + groupMember: ({ requester, group, member, member_type, + onSuccess, onError }) => { + const opData = { + requester, + name: group, + member, + member_type, + json_metadata: '{}', + extensions: [], + } + + const plugin = 'private_message' + const json = JSON.stringify(['private_group_member', opData]) + + dispatch(transaction.actions.broadcastOperation({ + type: 'custom_json', + operation: { + id: plugin, + required_posting_auths: [requester], + json, + }, + username: requester, + successCallback: onSuccess, + errorCallback: (err, errStr) => { + console.error(err) + if (onError) onError(err, errStr) + }, + })); + }, }) )(MyGroups) diff --git a/src/components/modules/groups/MyGroups.scss b/src/components/modules/groups/MyGroups.scss index bee27a8d..83ac2bb7 100644 --- a/src/components/modules/groups/MyGroups.scss +++ b/src/components/modules/groups/MyGroups.scss @@ -16,6 +16,7 @@ .group-buttons { float: right; padding-top: 0.8rem; + cursor: default; .button { vertical-align: middle; margin-bottom: 0px; diff --git a/src/locales/en.json b/src/locales/en.json index adc9d20a..d426e765 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -104,6 +104,22 @@ "blocked_BY": "You are blocked by @%(BY)s.", "do_not_bother_BY": "@%(BY)s wants to not be bothered by low-reputation users." }, + "msgs_group_dropdown": { + "join": "Join", + "retire": "Retire", + "cancel": "Cancel", + "public": "Public group", + "read_only": "Public only as read-only", + "private": "Private group", + "encrypted": "Messages are encrypted", + "not_encrypted": "Messages are not encrypted", + "banned": "You are banned.", + "pending": "You requested membership.", + "moder": "You are moderator.", + "owner": "You are owner.", + "are_you_sure_retire": "Are you sure you want to retire from ", + "joining_back_will_require_approval": "Joining it back will require approval." + }, "msgs_start_panel": { "start_chat": "Start chat", "create_group": "Create group" @@ -163,9 +179,11 @@ "group_members_jsx": { "title": "Members Of ", "title2": " Group", - "check_pending": "Join Requests", + "check_pending": "Requests", "check_pending_hint": "Pending Group Join Requests", "check_banned": "Blocked", + "all": "All", + "moders": "Moderators", "member": "Member", "moder": "Moderator", "owner": "Group Owner", @@ -277,6 +295,13 @@ "confirm": "Confirmation", "prompt": "Enter the data" }, + "plurals": { + "member_count": { + "zero": "0 members", + "one": "1 member", + "other": "%(count)s members" + } + }, "g": { "and": "and", "blog": "Blog", diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json index cfa69686..c541f558 100644 --- a/src/locales/ru-RU.json +++ b/src/locales/ru-RU.json @@ -118,7 +118,8 @@ "pending": "Вы подали заявку на вступление.", "moder": "Вы модератор.", "owner": "Вы владелец.", - "are_you_sure_retire": "Вы уверены, что хотите покинуть группу" + "are_you_sure_retire": "Вы уверены, что хотите покинуть группу", + "joining_back_will_require_approval": "Вступить обратно вы сможете только после одобрения заявки." }, "msgs_start_panel": { "start_chat": "Начать чат", @@ -184,7 +185,9 @@ "create_more": "+ Создать еще группу", "edit": "Изменить", "login_hint_GROUP": "(удаления группы \"%(GROUP)s\")", - "members": "Участники" + "members": "Участники", + "cancel_pending": "Отменить заявку", + "are_you_sure_cancel": "Вы уверены, что хотите отказаться от вступления в группу" }, "group_members_jsx": { "title": "Участники группы ", @@ -192,6 +195,8 @@ "check_pending": "Заявки", "check_pending_hint": "Заявки на вступление в группу", "check_banned": "Забаненные", + "all": "Все", + "moders": "Модераторы", "member": "Обычный участник", "moder": "Модератор", "owner": "Владелец группы", diff --git a/src/redux/FetchDataSaga.js b/src/redux/FetchDataSaga.js index 232521e3..c4127b6b 100644 --- a/src/redux/FetchDataSaga.js +++ b/src/redux/FetchDataSaga.js @@ -167,7 +167,7 @@ export function* watchFetchGroupMembers() { yield takeLatest('global/FETCH_GROUP_MEMBERS', fetchGroupMembers) } -export function* fetchGroupMembers({ payload: { group, creatingNew } }) { +export function* fetchGroupMembers({ payload: { group, creatingNew, memberTypes } }) { try { if (creatingNew) { yield put(g.actions.receiveGroupMembers({ group, members: [], append: true })) @@ -178,7 +178,7 @@ export function* fetchGroupMembers({ payload: { group, creatingNew } }) { const members = yield call([api, api.getGroupMembersAsync], { group, - member_types: ['pending', 'member', 'moder'/*, 'banned'*/], + member_types: memberTypes, start_member: '', limit: 100, }) diff --git a/src/redux/GlobalReducer.js b/src/redux/GlobalReducer.js index 80b8ea02..1942a143 100644 --- a/src/redux/GlobalReducer.js +++ b/src/redux/GlobalReducer.js @@ -4,6 +4,31 @@ import { Asset } from 'golos-lib-js/lib/utils' import { processDatedGroup } from 'app/utils/MessageUtils' +const updateInMyGroups = (state, group, groupUpdater, groupsUpserter = mg => mg) => { + state = state.update('my_groups', null, mg => { + if (!mg) return mg + const i = mg.findIndex(gro => gro.get('name') === group) + if (i === -1) return groupsUpserter(mg) + mg = mg.update(i, (gro) => { + if (!gro) return + + return groupUpdater(gro) + }) + return mg + }) + return state +} + +const updateTheGroup = (state, group, groupUpdater) => { + state = state.update('the_group', null, (gro) => { + if (!gro) return + if (gro.get('name') !== group) return gro + + return groupUpdater(gro) + }) + return state +} + export default createModule({ name: 'global', initialState: new Map({ @@ -308,11 +333,45 @@ export default createModule({ return new_state }, }, + { + action: 'UPSERT_GROUP', + reducer: (state, { payload }) => { + const { creator, name, is_encrypted, privacy, json_metadata } = payload + let new_state = state + const groupUpdater = gro => { + gro = gro.set('json_metadata', json_metadata) + gro = gro.set('privacy', privacy) + return gro + } + const groupsUpserter = myGroups => { + const now = new Date().toISOString().split('.')[0] + myGroups = myGroups.insert(0, fromJS({ + owner: creator, + name, + json_metadata, + is_encrypted, + privacy, + created: now, + admins: 0, + moders: 0, + members: 0, + pendings: 0, + banneds: 0, + member_list: [] + })) + return myGroups + } + new_state = updateInMyGroups(new_state, name, groupUpdater, groupsUpserter) + new_state = updateTheGroup(new_state, name, groupUpdater) + return new_state + } + }, { action: 'UPDATE_GROUP_MEMBER', reducer: (state, { payload: { group, member, member_type } }) => { const now = new Date().toISOString().split('.')[0] let new_state = state + let oldType new_state = state.updateIn(['groups', group], Map(), gro => { @@ -320,6 +379,7 @@ export default createModule({ const retiring = member_type === 'retired' const idx = mems.findIndex(i => i.get('account') === member) if (idx !== -1) { + oldType = mems.get(idx).get('member_type') if (retiring) { mems = mems.remove(idx) } else { @@ -343,50 +403,52 @@ export default createModule({ }) return gro }) - return new_state - }, - }, - { - action: 'UPDATE_MEMBER_LIST', - reducer: (state, { payload: { member_list } }) => { - let new_state = state - const updater = (gro) => { - const mMap = {} - for (const mem of member_list) { - const { account } = mem - mMap[account] = { ...mMap[account], ...mem } - } + const groupUpdater = gro => { if (!gro.has('member_list')) { gro = gro.set('member_list', List()) } gro = gro.update('member_list', List(), data => { let newList = List() + let found data.forEach((mem, i) => { - const acc = mem.get('account') - if (mMap[acc]) { - if (mMap[acc].member_type !== 'retired') { - const newMem = mem.mergeDeep(fromJS(mMap[acc])) + if (mem.get('account') === member) { + found = true + if (!oldType) oldType = mem.get('member_type') + if (member_type !== 'retired') { + const newMem = mem.set('member_type', member_type) newList = newList.push(newMem) } - delete mMap[acc] } else { newList = newList.push(mem) } }) - const addVals = Object.values(mMap) - for (const av of addVals) { - if (av.member_type !== 'retired') { - newList = newList.push(fromJS(av)) - } + if (!found) { + newList = newList.push(fromJS({ + account: member, + member_type, + })) } return newList }) + + const updateByType = (t, updater) => { + if (t === 'member') { + gro = gro.update('members', updater) + } else if (t === 'moder') { + gro = gro.update('moders', updater) + } else if (t === 'pending') { + gro = gro.update('pendings', updater) + } else if (t === 'banned') { + gro = gro.update('banneds', updater) + } + } + updateByType(oldType, n => --n) + updateByType(member_type, n => ++n) + return gro } - new_state = new_state.update('the_group', Map(), gro => { - gro = updater(gro) - return gro - }) + new_state = updateInMyGroups(new_state, group, groupUpdater) + new_state = updateTheGroup(new_state, group, groupUpdater) return new_state }, }, diff --git a/src/redux/TransactionSaga.js b/src/redux/TransactionSaga.js index d543f2ac..534e3850 100644 --- a/src/redux/TransactionSaga.js +++ b/src/redux/TransactionSaga.js @@ -23,43 +23,10 @@ function* accepted_custom_json({operation}) { const json = JSON.parse(operation.json) if (operation.id === 'private_message') { if (json[0] === 'private_group') { - yield put(g.actions.update({ - key: ['my_groups'], - notSet: List(), - updater: groups => { - const idx = groups.findIndex(i => i.get('name') === json[1].name) - if (idx === -1) { - const now = new Date().toISOString().split('.')[0] - groups = groups.insert(0, fromJS({ - owner: json[1].creator, - name: json[1].name, - json_metadata: json[1].json_metadata, - is_encrypted: json[1].is_encrypted, - privacy: json[1].privacy, - created: now, - admins: 0, - moders: 0, - members: 0, - pendings: 0, - member_list: [{ - account: json[1].creator, - group: json[1].name, - invited: json[1].creator, - joined: now, - json_metadata: '{}', - member_type: 'admin', - updated: now - }] - })) - } else { - groups = groups.update(idx, g => { - g = g.set('json_metadata', json[1].json_metadata); - return g; - }); - } - return groups - } - })) + yield put(g.actions.upsertGroup(json[1])) + } else if (json[0] === 'private_group_member') { + const { name, member, member_type } = json[1] + yield put(g.actions.updateGroupMember({ group: name, member, member_type, })) } } return operation diff --git a/src/redux/UserReducer.js b/src/redux/UserReducer.js index 8f50ddf6..1ab53e4f 100644 --- a/src/redux/UserReducer.js +++ b/src/redux/UserReducer.js @@ -146,9 +146,12 @@ export default createModule({ return state }}, { action: 'HIDE_GROUP_SETTINGS', reducer: state => state.set('show_group_settings_modal', false) }, - { action: 'SHOW_GROUP_MEMBERS', reducer: (state, { payload: { group }}) => { + { action: 'SHOW_GROUP_MEMBERS', reducer: (state, { payload: { group, show_pendings }}) => { state = state.set('show_group_members_modal', true) - state = state.set('current_group', fromJS(group)) + state = state.set('group_members_modal', fromJS({ + group, + show_pendings, + })) return state }}, { action: 'HIDE_GROUP_MEMBERS', reducer: state => state.set('show_group_members_modal', false) }, diff --git a/src/utils/groups.js b/src/utils/groups.js index 3106c1fb..a6d63602 100644 --- a/src/utils/groups.js +++ b/src/utils/groups.js @@ -1,11 +1,5 @@ import { proxifyImageUrlWithStrip } from 'app/utils/ProxifyUrl' -const getMemberType = (member_list, username) => { - const mem = member_list.find(pgm => pgm.account === username) - const { member_type } = (mem || {}) - return member_type -} - const getGroupMeta = (json_metadata) => { let meta if (json_metadata) { @@ -37,9 +31,29 @@ const getGroupLogo = (json_metadata) => { return logo } +const getMemberType = (member_list, username) => { + const mem = member_list.find(pgm => pgm.account === username) + const { member_type } = (mem || {}) + return member_type +} + +const getRoleInGroup = (group, username) => { + if (group.toJS) group = group.toJS() + const { owner, member_list } = group + const memberType = member_list && getMemberType(member_list, username) + + const amOwner = owner === username + const amModer = amOwner || memberType === 'moder' + const amPending = memberType === 'pending' + const amMember = memberType === 'member' + + return { amOwner, amModer, amPending, amMember } +} + export { - getMemberType, getGroupMeta, getGroupTitle, getGroupLogo, + getMemberType, + getRoleInGroup, }