refactor most of the components to outside of their Session folder (#2072)

* refactor most of the components to outside of their Session folder

* finish moving overlay and memberListItem to react hook

* fix bug with kicked member len >2 not being displayed

also sort admins first in UpdateGroupMembers dialog

* fix admin leaving text of groupNotification

* add a useFocusMount hook to focus input fields on mount

* make click avatar convo item open only user dialog

* cleanup config default.json

* make sure to use convoController to build sync message

* disable showing pubkey on opengroups

* add a pause on audio playback

Fixes #2079
This commit is contained in:
Audric Ackermann 2021-12-14 15:15:12 +11:00 committed by GitHub
parent 95e40c9509
commit 28c7445dce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
230 changed files with 2276 additions and 2421 deletions

View File

@ -151,14 +151,6 @@ module.exports = grunt => {
grunt.file.write(configPath, `${JSON.stringify(localConfig)}\n`); grunt.file.write(configPath, `${JSON.stringify(localConfig)}\n`);
} }
grunt.registerTask('getExpireTime', () => {
grunt.task.requires('gitinfo');
const gitinfo = grunt.config.get('gitinfo');
const committed = gitinfo.local.branch.current.lastCommitTime;
const time = Date.parse(committed) + 1000 * 60 * 60 * 24 * 90;
updateLocalConfig({ buildExpiration: time });
});
grunt.registerTask('getCommitHash', () => { grunt.registerTask('getCommitHash', () => {
grunt.task.requires('gitinfo'); grunt.task.requires('gitinfo');
const gitinfo = grunt.config.get('gitinfo'); const gitinfo = grunt.config.get('gitinfo');
@ -167,7 +159,7 @@ module.exports = grunt => {
}); });
grunt.registerTask('dev', ['default', 'watch']); grunt.registerTask('dev', ['default', 'watch']);
grunt.registerTask('date', ['gitinfo', 'getExpireTime']); grunt.registerTask('date', ['gitinfo']);
grunt.registerTask('default', [ grunt.registerTask('default', [
'exec:build-protobuf', 'exec:build-protobuf',
'exec:transpile', 'exec:transpile',

View File

@ -327,7 +327,7 @@
"addAsModerator": "Add as Moderator", "addAsModerator": "Add as Moderator",
"removeFromModerators": "Remove From Moderators", "removeFromModerators": "Remove From Moderators",
"add": "Add", "add": "Add",
"addingContacts": "Adding contacts to", "addingContacts": "Adding contacts to $name$",
"noContactsToAdd": "No contacts to add", "noContactsToAdd": "No contacts to add",
"noMembersInThisGroup": "No other members in this group", "noMembersInThisGroup": "No other members in this group",
"noModeratorsToRemove": "no moderators to remove", "noModeratorsToRemove": "no moderators to remove",

View File

@ -16,8 +16,6 @@
], ],
"updatesEnabled": false, "updatesEnabled": false,
"openDevTools": false, "openDevTools": false,
"buildExpiration": 0,
"commitHash": "", "commitHash": "",
"import": false, "import": false
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"
} }

View File

@ -8,12 +8,14 @@ const OS = require('../../ts/OS');
const Settings = require('./settings'); const Settings = require('./settings');
const Util = require('../../ts/util'); const Util = require('../../ts/util');
const LinkPreviews = require('./link_previews'); const LinkPreviews = require('./link_previews');
const { Message } = require('../../ts/components/conversation/Message'); const { Message } = require('../../ts/components/conversation/message/message-item/Message');
// Components // Components
const { SessionRegistrationView } = require('../../ts/components/session/SessionRegistrationView'); const {
SessionRegistrationView,
} = require('../../ts/components/registration/SessionRegistrationView');
const { SessionInboxView } = require('../../ts/components/session/SessionInboxView'); const { SessionInboxView } = require('../../ts/components/SessionInboxView');
// Types // Types
const AttachmentType = require('./types/attachment'); const AttachmentType = require('./types/attachment');

View File

@ -154,7 +154,6 @@ function prepareURL(pathSegments, moreKeys) {
name: packageJson.productName, name: packageJson.productName,
locale: locale.name, locale: locale.name,
version: app.getVersion(), version: app.getVersion(),
buildExpiration: config.get('buildExpiration'),
commitHash: config.get('commitHash'), commitHash: config.get('commitHash'),
serverUrl: config.get('serverUrl'), serverUrl: config.get('serverUrl'),
localUrl: config.get('localUrl'), localUrl: config.get('localUrl'),
@ -167,7 +166,6 @@ function prepareURL(pathSegments, moreKeys) {
appInstance: process.env.NODE_APP_INSTANCE, appInstance: process.env.NODE_APP_INSTANCE,
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy, proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
contentProxyUrl: config.contentProxyUrl, contentProxyUrl: config.contentProxyUrl,
serverTrustRoot: config.get('serverTrustRoot'),
appStartInitialSpellcheckSetting, appStartInitialSpellcheckSetting,
...moreKeys, ...moreKeys,
}, },

View File

@ -21,7 +21,7 @@ window.getEnvironment = () => config.environment;
window.getVersion = () => config.version; window.getVersion = () => config.version;
window.getAppInstance = () => config.appInstance; window.getAppInstance = () => config.appInstance;
const { SessionPasswordPrompt } = require('./ts/components/session/SessionPasswordPrompt'); const { SessionPasswordPrompt } = require('./ts/components/SessionPasswordPrompt');
window.Signal = { window.Signal = {
Components: { Components: {

View File

@ -30,11 +30,9 @@ window.getEnvironment = () => config.environment;
window.getAppInstance = () => config.appInstance; window.getAppInstance = () => config.appInstance;
window.getVersion = () => config.version; window.getVersion = () => config.version;
window.isDev = () => config.environment === 'development'; window.isDev = () => config.environment === 'development';
window.getExpiration = () => config.buildExpiration;
window.getCommitHash = () => config.commitHash; window.getCommitHash = () => config.commitHash;
window.getNodeVersion = () => config.node_version; window.getNodeVersion = () => config.node_version;
window.getHostName = () => config.hostname; window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.isBehindProxy = () => Boolean(config.proxyUrl); window.isBehindProxy = () => Boolean(config.proxyUrl);
window.lokiFeatureFlags = { window.lokiFeatureFlags = {
@ -214,7 +212,7 @@ window.Signal = Signal.setup({
logger: window.log, logger: window.log,
}); });
window.getSwarmPollingInstance = require('./ts/session/snode_api/').getSwarmPollingInstance; window.getSwarmPollingInstance = require('./ts/session/apis/snode_api/').getSwarmPollingInstance;
const WorkerInterface = require('./js/modules/util_worker_interface'); const WorkerInterface = require('./js/modules/util_worker_interface');

View File

@ -8,6 +8,7 @@ $borderAvatarColor: unquote(
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
border-radius: 50%; border-radius: 50%;
flex-shrink: 0;
img { img {
object-fit: cover; object-fit: cover;
@ -16,21 +17,6 @@ $borderAvatarColor: unquote(
} }
} }
.module-avatar__label {
width: 100%;
text-align: center;
font-weight: 300;
text-transform: uppercase;
color: $color-white;
}
.module-avatar__icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.module-avatar__icon--crown-wrapper { .module-avatar__icon--crown-wrapper {
position: absolute; position: absolute;
bottom: 0%; bottom: 0%;
@ -129,7 +115,8 @@ $borderAvatarColor: unquote(
.module-avatar-clickable { .module-avatar-clickable {
transition: $session-transition-duration; transition: $session-transition-duration;
cursor: pointer;
&:hover { &:hover {
opacity: $session-subtle-factor; filter: grayscale(0.7);
} }
} }

View File

@ -26,6 +26,12 @@
padding: 1.1em; padding: 1.1em;
} }
.session-modal {
.contact-selection-list {
width: 100%;
}
}
.create-group-dialog, .create-group-dialog,
.add-moderators-dialog, .add-moderators-dialog,
.remove-moderators-dialog, .remove-moderators-dialog,

View File

@ -494,6 +494,7 @@ label {
.group-member-list__selection { .group-member-list__selection {
overflow-y: auto; overflow-y: auto;
width: 100%;
} }
&__centered { &__centered {

View File

@ -158,10 +158,8 @@
z-index: 1; z-index: 1;
.session-icon-button { .session-icon-button {
// & > .session-icon-button {
margin-right: $session-margin-sm; margin-right: $session-margin-sm;
}
.session-icon-button {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -169,7 +167,7 @@
&:hover { &:hover {
opacity: 1; opacity: 1;
transform: scale(0.93); filter: brightness(0.9);
transition: $session-transition-duration; transition: $session-transition-duration;
} }

View File

@ -5,9 +5,9 @@ import * as GoogleChrome from '../util/GoogleChrome';
import { AttachmentType } from '../types/Attachment'; import { AttachmentType } from '../types/Attachment';
import { SessionInput } from './session/SessionInput';
import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
import { SessionInput } from './basic/SessionInput';
interface Props { interface Props {
attachment: AttachmentType; attachment: AttachmentType;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { ContactType } from './session/SessionMemberListItem';
import { ToastUtils } from '../session/utils'; import { ToastUtils } from '../session/utils';
import { createClosedGroup as createClosedGroupV2 } from '../receiver/closedGroups'; import { createClosedGroup as createClosedGroupV2 } from '../receiver/closedGroups';
import { VALIDATION } from '../session/constants'; import { VALIDATION } from '../session/constants';
@ -38,7 +37,7 @@ export class MessageView extends React.Component {
*/ */
async function createClosedGroup( async function createClosedGroup(
groupName: string, groupName: string,
groupMembers: Array<ContactType> groupMemberIds: Array<string>
): Promise<boolean> { ): Promise<boolean> {
// Validate groupName and groupMembers length // Validate groupName and groupMembers length
if (groupName.length === 0) { if (groupName.length === 0) {
@ -53,16 +52,14 @@ async function createClosedGroup(
// >= because we add ourself as a member AFTER this. so a 10 group is already invalid as it will be 11 with ourself // >= because we add ourself as a member AFTER this. so a 10 group is already invalid as it will be 11 with ourself
// the same is valid with groups count < 1 // the same is valid with groups count < 1
if (groupMembers.length < 1) { if (groupMemberIds.length < 1) {
ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('pickClosedGroupMember')); ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('pickClosedGroupMember'));
return false; return false;
} else if (groupMembers.length >= VALIDATION.CLOSED_GROUP_SIZE_LIMIT) { } else if (groupMemberIds.length >= VALIDATION.CLOSED_GROUP_SIZE_LIMIT) {
ToastUtils.pushToastError('closedGroupMaxSize', window.i18n('closedGroupMaxSize')); ToastUtils.pushToastError('closedGroupMaxSize', window.i18n('closedGroupMaxSize'));
return false; return false;
} }
const groupMemberIds = groupMembers.map(m => m.id);
await createClosedGroupV2(groupName, groupMemberIds); await createClosedGroupV2(groupName, groupMemberIds);
return true; return true;

View File

@ -0,0 +1,43 @@
import React from 'react';
import classNames from 'classnames';
import { Avatar, AvatarSize } from './avatar/Avatar';
import { Constants } from '../session';
import { SessionIcon } from './icon';
import { useConversationUsernameOrShorten } from '../hooks/useParamSelector';
const AvatarItem = (props: { memberPubkey: string }) => {
return <Avatar size={AvatarSize.XS} pubkey={props.memberPubkey} />;
};
export const MemberListItem = (props: {
pubkey: string;
isSelected: boolean;
// this bool is used to make a zombie appear with less opacity than a normal member
isZombie?: boolean;
onSelect?: (pubkey: string) => void;
onUnselect?: (pubkey: string) => void;
}) => {
const { isSelected, pubkey, isZombie, onSelect, onUnselect } = props;
const memberName = useConversationUsernameOrShorten(pubkey);
return (
<div
className={classNames('session-member-item', isSelected && 'selected', isZombie && 'zombie')}
onClick={() => {
isSelected ? onUnselect?.(pubkey) : onSelect?.(pubkey);
}}
role="button"
>
<div className="session-member-item__info">
<span className="session-member-item__avatar">
<AvatarItem memberPubkey={pubkey} />
</span>
<span className="session-member-item__name">{memberName}</span>
</div>
<span className={classNames('session-member-item__checkmark', isSelected && 'selected')}>
<SessionIcon iconType="check" iconSize="medium" iconColor={Constants.UI.COLORS.GREEN} />
</span>
</div>
);
};

View File

@ -1,32 +1,32 @@
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { getConversationController } from '../../session/conversations'; import { LeftPane } from './leftpane/LeftPane';
import { UserUtils } from '../../session/utils';
import { createStore } from '../../state/createStore';
import {
actions as conversationActions,
getEmptyConversationState,
openConversationWithMessages,
} from '../../state/ducks/conversations';
import { initialDefaultRoomState } from '../../state/ducks/defaultRooms';
import { initialModalState } from '../../state/ducks/modalDialog';
import { initialOnionPathState } from '../../state/ducks/onion';
import { initialSearchState } from '../../state/ducks/search';
import { initialSectionState } from '../../state/ducks/section';
import { initialThemeState } from '../../state/ducks/theme';
import { initialUserConfigState } from '../../state/ducks/userConfig';
import { StateType } from '../../state/reducer';
import { makeLookup } from '../../util';
import { LeftPane } from '../LeftPane';
import { SessionMainPanel } from '../SessionMainPanel';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist'; import { persistStore } from 'redux-persist';
import { TimerOptionsArray } from '../../state/ducks/timerOptions'; import { getConversationController } from '../session/conversations';
import { getEmptyStagedAttachmentsState } from '../../state/ducks/stagedAttachments'; import { UserUtils } from '../session/utils';
import { initialCallState } from '../../state/ducks/call'; import { initialCallState } from '../state/ducks/call';
import {
actions as conversationActions,
getEmptyConversationState,
openConversationWithMessages,
} from '../state/ducks/conversations';
import { initialDefaultRoomState } from '../state/ducks/defaultRooms';
import { initialModalState } from '../state/ducks/modalDialog';
import { initialOnionPathState } from '../state/ducks/onion';
import { initialSearchState } from '../state/ducks/search';
import { initialSectionState } from '../state/ducks/section';
import { getEmptyStagedAttachmentsState } from '../state/ducks/stagedAttachments';
import { initialThemeState } from '../state/ducks/theme';
import { TimerOptionsArray } from '../state/ducks/timerOptions';
import { initialUserConfigState } from '../state/ducks/userConfig';
import { StateType } from '../state/reducer';
import { makeLookup } from '../util';
import { SessionMainPanel } from './SessionMainPanel';
import { createStore } from '../state/createStore';
// Workaround: A react component's required properties are filtering up through connect() // Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363

View File

@ -4,7 +4,7 @@ import { useAppIsFocused } from '../hooks/useAppFocused';
import { getFocusedSettingsSection } from '../state/selectors/section'; import { getFocusedSettingsSection } from '../state/selectors/section';
import { SmartSessionConversation } from '../state/smart/SessionConversation'; import { SmartSessionConversation } from '../state/smart/SessionConversation';
import { SessionSettingsView } from './session/settings/SessionSettings'; import { SessionSettingsView } from './settings/SessionSettings';
const FilteredSettingsView = SessionSettingsView as any; const FilteredSettingsView = SessionSettingsView as any;

View File

@ -2,10 +2,10 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { SessionIcon } from './icon'; import { SessionIcon } from './icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton';
import { Constants } from '../../session';
import { withTheme } from 'styled-components'; import { withTheme } from 'styled-components';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
import { Constants } from '../session';
interface State { interface State {
error: string; error: string;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { getShowScrollButton } from '../../state/selectors/conversations'; import { getShowScrollButton } from '../state/selectors/conversations';
import { SessionIconButton } from './icon'; import { SessionIconButton } from './icon';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { getConversationsCount } from '../../state/selectors/conversations'; import { getConversationsCount } from '../state/selectors/conversations';
import { SessionIconButton } from './icon'; import { SessionIconButton } from './icon';
type Props = { type Props = {

View File

@ -2,10 +2,10 @@ import React, { useEffect, useRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { SessionIconButton } from './icon/'; import { SessionIconButton } from './icon/';
import { SessionButton } from './SessionButton';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
import { SessionButton } from './basic/SessionButton';
export type SessionWrapperModalType = { export type SessionWrapperModalType = {
title?: string; title?: string;

View File

@ -1,15 +1,15 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import _ from 'underscore'; import _ from 'underscore';
import { import {
useAvatarPath, useAvatarPath,
useConversationUsername, useConversationUsername,
useIsClosedGroup, useIsClosedGroup,
} from '../hooks/useParamSelector'; } from '../../hooks/useParamSelector';
import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder'; import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder';
import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar'; import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar';
import { useDisableDrag } from '../hooks/useDisableDrag'; import { useDisableDrag } from '../../hooks/useDisableDrag';
export enum AvatarSize { export enum AvatarSize {
XS = 28, XS = 28,
@ -111,10 +111,12 @@ const AvatarInner = (props: Props) => {
hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image', hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image',
isClickable && 'module-avatar-clickable' isClickable && 'module-avatar-clickable'
)} )}
onClick={e => { onMouseDown={e => {
e.stopPropagation(); if (props.onAvatarClick) {
e.preventDefault(); e.stopPropagation();
props.onAvatarClick?.(); e.preventDefault();
props.onAvatarClick?.();
}
}} }}
role="button" role="button"
data-testid={dataTestId} data-testid={dataTestId}

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { getInitials } from '../../util/getInitials'; import { getInitials } from '../../../util/getInitials';
type Props = { type Props = {
diameter: number; diameter: number;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useMembersAvatars } from '../../hooks/useMembersAvatars'; import { useMembersAvatars } from '../../../hooks/useMembersAvatars';
import { Avatar, AvatarSize } from '../Avatar'; import { Avatar, AvatarSize } from '../Avatar';
type Props = { type Props = {

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { SessionIcon, SessionIconType } from '../icon';
import { SessionIcon, SessionIconType } from './icon/';
import { SessionDropdownItem, SessionDropDownItemType } from './SessionDropdownItem'; import { SessionDropdownItem, SessionDropDownItemType } from './SessionDropdownItem';
// THIS IS DROPDOWN ACCORDIAN STYLE OPTIONS SELECTOR ELEMENT, NOT A CONTEXTMENU // THIS IS DROPDOWN ACCORDIAN STYLE OPTIONS SELECTOR ELEMENT, NOT A CONTEXTMENU

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { SessionIcon, SessionIconType } from '../icon';
import { SessionIcon, SessionIconType } from './icon/';
export enum SessionDropDownItemType { export enum SessionDropDownItemType {
Default = 'default', Default = 'default',

View File

@ -20,6 +20,7 @@ export const SessionHtmlRenderer: React.SFC<Props> = ({ tag = 'div', key, html,
return React.createElement(tag, { return React.createElement(tag, {
key, key,
className, className,
// tslint:disable-next-line: react-no-dangerous-html
dangerouslySetInnerHTML: { __html: clean }, dangerouslySetInnerHTML: { __html: clean },
}); });
}; };

View File

@ -0,0 +1,54 @@
import React, { ChangeEvent, KeyboardEvent, useRef } from 'react';
import classNames from 'classnames';
import { useFocusMount } from '../../hooks/useFocusMount';
type Props = {
placeholder?: string;
value?: string;
text?: string;
editable?: boolean;
onChange?: (value: string) => void;
onPressEnter?: any;
maxLength?: number;
isGroup?: boolean;
};
export const SessionIdEditable = (props: Props) => {
const { placeholder, onPressEnter, onChange, editable, text, value, maxLength, isGroup } = props;
const inputRef = useRef(null);
useFocusMount(inputRef, editable);
function handleChange(e: ChangeEvent<HTMLTextAreaElement>) {
if (editable && onChange) {
const eventValue = e.target.value?.replace(/(\r\n|\n|\r)/gm, '');
onChange(eventValue);
}
}
function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
if (editable && e.key === 'Enter') {
e.preventDefault();
// tslint:disable-next-line: no-unused-expression
onPressEnter && onPressEnter();
}
}
return (
<div className={classNames('session-id-editable', !editable && 'session-id-editable-disabled')}>
<textarea
className={classNames(
isGroup ? 'group-id-editable-textarea' : 'session-id-editable-textarea'
)}
ref={inputRef}
placeholder={placeholder}
disabled={!editable}
spellCheck={false}
onKeyDown={handleKeyDown}
onChange={handleChange}
onBlur={handleChange}
value={value || text}
maxLength={maxLength}
/>
</div>
);
};

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { SessionIconButton } from './icon'; import { SessionIconButton } from '../icon';
type Props = { type Props = {
label?: string; label?: string;

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { SessionIcon, SessionIconType } from './icon/';
import { Flex } from '../basic/Flex'; import { Flex } from '../basic/Flex';
import styled from 'styled-components'; import styled from 'styled-components';
import { noop } from 'lodash'; import { noop } from 'lodash';
import { SessionIcon, SessionIconType } from '../icon';
export enum SessionToastType { export enum SessionToastType {
Info = 'info', Info = 'info',

View File

@ -1,11 +1,11 @@
import { SessionIconButton } from '../icon'; import { SessionIconButton } from '../icon';
import { animation, contextMenu, Item, Menu } from 'react-contexify'; import { animation, contextMenu, Item, Menu } from 'react-contexify';
import { InputItem } from '../../../session/utils/calling/CallManager'; import { InputItem } from '../../session/utils/calling/CallManager';
import { setFullScreenCall } from '../../../state/ducks/call'; import { setFullScreenCall } from '../../state/ducks/call';
import { CallManager, ToastUtils } from '../../../session/utils'; import { CallManager, ToastUtils } from '../../session/utils';
import React from 'react'; import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getHasOngoingCallWithPubkey } from '../../../state/selectors/call'; import { getHasOngoingCallWithPubkey } from '../../state/selectors/call';
import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton'; import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton';
import styled from 'styled-components'; import styled from 'styled-components';

View File

@ -3,12 +3,12 @@ import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
import styled from 'styled-components'; import styled from 'styled-components';
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener'; import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { setFullScreenCall } from '../../../state/ducks/call'; import { setFullScreenCall } from '../../state/ducks/call';
import { import {
getCallIsInFullScreen, getCallIsInFullScreen,
getHasOngoingCallWithFocusedConvo, getHasOngoingCallWithFocusedConvo,
} from '../../../state/selectors/call'; } from '../../state/selectors/call';
import { CallWindowControls } from './CallButtons'; import { CallWindowControls } from './CallButtons';
import { StyledVideoElement } from './DraggableCallContainer'; import { StyledVideoElement } from './DraggableCallContainer';

View File

@ -4,11 +4,11 @@ import Draggable, { DraggableData, DraggableEvent } from 'react-draggable';
import styled from 'styled-components'; import styled from 'styled-components';
import _ from 'underscore'; import _ from 'underscore';
import { getSelectedConversationKey } from '../../../state/selectors/conversations'; import { getSelectedConversationKey } from '../../state/selectors/conversations';
import { getHasOngoingCall, getHasOngoingCallWith } from '../../../state/selectors/call'; import { getHasOngoingCall, getHasOngoingCallWith } from '../../state/selectors/call';
import { openConversationWithMessages } from '../../../state/ducks/conversations'; import { openConversationWithMessages } from '../../state/ducks/conversations';
import { Avatar, AvatarSize } from '../../Avatar'; import { Avatar, AvatarSize } from '../avatar/Avatar';
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener'; import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { VideoLoadingSpinner } from './InConversationCallContainer'; import { VideoLoadingSpinner } from './InConversationCallContainer';
export const DraggableCallWindow = styled.div` export const DraggableCallWindow = styled.div`

View File

@ -3,7 +3,7 @@ import React, { useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import _ from 'underscore'; import _ from 'underscore';
import { CallManager, UserUtils } from '../../../session/utils'; import { CallManager, UserUtils } from '../../session/utils';
import { import {
getCallIsInFullScreen, getCallIsInFullScreen,
getCallWithFocusedConvoIsOffering, getCallWithFocusedConvoIsOffering,
@ -11,18 +11,18 @@ import {
getCallWithFocusedConvosIsConnecting, getCallWithFocusedConvosIsConnecting,
getHasOngoingCallWithFocusedConvo, getHasOngoingCallWithFocusedConvo,
getHasOngoingCallWithPubkey, getHasOngoingCallWithPubkey,
} from '../../../state/selectors/call'; } from '../../state/selectors/call';
import { StyledVideoElement } from './DraggableCallContainer'; import { StyledVideoElement } from './DraggableCallContainer';
import { Avatar, AvatarSize } from '../../Avatar'; import { Avatar, AvatarSize } from '../avatar/Avatar';
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener'; import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { useModuloWithTripleDots } from '../../../hooks/useModuloWithTripleDots'; import { useModuloWithTripleDots } from '../../hooks/useModuloWithTripleDots';
import { CallWindowControls } from './CallButtons'; import { CallWindowControls } from './CallButtons';
import { SessionSpinner } from '../SessionSpinner'; import { DEVICE_DISABLED_DEVICE_ID } from '../../session/utils/calling/CallManager';
import { DEVICE_DISABLED_DEVICE_ID } from '../../../session/utils/calling/CallManager';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval'; import useInterval from 'react-use/lib/useInterval';
import moment from 'moment'; import moment from 'moment';
import { SessionSpinner } from '../basic/SessionSpinner';
const VideoContainer = styled.div` const VideoContainer = styled.div`
height: 100%; height: 100%;

View File

@ -3,13 +3,13 @@ import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import _ from 'underscore'; import _ from 'underscore';
import { useConversationUsername } from '../../../hooks/useParamSelector'; import { useConversationUsername } from '../../hooks/useParamSelector';
import { ed25519Str } from '../../../session/onions/onionPath'; import { ed25519Str } from '../../session/onions/onionPath';
import { CallManager } from '../../../session/utils'; import { CallManager } from '../../session/utils';
import { callTimeoutMs } from '../../../session/utils/calling/CallManager'; import { callTimeoutMs } from '../../session/utils/calling/CallManager';
import { getHasIncomingCall, getHasIncomingCallFrom } from '../../../state/selectors/call'; import { getHasIncomingCall, getHasIncomingCallFrom } from '../../state/selectors/call';
import { Avatar, AvatarSize } from '../../Avatar'; import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SessionButton, SessionButtonColor } from '../SessionButton'; import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionWrapperModal } from '../SessionWrapperModal';
export const CallWindow = styled.div` export const CallWindow = styled.div`

View File

@ -1,11 +1,7 @@
import React from 'react'; import React from 'react';
import { Avatar, AvatarSize } from '../Avatar'; import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SessionIconButton } from '../session/icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { MemoConversationHeaderMenu } from '../session/menu/ConversationHeaderMenu';
import { contextMenu } from 'react-contexify'; import { contextMenu } from 'react-contexify';
import styled from 'styled-components'; import styled from 'styled-components';
import { ConversationNotificationSettingType } from '../../models/conversation'; import { ConversationNotificationSettingType } from '../../models/conversation';
@ -38,6 +34,9 @@ import {
import { callRecipient } from '../../interactions/conversationInteractions'; import { callRecipient } from '../../interactions/conversationInteractions';
import { getHasIncomingCall, getHasOngoingCall } from '../../state/selectors/call'; import { getHasIncomingCall, getHasOngoingCall } from '../../state/selectors/call';
import { useConversationUsername } from '../../hooks/useParamSelector'; import { useConversationUsername } from '../../hooks/useParamSelector';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionIconButton } from '../icon';
import { MemoConversationHeaderMenu } from '../menu/ConversationHeaderMenu';
export interface TimerOption { export interface TimerOption {
name: string; name: string;

View File

@ -3,7 +3,7 @@ import React, { useCallback, useState } from 'react';
import { getTimerBucketIcon } from '../../util/timer'; import { getTimerBucketIcon } from '../../util/timer';
import { useInterval } from '../../hooks/useInterval'; import { useInterval } from '../../hooks/useInterval';
import styled from 'styled-components'; import styled from 'styled-components';
import { SessionIcon } from '../session/icon'; import { SessionIcon } from '../icon/SessionIcon';
type Props = { type Props = {
expirationLength: number; expirationLength: number;

View File

@ -8,8 +8,8 @@ import {
PropsForGroupUpdateType, PropsForGroupUpdateType,
} from '../../state/ducks/conversations'; } from '../../state/ducks/conversations';
import _ from 'underscore'; import _ from 'underscore';
import { ReadableMessage } from './ReadableMessage'; import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble';
import { NotificationBubble } from './notification-bubble/NotificationBubble'; import { ReadableMessage } from './message/message-item/ReadableMessage';
// This component is used to display group updates in the conversation view. // This component is used to display group updates in the conversation view.
// This is a not a "notification" as the name suggests, but a message inside the conversation // This is a not a "notification" as the name suggests, but a message inside the conversation

View File

@ -9,8 +9,8 @@ import {
getSortedMessagesOfSelectedConversation, getSortedMessagesOfSelectedConversation,
} from '../../state/selectors/conversations'; } from '../../state/selectors/conversations';
import { getAudioAutoplay } from '../../state/selectors/userConfig'; import { getAudioAutoplay } from '../../state/selectors/userConfig';
import { SessionIcon } from '../session/icon'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton'; import { SessionIcon } from '../icon';
export const AudioPlayerWithEncryptedFile = (props: { export const AudioPlayerWithEncryptedFile = (props: {
src: string; src: string;

View File

@ -12,7 +12,7 @@ import {
} from '../../types/Attachment'; } from '../../types/Attachment';
import { Image } from './Image'; import { Image } from './Image';
import { IsMessageVisibleContext } from './message/MessageContent'; import { IsMessageVisibleContext } from './message/message-content/MessageContent';
type Props = { type Props = {
attachments: Array<AttachmentTypeWithPath>; attachments: Array<AttachmentTypeWithPath>;

View File

@ -8,36 +8,38 @@ import {
StagedAttachmentType, StagedAttachmentType,
} from './composition/CompositionBox'; } from './composition/CompositionBox';
import { Constants } from '../../../session';
import _ from 'lodash'; import _ from 'lodash';
import { AttachmentUtil, GoogleChrome } from '../../../util';
import { ConversationHeaderWithDetails } from '../../conversation/ConversationHeader';
import { SessionRightPanelWithDetails } from './SessionRightPanel';
import { SessionTheme } from '../../../state/ducks/SessionTheme';
import { SessionMessagesListContainer } from './SessionMessagesListContainer';
import { LightboxGallery, MediaItemType } from '../../LightboxGallery';
import { AttachmentTypeWithPath } from '../../../types/Attachment'; import { SessionMessagesListContainer } from './SessionMessagesListContainer';
import { ToastUtils, UserUtils } from '../../../session/utils';
import * as MIME from '../../../types/MIME';
import { SessionFileDropzone } from './SessionFileDropzone'; import { SessionFileDropzone } from './SessionFileDropzone';
import autoBind from 'auto-bind';
import { InConversationCallContainer } from '../calling/InConversationCallContainer';
import { SplitViewContainer } from '../SplitViewContainer';
import { LightboxGallery, MediaItemType } from '../lightbox/LightboxGallery';
import { getPubkeysInPublicConversation } from '../../data/data';
import { Constants } from '../../session';
import { getConversationController } from '../../session/conversations';
import { ToastUtils, UserUtils } from '../../session/utils';
import { import {
quoteMessage, quoteMessage,
ReduxConversationType, ReduxConversationType,
resetSelectedMessageIds, resetSelectedMessageIds,
SortedMessageModelProps, SortedMessageModelProps,
updateMentionsMembers, updateMentionsMembers,
} from '../../../state/ducks/conversations'; } from '../../state/ducks/conversations';
import { MessageView } from '../../MainViewController'; import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { MessageDetail } from '../../conversation/MessageDetail'; import { SessionTheme } from '../../state/ducks/SessionTheme';
import { getConversationController } from '../../../session/conversations'; import { addStagedAttachmentsInConversation } from '../../state/ducks/stagedAttachments';
import { getPubkeysInPublicConversation } from '../../../data/data'; import { MIME } from '../../types';
import autoBind from 'auto-bind'; import { AttachmentTypeWithPath } from '../../types/Attachment';
import { SessionButtonColor } from '../SessionButton'; import { AttachmentUtil, GoogleChrome } from '../../util';
import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { SessionButtonColor } from '../basic/SessionButton';
import { addStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments'; import { MessageView } from '../MainViewController';
import { InConversationCallContainer } from '../calling/InConversationCallContainer'; import { ConversationHeaderWithDetails } from './ConversationHeader';
import { SplitViewContainer } from '../SplitViewContainer'; import { MessageDetail } from './message/message-item/MessageDetail';
import { SessionRightPanelWithDetails } from './SessionRightPanel';
// tslint:disable: jsx-curly-spacing // tslint:disable: jsx-curly-spacing
interface State { interface State {

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Picker } from 'emoji-mart'; import { Picker } from 'emoji-mart';
import { Constants } from '../../../session'; import { Constants } from '../../session';
type Props = { type Props = {
onEmojiClicked: (emoji: any) => void; onEmojiClicked: (emoji: any) => void;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Flex } from '../../basic/Flex'; import { Flex } from '../basic/Flex';
import { SessionIcon } from '../icon'; import { SessionIcon } from '../icon';
const DropZoneContainer = styled.div` const DropZoneContainer = styled.div`

View File

@ -2,22 +2,23 @@ import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../../models/messageType'; import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../models/messageType';
import { import {
PropsForCallNotification, PropsForCallNotification,
PropsForExpirationTimer, PropsForExpirationTimer,
PropsForGroupInvitation, PropsForGroupInvitation,
PropsForGroupUpdate, PropsForGroupUpdate,
} from '../../../state/ducks/conversations'; } from '../../state/ducks/conversations';
import { getSortedMessagesTypesOfSelectedConversation } from '../../../state/selectors/conversations'; import { getSortedMessagesTypesOfSelectedConversation } from '../../state/selectors/conversations';
import { CallNotification } from '../../conversation/notification-bubble/CallNotification'; import { GroupNotification } from './GroupNotification';
import { DataExtractionNotification } from '../../conversation/DataExtractionNotification'; import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
import { GroupInvitation } from '../../conversation/GroupInvitation'; import { MessageDateBreak } from './message/message-item/DateBreak';
import { GroupNotification } from '../../conversation/GroupNotification'; import { GroupInvitation } from './message/message-item/GroupInvitation';
import { Message } from '../../conversation/Message'; import { Message } from './message/message-item/Message';
import { MessageDateBreak } from '../../conversation/message/DateBreak'; import { CallNotification } from './message/message-item/notification-bubble/CallNotification';
import { TimerNotification } from '../../conversation/TimerNotification';
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator'; import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
import { TimerNotification } from './TimerNotification';
export const SessionMessagesList = (props: { export const SessionMessagesList = (props: {
scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>; scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>;

View File

@ -2,23 +2,26 @@ import React from 'react';
import { SessionScrollButton } from '../SessionScrollButton'; import { SessionScrollButton } from '../SessionScrollButton';
import { contextMenu } from 'react-contexify'; import { contextMenu } from 'react-contexify';
import { connect, useSelector } from 'react-redux';
import { SessionMessagesList } from './SessionMessagesList';
import styled from 'styled-components';
import autoBind from 'auto-bind';
import { getMessagesBySentAt } from '../../data/data';
import { ConversationTypeEnum } from '../../models/conversation';
import { MessageModel } from '../../models/message';
import { QuoteClickOptions } from '../../models/messageType';
import { getConversationController } from '../../session/conversations';
import { ToastUtils } from '../../session/utils';
import { import {
quotedMessageToAnimate, quotedMessageToAnimate,
ReduxConversationType, ReduxConversationType,
showScrollToBottomButton, showScrollToBottomButton,
SortedMessageModelProps, SortedMessageModelProps,
updateHaveDoneFirstScroll, updateHaveDoneFirstScroll,
} from '../../../state/ducks/conversations'; } from '../../state/ducks/conversations';
import { ToastUtils } from '../../../session/utils'; import { StateType } from '../../state/reducer';
import { TypingBubble } from '../../conversation/TypingBubble';
import { getConversationController } from '../../../session/conversations';
import { MessageModel } from '../../../models/message';
import { QuoteClickOptions } from '../../../models/messageType';
import { getMessagesBySentAt } from '../../../data/data';
import autoBind from 'auto-bind';
import { ConversationTypeEnum } from '../../../models/conversation';
import { StateType } from '../../../state/reducer';
import { connect, useSelector } from 'react-redux';
import { import {
getFirstUnreadMessageId, getFirstUnreadMessageId,
getQuotedMessageToAnimate, getQuotedMessageToAnimate,
@ -27,9 +30,8 @@ import {
getShowScrollButton, getShowScrollButton,
getSortedMessagesOfSelectedConversation, getSortedMessagesOfSelectedConversation,
isFirstUnreadMessageIdAbove, isFirstUnreadMessageIdAbove,
} from '../../../state/selectors/conversations'; } from '../../state/selectors/conversations';
import { SessionMessagesList } from './SessionMessagesList'; import { TypingBubble } from './TypingBubble';
import styled from 'styled-components';
export type SessionMessageListProps = { export type SessionMessageListProps = {
messageContainerRef: React.RefObject<HTMLDivElement>; messageContainerRef: React.RefObject<HTMLDivElement>;

View File

@ -1,13 +1,13 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { Flex } from '../../basic/Flex';
import { SessionIcon, SessionIconButton } from '../icon'; import { SessionIcon, SessionIconButton } from '../icon';
import styled from 'styled-components'; import styled from 'styled-components';
import { getAlt, isAudio } from '../../../types/Attachment';
import { Image } from '../../conversation/Image';
import { AUDIO_MP3 } from '../../../types/MIME';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getQuotedMessage } from '../../../state/selectors/conversations'; import { quoteMessage } from '../../state/ducks/conversations';
import { quoteMessage } from '../../../state/ducks/conversations'; import { getQuotedMessage } from '../../state/selectors/conversations';
import { getAlt, isAudio } from '../../types/Attachment';
import { AUDIO_MP3 } from '../../types/MIME';
import { Flex } from '../basic/Flex';
import { Image } from '../../../ts/components/conversation/Image';
const QuotedMessageComposition = styled.div` const QuotedMessageComposition = styled.div`
width: 100%; width: 100%;

View File

@ -3,11 +3,11 @@ import classNames from 'classnames';
import moment from 'moment'; import moment from 'moment';
import { SessionIconButton } from '../icon'; import { SessionIconButton } from '../icon';
import { Constants } from '../../../session';
import { ToastUtils } from '../../../session/utils';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import MicRecorder from 'mic-recorder-to-mp3'; import MicRecorder from 'mic-recorder-to-mp3';
import styled from 'styled-components'; import styled from 'styled-components';
import { Constants } from '../../session';
import { ToastUtils } from '../../session/utils';
interface Props { interface Props {
onExitVoiceNoteView: () => void; onExitVoiceNoteView: () => void;
@ -124,7 +124,7 @@ export class SessionRecording extends React.Component<Props, State> {
<StyledFlexWrapper marginHorizontal={Constants.UI.SPACING.marginXs}> <StyledFlexWrapper marginHorizontal={Constants.UI.SPACING.marginXs}>
{isRecording && ( {isRecording && (
<SessionIconButton <SessionIconButton
iconType="pause" iconType="stop"
iconSize="medium" iconSize="medium"
iconColor={Constants.UI.COLORS.DANGER_ALT} iconColor={Constants.UI.COLORS.DANGER_ALT}
onClick={actionPauseFn} onClick={actionPauseFn}

View File

@ -1,17 +1,13 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { SessionIconButton } from '../icon'; import { SessionIconButton } from '../icon';
import { Avatar, AvatarSize } from '../../Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionDropdown } from '../SessionDropdown';
import { MediaGallery } from '../../conversation/media-gallery/MediaGallery';
import _ from 'lodash'; import _ from 'lodash';
import { Constants } from '../../../session'; // tslint:disable-next-line: no-submodule-imports
import { AttachmentTypeWithPath } from '../../../types/Attachment'; import useInterval from 'react-use/lib/useInterval';
import { useDispatch, useSelector } from 'react-redux';
import { import {
getMessagesWithFileAttachments, getMessagesWithFileAttachments,
getMessagesWithVisualMediaAttachments, getMessagesWithVisualMediaAttachments,
} from '../../../data/data'; } from '../../data/data';
import { SpacerLG } from '../../basic/Text';
import { import {
deleteAllMessagesByConvoIdWithConfirmation, deleteAllMessagesByConvoIdWithConfirmation,
setDisappearingMessagesByConvoId, setDisappearingMessagesByConvoId,
@ -21,17 +17,18 @@ import {
showRemoveModeratorsByConvoId, showRemoveModeratorsByConvoId,
showUpdateGroupMembersByConvoId, showUpdateGroupMembersByConvoId,
showUpdateGroupNameByConvoId, showUpdateGroupNameByConvoId,
} from '../../../interactions/conversationInteractions'; } from '../../interactions/conversationInteractions';
import { MediaItemType } from '../../LightboxGallery'; import { Constants } from '../../session';
// tslint:disable-next-line: no-submodule-imports import { closeRightPanel } from '../../state/ducks/conversations';
import useInterval from 'react-use/lib/useInterval'; import { getSelectedConversation, isRightPanelShowing } from '../../state/selectors/conversations';
import { useDispatch, useSelector } from 'react-redux'; import { getTimerOptions } from '../../state/selectors/timerOptions';
import { getTimerOptions } from '../../../state/selectors/timerOptions'; import { AttachmentTypeWithPath } from '../../types/Attachment';
import { import { Avatar, AvatarSize } from '../avatar/Avatar';
getSelectedConversation, import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
isRightPanelShowing, import { SessionDropdown } from '../basic/SessionDropdown';
} from '../../../state/selectors/conversations'; import { SpacerLG } from '../basic/Text';
import { closeRightPanel } from '../../../state/ducks/conversations'; import { MediaItemType } from '../lightbox/LightboxGallery';
import { MediaGallery } from './media-gallery/MediaGallery';
async function getMediaGalleryProps( async function getMediaGalleryProps(
conversationId: string conversationId: string

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { arrayBufferFromFile } from '../../../types/Attachment';
import { AttachmentUtil, LinkPreviewUtil } from '../../../util';
import { StagedLinkPreviewData } from './composition/CompositionBox'; import { StagedLinkPreviewData } from './composition/CompositionBox';
import { default as insecureNodeFetch } from 'node-fetch'; import { default as insecureNodeFetch } from 'node-fetch';
import { fetchLinkPreviewImage } from '../../../util/linkPreviewFetch';
import { AbortSignal } from 'abort-controller'; import { AbortSignal } from 'abort-controller';
import { StagedLinkPreview } from '../../conversation/StagedLinkPreview'; import { arrayBufferFromFile } from '../../types/Attachment';
import { AttachmentUtil, LinkPreviewUtil } from '../../util';
import { fetchLinkPreviewImage } from '../../util/linkPreviewFetch';
import { StagedLinkPreview } from './StagedLinkPreview';
export interface StagedLinkPreviewProps extends StagedLinkPreviewData { export interface StagedLinkPreviewProps extends StagedLinkPreviewData {
onClose: (url: string) => void; onClose: (url: string) => void;

View File

@ -4,7 +4,7 @@ import classNames from 'classnames';
import { Image } from './Image'; import { Image } from './Image';
import { AttachmentType, isImageAttachment } from '../../types/Attachment'; import { AttachmentType, isImageAttachment } from '../../types/Attachment';
import { SessionSpinner } from '../session/SessionSpinner'; import { SessionSpinner } from '../basic/SessionSpinner';
type Props = { type Props = {
isLoaded: boolean; isLoaded: boolean;

View File

@ -2,8 +2,8 @@ import React from 'react';
import { missingCaseError } from '../../util/missingCaseError'; import { missingCaseError } from '../../util/missingCaseError';
import { PropsForExpirationTimer } from '../../state/ducks/conversations'; import { PropsForExpirationTimer } from '../../state/ducks/conversations';
import { ReadableMessage } from './ReadableMessage'; import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble';
import { NotificationBubble } from './notification-bubble/NotificationBubble'; import { ReadableMessage } from './message/message-item/ReadableMessage';
export const TimerNotification = (props: PropsForExpirationTimer) => { export const TimerNotification = (props: PropsForExpirationTimer) => {
const { messageId, receivedAt, isUnread, pubkey, profileName, timespan, type, disabled } = props; const { messageId, receivedAt, isUnread, pubkey, profileName, timespan, type, disabled } = props;

View File

@ -1,19 +1,12 @@
import React from 'react'; import React from 'react';
import _, { debounce } from 'lodash'; import _, { debounce } from 'lodash';
import { AttachmentType } from '../../../../types/Attachment'; import * as MIME from '../../../types/MIME';
import * as MIME from '../../../../types/MIME';
import { SessionEmojiPanel } from '../SessionEmojiPanel'; import { SessionEmojiPanel } from '../SessionEmojiPanel';
import { SessionRecording } from '../SessionRecording'; import { SessionRecording } from '../SessionRecording';
import { Constants } from '../../../../session';
import { toArray } from 'react-emoji-render'; import { toArray } from 'react-emoji-render';
import { Flex } from '../../../basic/Flex';
import { StagedAttachmentList } from '../../../conversation/StagedAttachmentList';
import { ToastUtils } from '../../../../session/utils';
import { AttachmentUtil } from '../../../../util';
import { import {
getPreview, getPreview,
LINK_PREVIEW_TIMEOUT, LINK_PREVIEW_TIMEOUT,
@ -22,31 +15,37 @@ import {
import { AbortController } from 'abort-controller'; import { AbortController } from 'abort-controller';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition'; import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import { Mention, MentionsInput } from 'react-mentions'; import { Mention, MentionsInput } from 'react-mentions';
import { CaptionEditor } from '../../../CaptionEditor'; import { MemberListItem } from '../../MemberListItem';
import { getConversationController } from '../../../../session/conversations';
import { ReduxConversationType } from '../../../../state/ducks/conversations';
import { SessionMemberListItem } from '../../SessionMemberListItem';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings'; import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import {
getIsTypingEnabled,
getMentionsInput,
getQuotedMessage,
getSelectedConversation,
getSelectedConversationKey,
} from '../../../../state/selectors/conversations';
import { connect } from 'react-redux';
import { StateType } from '../../../../state/reducer';
import { getTheme } from '../../../../state/selectors/theme';
import { removeAllStagedAttachmentsInConversation } from '../../../../state/ducks/stagedAttachments';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts'; import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import { showLinkSharingConfirmationModalDialog } from '../../../../interactions/conversationInteractions';
import { import {
AddStagedAttachmentButton, AddStagedAttachmentButton,
SendMessageButton, SendMessageButton,
StartRecordingButton, StartRecordingButton,
ToggleEmojiButton, ToggleEmojiButton,
} from './CompositionButtons'; } from './CompositionButtons';
import { AttachmentType } from '../../../types/Attachment';
import { connect } from 'react-redux';
import { showLinkSharingConfirmationModalDialog } from '../../../interactions/conversationInteractions';
import { Constants } from '../../../session';
import { getConversationController } from '../../../session/conversations';
import { ToastUtils } from '../../../session/utils';
import { ReduxConversationType } from '../../../state/ducks/conversations';
import { removeAllStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
import { StateType } from '../../../state/reducer';
import {
getIsTypingEnabled,
getMentionsInput,
getQuotedMessage,
getSelectedConversation,
getSelectedConversationKey,
} from '../../../state/selectors/conversations';
import { getTheme } from '../../../state/selectors/theme';
import { AttachmentUtil } from '../../../util';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { StagedAttachmentList } from '../StagedAttachmentList';
export interface ReplyingToMessageProps { export interface ReplyingToMessageProps {
convoId: string; convoId: string;
@ -413,7 +412,6 @@ class CompositionBoxInner extends React.Component<Props, State> {
? i18n('unblockGroupToSend') ? i18n('unblockGroupToSend')
: i18n('sendMessage'); : i18n('sendMessage');
const { typingEnabled } = this.props; const { typingEnabled } = this.props;
let index = 0;
return ( return (
<MentionsInput <MentionsInput
@ -439,21 +437,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
displayTransform={(_id, display) => `@${display}`} displayTransform={(_id, display) => `@${display}`}
data={this.fetchUsersForGroup} data={this.fetchUsersForGroup}
renderSuggestion={(suggestion, _search, _highlightedDisplay, _index, focused) => ( renderSuggestion={(suggestion, _search, _highlightedDisplay, _index, focused) => (
<SessionMemberListItem <MemberListItem isSelected={focused} key={suggestion.id} pubkey={`${suggestion.id}`} />
isSelected={focused}
index={index++}
key={suggestion.id}
member={{
id: `${suggestion.id}`,
authorPhoneNumber: `${suggestion.id}`,
selected: focused,
authorProfileName: `${suggestion.display}`,
authorName: `${suggestion.display}`,
existingMember: false,
checkmarked: false,
authorAvatarPath: '',
}}
/>
)} )}
/> />
</MentionsInput> </MentionsInput>

View File

@ -2,8 +2,8 @@ import React from 'react';
import { DocumentListItem } from './DocumentListItem'; import { DocumentListItem } from './DocumentListItem';
import { MediaGridItem } from './MediaGridItem'; import { MediaGridItem } from './MediaGridItem';
import { MediaItemType } from '../../LightboxGallery';
import { missingCaseError } from '../../../util/missingCaseError'; import { missingCaseError } from '../../../util/missingCaseError';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = { type Props = {
type: 'media' | 'documents'; type: 'media' | 'documents';

View File

@ -4,10 +4,10 @@ import classNames from 'classnames';
import moment from 'moment'; import moment from 'moment';
// tslint:disable-next-line:match-default-export-name // tslint:disable-next-line:match-default-export-name
import formatFileSize from 'filesize'; import formatFileSize from 'filesize';
import { MediaItemType } from '../../LightboxGallery';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { getSelectedConversationKey } from '../../../state/selectors/conversations'; import { getSelectedConversationKey } from '../../../state/selectors/conversations';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil'; import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = { type Props = {
// Required // Required

View File

@ -3,8 +3,8 @@ import classNames from 'classnames';
import { AttachmentSection } from './AttachmentSection'; import { AttachmentSection } from './AttachmentSection';
import { EmptyState } from './EmptyState'; import { EmptyState } from './EmptyState';
import { MediaItemType } from '../../lightbox/LightboxGallery';
import { MediaItemType } from '../../LightboxGallery';
type Props = { type Props = {
documents: Array<MediaItemType>; documents: Array<MediaItemType>;
media: Array<MediaItemType>; media: Array<MediaItemType>;

View File

@ -2,11 +2,11 @@ import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome'; import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome';
import { MediaItemType } from '../../LightboxGallery';
import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch'; import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch';
import { showLightBox } from '../../../state/ducks/conversations'; import { showLightBox } from '../../../state/ducks/conversations';
import { LightBoxOptions } from '../../session/conversation/SessionConversation';
import { useDisableDrag } from '../../../hooks/useDisableDrag'; import { useDisableDrag } from '../../../hooks/useDisableDrag';
import { LightBoxOptions } from '../SessionConversation';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = { type Props = {
mediaItem: MediaItemType; mediaItem: MediaItemType;

View File

@ -1,7 +1,6 @@
import moment from 'moment'; import moment from 'moment';
import { compact, groupBy, sortBy } from 'lodash'; import { compact, groupBy, sortBy } from 'lodash';
import { MediaItemType } from '../../lightbox/LightboxGallery';
import { MediaItemType } from '../../LightboxGallery';
// import { missingCaseError } from '../../../util/missingCaseError'; // import { missingCaseError } from '../../../util/missingCaseError';

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { getMessageById, getMessagesByConversation } from '../../../data/data'; import { getMessageById, getMessagesByConversation } from '../../../../data/data';
import { getConversationController } from '../../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
import { AttachmentDownloads } from '../../../session/utils'; import { AttachmentDownloads } from '../../../../session/utils';
import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { updateConfirmModal } from '../../../../state/ducks/modalDialog';
import { SessionIcon } from '../../session/icon'; import { SessionButtonColor } from '../../../basic/SessionButton';
import { SessionButtonColor } from '../../session/SessionButton'; import { SessionIcon } from '../../../icon';
const StyledTrustSenderUI = styled.div` const StyledTrustSenderUI = styled.div`
padding-inline: var(--margins-xs); padding-inline: var(--margins-xs);

View File

@ -2,17 +2,17 @@ import classNames from 'classnames';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import _ from 'underscore'; import _ from 'underscore';
import { getMessageById } from '../../../data/data'; import { getMessageById } from '../../../../data/data';
import { MessageRenderingProps } from '../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { import {
PropsForAttachment, PropsForAttachment,
showLightBox, showLightBox,
toggleSelectedMessageId, toggleSelectedMessageId,
} from '../../../state/ducks/conversations'; } from '../../../../state/ducks/conversations';
import { import {
getMessageAttachmentProps, getMessageAttachmentProps,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { import {
AttachmentType, AttachmentType,
AttachmentTypeWithPath, AttachmentTypeWithPath,
@ -23,13 +23,13 @@ import {
isAudio, isAudio,
isImage, isImage,
isVideo, isVideo,
} from '../../../types/Attachment'; } from '../../../../types/Attachment';
import { isFileDangerous } from '../../../util'; import { isFileDangerous } from '../../../../util';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil'; import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil';
import { Spinner } from '../../basic/Spinner'; import { Spinner } from '../../../basic/Spinner';
import { LightBoxOptions } from '../../session/conversation/SessionConversation'; import { AudioPlayerWithEncryptedFile } from '../../H5AudioPlayer';
import { AudioPlayerWithEncryptedFile } from '../H5AudioPlayer'; import { ImageGrid } from '../../ImageGrid';
import { ImageGrid } from '../ImageGrid'; import { LightBoxOptions } from '../../SessionConversation';
import { ClickToTrustSender } from './ClickToTrustSender'; import { ClickToTrustSender } from './ClickToTrustSender';
export type MessageAttachmentSelectorProps = Pick< export type MessageAttachmentSelectorProps = Pick<

View File

@ -1,14 +1,14 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { PubKey } from '../../../session/types/PubKey'; import { PubKey } from '../../../../session/types';
import { import {
getMessageAuthorProps, getMessageAuthorProps,
isGroupConversation, isGroupConversation,
isPublicGroupConversation, isPublicGroupConversation,
} from '../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { Flex } from '../../basic/Flex'; import { Flex } from '../../../basic/Flex';
import { ContactName } from '../ContactName'; import { ContactName } from '../../ContactName';
export type MessageAuthorSelectorProps = Pick< export type MessageAuthorSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,

View File

@ -1,9 +1,10 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { updateUserDetailsModal } from '../../../state/ducks/modalDialog'; import { updateUserDetailsModal } from '../../../../state/ducks/modalDialog';
import { getMessageAvatarProps } from '../../../state/selectors/conversations'; import { getMessageAvatarProps } from '../../../../state/selectors/conversations';
import { Avatar, AvatarSize } from '../../Avatar'; import { Avatar, AvatarSize } from '../../../avatar/Avatar';
// tslint:disable: use-simple-attributes
export type MessageAvatarSelectorProps = Pick< export type MessageAvatarSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
@ -62,7 +63,11 @@ export const MessageAvatar = (props: Props) => {
return ( return (
<div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}> <div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}>
<Avatar size={AvatarSize.S} onAvatarClick={onMessageAvatarClick} pubkey={authorPhoneNumber} /> <Avatar
size={AvatarSize.S}
onAvatarClick={(!isPublic && onMessageAvatarClick) || undefined}
pubkey={authorPhoneNumber}
/>
{isPublic && isSenderAdmin && ( {isPublic && isSenderAdmin && (
<div className="module-avatar__icon--crown-wrapper"> <div className="module-avatar__icon--crown-wrapper">
<div className="module-avatar__icon--crown" /> <div className="module-avatar__icon--crown" />

View File

@ -1,12 +1,10 @@
import React from 'react'; import React from 'react';
import { RenderTextCallbackType } from '../../../../types/Util';
import { getSizeClass, SizeClassType } from '../../util/emoji'; import { getSizeClass, SizeClassType } from '../../../../util/emoji';
import { Emojify } from './Emojify'; import { AddMentions } from '../../AddMentions';
import { AddNewLines } from './AddNewLines'; import { AddNewLines } from '../../AddNewLines';
import { AddMentions } from './AddMentions'; import { Emojify } from '../../Emojify';
import { Linkify } from './Linkify'; import { Linkify } from '../../Linkify';
import { RenderTextCallbackType } from '../../types/Util';
interface Props { interface Props {
text: string; text: string;

View File

@ -4,11 +4,11 @@ import React, { createContext, useCallback, useState } from 'react';
import { InView } from 'react-intersection-observer'; import { InView } from 'react-intersection-observer';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import _ from 'underscore'; import _ from 'underscore';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType'; import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import { import {
getMessageContentSelectorProps, getMessageContentSelectorProps,
getMessageTextProps, getMessageTextProps,
} from '../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { import {
canDisplayImage, canDisplayImage,
getGridDimensions, getGridDimensions,
@ -18,9 +18,9 @@ import {
isImage, isImage,
isImageAttachment, isImageAttachment,
isVideo, isVideo,
} from '../../../types/Attachment'; } from '../../../../types/Attachment';
import { Flex } from '../../basic/Flex'; import { Flex } from '../../../basic/Flex';
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../Message'; import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../message-item/Message';
import { MessageAttachment } from './MessageAttachment'; import { MessageAttachment } from './MessageAttachment';
import { MessagePreview } from './MessagePreview'; import { MessagePreview } from './MessagePreview';
import { MessageQuote } from './MessageQuote'; import { MessageQuote } from './MessageQuote';

View File

@ -2,13 +2,14 @@ import classNames from 'classnames';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import _ from 'underscore'; import _ from 'underscore';
import { replyToMessage } from '../../../interactions/conversationInteractions'; import { replyToMessage } from '../../../../interactions/conversationInteractions';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType'; import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import { toggleSelectedMessageId } from '../../../state/ducks/conversations'; import { toggleSelectedMessageId } from '../../../../state/ducks/conversations';
import { import {
getMessageContentWithStatusesSelectorProps, getMessageContentWithStatusesSelectorProps,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { MessageAuthorText } from './MessageAuthorText'; import { MessageAuthorText } from './MessageAuthorText';
import { MessageContent } from './MessageContent'; import { MessageContent } from './MessageContent';
import { MessageContextMenu } from './MessageContextMenu'; import { MessageContextMenu } from './MessageContextMenu';

View File

@ -2,26 +2,26 @@ import React, { useCallback } from 'react';
import { animation, Item, Menu } from 'react-contexify'; import { animation, Item, Menu } from 'react-contexify';
import { MessageInteraction } from '../../../interactions';
import { getMessageById } from '../../../data/data';
import { replyToMessage } from '../../../interactions/conversationInteractions';
import {
showMessageDetailsView,
toggleSelectedMessageId,
} from '../../../state/ducks/conversations';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
import {
addSenderAsModerator,
removeSenderFromModerator,
} from '../../../interactions/messageInteractions';
import { MessageRenderingProps } from '../../../models/messageType';
import { pushUnblockToSend } from '../../../session/utils/Toast';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getMessageContextMenuProps } from '../../../state/selectors/conversations'; import { getMessageById } from '../../../../data/data';
import { MessageInteraction } from '../../../../interactions';
import { replyToMessage } from '../../../../interactions/conversationInteractions';
import { import {
deleteMessagesById, deleteMessagesById,
deleteMessagesByIdForEveryone, deleteMessagesByIdForEveryone,
} from '../../../interactions/conversations/unsendingInteractions'; } from '../../../../interactions/conversations/unsendingInteractions';
import {
addSenderAsModerator,
removeSenderFromModerator,
} from '../../../../interactions/messageInteractions';
import { MessageRenderingProps } from '../../../../models/messageType';
import { pushUnblockToSend } from '../../../../session/utils/Toast';
import {
showMessageDetailsView,
toggleSelectedMessageId,
} from '../../../../state/ducks/conversations';
import { getMessageContextMenuProps } from '../../../../state/selectors/conversations';
import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil';
export type MessageContextMenuSelectorProps = Pick< export type MessageContextMenuSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
@ -183,7 +183,9 @@ export const MessageContextMenu = (props: Props) => {
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item> <Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
{(isSent || !isOutgoing) && <Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>} {(isSent || !isOutgoing) && <Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>}
<Item onClick={onShowDetail}>{window.i18n('moreInformation')}</Item> {(!isPublic || isOutgoing) && (
<Item onClick={onShowDetail}>{window.i18n('moreInformation')}</Item>
)}
{showRetry ? <Item onClick={onRetry}>{window.i18n('resend')}</Item> : null} {showRetry ? <Item onClick={onRetry}>{window.i18n('resend')}</Item> : null}
{isDeletable ? ( {isDeletable ? (
<> <>

View File

@ -1,13 +1,13 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; import React from 'react';
import { isImageAttachment } from '../../../types/Attachment'; import { isImageAttachment } from '../../../../types/Attachment';
import { SessionIcon } from '../../session/icon'; import { ImageGrid } from '../../ImageGrid';
import { ImageGrid } from '../ImageGrid'; import { Image } from '../../Image';
import { Image } from '../Image'; import { MessageRenderingProps } from '../../../../models/messageType';
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../Message';
import { MessageRenderingProps } from '../../../models/messageType';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { getMessagePreviewProps } from '../../../state/selectors/conversations'; import { getMessagePreviewProps } from '../../../../state/selectors/conversations';
import { SessionIcon } from '../../../icon';
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../message-item/Message';
export type MessagePreviewSelectorProps = Pick<MessageRenderingProps, 'attachments' | 'previews'>; export type MessagePreviewSelectorProps = Pick<MessageRenderingProps, 'attachments' | 'previews'>;

View File

@ -1,14 +1,15 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import _ from 'lodash'; import _ from 'lodash';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType'; import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import { PubKey } from '../../../session/types'; import { PubKey } from '../../../../session/types';
import { toggleSelectedMessageId } from '../../../state/ducks/conversations'; import { toggleSelectedMessageId } from '../../../../state/ducks/conversations';
import { import {
getMessageQuoteProps, getMessageQuoteProps,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { Quote } from '../Quote'; import { Quote } from './Quote';
// tslint:disable: use-simple-attributes // tslint:disable: use-simple-attributes
type Props = { type Props = {

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { getMessageStatusProps } from '../../../state/selectors/conversations'; import { getMessageStatusProps } from '../../../../state/selectors/conversations';
import { OutgoingMessageStatus } from './OutgoingMessageStatus'; import { OutgoingMessageStatus } from './OutgoingMessageStatus';
type Props = { type Props = {

View File

@ -1,13 +1,13 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { import {
getMessageTextProps, getMessageTextProps,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { SessionIcon } from '../../session/icon'; import { SessionIcon } from '../../../icon';
import { MessageBody } from '../MessageBody'; import { MessageBody } from './MessageBody';
type Props = { type Props = {
messageId: string; messageId: string;

View File

@ -1,8 +1,8 @@
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { MessageDeliveryStatus } from '../../../models/messageType'; import { MessageDeliveryStatus } from '../../../../models/messageType';
import { SessionIcon } from '../../session/icon'; import { SessionIcon } from '../../../icon';
const MessageStatusSendingContainer = styled.div` const MessageStatusSendingContainer = styled.div`
display: inline-block; display: inline-block;

View File

@ -1,22 +1,22 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import * as MIME from '../../../ts/types/MIME'; import * as MIME from '../../../../../ts/types/MIME';
import * as GoogleChrome from '../../../ts/util/GoogleChrome'; import * as GoogleChrome from '../../../../../ts/util/GoogleChrome';
import { MessageBody } from './MessageBody';
import { ContactName } from './ContactName';
import { PubKey } from '../../session/types';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { noop } from 'underscore';
import { useDisableDrag } from '../../../../hooks/useDisableDrag';
import { useEncryptedFileFetch } from '../../../../hooks/useEncryptedFileFetch';
import { PubKey } from '../../../../session/types';
import { import {
getSelectedConversationKey, getSelectedConversationKey,
isGroupConversation, isGroupConversation,
isPublicGroupConversation, isPublicGroupConversation,
} from '../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { noop } from 'underscore'; import { ContactName } from '../../ContactName';
import { useDisableDrag } from '../../hooks/useDisableDrag'; import { MessageBody } from './MessageBody';
export type QuotePropsWithoutListener = { export type QuotePropsWithoutListener = {
attachment?: QuotedAttachmentType; attachment?: QuotedAttachmentType;

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { PropsForDataExtractionNotification } from '../../models/messageType'; import { PropsForDataExtractionNotification } from '../../../../models/messageType';
import { SignalService } from '../../protobuf'; import { SignalService } from '../../../../protobuf';
import { Flex } from '../basic/Flex'; import { Flex } from '../../../basic/Flex';
import { SessionIcon } from '../session/icon'; import { SpacerSM, Text } from '../../../basic/Text';
import { SpacerSM, Text } from '../basic/Text'; import { SessionIcon } from '../../../icon';
import { ReadableMessage } from './ReadableMessage'; import { ReadableMessage } from './ReadableMessage';
export const DataExtractionNotification = (props: PropsForDataExtractionNotification) => { export const DataExtractionNotification = (props: PropsForDataExtractionNotification) => {

View File

@ -5,21 +5,21 @@ import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval'; import useInterval from 'react-use/lib/useInterval';
import _ from 'lodash'; import _ from 'lodash';
import { removeMessage } from '../../../data/data'; import { removeMessage } from '../../../../data/data';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType'; import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import { getConversationController } from '../../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
import { messageExpired } from '../../../state/ducks/conversations'; import { messageExpired } from '../../../../state/ducks/conversations';
import { import {
getGenericReadableMessageSelectorProps, getGenericReadableMessageSelectorProps,
getIsMessageSelected, getIsMessageSelected,
getQuotedMessageToAnimate, getQuotedMessageToAnimate,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { getIncrement } from '../../../util/timer'; import { getIncrement } from '../../../../util/timer';
import { ExpireTimer } from '../ExpireTimer'; import { ExpireTimer } from '../../ExpireTimer';
import { ReadableMessage } from '../ReadableMessage'; import { MessageAvatar } from '../message-content/MessageAvatar';
import { MessageAvatar } from './MessageAvatar'; import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
import { MessageContentWithStatuses } from './MessageContentWithStatus'; import { ReadableMessage } from './ReadableMessage';
export type GenericReadableMessageSelectorProps = Pick< export type GenericReadableMessageSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { SessionIconButton } from '../session/icon'; import { PropsForGroupInvitation } from '../../../../state/ducks/conversations';
import { PropsForGroupInvitation } from '../../state/ducks/conversations'; import { acceptOpenGroupInvitation } from '../../../../interactions/messageInteractions';
import { acceptOpenGroupInvitation } from '../../interactions/messageInteractions'; import { SessionIconButton } from '../../../icon';
import { ReadableMessage } from './ReadableMessage'; import { ReadableMessage } from './ReadableMessage';
export const GroupInvitation = (props: PropsForGroupInvitation) => { export const GroupInvitation = (props: PropsForGroupInvitation) => {

View File

@ -2,10 +2,10 @@ import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import uuid from 'uuid'; import uuid from 'uuid';
import { QuoteClickOptions } from '../../models/messageType';
import { GenericReadableMessage } from './message/GenericReadableMessage';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { getGenericReadableMessageSelectorProps } from '../../state/selectors/conversations'; import { QuoteClickOptions } from '../../../../models/messageType';
import { getGenericReadableMessageSelectorProps } from '../../../../state/selectors/conversations';
import { GenericReadableMessage } from './GenericReadableMessage';
// Same as MIN_WIDTH in ImageGrid.tsx // Same as MIN_WIDTH in ImageGrid.tsx
export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200; export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;

View File

@ -2,16 +2,16 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import moment from 'moment'; import moment from 'moment';
import { Avatar, AvatarSize } from '../Avatar';
import { ContactName } from './ContactName';
import { Message } from './Message'; import { Message } from './Message';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { ContactPropsMessageDetail } from '../../state/ducks/conversations'; import { Avatar, AvatarSize } from '../../../avatar/Avatar';
import { deleteMessagesById } from '../../../../interactions/conversations/unsendingInteractions';
import { ContactPropsMessageDetail } from '../../../../state/ducks/conversations';
import { import {
getMessageDetailsViewProps, getMessageDetailsViewProps,
getMessageIsDeletable, getMessageIsDeletable,
} from '../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { deleteMessagesById } from '../../interactions/conversations/unsendingInteractions'; import { ContactName } from '../../ContactName';
const AvatarItem = (props: { pubkey: string }) => { const AvatarItem = (props: { pubkey: string }) => {
const { pubkey } = props; const { pubkey } = props;

View File

@ -2,14 +2,14 @@ import _, { noop } from 'lodash';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { InView } from 'react-intersection-observer'; import { InView } from 'react-intersection-observer';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getMessageById } from '../../data/data'; import { getMessageById } from '../../../../data/data';
import { Constants } from '../../session'; import { Constants } from '../../../../session';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
import { import {
fetchMessagesForConversation, fetchMessagesForConversation,
markConversationFullyRead, markConversationFullyRead,
showScrollToBottomButton, showScrollToBottomButton,
} from '../../state/ducks/conversations'; } from '../../../../state/ducks/conversations';
import { import {
areMoreMessagesBeingFetched, areMoreMessagesBeingFetched,
getHaveDoneFirstScroll, getHaveDoneFirstScroll,
@ -17,8 +17,8 @@ import {
getMostRecentMessageId, getMostRecentMessageId,
getOldestMessageId, getOldestMessageId,
getSelectedConversationKey, getSelectedConversationKey,
} from '../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { getIsAppFocused } from '../../state/selectors/section'; import { getIsAppFocused } from '../../../../state/selectors/section';
type ReadableMessageProps = { type ReadableMessageProps = {
children: React.ReactNode; children: React.ReactNode;

View File

@ -1,11 +1,14 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { PubKey } from '../../../session/types'; import { PubKey } from '../../../../../session/types';
import { CallNotificationType, PropsForCallNotification } from '../../../state/ducks/conversations'; import {
import { getSelectedConversation } from '../../../state/selectors/conversations'; CallNotificationType,
import { LocalizerKeys } from '../../../types/LocalizerKeys'; PropsForCallNotification,
import { SessionIconType } from '../../session/icon'; } from '../../../../../state/ducks/conversations';
import { getSelectedConversation } from '../../../../../state/selectors/conversations';
import { LocalizerKeys } from '../../../../../types/LocalizerKeys';
import { SessionIconType } from '../../../../icon';
import { ReadableMessage } from '../ReadableMessage'; import { ReadableMessage } from '../ReadableMessage';
import { NotificationBubble } from './NotificationBubble'; import { NotificationBubble } from './NotificationBubble';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { SessionIcon, SessionIconType } from '../../session/icon'; import { SessionIcon, SessionIconType } from '../../../../icon';
const NotificationBubbleFlex = styled.div` const NotificationBubbleFlex = styled.div`
display: flex; display: flex;

View File

@ -1,10 +1,9 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { SpacerLG } from '../basic/Text'; import { SpacerLG } from '../basic/Text';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { adminLeaveClosedGroup } from '../../state/ducks/modalDialog'; import { adminLeaveClosedGroup } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
type Props = { type Props = {
conversationId: string; conversationId: string;

View File

@ -1,14 +1,14 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { ed25519Str } from '../../session/onions/onionPath'; import { ed25519Str } from '../../session/onions/onionPath';
import { forceNetworkDeletion } from '../../session/snode_api/SNodeAPI'; import { forceNetworkDeletion } from '../../session/apis/snode_api/SNodeAPI';
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils'; import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog'; import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text'; import { SpacerLG } from '../basic/Text';
import { SessionButton, SessionButtonColor } from '../session/SessionButton'; import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionHtmlRenderer } from '../session/SessionHTMLRenderer'; import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { SessionSpinner } from '../session/SessionSpinner'; import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionWrapperModal } from '../session/SessionWrapperModal'; import { SessionWrapperModal } from '../SessionWrapperModal';
const deleteDbLocally = async () => { const deleteDbLocally = async () => {
window?.log?.info('last message sent successfully. Deleting everything'); window?.log?.info('last message sent successfully. Deleting everything');

View File

@ -2,24 +2,24 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { QRCode } from 'react-qr-svg'; import { QRCode } from 'react-qr-svg';
import { Avatar, AvatarSize } from '../Avatar'; import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton'; import { PillDivider } from '../basic/PillDivider';
import { SessionIconButton } from '../session/icon';
import { PillDivider } from '../session/PillDivider';
import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils'; import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils';
import { MAX_USERNAME_LENGTH } from '../session/registration/RegistrationStages';
import { SessionSpinner } from '../session/SessionSpinner';
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation'; import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { AttachmentUtil } from '../../util'; import { AttachmentUtil } from '../../util';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { SpacerLG, SpacerMD } from '../basic/Text'; import { SpacerLG, SpacerMD } from '../basic/Text';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import { editProfileModal } from '../../state/ducks/modalDialog'; import { editProfileModal } from '../../state/ducks/modalDialog';
import { uploadOurAvatar } from '../../interactions/conversationInteractions'; import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIconButton } from '../icon';
import { MAX_USERNAME_LENGTH } from '../registration/RegistrationStages';
import { SessionWrapperModal } from '../SessionWrapperModal';
interface State { interface State {
profileName: string; profileName: string;

View File

@ -1,87 +1,134 @@
import React, { useState } from 'react'; import React from 'react';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { ToastUtils, UserUtils } from '../../session/utils'; import { ToastUtils, UserUtils } from '../../session/utils';
import { initiateGroupUpdate } from '../../session/group'; import { initiateGroupUpdate } from '../../session/group';
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation'; import { ConversationTypeEnum } from '../../models/conversation';
import { getCompleteUrlForV2ConvoId } from '../../interactions/conversationInteractions'; import { getCompleteUrlForV2ConvoId } from '../../interactions/conversationInteractions';
import _ from 'lodash'; import _ from 'lodash';
import { VALIDATION } from '../../session/constants'; import { VALIDATION } from '../../session/constants';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { SpacerLG } from '../basic/Text'; import { SpacerLG } from '../basic/Text';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { updateInviteContactModal } from '../../state/ducks/modalDialog'; import { updateInviteContactModal } from '../../state/ducks/modalDialog';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { MemberListItem } from '../MemberListItem';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { getPrivateContactsPubkeys } from '../../state/selectors/conversations';
import { useConversationPropsById } from '../../hooks/useParamSelector';
import { useSet } from '../../hooks/useSet';
type Props = { type Props = {
conversationId: string; conversationId: string;
}; };
const submitForOpenGroup = async (conversationId: string, pubkeys: Array<string>) => {
const completeUrl = await getCompleteUrlForV2ConvoId(conversationId);
const convo = getConversationController().get(conversationId);
if (!convo || !convo.isPublic()) {
throw new Error('submitForOpenGroup group not found');
}
const groupInvitation = {
url: completeUrl,
name: convo.getName() || 'Unknown',
};
pubkeys.forEach(async pubkeyStr => {
const privateConvo = await getConversationController().getOrCreateAndWait(
pubkeyStr,
ConversationTypeEnum.PRIVATE
);
if (privateConvo) {
void privateConvo.sendMessage({
body: '',
attachments: undefined,
groupInvitation,
preview: undefined,
quote: undefined,
});
}
});
};
const submitForClosedGroup = async (convoId: string, pubkeys: Array<string>) => {
const convo = getConversationController().get(convoId);
if (!convo || !convo.isGroup()) {
throw new Error('submitForClosedGroup group not found');
}
// closed group chats
const ourPK = UserUtils.getOurPubKeyStrFromCache();
// we only care about real members. If a member is currently a zombie we have to be able to add him back
let existingMembers = convo.get('members') || [];
// at least make sure it's an array
if (!Array.isArray(existingMembers)) {
existingMembers = [];
}
existingMembers = _.compact(existingMembers);
const existingZombies = convo.get('zombies') || [];
const newMembers = pubkeys.filter(d => !existingMembers.includes(d));
if (newMembers.length > 0) {
// Do not trigger an update if there is too many members
// be sure to include current zombies in this count
if (
newMembers.length + existingMembers.length + existingZombies.length >
VALIDATION.CLOSED_GROUP_SIZE_LIMIT
) {
ToastUtils.pushTooManyMembers();
return;
}
const allMembers = _.concat(existingMembers, newMembers, [ourPK]);
const uniqMembers = _.uniq(allMembers);
const groupId = convo.get('id');
const groupName = convo.get('name');
await initiateGroupUpdate(groupId, groupName || window.i18n('unknown'), uniqMembers, undefined);
}
};
// tslint:disable-next-line: max-func-body-length
const InviteContactsDialogInner = (props: Props) => { const InviteContactsDialogInner = (props: Props) => {
const { conversationId } = props; const { conversationId } = props;
const dispatch = useDispatch(); const dispatch = useDispatch();
const convo = getConversationController().get(conversationId); const privateContactPubkeys = useSelector(getPrivateContactsPubkeys);
// tslint:disable-next-line: max-func-body-length let validContactsForInvite = _.clone(privateContactPubkeys);
let contacts = getConversationController() const convoProps = useConversationPropsById(conversationId);
.getConversations()
.filter(d => !!d && !d.isBlocked() && d.isPrivate() && !d.isMe() && !!d.get('active_at')); const { uniqueValues: selectedContacts, addTo, removeFrom } = useSet<string>();
if (!convo.isPublic()) {
if (!convoProps) {
throw new Error('InviteContactsDialogInner not a valid convoId given');
}
if (!convoProps.isGroup) {
throw new Error('InviteContactsDialogInner must be a group');
}
if (!convoProps.isPublic) {
// filter our zombies and current members from the list of contact we can add // filter our zombies and current members from the list of contact we can add
const members = convoProps.members || [];
const members = convo.get('members') || []; const zombies = convoProps.zombies || [];
const zombies = convo.get('zombies') || []; validContactsForInvite = validContactsForInvite.filter(
contacts = contacts.filter(d => !members.includes(d.id) && !zombies.includes(d.id)); d => !members.includes(d) && !zombies.includes(d)
);
} }
const chatName = convo.get('name'); const chatName = convoProps.name;
const isPublicConvo = convo.isPublic(); const isPublicConvo = convoProps.isPublic;
const [contactList, setContactList] = useState(
contacts.map((d: ConversationModel) => {
const lokiProfile = d.getLokiProfile();
const nickname = d.getNickname();
const name = nickname
? nickname
: lokiProfile
? lokiProfile.displayName
: window.i18n('anonymous');
// TODO: should take existing members into account
const existingMember = false;
return {
id: d.id,
authorPhoneNumber: d.id,
authorProfileName: name,
authorAvatarPath: d?.getAvatarPath() || '',
selected: false,
authorName: name,
checkmarked: false,
existingMember,
};
})
);
const closeDialog = () => { const closeDialog = () => {
dispatch(updateInviteContactModal(null)); dispatch(updateInviteContactModal(null));
}; };
const onClickOK = () => { const onClickOK = () => {
const selectedContacts = contactList
.filter((d: ContactType) => d.checkmarked)
.map((d: ContactType) => d.id);
if (selectedContacts.length > 0) { if (selectedContacts.length > 0) {
if (isPublicConvo) { if (isPublicConvo) {
void submitForOpenGroup(selectedContacts); void submitForOpenGroup(conversationId, selectedContacts);
} else { } else {
void submitForClosedGroup(selectedContacts); void submitForClosedGroup(conversationId, selectedContacts);
} }
} }
@ -96,123 +143,37 @@ const InviteContactsDialogInner = (props: Props) => {
return event.key === 'Esc' || event.key === 'Escape'; return event.key === 'Esc' || event.key === 'Escape';
}, closeDialog); }, closeDialog);
const titleText = `${window.i18n('addingContacts')} ${chatName}`; const unknown = window.i18n('unknown');
const titleText = `${window.i18n('addingContacts', [chatName || unknown])}`;
const cancelText = window.i18n('cancel'); const cancelText = window.i18n('cancel');
const okText = window.i18n('ok'); const okText = window.i18n('ok');
const hasContacts = contactList.length !== 0; const hasContacts = validContactsForInvite.length > 0;
const submitForOpenGroup = async (pubkeys: Array<string>) => {
if (convo.isOpenGroupV2()) {
const completeUrl = await getCompleteUrlForV2ConvoId(convo.id);
const groupInvitation = {
url: completeUrl,
name: convo.getName() || 'Unknown',
};
pubkeys.forEach(async pubkeyStr => {
const privateConvo = await getConversationController().getOrCreateAndWait(
pubkeyStr,
ConversationTypeEnum.PRIVATE
);
if (privateConvo) {
void privateConvo.sendMessage({
body: '',
attachments: undefined,
groupInvitation,
preview: undefined,
quote: undefined,
});
}
});
}
};
const submitForClosedGroup = async (pubkeys: Array<string>) => {
// closed group chats
const ourPK = UserUtils.getOurPubKeyStrFromCache();
// we only care about real members. If a member is currently a zombie we have to be able to add him back
let existingMembers = convo.get('members') || [];
// at least make sure it's an array
if (!Array.isArray(existingMembers)) {
existingMembers = [];
}
existingMembers = _.compact(existingMembers);
const existingZombies = convo.get('zombies') || [];
const newMembers = pubkeys.filter(d => !existingMembers.includes(d));
if (newMembers.length > 0) {
// Do not trigger an update if there is too many members
// be sure to include current zombies in this count
if (
newMembers.length + existingMembers.length + existingZombies.length >
VALIDATION.CLOSED_GROUP_SIZE_LIMIT
) {
ToastUtils.pushTooManyMembers();
return;
}
const allMembers = _.concat(existingMembers, newMembers, [ourPK]);
const uniqMembers = _.uniq(allMembers);
const groupId = convo.get('id');
const groupName = convo.get('name');
await initiateGroupUpdate(
groupId,
groupName || window.i18n('unknown'),
uniqMembers,
undefined
);
}
};
const renderMemberList = () => {
const members = contactList;
const selectedContacts = contactList
.filter((d: ContactType) => d.checkmarked)
.map((d: ContactType) => d.id);
return members.map((member: ContactType, index: number) => (
<SessionMemberListItem
member={member}
key={member.id}
index={index}
isSelected={selectedContacts.some(m => m === member.id)}
onSelect={(selectedMember: ContactType) => {
onMemberClicked(selectedMember);
}}
onUnselect={(selectedMember: ContactType) => {
onMemberClicked(selectedMember);
}}
/>
));
};
const onMemberClicked = (clickedMember: ContactType) => {
const updatedContacts = contactList.map((member: ContactType) => {
if (member.id === clickedMember.id) {
return { ...member, checkmarked: !member.checkmarked };
} else {
return member;
}
});
setContactList(updatedContacts);
};
return ( return (
<SessionWrapperModal title={titleText} onClose={closeDialog}> <SessionWrapperModal title={titleText} onClose={closeDialog}>
<SpacerLG /> <SpacerLG />
<div className="contact-selection-list">{renderMemberList()}</div> <div className="contact-selection-list">
{hasContacts ? null : ( {hasContacts ? (
<> validContactsForInvite.map((member: string) => (
<SpacerLG /> <MemberListItem
<p className="no-contacts">{window.i18n('noContactsToAdd')}</p> key={member}
<SpacerLG /> pubkey={member}
</> isSelected={selectedContacts.includes(member)}
)} onSelect={addTo}
onUnselect={removeFrom}
/>
))
) : (
<>
<SpacerLG />
<p className="no-contacts">{window.i18n('noContactsToAdd')}</p>
<SpacerLG />
</>
)}
</div>
<SpacerLG /> <SpacerLG />
<div className="session-modal__button-group"> <div className="session-modal__button-group">

View File

@ -1,14 +1,14 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { PubKey } from '../../session/types'; import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils'; import { ToastUtils } from '../../session/utils';
import { SessionSpinner } from '../session/SessionSpinner';
import { Flex } from '../basic/Flex'; import { Flex } from '../basic/Flex';
import { ApiV2 } from '../../opengroup/opengroupV2'; import { ApiV2 } from '../../session/apis/open_group_api/opengroupV2';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { updateAddModeratorsModal } from '../../state/ducks/modalDialog'; import { updateAddModeratorsModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionWrapperModal } from '../SessionWrapperModal';
type Props = { type Props = {
conversationId: string; conversationId: string;

View File

@ -1,217 +1,134 @@
import React from 'react'; import React, { useState } from 'react';
import { ApiV2 } from '../../opengroup/opengroupV2'; import { ApiV2 } from '../../session/apis/open_group_api/opengroupV2';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types'; import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils'; import { ToastUtils } from '../../session/utils';
import { Flex } from '../basic/Flex'; import { Flex } from '../basic/Flex';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem';
import { SessionSpinner } from '../session/SessionSpinner';
import _ from 'lodash'; import _ from 'lodash';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { updateRemoveModeratorsModal } from '../../state/ducks/modalDialog'; import { updateRemoveModeratorsModal } from '../../state/ducks/modalDialog';
interface Props { import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { MemberListItem } from '../MemberListItem';
import { useDispatch } from 'react-redux';
import { useConversationPropsById } from '../../hooks/useParamSelector';
type Props = {
conversationId: string; conversationId: string;
} };
interface State { async function removeMods(convoId: string, modsToRemove: Array<string>) {
modList: Array<ContactType>; if (modsToRemove.length === 0) {
removingInProgress: boolean; window?.log?.info('No moderators removed. Nothing todo');
firstLoading: boolean; return false;
}
export class RemoveModeratorsDialog extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.onModClicked = this.onModClicked.bind(this);
this.closeDialog = this.closeDialog.bind(this);
this.removeThem = this.removeThem.bind(this);
this.state = {
modList: [],
removingInProgress: false,
firstLoading: true,
};
} }
window?.log?.info(`asked to remove moderators: ${modsToRemove}`);
public componentDidMount() { try {
this.refreshModList(); let res;
} const convo = getConversationController().get(convoId);
public render() { const roomInfos = convo.toOpenGroupV2();
const { i18n } = window; const modsToRemovePubkey = _.compact(modsToRemove.map(m => PubKey.from(m)));
const { removingInProgress, firstLoading } = this.state; res = await Promise.all(
const hasMods = this.state.modList.length !== 0; modsToRemovePubkey.map(async m => {
return ApiV2.removeModerator(m, roomInfos);
const convo = getConversationController().get(this.props.conversationId); })
const chatName = convo.get('name');
const title = `${i18n('removeModerators')}: ${chatName}`;
const renderContent = !firstLoading;
return (
<SessionWrapperModal title={title} onClose={this.closeDialog}>
<Flex container={true} flexDirection="column" alignItems="center">
{renderContent && (
<>
<p>Existing moderators:</p>
<div className="contact-selection-list">{this.renderMemberList()}</div>
{hasMods ? null : <p>{i18n('noModeratorsToRemove')}</p>}
<SessionSpinner loading={removingInProgress} />
<div className="session-modal__button-group">
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Green}
onClick={this.removeThem}
disabled={removingInProgress}
text={i18n('ok')}
/>
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Primary}
onClick={this.closeDialog}
disabled={removingInProgress}
text={i18n('cancel')}
/>
</div>
</>
)}
<SessionSpinner loading={firstLoading} />
</Flex>
</SessionWrapperModal>
); );
} // all moderators are removed means all promise resolved with bool= true
res = res.every(r => !!r);
private closeDialog() { if (!res) {
window.inboxStore?.dispatch(updateRemoveModeratorsModal(null)); window?.log?.warn('failed to remove moderators:', res);
}
private renderMemberList() { ToastUtils.pushFailedToRemoveFromModerator();
const members = this.state.modList; return false;
const selectedContacts = members.filter(d => d.checkmarked).map(d => d.id); } else {
window?.log?.info(`${modsToRemove} removed from moderators...`);
return members.map((member: ContactType, index: number) => ( ToastUtils.pushUserRemovedFromModerators();
<SessionMemberListItem return true;
member={member}
key={member.id}
index={index}
isSelected={selectedContacts.some(m => m === member.id)}
onSelect={(selectedMember: ContactType) => {
this.onModClicked(selectedMember);
}}
onUnselect={(selectedMember: ContactType) => {
this.onModClicked(selectedMember);
}}
/>
));
}
private onModClicked(selected: ContactType) {
const updatedContacts = this.state.modList.map(member => {
if (member.id === selected.id) {
return { ...member, checkmarked: !member.checkmarked };
} else {
return member;
}
});
this.setState(state => {
return {
...state,
modList: updatedContacts,
};
});
}
private refreshModList() {
let modPubKeys: Array<string> = [];
const convo = getConversationController().get(this.props.conversationId);
modPubKeys = convo.getGroupAdmins() || [];
const convos = getConversationController().getConversations();
const moderatorsConvos = modPubKeys
.map(
pubKey =>
convos.find(c => c.id === pubKey) || {
id: pubKey, // memberList need a key
authorPhoneNumber: pubKey,
}
)
.filter(c => !!c);
const mods = moderatorsConvos.map((d: any) => {
let name = '';
if (d.getLokiProfile) {
const lokiProfile = d.getLokiProfile();
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,
authorAvatarPath: '',
authorName: name,
checkmarked: true,
existingMember,
};
});
this.setState({
modList: mods,
firstLoading: false,
removingInProgress: false,
});
}
private async removeThem() {
const removedMods = this.state.modList.filter(d => !d.checkmarked).map(d => d.id);
if (removedMods.length === 0) {
window?.log?.info('No moderators removed. Nothing todo');
return;
}
window?.log?.info(`asked to remove moderator: ${removedMods}`);
try {
this.setState({
removingInProgress: true,
});
let res;
const convo = getConversationController().get(this.props.conversationId);
const roomInfos = convo.toOpenGroupV2();
const modsToRemove = _.compact(removedMods.map(m => PubKey.from(m)));
res = await Promise.all(
modsToRemove.map(async m => {
return ApiV2.removeModerator(m, roomInfos);
})
);
// all moderators are removed means all promise resolved with bool= true
res = res.every(r => !!r);
if (!res) {
window?.log?.warn('failed to remove moderators:', res);
ToastUtils.pushFailedToRemoveFromModerator();
} else {
window?.log?.info(`${removedMods} removed from moderators...`);
ToastUtils.pushUserRemovedFromModerators();
this.closeDialog();
}
} catch (e) {
window?.log?.error('Got error while removing moderator:', e);
} finally {
this.refreshModList();
} }
} catch (e) {
window?.log?.error('Got error while removing moderator:', e);
return false;
} }
} }
export const RemoveModeratorsDialog = (props: Props) => {
const { conversationId } = props;
const [removingInProgress, setRemovingInProgress] = useState(false);
const [modsToRemove, setModsToRemove] = useState<Array<string>>([]);
const { i18n } = window;
const dispatch = useDispatch();
const closeDialog = () => {
dispatch(updateRemoveModeratorsModal(null));
};
const removeModsCall = async () => {
if (modsToRemove.length) {
setRemovingInProgress(true);
const removed = await removeMods(conversationId, modsToRemove);
setRemovingInProgress(false);
if (removed) {
closeDialog();
}
}
};
const convoProps = useConversationPropsById(conversationId);
if (!convoProps || !convoProps.isPublic || !convoProps.weAreAdmin) {
throw new Error('RemoveModeratorsDialog: convoProps invalid');
}
const existingMods = convoProps.groupAdmins || [];
const hasMods = existingMods.length !== 0;
const title = `${i18n('removeModerators')}: ${convoProps.name}`;
return (
<SessionWrapperModal title={title} onClose={closeDialog}>
<Flex container={true} flexDirection="column" alignItems="center">
{hasMods ? (
<div className="contact-selection-list">
{existingMods.map(modId => (
<MemberListItem
key={modId}
pubkey={modId}
isSelected={modsToRemove.some(m => m === modId)}
onSelect={(selectedMember: string) => {
const updatedList = [...modsToRemove, selectedMember];
setModsToRemove(updatedList);
}}
onUnselect={(selectedMember: string) => {
const updatedList = modsToRemove.filter(m => m !== selectedMember);
setModsToRemove(updatedList);
}}
/>
))}
</div>
) : (
<p>{i18n('noModeratorsToRemove')}</p>
)}
<SessionSpinner loading={removingInProgress} />
<div className="session-modal__button-group">
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Green}
onClick={removeModsCall}
disabled={removingInProgress}
text={i18n('ok')}
/>
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Primary}
onClick={closeDialog}
disabled={removingInProgress}
text={i18n('cancel')}
/>
</div>
<SessionSpinner loading={removingInProgress} />
</Flex>
</SessionWrapperModal>
);
};

View File

@ -15,11 +15,11 @@ import {
getOnionPathsCount, getOnionPathsCount,
} from '../../state/selectors/onions'; } from '../../state/selectors/onions';
import { Flex } from '../basic/Flex'; import { Flex } from '../basic/Flex';
import { SessionIcon, SessionIconButton } from '../session/icon';
import { SessionSpinner } from '../session/SessionSpinner';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useHover from 'react-use/lib/useHover'; import useHover from 'react-use/lib/useHover';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIcon, SessionIconButton } from '../icon';
import { SessionWrapperModal } from '../SessionWrapperModal';
export type StatusLightType = { export type StatusLightType = {
glowStartDelay: number; glowStartDelay: number;

View File

@ -1,11 +1,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { SessionButton, SessionButtonColor } from '../session/SessionButton'; import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { SessionHtmlRenderer } from '../session/SessionHTMLRenderer';
import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { updateConfirmModal } from '../../state/ducks/modalDialog'; import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text'; import { SpacerLG } from '../basic/Text';
import { SessionSpinner } from '../session/SessionSpinner'; import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIcon, SessionIconSize, SessionIconType } from '../icon';
import { SessionWrapperModal } from '../SessionWrapperModal';
export interface SessionConfirmDialogProps { export interface SessionConfirmDialogProps {
message?: string; message?: string;

View File

@ -1,8 +1,7 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionIconButton, SessionIconType } from '../session/icon'; import { SessionIconButton, SessionIconType } from '../icon';
import { SessionButtonColor, SessionButtonType } from '../session/SessionButton';
interface Props { interface Props {
title: string; title: string;

View File

@ -5,8 +5,8 @@ import _ from 'lodash';
import { SpacerLG } from '../basic/Text'; import { SpacerLG } from '../basic/Text';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { changeNickNameModal } from '../../state/ducks/modalDialog'; import { changeNickNameModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor } from '../session/SessionButton'; import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionWrapperModal } from '../session/SessionWrapperModal'; import { SessionWrapperModal } from '../SessionWrapperModal';
type Props = { type Props = {
conversationId: string; conversationId: string;

Some files were not shown because too many files have changed in this diff Show More