From ec39f7ae9ba3075c28f1b80df297d6616d396d47 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Fri, 4 Jun 2021 14:48:05 +1000 Subject: [PATCH] Moved edit profile saving into edit profile component file. --- js/background.js | 233 ------------------ js/modules/loki_public_chat_api.d.ts | 1 + js/views/edit_profile_dialog_view.js | 78 +++--- libtextsecure/crypto.d.ts | 1 + stylesheets/_modal.scss | 49 +--- stylesheets/_session.scss | 18 -- ts/components/EditProfileDialog.tsx | 127 +++++++++- .../session/SessionNicknameDialog.tsx | 91 ++++--- ts/components/session/SessionWrapperModal.tsx | 3 +- .../session/menu/ConversationHeaderMenu.tsx | 77 +++--- ts/components/session/menu/Menu.tsx | 30 ++- ts/util/attachmentsUtil.ts | 10 +- 12 files changed, 309 insertions(+), 409 deletions(-) diff --git a/js/background.js b/js/background.js index 12aa4ab98..10d6f1958 100644 --- a/js/background.js +++ b/js/background.js @@ -393,239 +393,6 @@ appView.showResetSessionIdDialog(); }; - window.showEditProfileDialog = async () => { - const ourNumber = window.storage.get('primaryDevicePubKey'); - const conversation = await window - .getConversationController() - .getOrCreateAndWait(ourNumber, 'private'); - - const readFile = attachment => - new Promise((resolve, reject) => { - const fileReader = new FileReader(); - fileReader.onload = e => { - const data = e.target.result; - resolve({ - ...attachment, - data, - size: data.byteLength, - }); - }; - fileReader.onerror = reject; - fileReader.onabort = reject; - fileReader.readAsArrayBuffer(attachment.file); - }); - - const avatarPath = conversation.getAvatarPath(); - const profile = conversation.getLokiProfile(); - const displayName = profile && profile.displayName; - - if (appView) { - appView.showEditProfileDialog({ - profileName: displayName, - pubkey: ourNumber, - avatarPath, - onOk: async (newName, avatar) => { - let newAvatarPath = ''; - let url = null; - let profileKey = null; - if (avatar) { - const data = await readFile({ file: avatar }); - // Ensure that this file is either small enough or is resized to meet our - // requirements for attachments - try { - const withBlob = await winvadow.Signal.Util.AttachmentUtil.autoScale( - { - contentType: avatar.type, - file: new Blob([data.data], { - type: avatar.contentType, - }), - }, - { - maxSide: 640, - maxSize: 1000 * 1024, - } - ); - const dataResized = await window.Signal.Types.Attachment.arrayBufferFromFile( - withBlob.file - ); - - // For simplicity we use the same attachment pointer that would send to - // others, which means we need to wait for the database response. - // To avoid the wait, we create a temporary url for the local image - // and use it until we the the response from the server - const tempUrl = window.URL.createObjectURL(avatar); - conversation.setLokiProfile({ displayName: newName }); - conversation.set('avatar', tempUrl); - - // Encrypt with a new key every time - profileKey = libsignal.crypto.getRandomBytes(32); - const encryptedData = await textsecure.crypto.encryptProfile( - dataResized, - profileKey - ); - - const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatarV1({ - ...dataResized, - data: encryptedData, - size: encryptedData.byteLength, - }); - - ({ url } = avatarPointer); - - storage.put('profileKey', profileKey); - - conversation.set('avatarPointer', url); - - const upgraded = await Signal.Migrations.processNewAttachment({ - isRaw: true, - data: data.data, - url, - }); - newAvatarPath = upgraded.path; - // Replace our temporary image with the attachment pointer from the server: - conversation.set('avatar', null); - conversation.setLokiProfile({ - displayName: newName, - avatar: newAvatarPath, - }); - await conversation.commit(); - window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now()); - await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true); - } catch (error) { - window.log.error( - 'showEditProfileDialog Error ensuring that image is properly sized:', - error && error.stack ? error.stack : error - ); - } - } else { - // do not update the avatar if it did not change - conversation.setLokiProfile({ - displayName: newName, - }); - // might be good to not trigger a sync if the name did not change - await conversation.commit(); - window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now()); - await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true); - } - - // inform all your registered public servers - // could put load on all the servers - // if they just keep changing their names without sending messages - // so we could disable this here - // or least it enable for the quickest response - window.lokiPublicChatAPI.setProfileName(newName); - - if (avatar) { - window - .getConversationController() - .getConversations() - .filter(convo => convo.isPublic()) - .forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey })); - } - }, - }); - } - }; - - window.onProfileEditOk = async (newName, avatar) => { - let newAvatarPath = ''; - let url = null; - let profileKey = null; - if (avatar) { - const data = await readFile({ file: avatar }); - // Ensure that this file is either small enough or is resized to meet our - // requirements for attachments - try { - const withBlob = await winvadow.Signal.Util.AttachmentUtil.autoScale( - { - contentType: avatar.type, - file: new Blob([data.data], { - type: avatar.contentType, - }), - }, - { - maxSide: 640, - maxSize: 1000 * 1024, - } - ); - const dataResized = await window.Signal.Types.Attachment.arrayBufferFromFile( - withBlob.file - ); - - // For simplicity we use the same attachment pointer that would send to - // others, which means we need to wait for the database response. - // To avoid the wait, we create a temporary url for the local image - // and use it until we the the response from the server - const tempUrl = window.URL.createObjectURL(avatar); - conversation.setLokiProfile({ displayName: newName }); - conversation.set('avatar', tempUrl); - - // Encrypt with a new key every time - profileKey = libsignal.crypto.getRandomBytes(32); - const encryptedData = await textsecure.crypto.encryptProfile( - dataResized, - profileKey - ); - - const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatarV1({ - ...dataResized, - data: encryptedData, - size: encryptedData.byteLength, - }); - - ({ url } = avatarPointer); - - storage.put('profileKey', profileKey); - - conversation.set('avatarPointer', url); - - const upgraded = await Signal.Migrations.processNewAttachment({ - isRaw: true, - data: data.data, - url, - }); - newAvatarPath = upgraded.path; - // Replace our temporary image with the attachment pointer from the server: - conversation.set('avatar', null); - conversation.setLokiProfile({ - displayName: newName, - avatar: newAvatarPath, - }); - await conversation.commit(); - window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now()); - await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true); - } catch (error) { - window.log.error( - 'showEditProfileDialog Error ensuring that image is properly sized:', - error && error.stack ? error.stack : error - ); - } - } else { - // do not update the avatar if it did not change - conversation.setLokiProfile({ - displayName: newName, - }); - // might be good to not trigger a sync if the name did not change - await conversation.commit(); - window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now()); - await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true); - } - - // inform all your registered public servers - // could put load on all the servers - // if they just keep changing their names without sending messages - // so we could disable this here - // or least it enable for the quickest response - window.lokiPublicChatAPI.setProfileName(newName); - - if (avatar) { - window - .getConversationController() - .getConversations() - .filter(convo => convo.isPublic()) - .forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey })); - } - } window.showOnionStatusDialog = () => { appView.showOnionStatusDialog(); diff --git a/js/modules/loki_public_chat_api.d.ts b/js/modules/loki_public_chat_api.d.ts index 0c8524c5e..da14c161a 100644 --- a/js/modules/loki_public_chat_api.d.ts +++ b/js/modules/loki_public_chat_api.d.ts @@ -11,6 +11,7 @@ export interface LokiPublicChatFactoryInterface { ): Promise; getListOfMembers(): Promise>; setListOfMembers(members: Array<{ authorPhoneNumber: string; authorProfileName?: string }>); + setProfileName(name: string); } declare class LokiPublicChatFactoryAPI implements LokiPublicChatFactoryInterface { diff --git a/js/views/edit_profile_dialog_view.js b/js/views/edit_profile_dialog_view.js index 2c7b3ade9..4555e5f8b 100644 --- a/js/views/edit_profile_dialog_view.js +++ b/js/views/edit_profile_dialog_view.js @@ -1,45 +1,45 @@ -/* global i18n, Whisper */ +// /* global i18n, Whisper */ -// eslint-disable-next-line func-names -(function() { - 'use strict'; +// // eslint-disable-next-line func-names +// (function() { +// 'use strict'; - window.Whisper = window.Whisper || {}; +// window.Whisper = window.Whisper || {}; - Whisper.EditProfileDialogView = Whisper.View.extend({ - className: 'loki-dialog modal', - initialize({ profileName, avatarPath, pubkey, onOk, theme }) { - this.close = this.close.bind(this); +// Whisper.EditProfileDialogView = Whisper.View.extend({ +// className: 'loki-dialog modal', +// initialize({ profileName, avatarPath, pubkey, onOk, theme }) { +// this.close = this.close.bind(this); - this.profileName = profileName; - this.pubkey = pubkey; - this.avatarPath = avatarPath; - this.onOk = onOk; - this.theme = theme; +// this.profileName = profileName; +// this.pubkey = pubkey; +// this.avatarPath = avatarPath; +// this.onOk = onOk; +// this.theme = theme; - this.$el.focus(); - this.render(); - }, - render() { - this.dialogView = new Whisper.ReactWrapperView({ - className: 'edit-profile-dialog', - Component: window.Signal.Components.EditProfileDialog, - props: { - onOk: this.onOk, - onClose: this.close, - profileName: this.profileName, - pubkey: this.pubkey, - avatarPath: this.avatarPath, - i18n, - theme: this.theme, - }, - }); +// this.$el.focus(); +// this.render(); +// }, +// render() { +// this.dialogView = new Whisper.ReactWrapperView({ +// className: 'edit-profile-dialog', +// Component: window.Signal.Components.EditProfileDialog, +// props: { +// onOk: this.onOk, +// onClose: this.close, +// profileName: this.profileName, +// pubkey: this.pubkey, +// avatarPath: this.avatarPath, +// i18n, +// theme: this.theme, +// }, +// }); - this.$el.append(this.dialogView.el); - return this; - }, - close() { - this.remove(); - }, - }); -})(); +// this.$el.append(this.dialogView.el); +// return this; +// }, +// close() { +// this.remove(); +// }, +// }); +// })(); diff --git a/libtextsecure/crypto.d.ts b/libtextsecure/crypto.d.ts index 441d59ee5..54b1d1b65 100644 --- a/libtextsecure/crypto.d.ts +++ b/libtextsecure/crypto.d.ts @@ -13,4 +13,5 @@ export interface LibTextsecureCryptoInterface { theirDigest: ArrayBuffer ): Promise; decryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise; + encryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise; } diff --git a/stylesheets/_modal.scss b/stylesheets/_modal.scss index 266ade6d5..94ef11ddb 100644 --- a/stylesheets/_modal.scss +++ b/stylesheets/_modal.scss @@ -1,5 +1,5 @@ .modal { - position: absolute; + position: fixed; top: 0; left: 0; height: 100%; @@ -311,42 +311,17 @@ } } -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 10000; - padding: 2rem; - background-color: rgba(0, 0, 0, 0.55); - - .modal-content { - background-color: white; - max-height: 80%; - height: 80%; - - .modal-title { - padding: 0 2.25rem; - height: 5rem; - max-height: 5rem; - text-transform: uppercase; - font-size: 1.8rem; - } - - .modal-body { - height: calc(100% - 16rem); - overflow-y: scroll; - padding: 0 2rem; - } - - .modal-footer { - height: 5rem; - max-height: 5rem; - padding: 0 2.25rem; - } - } -} +// .modal { +// position: fixed; +// top: 0; +// left: 0; +// width: 100%; +// height: 100%; +// z-index: 10000; +// padding: 2rem; +// background-color: rgba(0, 0, 0, 0.55); + +// } .onion-status-dialog { .session-modal__header__title { diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 857104625..9f02a97c0 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -1220,27 +1220,9 @@ input { .session-icon-button { margin: $session-margin-sm !important; } - - // svg { - // animation: glow 1s ease-in-out infinite alternate; - // } } } -// @keyframes glow { -// from { -// -webkit-filter: drop-shadow( 0px 0px 3px rgba(0,0,0,0)); -// filter: drop-shadow( 0px 0px 3px rgba(0,0,0,0)); -// } -// to { -// -webkit-filter: drop-shadow( 0px 0px 5px rgba(46, 247, 28, 0.7)); -// filter: drop-shadow( 0px 0px 5px rgba(68, 236, 2, 0.7)); -// color: red; -// } -// } - -// } - .session-nickname-wrapper { position: absolute; height: 100%; diff --git a/ts/components/EditProfileDialog.tsx b/ts/components/EditProfileDialog.tsx index fd4f7dc33..84be6a366 100644 --- a/ts/components/EditProfileDialog.tsx +++ b/ts/components/EditProfileDialog.tsx @@ -9,7 +9,7 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from './session/ import { SessionIconButton, SessionIconSize, SessionIconType } from './session/icon'; import { SessionModal } from './session/SessionModal'; import { PillDivider } from './session/PillDivider'; -import { ToastUtils, UserUtils } from '../session/utils'; +import { AttachmentUtils, SyncUtils, ToastUtils, UserUtils } from '../session/utils'; import { DefaultTheme, useTheme } from 'styled-components'; import { MAX_USERNAME_LENGTH } from './session/registration/RegistrationTabs'; import { SessionSpinner } from './session/SessionSpinner'; @@ -20,6 +20,7 @@ import { useSelector } from 'react-redux'; import { getOurNumber } from '../state/selectors/user'; import { SessionWrapperModal } from './session/SessionWrapperModal'; import { times } from 'underscore'; +import { AttachmentUtil, } from '../util'; interface Props { i18n?: any; @@ -42,6 +43,7 @@ interface State { export class EditProfileDialog extends React.Component { private readonly inputEl: any; + constructor(props: any) { super(props); @@ -134,11 +136,11 @@ export class EditProfileDialog extends React.Component {
@@ -182,6 +184,8 @@ export class EditProfileDialog extends React.Component { ); } + + private renderProfileHeader() { return ( <> @@ -310,6 +314,7 @@ export class EditProfileDialog extends React.Component { switch (event.key) { case 'Enter': if (this.state.mode === 'edit') { + // this.onClickOK(); this.onClickOK(); } break; @@ -326,6 +331,10 @@ export class EditProfileDialog extends React.Component { ToastUtils.pushCopiedToClipBoard(); } + /** + * Tidy the profile name input text and save the new profile name and avatar + * @returns + */ private onClickOK() { const newName = this.state.profileName.trim(); @@ -346,8 +355,7 @@ export class EditProfileDialog extends React.Component { loading: true, }, async () => { - // await this.props.onOk(newName, avatar); - await window.commitProfileEdits(newName, avatar); + await this.commitProfileEdits(newName, avatar); this.setState({ loading: false, @@ -363,4 +371,107 @@ export class EditProfileDialog extends React.Component { this.props.onClose(); } + + private async commitProfileEdits(newName: string, avatar: any) { + let ourNumber = window.storage.get('primaryDevicePubKey'); + const conversation = await window.getConversationController().getOrCreateAndWait(ourNumber, ConversationTypeEnum.PRIVATE); + + let newAvatarPath = ''; + let url: any = null; + let profileKey: any = null; + if (avatar) { + const data = await AttachmentUtil.readFile({ file: avatar }); + // Ensure that this file is either small enough or is resized to meet our + // requirements for attachments + try { + + const withBlob = await AttachmentUtil.autoScale( + { + contentType: avatar.type, + file: new Blob([data.data], { + type: avatar.contentType, + }), + }, + { + maxSide: 640, + maxSize: 1000 * 1024, + } + ); + const dataResized = await window.Signal.Types.Attachment.arrayBufferFromFile( + withBlob.file + ); + + // For simplicity we use the same attachment pointer that would send to + // others, which means we need to wait for the database response. + // To avoid the wait, we create a temporary url for the local image + // and use it until we the the response from the server + const tempUrl = window.URL.createObjectURL(avatar); + conversation.setLokiProfile({ displayName: newName }); + conversation.set('avatar', tempUrl); + + // Encrypt with a new key every time + profileKey = window.libsignal.crypto.getRandomBytes(32); + const encryptedData = await window.textsecure.crypto.encryptProfile( + dataResized, + profileKey + ); + + const avatarPointer = await AttachmentUtils.uploadAvatarV1({ + ...dataResized, + data: encryptedData, + size: encryptedData.byteLength, + }); + + url = avatarPointer ? avatarPointer.url : null; + window.storage.put('profileKey', profileKey); + conversation.set('avatarPointer', url); + + const upgraded = await window.Signal.Migrations.processNewAttachment({ + isRaw: true, + data: data.data, + url, + }); + newAvatarPath = upgraded.path; + // Replace our temporary image with the attachment pointer from the server: + conversation.set('avatar', null); + conversation.setLokiProfile({ + displayName: newName, + avatar: newAvatarPath, + }); + + await conversation.commit(); + UserUtils.setLastProfileUpdateTimestamp(Date.now()); + await SyncUtils.forceSyncConfigurationNowIfNeeded(true); + } catch (error) { + window.log.error( + 'showEditProfileDialog Error ensuring that image is properly sized:', + error && error.stack ? error.stack : error + ); + } + } else { + // do not update the avatar if it did not change + conversation.setLokiProfile({ + displayName: newName, + }); + // might be good to not trigger a sync if the name did not change + await conversation.commit(); + UserUtils.setLastProfileUpdateTimestamp(Date.now()); + await SyncUtils.forceSyncConfigurationNowIfNeeded(true); + } + + // inform all your registered public servers + // could put load on all the servers + // if they just keep changing their names without sending messages + // so we could disable this here + // or least it enable for the quickest response + window.lokiPublicChatAPI.setProfileName(newName); + + if (avatar) { + window + .getConversationController() + .getConversations() + .filter(convo => convo.isPublic()) + .forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey })); + } + } } diff --git a/ts/components/session/SessionNicknameDialog.tsx b/ts/components/session/SessionNicknameDialog.tsx index d68143f6e..4812dc466 100644 --- a/ts/components/session/SessionNicknameDialog.tsx +++ b/ts/components/session/SessionNicknameDialog.tsx @@ -2,21 +2,25 @@ import React, { useState } from 'react'; import { ConversationController } from '../../session/conversations/ConversationController'; import { SessionModal } from './SessionModal'; import { SessionButton } from './SessionButton'; -import { DefaultTheme, withTheme } from 'styled-components'; +import { DefaultTheme, withTheme, useTheme } from 'styled-components'; import _ from 'lodash'; +import { SessionWrapperModal } from './SessionWrapperModal'; type Props = { - onClickOk: any; - onClickClose: any; - theme: DefaultTheme; - convoId: string; + onClickOk?: any; + onClickClose?: any; + theme?: DefaultTheme; + conversationId?: string; }; const SessionNicknameInner = (props: Props) => { - const { onClickOk, onClickClose, convoId, theme } = props; + const { onClickOk, onClickClose, conversationId } = props; + let { theme } = props; const [nickname, setNickname] = useState(''); + theme = theme ? theme : useTheme(); + /** * Changes the state of nickname variable. If enter is pressed, saves the current * entered nickname value as the nickname. @@ -34,41 +38,58 @@ const SessionNicknameInner = (props: Props) => { * Saves the currently entered nickname. */ const saveNickname = async () => { - if (!convoId) { - return; + if (!conversationId) { + throw "Cant save withou conversation id" + // return; + } + const conversation = ConversationController.getInstance().get(conversationId); + if (onClickOk) { + onClickOk(nickname); } - const convo = ConversationController.getInstance().get(convoId); - onClickOk(nickname); - await convo.setNickname(nickname); + await conversation.setNickname(nickname); + onClickClose(); }; return ( - -
- {window.i18n('changeNicknameMessage')} -
-
+ // + + // TODO: Implement showHeader option for modal + + +
+ {window.i18n('changeNicknameMessage')} +
+
+ + { + void onNicknameInput(_.cloneDeep(e)); + }} + /> + +
+ + +
+ - { - void onNicknameInput(_.cloneDeep(e)); - }} - /> -
- - -
- ); }; diff --git a/ts/components/session/SessionWrapperModal.tsx b/ts/components/session/SessionWrapperModal.tsx index 4772f221a..f2ed0ec52 100644 --- a/ts/components/session/SessionWrapperModal.tsx +++ b/ts/components/session/SessionWrapperModal.tsx @@ -74,7 +74,8 @@ export const SessionWrapperModal = (props: SessionWrapperModalType) => { }; return ( -
+
+ {/*
*/}
diff --git a/ts/components/session/menu/ConversationHeaderMenu.tsx b/ts/components/session/menu/ConversationHeaderMenu.tsx index 8e3dd71a5..84c80f045 100644 --- a/ts/components/session/menu/ConversationHeaderMenu.tsx +++ b/ts/components/session/menu/ConversationHeaderMenu.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { animation, Menu } from 'react-contexify'; import { getAddModeratorsMenuItem, @@ -18,6 +18,7 @@ import { import { TimerOption } from '../../conversation/ConversationHeader'; export type PropsConversationHeaderMenu = { + id: string; triggerId: string; isMe: boolean; isPublic?: boolean; @@ -49,6 +50,7 @@ export type PropsConversationHeaderMenu = { export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { const { + id, triggerId, isMe, isPublic, @@ -77,39 +79,48 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { onSetDisappearingMessages, } = props; + + const [modal, setModal] = useState(null); + return ( - - {getDisappearingMenuItem( - isPublic, - isKickedFromGroup, - left, - isBlocked, - timerOptions, - onSetDisappearingMessages, - window.i18n - )} - {getBlockMenuItem(isMe, isPrivate, isBlocked, onBlockUser, onUnblockUser, window.i18n)} + <> + {modal ? modal : null} + + + + + {getDisappearingMenuItem( + isPublic, + isKickedFromGroup, + left, + isBlocked, + timerOptions, + onSetDisappearingMessages, + window.i18n + )} + {getBlockMenuItem(isMe, isPrivate, isBlocked, onBlockUser, onUnblockUser, window.i18n)} - {getCopyMenuItem(isPublic, isGroup, onCopyPublicKey, window.i18n)} - {getMarkAllReadMenuItem(onMarkAllRead, window.i18n)} - {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup, window.i18n)} - {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup, window.i18n)} - {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)} - {getAddModeratorsMenuItem(isAdmin, isKickedFromGroup, onAddModerators, window.i18n)} - {getRemoveModeratorsMenuItem(isAdmin, isKickedFromGroup, onRemoveModerators, window.i18n)} - {getUpdateGroupNameMenuItem(isAdmin, isKickedFromGroup, left, onUpdateGroupName, window.i18n)} - {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n)} - {/* TODO: add delete group */} - {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts, window.i18n)} - {getDeleteContactMenuItem( - isMe, - isGroup, - isPublic, - left, - isKickedFromGroup, - onDeleteContact, - window.i18n - )} - + {getCopyMenuItem(isPublic, isGroup, onCopyPublicKey, window.i18n)} + {getMarkAllReadMenuItem(onMarkAllRead, window.i18n)} + {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup, window.i18n, id, setModal)} + {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup, window.i18n)} + {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)} + {getAddModeratorsMenuItem(isAdmin, isKickedFromGroup, onAddModerators, window.i18n)} + {getRemoveModeratorsMenuItem(isAdmin, isKickedFromGroup, onRemoveModerators, window.i18n)} + {getUpdateGroupNameMenuItem(isAdmin, isKickedFromGroup, left, onUpdateGroupName, window.i18n)} + {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n)} + {/* TODO: add delete group */} + {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts, window.i18n)} + {getDeleteContactMenuItem( + isMe, + isGroup, + isPublic, + left, + isKickedFromGroup, + onDeleteContact, + window.i18n + )} + + ); }; diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx index 99bc6a4ba..fab18049b 100644 --- a/ts/components/session/menu/Menu.tsx +++ b/ts/components/session/menu/Menu.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import React, { Dispatch } from 'react'; import { LocalizerType } from '../../../types/Util'; import { TimerOption } from '../../conversation/ConversationHeader'; import { Item, Submenu } from 'react-contexify'; +import { SessionNicknameDialog } from '../SessionNicknameDialog'; function showTimerOptions( isPublic: boolean, @@ -205,7 +206,7 @@ export function getDisappearingMenuItem( // Remove the && false to make context menu work with RTL support {(timerOptions || []).map(item => ( {i18n('changeNickname')}; + + const clearModal = () => { + setModal(null); + } + + // const onClickOk = () => { + // console.log("@@ onclickok clicked"); + // } + + const onClickCustom = () => { + setModal(); + // setModal(null); + } + + return ( + <> + {i18n('changeNickname')} + {/* {i18n('changeNickname')} */} + + ); } return null; } diff --git a/ts/util/attachmentsUtil.ts b/ts/util/attachmentsUtil.ts index 70be12658..edb0d8c80 100644 --- a/ts/util/attachmentsUtil.ts +++ b/ts/util/attachmentsUtil.ts @@ -112,7 +112,15 @@ export async function getFile(attachment: StagedAttachmentType, maxMeasurements? }; } -export async function readFile(attachment: any): Promise { +export type AttachmentFileType = { + attachment: any; + data: ArrayBuffer; + size: number; +} + + +// export async function readFile(attachment: any): Promise { +export async function readFile(attachment: any): Promise { return new Promise((resolve, reject) => { const FR = new FileReader(); FR.onload = e => {