make moderators and groupAdmins a single name

This commit is contained in:
Audric Ackermann 2021-01-19 13:57:05 +11:00
parent 6a776b56f6
commit dc0733968d
No known key found for this signature in database
GPG key ID: 999F434D76324AD4
22 changed files with 218 additions and 182 deletions

View file

@ -65,7 +65,7 @@ export interface ConversationModel
isRss: () => boolean;
isBlocked: () => boolean;
isClosable: () => boolean;
isModerator: (id: string) => boolean;
isAdmin: (id: string) => boolean;
throttledBumpTyping: () => void;
messageCollection: Backbone.Collection<MessageModel>;

View file

@ -457,14 +457,18 @@
format() {
return this.cachedProps;
},
getGroupAdmins() {
return this.get('groupAdmins') || this.get('moderators');
},
getProps() {
const { format } = PhoneNumber;
const regionCode = storage.get('regionCode');
const typingKeys = Object.keys(this.contactTypingTimers || {});
const groupAdmins = this.isPublic()
? this.get('moderators')
: this.get('groupAdmins');
const groupAdmins = this.getGroupAdmins();
const members =
this.isGroup() && !this.isPublic() ? this.get('members') : undefined;
const result = {
id: this.id,
@ -499,7 +503,7 @@
isKickedFromGroup: !!this.get('isKickedFromGroup'),
left: !!this.get('left'),
groupAdmins,
members,
onClick: () => this.trigger('select', this),
onBlockContact: () => this.block(),
onUnblockContact: () => this.unblock(),
@ -676,6 +680,15 @@
}
},
async updateGroupAdmins(groupAdmins) {
const existingAdmins = _.sortBy(this.getGroupAdmins());
const newAdmins = _.sortBy(groupAdmins);
if (_.isEqual(existingAdmins, newAdmins)) {
window.log.info(
'Skipping updates of groupAdmins/moderators. No change detected.'
);
return;
}
this.set({ groupAdmins });
await this.commit();
},
@ -1828,27 +1841,16 @@
await this.commit();
}
},
isModerator(pubKey) {
isAdmin(pubKey) {
if (!this.isPublic()) {
return false;
}
if (!pubKey) {
throw new Error('isModerator() pubKey is falsy');
throw new Error('isAdmin() pubKey is falsy');
}
const moderators = this.get('moderators');
return Array.isArray(moderators) && moderators.includes(pubKey);
const groupAdmins = this.getGroupAdmins();
return Array.isArray(groupAdmins) && groupAdmins.includes(pubKey);
},
async setModerators(moderators) {
if (!this.isPublic()) {
return;
}
// TODO: compare array properly
if (!_.isEqual(this.get('moderators'), moderators)) {
this.set({ moderators });
await this.commit();
}
},
// SIGNAL PROFILES
onChangeProfileKey() {

View file

@ -568,8 +568,7 @@
// for the public group chat
const conversation = this.getConversation();
const isModerator =
conversation && !!conversation.isModerator(phoneNumber);
const isAdmin = conversation && !!conversation.isAdmin(phoneNumber);
const convoId = conversation ? conversation.id : undefined;
const isGroup = !!conversation && !conversation.isPrivate();
@ -606,9 +605,9 @@
conversation && conversation.get('isKickedFromGroup'),
isDeletable:
!this.get('isPublic') ||
isModerator ||
isAdmin ||
phoneNumber === textsecure.storage.user.getNumber(),
isModerator,
isAdmin,
onCopyText: () => this.copyText(),
onCopyPubKey: () => this.copyPubKey(),

View file

@ -1204,7 +1204,7 @@ class LokiPublicChannelAPI {
}
if (this.running) {
await this.conversation.setModerators(moderators || []);
await this.conversation.updateGroupAdmins(moderators || []);
}
}

View file

@ -31,7 +31,7 @@
this.titleText = i18n('updateGroupDialogTitle', this.groupName);
// I'd much prefer to integrate mods with groupAdmins
// but lets discuss first...
this.isAdmin = groupConvo.isModerator(
this.isAdmin = groupConvo.isAdmin(
window.storage.get('primaryDevicePubKey')
);
}
@ -92,7 +92,7 @@
this.titleText = i18n('updateGroupDialogTitle', this.groupName);
// I'd much prefer to integrate mods with groupAdmins
// but lets discuss first...
this.isAdmin = groupConvo.isModerator(
this.isAdmin = groupConvo.isAdmin(
window.storage.get('primaryDevicePubKey')
);
// zero out contactList for now

View file

@ -69,7 +69,6 @@ export class LeftPane extends React.Component<Props> {
}
public render(): JSX.Element {
const ourPrimaryConversation = this.props.ourPrimaryConversation;
return (
<SessionTheme theme={this.props.theme}>
<div className="module-left-pane-session">
@ -77,8 +76,6 @@ export class LeftPane extends React.Component<Props> {
{...this.props}
selectedSection={this.props.focusedSection}
onSectionSelected={this.handleSectionSelected}
unreadMessageCount={this.props.unreadMessageCount}
ourPrimaryConversation={ourPrimaryConversation}
/>
<div className="module-left-pane">{this.renderSection()}</div>
</div>
@ -162,22 +159,13 @@ export class LeftPane extends React.Component<Props> {
}
private renderSettingSection() {
const {
isSecondaryDevice,
showSessionSettingsCategory,
settingsCategory,
} = this.props;
const { settingsCategory } = this.props;
const category = settingsCategory || SessionSettingCategory.Appearance;
return (
<>
<LeftPaneSettingSection
{...this.props}
isSecondaryDevice={isSecondaryDevice}
showSessionSettingsCategory={showSessionSettingsCategory}
settingsCategory={category}
/>
<LeftPaneSettingSection {...this.props} settingsCategory={category} />
</>
);
}

View file

@ -52,8 +52,8 @@ interface LinkPreviewType {
export interface Props {
disableMenu?: boolean;
isDeletable: boolean;
isModerator?: boolean;
weAreModerator?: boolean;
isAdmin?: boolean;
weAreAdmin?: boolean;
text?: string;
bodyPending?: boolean;
id: string;
@ -574,7 +574,7 @@ class MessageInner extends React.PureComponent<Props, State> {
authorPhoneNumber,
authorProfileName,
collapseMetadata,
isModerator,
isAdmin,
conversationType,
direction,
onShowUserDetails,
@ -605,7 +605,7 @@ class MessageInner extends React.PureComponent<Props, State> {
}}
pubkey={authorPhoneNumber}
/>
{isModerator && (
{isAdmin && (
<div className="module-avatar__icon--crown-wrapper">
<div className="module-avatar__icon--crown" />
</div>
@ -692,7 +692,7 @@ class MessageInner extends React.PureComponent<Props, State> {
onRetrySend,
onShowDetail,
isPublic,
weAreModerator,
weAreAdmin,
onBanUser,
} = this.props;
@ -760,7 +760,7 @@ class MessageInner extends React.PureComponent<Props, State> {
</Item>
</>
) : null}
{weAreModerator && isPublic ? (
{weAreAdmin && isPublic ? (
<Item onClick={onBanUser}>{window.i18n('banUser')}</Item>
) : null}
</Menu>

View file

@ -14,7 +14,7 @@ import styled, { DefaultTheme } from 'styled-components';
type Props = {
disableMenu?: boolean;
isModerator?: boolean;
isAdmin?: boolean;
isDeletable: boolean;
text?: string;
bodyPending?: boolean;
@ -74,7 +74,7 @@ export const MessageMetadata = (props: Props) => {
serverTimestamp,
isShowingImage,
isPublic,
isModerator,
isAdmin,
theme,
} = props;
@ -109,7 +109,7 @@ export const MessageMetadata = (props: Props) => {
<MetadataBadges
direction={direction}
isPublic={isPublic}
isModerator={isModerator}
isAdmin={isAdmin}
id={id}
withImageNoCaption={withImageNoCaption}
/>

View file

@ -45,15 +45,15 @@ type BadgesProps = {
id: string;
direction: string;
isPublic?: boolean;
isModerator?: boolean;
isAdmin?: boolean;
withImageNoCaption: boolean;
};
export const MetadataBadges = (props: BadgesProps): JSX.Element => {
const { id, direction, isPublic, isModerator, withImageNoCaption } = props;
const { id, direction, isPublic, isAdmin, withImageNoCaption } = props;
const badges = [
(isPublic && 'Public') || null,
(isModerator && 'Mod') || null,
(isAdmin && 'Mod') || null,
].filter(nonNullish);
if (!badges || badges.length === 0) {

View file

@ -10,10 +10,11 @@ import { ConversationType } from '../../state/ducks/conversations';
import { noop } from 'lodash';
import { DefaultTheme } from 'styled-components';
import { StateType } from '../../state/reducer';
import { MessageEncrypter } from '../../session/crypto';
import { PubKey } from '../../session/types';
import { UserUtil } from '../../util';
import { ConversationController } from '../../session/conversations';
import { getFocusedSection } from '../../state/selectors/section';
import { getTheme } from '../../state/selectors/theme';
import { getPrimaryPubkey } from '../../state/selectors/user';
// tslint:disable-next-line: no-import-side-effect no-submodule-imports
export enum SectionType {
@ -30,6 +31,7 @@ interface Props {
selectedSection: SectionType;
unreadMessageCount: number;
ourPrimaryConversation: ConversationType;
ourPrimary: string;
applyTheme?: any;
theme: DefaultTheme;
}
@ -68,6 +70,7 @@ class ActionsPanelPrivate extends React.Component<Props> {
avatarPath?: string;
notificationCount?: number;
}) => {
const { ourPrimary } = this.props;
const handleClick = onSelect
? () => {
/* tslint:disable:no-void-expression */
@ -89,7 +92,6 @@ class ActionsPanelPrivate extends React.Component<Props> {
: undefined;
if (type === SectionType.Profile) {
const ourPrimary = window.storage.get('primaryDevicePubKey');
const conversation = ConversationController.getInstance().get(ourPrimary);
const profile = conversation?.getLokiProfile();
@ -202,11 +204,10 @@ class ActionsPanelPrivate extends React.Component<Props> {
}
const mapStateToProps = (state: StateType) => {
const { section, theme } = state;
return {
section: section.focusedSection,
theme,
section: getFocusedSection(state),
theme: getTheme(state),
ourPrimary: getPrimaryPubkey(state),
};
};

View file

@ -29,6 +29,7 @@ import { MemberItem } from '../../conversation/MemberList';
import { CaptionEditor } from '../../CaptionEditor';
import { DefaultTheme } from 'styled-components';
import { ConversationController } from '../../../session/conversations/ConversationController';
import { ConversationType } from '../../../state/ducks/conversations';
export interface ReplyingToMessageProps {
convoId: string;
@ -64,7 +65,8 @@ interface Props {
isPrivate: boolean;
isKickedFromGroup: boolean;
left: boolean;
conversationKey: string;
selectedConversationKey: string;
selectedConversation: ConversationType | undefined;
isPublic: boolean;
quotedMessageProps?: ReplyingToMessageProps;
@ -186,7 +188,9 @@ export class SessionCompositionBox extends React.Component<Props, State> {
}
public componentDidUpdate(prevProps: Props, _prevState: State) {
// reset the state on new conversation key
if (prevProps.conversationKey !== this.props.conversationKey) {
if (
prevProps.selectedConversationKey !== this.props.selectedConversationKey
) {
this.setState(getDefaultState(), this.focusCompositionBox);
this.lastBumpTypingMessageLength = 0;
} else if (
@ -452,13 +456,14 @@ export class SessionCompositionBox extends React.Component<Props, State> {
}
private fetchUsersForClosedGroup(query: any, callback: any) {
const conversationModel = ConversationController.getInstance().get(
this.props.conversationKey
);
if (!conversationModel) {
const { selectedConversation } = this.props;
if (!selectedConversation) {
return;
}
const allPubKeys = selectedConversation.members;
if (!allPubKeys || allPubKeys.length === 0) {
return;
}
const allPubKeys = conversationModel.get('members');
const allMembers = allPubKeys.map(pubKey => {
const conv = ConversationController.getInstance().get(pubKey);
@ -724,7 +729,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
// catching ESC, tab, or whatever which is not typing
if (message.length && message.length !== this.lastBumpTypingMessageLength) {
const conversationModel = ConversationController.getInstance().get(
this.props.conversationKey
this.props.selectedConversationKey
);
if (!conversationModel) {
return;

View file

@ -72,8 +72,8 @@ interface State {
interface Props {
ourPrimary: string;
conversationKey: string;
conversation: ConversationType;
selectedConversationKey: string;
selectedConversation?: ConversationType;
theme: DefaultTheme;
messages: Array<any>;
actions: any;
@ -88,13 +88,7 @@ export class SessionConversation extends React.Component<Props, State> {
constructor(props: any) {
super(props);
const { conversationKey } = this.props;
const conversationModel = ConversationController.getInstance().get(
conversationKey
);
const unreadCount = conversationModel?.get('unreadCount') || 0;
const unreadCount = this.props.selectedConversation?.unreadCount || 0;
this.state = {
messageProgressVisible: false,
sendingProgress: 0,
@ -163,10 +157,10 @@ export class SessionConversation extends React.Component<Props, State> {
public componentDidUpdate(prevProps: Props, prevState: State) {
const {
conversationKey: newConversationKey,
conversation: newConversation,
selectedConversationKey: newConversationKey,
selectedConversation: newConversation,
} = this.props;
const { conversationKey: oldConversationKey } = prevProps;
const { selectedConversationKey: oldConversationKey } = prevProps;
// if the convo is valid, and it changed, register for drag events
if (
@ -255,18 +249,19 @@ export class SessionConversation extends React.Component<Props, State> {
} = this.state;
const selectionMode = !!selectedMessages.length;
const { conversation, conversationKey, messages } = this.props;
const conversationModel = ConversationController.getInstance().get(
conversationKey
);
const {
selectedConversation,
selectedConversationKey,
messages,
} = this.props;
if (!conversationModel || !messages) {
if (!selectedConversation || !messages) {
// return an empty message view
return <MessageView />;
}
const { isRss } = conversation;
const conversationModel = ConversationController.getInstance().get(
selectedConversationKey
);
// TODO VINCE: OPTIMISE FOR NEW SENDING???
const sendMessageFn = (
body: any,
@ -276,6 +271,9 @@ export class SessionConversation extends React.Component<Props, State> {
groupInvitation: any,
otherOptions: any
) => {
if (!conversationModel) {
return;
}
void conversationModel.sendMessage(
body,
attachments,
@ -292,11 +290,12 @@ export class SessionConversation extends React.Component<Props, State> {
}
};
const shouldRenderRightPanel = !conversationModel.isRss();
const showSafetyNumber = infoViewState === 'safetyNumber';
const showMessageDetails = !!messageDetailShowProps;
const isPublic = selectedConversation.isPublic || false;
const isPrivate = selectedConversation.type === 'direct';
return (
<SessionTheme theme={this.props.theme}>
<div className="conversation-header">{this.renderHeader()}</div>
@ -344,45 +343,41 @@ export class SessionConversation extends React.Component<Props, State> {
{isDraggingFile && <SessionFileDropzone />}
</div>
{!isRss && (
// tslint:disable-next-line: use-simple-attributes
<SessionCompositionBox
isBlocked={conversation.isBlocked}
left={conversation.left}
isKickedFromGroup={conversation.isKickedFromGroup}
isPrivate={conversation.type === 'direct'}
isPublic={conversation.isPublic || false}
conversationKey={conversationKey}
sendMessage={sendMessageFn}
stagedAttachments={stagedAttachments}
onMessageSending={this.onMessageSending}
onMessageSuccess={this.onMessageSuccess}
onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
quotedMessageProps={quotedMessageProps}
removeQuotedMessage={() => {
void this.replyToMessage(undefined);
}}
textarea={this.compositionBoxRef}
clearAttachments={this.clearAttachments}
removeAttachment={this.removeAttachment}
onChoseAttachments={this.onChoseAttachments}
theme={this.props.theme}
/>
)}
<SessionCompositionBox
isBlocked={selectedConversation.isBlocked}
left={selectedConversation.left}
isKickedFromGroup={selectedConversation.isKickedFromGroup}
isPrivate={isPrivate}
isPublic={isPublic}
selectedConversationKey={selectedConversationKey}
selectedConversation={selectedConversation}
sendMessage={sendMessageFn}
stagedAttachments={stagedAttachments}
onMessageSending={this.onMessageSending}
onMessageSuccess={this.onMessageSuccess}
onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
quotedMessageProps={quotedMessageProps}
removeQuotedMessage={() => {
void this.replyToMessage(undefined);
}}
textarea={this.compositionBoxRef}
clearAttachments={this.clearAttachments}
removeAttachment={this.removeAttachment}
onChoseAttachments={this.onChoseAttachments}
theme={this.props.theme}
/>
</div>
{shouldRenderRightPanel && (
<div
className={classNames(
'conversation-item__options-pane',
showOptionsPane && 'show'
)}
>
<SessionRightPanelWithDetails {...this.getRightPanelProps()} />
</div>
)}
<div
className={classNames(
'conversation-item__options-pane',
showOptionsPane && 'show'
)}
>
<SessionRightPanelWithDetails {...this.getRightPanelProps()} />
</div>
</SessionTheme>
);
}
@ -397,33 +392,34 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public async loadInitialMessages() {
const { conversationKey } = this.props;
const conversationModel = ConversationController.getInstance().get(
conversationKey
);
if (!conversationModel) {
const { selectedConversation, selectedConversationKey } = this.props;
if (!selectedConversation) {
return;
}
const conversationModel = ConversationController.getInstance().get(
selectedConversationKey
);
const unreadCount = await conversationModel.getUnreadCount();
const messagesToFetch = Math.max(
Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT,
unreadCount
);
this.props.actions.fetchMessagesForConversation({
conversationKey,
conversationKey: selectedConversationKey,
count: messagesToFetch,
});
}
public getHeaderProps() {
const { conversationKey } = this.props;
const { selectedConversationKey, ourPrimary } = this.props;
const {
selectedMessages,
infoViewState,
messageDetailShowProps,
} = this.state;
const conversation = ConversationController.getInstance().getOrThrow(
conversationKey
selectedConversationKey
);
const expireTimer = conversation.get('expireTimer');
const expirationSettingName = expireTimer
@ -446,9 +442,7 @@ export class SessionConversation extends React.Component<Props, State> {
isPrivate: conversation.isPrivate(),
isPublic: conversation.isPublic(),
isRss: conversation.isRss(),
isAdmin: conversation.isModerator(
window.storage.get('primaryDevicePubKey')
),
isAdmin: conversation.isAdmin(ourPrimary),
members,
subscriberCount: conversation.get('subscriberCount'),
isKickedFromGroup: conversation.get('isKickedFromGroup'),
@ -524,17 +518,23 @@ export class SessionConversation extends React.Component<Props, State> {
}
public getMessagesListProps() {
const { conversation, ourPrimary, messages, actions } = this.props;
const {
selectedConversation,
selectedConversationKey,
ourPrimary,
messages,
actions,
} = this.props;
const { quotedMessageTimestamp, selectedMessages } = this.state;
return {
selectedMessages,
ourPrimary,
conversationKey: conversation.id,
conversationKey: selectedConversationKey,
messages,
resetSelection: this.resetSelection,
quotedMessageTimestamp,
conversation,
conversation: selectedConversation as ConversationType,
selectMessage: this.selectMessage,
deleteMessage: this.deleteMessage,
fetchMessagesForConversation: actions.fetchMessagesForConversation,
@ -548,9 +548,9 @@ export class SessionConversation extends React.Component<Props, State> {
}
public getRightPanelProps() {
const { conversationKey } = this.props;
const { selectedConversationKey } = this.props;
const conversation = ConversationController.getInstance().getOrThrow(
conversationKey
selectedConversationKey
);
const ourPrimary = window.storage.get('primaryDevicePubKey');
@ -558,7 +558,7 @@ export class SessionConversation extends React.Component<Props, State> {
const isAdmin = conversation.isMediumGroup()
? true
: conversation.isPublic()
? conversation.isModerator(ourPrimary)
? conversation.isAdmin(ourPrimary)
: false;
return {
@ -670,26 +670,32 @@ export class SessionConversation extends React.Component<Props, State> {
askUserForConfirmation: boolean
) {
// Get message objects
const { conversationKey, messages } = this.props;
const {
selectedConversationKey,
selectedConversation,
messages,
} = this.props;
const conversationModel = ConversationController.getInstance().getOrThrow(
conversationKey
selectedConversationKey
);
if (!selectedConversation) {
window.log.info('No valid selected conversation.');
return;
}
const selectedMessages = messages.filter(message =>
messageIds.find(selectedMessage => selectedMessage === message.id)
);
const multiple = selectedMessages.length > 1;
const isPublic = conversationModel.isPublic();
// In future, we may be able to unsend private messages also
// isServerDeletable also defined in ConversationHeader.tsx for
// future reference
const isServerDeletable = isPublic;
const isServerDeletable = selectedConversation.isPublic;
const warningMessage = (() => {
if (isPublic) {
if (selectedConversation.isPublic) {
return multiple
? window.i18n('deleteMultiplePublicWarning')
: window.i18n('deletePublicWarning');
@ -704,7 +710,7 @@ export class SessionConversation extends React.Component<Props, State> {
// VINCE TODO: MARK TO-DELETE MESSAGES AS READ
if (isPublic) {
if (selectedConversation.isPublic) {
// Get our Moderator status
const ourDevicePubkey = await UserUtil.getCurrentDevicePubKey();
if (!ourDevicePubkey) {
@ -713,7 +719,7 @@ export class SessionConversation extends React.Component<Props, State> {
const ourPrimaryPubkey = (
await MultiDeviceProtocol.getPrimaryDevice(ourDevicePubkey)
).key;
const isModerator = conversationModel.isModerator(ourPrimaryPubkey);
const isAdmin = conversationModel.isAdmin(ourPrimaryPubkey);
const ourNumbers = (await MultiDeviceProtocol.getOurDevices()).map(
m => m.key
);
@ -721,7 +727,7 @@ export class SessionConversation extends React.Component<Props, State> {
ourNumbers.includes(message.attributes.source)
);
if (!isAllOurs && !isModerator) {
if (!isAllOurs && !isAdmin) {
ToastUtils.pushMessageDeleteForbidden();
this.setState({ selectedMessages: [] });
@ -820,14 +826,14 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~ MESSAGE QUOTE ~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private async replyToMessage(quotedMessageTimestamp?: number) {
if (this.props.conversation.isBlocked) {
if (this.props.selectedConversation?.isBlocked) {
pushUnblockToSend();
return;
}
if (!_.isEqual(this.state.quotedMessageTimestamp, quotedMessageTimestamp)) {
const { messages, conversationKey } = this.props;
const { messages, selectedConversationKey } = this.props;
const conversationModel = ConversationController.getInstance().getOrThrow(
conversationKey
selectedConversationKey
);
let quotedMessageProps = null;
@ -1229,7 +1235,7 @@ export class SessionConversation extends React.Component<Props, State> {
private async updateMemberList() {
const allPubKeys = await window.Signal.Data.getPubkeysInPublicConversation(
this.props.conversationKey
this.props.selectedConversationKey
);
const allMembers = allPubKeys.map((pubKey: string) => {

View file

@ -297,9 +297,8 @@ export class SessionMessagesList extends React.Component<Props, State> {
);
}
// allow moderators feature on messages (like banning a user)
if (messageProps.isPublic) {
messageProps.weAreModerator = conversation.groupAdmins?.includes(
if (messageProps.conversationType === 'group') {
messageProps.weAreAdmin = conversation.groupAdmins?.includes(
ourPrimary
);
}

View file

@ -19,6 +19,10 @@ import { mapDispatchToProps } from '../../../state/actions';
import { connect } from 'react-redux';
import { StateType } from '../../../state/reducer';
import { ConversationController } from '../../../session/conversations';
import {
getConversationLookup,
getConversations,
} from '../../../state/selectors/conversations';
export enum SessionSettingCategory {
Appearance = 'appearance',
@ -746,10 +750,8 @@ class SettingsViewInner extends React.Component<SettingsViewProps, State> {
}
const mapStateToProps = (state: StateType) => {
const { conversations } = state;
return {
conversations: conversations.conversationLookup,
conversations: getConversationLookup(state),
};
};

View file

@ -35,7 +35,7 @@ export type MessageType = {
isSelected?: boolean;
};
type MessageTypeInConvo = {
export type MessageTypeInConvo = {
id: string;
conversationId: string;
attributes: any;
@ -80,6 +80,7 @@ export type ConversationType = {
left: boolean;
avatarPath?: string; // absolute filepath to the avatar
groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
members?: Array<string>; // members for closed groups only
};
export type ConversationLookupType = {
[key: string]: ConversationType;

View file

@ -8,7 +8,6 @@ import {
import { reducer as user, UserStateType } from './ducks/user';
import { reducer as theme, ThemeStateType } from './ducks/theme';
import { reducer as section, SectionStateType } from './ducks/section';
import { PubKey } from '../session/types';
export type StateType = {
search: SearchStateType;

View file

@ -7,6 +7,7 @@ import {
ConversationLookupType,
ConversationsStateType,
ConversationType,
MessageTypeInConvo,
} from '../ducks/conversations';
import { getIntl, getRegionCode, getUserNumber } from './user';
@ -23,19 +24,33 @@ export const getConversationLookup = createSelector(
}
);
export const getSelectedConversation = createSelector(
export const getSelectedConversationKey = createSelector(
getConversations,
(state: ConversationsStateType): string | undefined => {
return state.selectedConversation;
}
);
export const getSelectedConversation = createSelector(
getConversations,
(state: ConversationsStateType): ConversationType | undefined => {
return state.selectedConversation
? state.conversationLookup[state.selectedConversation]
: undefined;
}
);
export const getOurPrimaryConversation = createSelector(
getConversations,
(state: ConversationsStateType): ConversationType =>
state.conversationLookup[window.storage.get('primaryDevicePubKey')]
);
export const getMessagesOfSelectedConversation = createSelector(
getConversations,
(state: ConversationsStateType): Array<MessageTypeInConvo> => state.messages
);
function getConversationTitle(
conversation: ConversationType,
options: { i18n: LocalizerType; ourRegionCode: string }
@ -229,14 +244,14 @@ export const _getSessionConversationInfo = (
export const getLeftPaneLists = createSelector(
getConversationLookup,
getConversationComparator,
getSelectedConversation,
getSelectedConversationKey,
_getLeftPaneLists
);
export const getSessionConversationInfo = createSelector(
getConversationLookup,
getConversationComparator,
getSelectedConversation,
getSelectedConversationKey,
_getSessionConversationInfo
);

View file

@ -7,6 +7,7 @@ import { SearchStateType } from '../ducks/search';
import {
getConversationLookup,
getSelectedConversation,
getSelectedConversationKey,
} from './conversations';
import { ConversationLookupType } from '../ducks/conversations';
@ -38,7 +39,7 @@ export const getSearchResults = createSelector(
getSearch,
getRegionCode,
getConversationLookup,
getSelectedConversation,
getSelectedConversationKey,
getSelectedMessage,
],
(

View file

@ -0,0 +1,12 @@
import { createSelector } from 'reselect';
import { StateType } from '../reducer';
import { SectionStateType } from '../ducks/section';
import { SectionType } from '../../components/session/ActionsPanel';
export const getSection = (state: StateType): SectionStateType => state.section;
export const getFocusedSection = createSelector(
getSection,
(state: SectionStateType): SectionType => state.focusedSection
);

View file

@ -0,0 +1,4 @@
import { StateType } from '../reducer';
import { ThemeStateType } from '../ducks/theme';
export const getTheme = (state: StateType): ThemeStateType => state.theme;

View file

@ -14,6 +14,8 @@ import {
getOurPrimaryConversation,
} from '../selectors/conversations';
import { mapDispatchToProps } from '../actions';
import { getFocusedSection } from '../selectors/section';
import { getTheme } from '../selectors/theme';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
@ -34,8 +36,8 @@ const mapStateToProps = (state: StateType) => {
searchResults,
i18n: getIntl(state),
unreadMessageCount: leftPaneList.unreadCount,
theme: state.theme,
focusedSection: state.section.focusedSection,
theme: getTheme(state),
focusedSection: getFocusedSection(state),
};
};

View file

@ -3,20 +3,20 @@ import { mapDispatchToProps } from '../actions';
import { SessionConversation } from '../../components/session/conversation/SessionConversation';
import { StateType } from '../reducer';
import { getPrimaryPubkey } from '../selectors/user';
import { getTheme } from '../selectors/theme';
import {
getMessagesOfSelectedConversation,
getSelectedConversation,
getSelectedConversationKey,
} from '../selectors/conversations';
const mapStateToProps = (state: StateType) => {
const conversationKey = state.conversations.selectedConversation;
const ourPrimary = getPrimaryPubkey(state);
const conversation =
(conversationKey &&
state.conversations.conversationLookup[conversationKey]) ||
null;
return {
conversation,
conversationKey,
theme: state.theme,
messages: state.conversations.messages,
ourPrimary,
selectedConversation: getSelectedConversation(state),
selectedConversationKey: getSelectedConversationKey(state),
theme: getTheme(state),
messages: getMessagesOfSelectedConversation(state),
ourPrimary: getPrimaryPubkey(state),
};
};