diff --git a/web/chat/chat-message-list.css b/web/chat/chat-message-list.css index 117afa50a..df9144432 100644 --- a/web/chat/chat-message-list.css +++ b/web/chat/chat-message-list.css @@ -1,253 +1,272 @@ div.container { margin-left: 400px; height: 100%; background-color: var(--bg); display: flex; flex-direction: column; box-sizing: border-box; } div.activeContainer { border: 2px solid #5989d6; margin-left: 402px; } div.outerMessageContainer { position: relative; height: calc(100vh - 128px); min-height: 0; display: flex; flex-direction: column; } div.messageContainer { height: 100%; overflow-y: auto; display: flex; flex-direction: column-reverse; } div.mirroredMessageContainer { flex-direction: column !important; transform: scaleY(-1); } div.mirroredMessageContainer > div { transform: scaleY(-1); } div.message { display: flex; flex-direction: column; flex-shrink: 0; } div.loading { text-align: center; padding: 12px; } div.conversationHeader { color: var(--chat-timestamp-color); font-size: var(--xs-font-12); line-height: var(--line-height-text); text-align: center; } div.conversationHeader:last-child { padding-top: 6px; } div.messageLeftTooltip:after { top: 7px; right: -14px; border-color: transparent transparent transparent var(--tool-tip-bg); } div.messageRightTooltip:after { top: 7px; left: -14px; border-color: transparent var(--tool-tip-bg) transparent transparent; } div.messageTopLeftTooltip:after { bottom: -14px; left: 4px; border-color: var(--tool-tip-bg) transparent transparent transparent; } div.messageTopRightTooltip:after { bottom: -14px; right: 4px; border-color: var(--tool-tip-bg) transparent transparent transparent; } div.messageBottomLeftTooltip:after { top: -14px; left: 4px; border-color: transparent transparent var(--tool-tip-bg) transparent; } div.messageBottomRightTooltip:after { top: -14px; right: 4px; border-color: transparent transparent var(--tool-tip-bg) transparent; } -div.messageActionLinks { +div.messageActionActiveArea { position: absolute; display: flex; top: 0; bottom: 0; align-items: center; - color: gray; - padding: 0 6px; + padding: 0 12px; } -div.viewerMessageActionLinks { + +div.viewerMessageActionActiveArea { right: 100%; } -div.nonViewerMessageActionLinks { +div.nonViewerMessageActiveArea { left: 100%; } -div.messageActionLinks > div + div { +div.messageActionActiveArea > div + div { margin-left: 4px; } + +div.messageActionContainer { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 0 6px; + background-color: var(--message-action-tooltip-bg); + border-radius: 8px; +} + +div.messageActionContainer svg { + padding: 10px 6px 6px; + color: var(--fg); +} +div.messageActionContainer svg:hover { + cursor: pointer; +} + div.messageActionLinkIcon:hover { cursor: pointer; } div.textMessage { padding: 6px 12px; white-space: pre-wrap; word-wrap: break-word; width: 100%; box-sizing: border-box; } div.textMessageDefaultBackground { background-color: var(--text-message-default-background); } div.normalTextMessage { font-size: 16px; } div.emojiOnlyTextMessage { font-size: 32px; font-family: emoji; } span.authorName { color: #777777; font-size: 14px; padding: 4px 24px; } div.darkTextMessage { color: white; } div.lightTextMessage { color: black; } div.content { display: flex; flex-shrink: 0; align-items: center; margin-bottom: 5px; box-sizing: border-box; width: 100%; } div.nonViewerContent { align-self: flex-start; justify-content: flex-start; padding-right: 8px; } div.viewerContent { align-self: flex-end; justify-content: flex-end; padding-right: 4px; } div.iconContainer { margin-right: 1px; } div.iconContainer > svg { height: 16px; } div.messageBoxContainer { position: relative; display: flex; max-width: calc(min(68%, 1000px)); margin: 0 4px 0 12px; } div.fixedWidthMessageBoxContainer { width: 68%; } div.messageBox { overflow: hidden; display: flex; flex-wrap: wrap; justify-content: space-between; flex-shrink: 0; max-width: 100%; } div.fixedWidthMessageBox { width: 100%; } div.failedSend { text-transform: uppercase; display: flex; justify-content: flex-end; flex-shrink: 0; font-size: 14px; margin-right: 30px; padding-bottom: 6px; } span.deliveryFailed { padding: 0 3px; color: #555555; } a.retrySend { padding: 0 3px; cursor: pointer; } div.messageBox > div.imageGrid { display: grid; width: 100%; grid-template-columns: repeat(6, 1fr); grid-gap: 5px; } div.messageBox span.multimedia > span.multimediaImage { min-height: initial; min-width: initial; } div.messageBox span.multimedia > span.multimediaImage > img { max-height: 600px; } div.imageGrid > span.multimedia { grid-column-end: span 3; } div.imageGrid > span.multimedia:first-child { margin-top: 0; } div.imageGrid > span.multimedia > span.multimediaImage { flex: 1; } div.imageGrid > span.multimedia > span.multimediaImage:after { content: ''; display: block; padding-bottom: calc(min(600px, 100%)); } div.imageGrid > span.multimedia > span.multimediaImage > img { position: absolute; width: 100%; height: 100%; object-fit: cover; } div.imageGrid > span.multimedia:nth-last-child(n + 3):first-child, div.imageGrid > span.multimedia:nth-last-child(n + 3):first-child ~ * { grid-column-end: span 2; } div.imageGrid > span.multimedia:nth-last-child(n + 4):first-child, div.imageGrid > span.multimedia:nth-last-child(n + 4):first-child ~ * { grid-column-end: span 3; } div.imageGrid > span.multimedia:nth-last-child(n + 5):first-child, div.imageGrid > span.multimedia:nth-last-child(n + 5):first-child ~ * { grid-column-end: span 2; } div.sidebarMarginBottom { margin-bottom: 8px; } svg.inlineSidebarIcon { color: #666666; } diff --git a/web/chat/composed-message.react.js b/web/chat/composed-message.react.js index caf47d016..8f85e3c9d 100644 --- a/web/chat/composed-message.react.js +++ b/web/chat/composed-message.react.js @@ -1,266 +1,270 @@ // @flow import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { Circle as CircleIcon, CheckCircle as CheckCircleIcon, XCircle as XCircleIcon, } from 'react-feather'; import { type ChatMessageInfoItem } from 'lib/selectors/chat-selectors'; import { useSidebarExistsOrCanBeCreated } from 'lib/shared/thread-utils'; import { stringForUser } from 'lib/shared/user-utils'; import { assertComposableMessageType } from 'lib/types/message-types'; import { type ThreadInfo } from 'lib/types/thread-types'; import { type InputState, InputStateContext } from '../input/input-state'; import css from './chat-message-list.css'; import FailedSend from './failed-send.react'; import { InlineSidebar } from './inline-sidebar.react'; import MessageActionButton from './message-action-button'; import MessageReplyButton from './message-reply-button.react'; import { type OnMessagePositionWithContainerInfo, type MessagePositionInfo, } from './position-types'; import { tooltipPositions } from './tooltip-utils'; const availableTooltipPositionsForViewerMessage = [ tooltipPositions.TOP_RIGHT, tooltipPositions.LEFT, ]; const availableTooltipPositionsForNonViewerMessage = [ tooltipPositions.TOP_LEFT, tooltipPositions.RIGHT, ]; type BaseProps = { +item: ChatMessageInfoItem, +threadInfo: ThreadInfo, +sendFailed: boolean, +setMouseOverMessagePosition: ( messagePositionInfo: MessagePositionInfo, ) => void, +mouseOverMessagePosition?: ?OnMessagePositionWithContainerInfo, +canReply: boolean, +children: React.Node, +fixedWidth?: boolean, +borderRadius: number, }; type BaseConfig = React.Config; type Props = { ...BaseProps, // Redux state +sidebarExistsOrCanBeCreated: boolean, // withInputState +inputState: ?InputState, }; class ComposedMessage extends React.PureComponent { static defaultProps: { +borderRadius: number } = { borderRadius: 8, }; render(): React.Node { assertComposableMessageType(this.props.item.messageInfo.type); const { borderRadius, item, threadInfo } = this.props; const { id, creator } = item.messageInfo; const threadColor = threadInfo.color; const { isViewer } = creator; const contentClassName = classNames({ [css.content]: true, [css.viewerContent]: isViewer, [css.nonViewerContent]: !isViewer, }); const messageBoxContainerClassName = classNames({ [css.messageBoxContainer]: true, [css.viewerMessageBoxContainer]: isViewer, [css.nonViewerMessageBoxContainer]: !isViewer, [css.fixedWidthMessageBoxContainer]: this.props.fixedWidth, }); const messageBoxClassName = classNames({ [css.messageBox]: true, [css.fixedWidthMessageBox]: this.props.fixedWidth, }); const messageBoxStyle = { borderTopRightRadius: isViewer && !item.startsCluster ? 0 : borderRadius, borderBottomRightRadius: isViewer && !item.endsCluster ? 0 : borderRadius, borderTopLeftRadius: !isViewer && !item.startsCluster ? 0 : borderRadius, borderBottomLeftRadius: !isViewer && !item.endsCluster ? 0 : borderRadius, }; let authorName = null; if (!isViewer && item.startsCluster) { authorName = ( {stringForUser(creator)} ); } let deliveryIcon = null; let failedSendInfo = null; if (isViewer) { let deliveryIconSpan; let deliveryIconColor = threadColor; if (id !== null && id !== undefined) { deliveryIconSpan = ; } else if (this.props.sendFailed) { deliveryIconSpan = ; deliveryIconColor = 'FF0000'; failedSendInfo = ; } else { deliveryIconSpan = ; } deliveryIcon = (
{deliveryIconSpan}
); } let replyButton; if ( this.props.mouseOverMessagePosition && this.props.mouseOverMessagePosition.item.messageInfo.id === id && this.props.canReply ) { const { inputState } = this.props; invariant(inputState, 'inputState should be set in ComposedMessage'); replyButton = ( ); } let messageActionButton; if ( this.props.mouseOverMessagePosition && this.props.mouseOverMessagePosition.item.messageInfo.id === id && this.props.sidebarExistsOrCanBeCreated ) { const availableTooltipPositions = isViewer ? availableTooltipPositionsForViewerMessage : availableTooltipPositionsForNonViewerMessage; messageActionButton = ( ); } let viewerActionLinks, nonViewerActionLinks; if (isViewer && (replyButton || messageActionButton)) { viewerActionLinks = (
- {messageActionButton} - {replyButton} +
+ {messageActionButton} + {replyButton} +
); } else if (replyButton || messageActionButton) { nonViewerActionLinks = (
- {replyButton} - {messageActionButton} +
+ {replyButton} + {messageActionButton} +
); } let inlineSidebar = null; if (item.threadCreatedFromMessage) { const positioning = isViewer ? 'right' : 'left'; inlineSidebar = (
); } return ( {authorName}
{viewerActionLinks}
{this.props.children}
{nonViewerActionLinks}
{deliveryIcon}
{failedSendInfo} {inlineSidebar}
); } onMouseEnter: (event: SyntheticEvent) => void = event => { const { item } = this.props; const rect = event.currentTarget.getBoundingClientRect(); const { top, bottom, left, right, height, width } = rect; const messagePosition = { top, bottom, left, right, height, width }; this.props.setMouseOverMessagePosition({ type: 'on', item, messagePosition, }); }; onMouseLeave: () => void = () => { const { item } = this.props; this.props.setMouseOverMessagePosition({ type: 'off', item }); }; } type ConnectedConfig = React.Config< BaseProps, typeof ComposedMessage.defaultProps, >; const ConnectedComposedMessage: React.ComponentType = React.memo( function ConnectedComposedMessage(props) { const sidebarExistsOrCanBeCreated = useSidebarExistsOrCanBeCreated( props.threadInfo, props.item, ); const inputState = React.useContext(InputStateContext); return ( ); }, ); export default ConnectedComposedMessage; diff --git a/web/theme.css b/web/theme.css index 74cc63bb1..e8ebfe436 100644 --- a/web/theme.css +++ b/web/theme.css @@ -1,103 +1,104 @@ :root { /* Never use color values defined here directly in CSS. Add color variables to "Color Theme" below The reason we never use color values defined here directly in CSS is 1. It makes changing themes from light / dark / user generated impossible. 2. Gives the programmer context into the color being used. 3. If our color system changes it's much easier to change color values in one place. Add a color value to the theme below, and then use it in your CSS. naming convention: - bg: background. - fg: foreground. - color: text-color */ --shades-white-100: #ffffff; --shades-white-90: #f5f5f5; --shades-white-80: #ebebeb; --shades-white-70: #e0e0e0; --shades-white-60: #cccccc; --shades-black-100: #0a0a0a; --shades-black-90: #1f1f1f; --shades-black-80: #404040; --shades-black-70: #666666; --shades-black-60: #808080; --violet-dark-100: #7e57c2; --violet-dark-80: #6d49ab; --violet-dark-60: #563894; --violet-dark-40: #44297a; --violet-dark-20: #331f5c; --violet-light-100: #ae94db; --violet-light-80: #b9a4df; --violet-light-60: #d3c6ec; --violet-light-40: #e8e0f5; --violet-light-20: #f3f0fa; --success-light-10: #d5f6e3; --success-light-50: #6cdf9c; --success-primary: #00c853; --success-dark-50: #029841; --success-dark-90: #034920; --error-light-10: #feebe6; --error-light-50: #f9947b; --error-primary: #f53100; --error-dark-50: #b62602; --error-dark-90: #4f1203; --bg: var(--shades-black-100); --fg: var(--shades-white-100); --color-disabled: var(--shades-black-60); --text-input-bg: var(--shades-black-80); --text-input-color: var(--shades-white-60); --text-input-placeholder: var(--shades-white-60); --border: var(--shades-black-80); --error: var(--error-primary); --success: var(--success-dark-50); /* Color Theme */ --btn-bg-primary: var(--violet-dark-100); --btn-bg-danger: var(--error-primary); --chat-bg: var(--violet-dark-80); --chat-confirmation-icon: var(--violet-dark-100); --keyserver-selection: var(--violet-dark-60); --thread-selection: var(--violet-light-80); --selected-thread-bg: var(--shades-black-90); --chat-timestamp-color: var(--shades-black-60); --tool-tip-bg: var(--shades-black-80); --tool-tip-color: var(--shades-black-60); --border-color: var(--shades-black-60); --calendar-chevron: var(--shades-black-60); --calendar-day-bg: var(--shades-black-60); --calendar-day-selected-color: var(--violet-dark-80); --community-bg: var(--shades-black-90); --unread-bg: var(--error-primary); --settings-btn-bg: var(--violet-dark-100); --modal-bg: var(--shades-black-90); --join-bg: var(--shades-black-90); --help-color: var(--shades-black-60); --breadcrumb-color: var(--shades-black-60); --breadcrumb-color-unread: var(--shades-white-60); --btn-secondary-border: var(--shades-black-60); --thread-color-read: var(--shades-black-60); --thread-from-color-read: var(--shades-black-80); --thread-last-message-color-read: var(--shades-black-60); --relationship-button-green: var(--success-dark-50); --relationship-button-red: var(--error-primary); --relationship-button-text: var(--fg); --disconnected-bar-alert-bg: var(--error-dark-50); --disconnected-bar-alert-color: var(--shades-white-100); --disconnected-bar-connecting-bg: var(--shades-white-70); --disconnected-bar-connecting-color: var(--shades-black-100); --permission-color: var(--shades-white-60); --thread-top-bar-color: var(--shades-white-100); --thread-top-bar-menu-color: var(--shades-white-70); --thread-ancestor-keyserver-border: var(--shades-black-70); --thread-ancestor-color-light: var(--shades-white-70); --thread-ancestor-color-dark: var(--shades-black-100); --text-message-default-background: var(--shades-black-80); + --message-action-tooltip-bg: var(--shades-black-90); }