From 9afb8b4d5e2cbeafabc0515b0edad7ef198d7ae2 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 20 Dec 2019 10:57:50 +1100 Subject: [PATCH] Major rework of context menus --- js/background.js | 8 +- js/modules/signal.js | 4 +- js/views/conversation_view.js | 8 +- js/views/session_dropdown_view.js | 62 +++++------ stylesheets/_session.scss | 58 +++++----- stylesheets/_session_theme_dark.scss | 2 - ts/components/ConversationListItem.tsx | 102 +++++------------- ts/components/UserDetailsDialog.tsx | 18 ++-- .../conversation/ConversationHeader.tsx | 6 +- ts/components/session/SessionDropdown.tsx | 86 ++++++++++----- ts/components/session/SessionDropdownItem.tsx | 97 ++++++++--------- .../session/SessionDropdownTrigger.tsx | 37 +++++++ .../session/tools/ComponentTools.tsx | 9 +- 13 files changed, 255 insertions(+), 242 deletions(-) create mode 100644 ts/components/session/SessionDropdownTrigger.tsx diff --git a/js/background.js b/js/background.js index 37cef4843..51e943424 100644 --- a/js/background.js +++ b/js/background.js @@ -802,8 +802,10 @@ appView.openConversation(groupId, {}); }; - - window.generateID = () => Math.random().toString(36).substring(3); + window.generateID = () => + Math.random() + .toString(36) + .substring(3); window.toasts = new Map(); window.pushToast = options => { @@ -817,7 +819,7 @@ description: options.description || '', type: options.type || '', }; - + // Give all toasts an ID. User may define. let currentToast; const toastID = params.id; diff --git a/js/modules/signal.js b/js/modules/signal.js index 1b0a6e10b..ecfee5e70 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -53,7 +53,9 @@ const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog'); const { SessionToast } = require('../../ts/components/session/SessionToast'); const { SessionToggle } = require('../../ts/components/session/SessionToggle'); const { SessionModal } = require('../../ts/components/session/SessionModal'); -const { SessionDropdown } = require('../../ts/components/session/SessionDropdown'); +const { + SessionDropdown, +} = require('../../ts/components/session/SessionDropdown'); const { SessionRegistrationView, } = require('../../ts/components/session/SessionRegistrationView'); diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 98893afcb..06df077dc 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1375,11 +1375,11 @@ if (!isAllOurs && !isModerator) { window.pushToast({ - title: i18n('messageDeletionForbidden'), - type: 'error', - id: 'messageDeletionForbidden', + title: i18n('messageDeletionForbidden'), + type: 'error', + id: 'messageDeletionForbidden', }); - + return; } diff --git a/js/views/session_dropdown_view.js b/js/views/session_dropdown_view.js index 8446fbb09..a655bad34 100644 --- a/js/views/session_dropdown_view.js +++ b/js/views/session_dropdown_view.js @@ -2,37 +2,31 @@ // eslint-disable-next-line func-names (function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.SessionDropdownView = Whisper.View.extend({ - initialize(options) { - this.props = { - items: options.items, - }; - - this.render(); - }, - - render() { - this.dropdownView = new Whisper.ReactWrapperView({ - className: 'session-dropdown-wrapper', - Component: window.Signal.Components.SessionDropdown, - props: this.props, - }); - - this.$el.append(this.dropdownView.el); - }, - - openDropdown() { - - }, - - closeDropdown() { - - }, - - }); - })(); - \ No newline at end of file + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.SessionDropdownView = Whisper.View.extend({ + initialize(options) { + this.props = { + items: options.items, + }; + + this.render(); + }, + + render() { + this.dropdownView = new Whisper.ReactWrapperView({ + className: 'session-dropdown-wrapper', + Component: window.Signal.Components.SessionDropdown, + props: this.props, + }); + + this.$el.append(this.dropdownView.el); + }, + + openDropdown() {}, + + closeDropdown() {}, + }); +})(); diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 823b7ca14..1316b1f10 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -60,7 +60,7 @@ $session-opaque-dark-3: rgba(0, 0, 0, 0.5); $session-color-white: #fff; $session-color-dark-grey: #353535; $session-color-black: #000; -$session-color-danger: #FF453A; +$session-color-danger: #ff453a; $session-color-primary: $session-shade-13; $session-color-secondary: $session-shade-16; @@ -561,37 +561,36 @@ label { } } +.react-contextmenu { + padding: 0px; + margin: 0px; -// .react-contextmenu { -// padding: 0px; -// margin: 0px; - -// border: none !important; -// border-radius: 0px; -// } - -// .react-contextmenu-item { -// display: flex; -// align-items: center; + border: none !important; + border-radius: 0px; +} -// height: 25px; -// padding: 0px 10px; +.react-contextmenu-item { + display: flex; + align-items: center; -// background-color: #1B1B1B; + height: 25px; + padding: 0px 10px; -// color: $session-color-white; -// font-family: 'Wasa'; -// font-size: 12px; -// line-height: $session-icon-size-sm; -// font-weight: 700; + background-color: #1b1b1b; -// &--active, &--selected{ -// background-color: $session-shade-7 !important; -// } -// } + color: $session-color-white; + font-family: 'Wasa'; + font-size: 12px; + line-height: $session-icon-size-sm; + font-weight: 700; + &--active, + &--selected { + background-color: $session-shade-7 !important; + } +} -.session-dropdown{ +.session-dropdown { position: absolute; top: 50px; left: 50px; @@ -608,7 +607,7 @@ label { height: 25px; padding-right: 7px; - background-color: #1B1B1B; + background-color: #1b1b1b; color: $session-color-white; font-family: 'Wasa'; @@ -618,7 +617,7 @@ label { display: flex; align-items: center; - + .session-icon { margin-left: 6px; } @@ -626,7 +625,8 @@ label { margin-left: 6px; } - &.active, &:hover{ + &.active, + &:hover { background-color: $session-shade-7; } @@ -635,4 +635,4 @@ label { } } } -} \ No newline at end of file +} diff --git a/stylesheets/_session_theme_dark.scss b/stylesheets/_session_theme_dark.scss index 08e836935..291853042 100644 --- a/stylesheets/_session_theme_dark.scss +++ b/stylesheets/_session_theme_dark.scss @@ -53,6 +53,4 @@ margin-left: 5px; } } - } - diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 8e0789b76..8d33a6f1a 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import ReactDOM from 'react-dom'; import classNames from 'classnames'; import { isEmpty } from 'lodash'; import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu'; @@ -12,9 +13,6 @@ import { TypingAnimation } from './conversation/TypingAnimation'; import { Colors, LocalizerType } from '../types/Util'; -import { SessionDropdown } from './session/SessionDropdown'; -import { SessionDropDownItemType } from './session/SessionDropdownItem'; - export type PropsData = { id: string; phoneNumber: string; @@ -191,78 +189,36 @@ export class ConversationListItem extends React.PureComponent { const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser'); const blockHandler = isBlocked ? onUnblockContact : onBlockContact; - - const items = [ - { - content: blockTitle, - display: true,//!isPublic && !isMe, - onClick: blockHandler - }, - { - content: i18n('changeNickname'), - display: true,//!isPublic && !isMe, - onClick: onChangeNickname, - }, - { - content: i18n('clearNickname'), - display: true,//!isPublic && !isMe && hasNickname, - onClick: onClearNickname, - }, - { - content: i18n('copyPublicKey'), - display: true,//!isPublic, - onClick: onCopyPublicKey, - }, - { - content: i18n('deleteMessages'), - onClick: onDeleteMessages, - }, - { - content: i18n('deleteContact'), - display: true,//!isMe && isClosable && !isPublic, - onClick: onDeleteContact, - }, - { - content: i18n('deletePublicChannel'), - display: true,//!isMe && isClosable && !isPublic, - type: SessionDropDownItemType.Default, - onClick: onDeleteContact, - }, - ]; - - return ( - + + {!isPublic && !isMe ? ( + {blockTitle} + ) : null} + {!isPublic && !isMe ? ( + + {i18n('changeNickname')} + + ) : null} + {!isPublic && !isMe && hasNickname ? ( + {i18n('clearNickname')} + ) : null} + {!isPublic ? ( + {i18n('copyPublicKey')} + ) : null} + {i18n('deleteMessages')} + {!isMe && isClosable ? ( + !isPublic ? ( + + {i18n('deleteContact')} + + ) : ( + + {i18n('deletePublicChannel')} + + ) + ) : null} + ); - // - // {!isPublic && !isMe ? ( - // {blockTitle} - // ) : null} - // {!isPublic && !isMe ? ( - // - // {i18n('changeNickname')} - // - // ) : null} - // {!isPublic && !isMe && hasNickname ? ( - // {i18n('clearNickname')} - // ) : null} - // {!isPublic ? ( - // {i18n('copyPublicKey')} - // ) : null} - // {i18n('deleteMessages')} - // {!isMe && isClosable ? ( - // !isPublic ? ( - // - // {i18n('deleteContact')} - // - // ) : ( - // - // {i18n('deletePublicChannel')} - // - // ) - // ) : null} - // - //); } public renderMessage() { diff --git a/ts/components/UserDetailsDialog.tsx b/ts/components/UserDetailsDialog.tsx index 9ed4e43dd..f0d765b96 100644 --- a/ts/components/UserDetailsDialog.tsx +++ b/ts/components/UserDetailsDialog.tsx @@ -34,40 +34,40 @@ export class UserDetailsDialog extends React.Component { const cancelText = i18n('cancel'); const startConversation = i18n('startConversation'); - + const items = [ { - content: "sdgsdfg", - display: true,//!isPublic && !isMe, + content: 'sdgsdfg', + display: true, //!isPublic && !isMe, }, { content: i18n('changeNickname'), - display: true,//!isPublic && !isMe, + display: true, //!isPublic && !isMe, }, { content: i18n('clearNickname'), - display: true,//!isPublic && !isMe && hasNickname, + display: true, //!isPublic && !isMe && hasNickname, }, { content: i18n('copyPublicKey'), - display: false,//!isPublic, + display: false, //!isPublic, }, { content: i18n('deleteMessages'), }, { content: i18n('deleteContact'), - display: true,//!isMe && isClosable && !isPublic, + display: true, //!isMe && isClosable && !isPublic, }, { content: i18n('deletePublicChannel'), - display: true,//!isMe && isClosable && !isPublic, + display: true, //!isMe && isClosable && !isPublic, }, ]; return (
- +
{this.renderAvatar()}
diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 8c071bb0d..fe065e6bc 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -22,6 +22,8 @@ import { SessionButtonType, } from '../session/SessionButton'; +import { SessionDropdownTrigger } from '../session/SessionDropdownTrigger'; + interface TimerOption { name: string; value: number; @@ -245,12 +247,12 @@ export class ConversationHeader extends React.Component { } return ( - + - + ); } diff --git a/ts/components/session/SessionDropdown.tsx b/ts/components/session/SessionDropdown.tsx index e88e5f037..8bbad8310 100644 --- a/ts/components/session/SessionDropdown.tsx +++ b/ts/components/session/SessionDropdown.tsx @@ -2,52 +2,80 @@ import React from 'react'; import classNames from 'classnames'; import { SessionIconType } from './icon/'; -import { SessionDropdownItem, SessionDropDownItemType } from './SessionDropdownItem'; +import { + SessionDropdownItem, + SessionDropDownItemType, +} from './SessionDropdownItem'; // THIS IS A FUTURE-PROOFING ELEMENT TO REPLACE ELECTRON CONTEXTMENUS IN PRELOAD.JS +interface State { + x: number; + y: number; + isVisible: boolean; +} + interface Props { - id?: string, - items: Array<{ - content: string, - id?: string, - icon?: SessionIconType | null, - type?: SessionDropDownItemType, - active?: boolean, - onClick?: any, - display?: boolean, - }>, - } + id?: string; + onClick?: any; + relativeTo: string | Array; + items: Array<{ + content: string; + id?: string; + icon?: SessionIconType | null; + type?: SessionDropDownItemType; + active?: boolean; + onClick?: any; + display?: boolean; + }>; +} -export class SessionDropdown extends React.PureComponent { - +export class SessionDropdown extends React.Component { constructor(props: any) { super(props); + + this.state = { + x: 0, + y: 0, + isVisible: false, + }; + } + + public show() { + this.setState({ + isVisible: true, + }); + } + + public hide() { + this.setState({ + isVisible: false, + }); } - + public render() { const { items } = this.props; + const { isVisible } = this.state; return (
    - {items.map((item: any) => { + {isVisible + ? items.map((item: any) => { return item.display ? ( - - ) : null - } - )} + + ) : null; + }) + : null}
); } - } - diff --git a/ts/components/session/SessionDropdownItem.tsx b/ts/components/session/SessionDropdownItem.tsx index 41341d681..2647339b2 100644 --- a/ts/components/session/SessionDropdownItem.tsx +++ b/ts/components/session/SessionDropdownItem.tsx @@ -1,68 +1,61 @@ - import React from 'react'; import classNames from 'classnames'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon/'; import { generateID } from './tools/ComponentTools'; - export enum SessionDropDownItemType { - Default = 'default', - Danger = 'danger', + Default = 'default', + Danger = 'danger', } interface Props { - id: string, - content: string, - type: SessionDropDownItemType, - icon: SessionIconType | null, - active: boolean, - onClick: any, + id: string; + content: string; + type: SessionDropDownItemType; + icon: SessionIconType | null; + active: boolean; + onClick: any; } export class SessionDropdownItem extends React.PureComponent { - public static defaultProps = { - id: generateID(), - type: SessionDropDownItemType.Default, - icon: null, - active: false, - onClick: () => null, - }; - - constructor(props: any) { - super(props); - this.clickHandler = this.clickHandler.bind(this); - } - - public render() { - const { id, content, type, icon, active } = this.props; - - return ( -
  • - { icon ? - : '' - } -
    - {content} -
    -
  • - ); - } - - private clickHandler(e: any){ - if (this.props.onClick) { - e.stopPropagation(); - this.props.onClick(); - } - } + public static defaultProps = { + id: generateID(), + type: SessionDropDownItemType.Default, + icon: null, + active: false, + onClick: () => null, + }; + + constructor(props: any) { + super(props); + this.clickHandler = this.clickHandler.bind(this); } + public render() { + const { id, content, type, icon, active } = this.props; + + return ( +
  • + {icon ? ( + + ) : ( + '' + )} +
    {content}
    +
  • + ); + } - + private clickHandler(e: any) { + if (this.props.onClick) { + e.stopPropagation(); + this.props.onClick(); + } + } +} diff --git a/ts/components/session/SessionDropdownTrigger.tsx b/ts/components/session/SessionDropdownTrigger.tsx new file mode 100644 index 000000000..0a366388f --- /dev/null +++ b/ts/components/session/SessionDropdownTrigger.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +interface Props { + //mouseButton: Number; + posX: number; + posY: number; +} + +export class SessionDropdownTrigger extends React.Component { + public static defaultProps = { + //mouseButton: 2, // 0 is left click, 2 is right click + posX: 0, + posY: 0, + }; + constructor(props: any) { + super(props); + } + + public handleDropdownClick = (event: any) => { + event.preventDefault(); + event.stopPropagation(); + + let x = event.clientX || (event.touches && event.touches[0].pageX); + let y = event.clientY || (event.touches && event.touches[0].pageY); + + if (this.props.posX) { + x -= this.props.posX; + } + if (this.props.posY) { + y -= this.props.posY; + } + }; + + public render() { + return
    ; + } +} diff --git a/ts/components/session/tools/ComponentTools.tsx b/ts/components/session/tools/ComponentTools.tsx index 7bdff6063..e91b8a295 100644 --- a/ts/components/session/tools/ComponentTools.tsx +++ b/ts/components/session/tools/ComponentTools.tsx @@ -1,7 +1,8 @@ // This is a collection of tools which can be ued to simplify the esign and rendering process of your components. -export function generateID(){ - // Generates a unique ID for your component - return Math.random().toString(36).substring(3); -} +export function generateID() { + // Generates a unique ID for your component + const buffer = new Uint32Array(10); + return window.crypto.getRandomValues(buffer)[0].toString(); +}