Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message reactions with emojis #2964

Merged
merged 84 commits into from Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
e6b22b7
first reaction experiments
Simon-Laux Oct 21, 2022
cd12ffb
modify reaction by clicking on it
Simon-Laux Oct 22, 2022
c971367
fix hover not applied on own reactions
Simon-Laux Oct 22, 2022
7693071
format document and add react key to fix react error
Simon-Laux Oct 24, 2022
ab1a97a
fix: react on ReactionsChanged event
Simon-Laux May 10, 2023
d9195b5
on reaction per user
Simon-Laux May 10, 2023
1a23bed
adjust to new core api
Simon-Laux May 15, 2023
5d5f5a6
Move all into own folder
adzialocha Dec 22, 2023
ed5627a
Convert styles to CSS module
adzialocha Dec 22, 2023
4d4028e
Make sendReaction a callback inside the same component
adzialocha Dec 22, 2023
55dde52
Make linter happy
adzialocha Dec 22, 2023
d63e935
Add entry to CHANGELOG.md
adzialocha Dec 22, 2023
c626d39
Add hover state to message, remove reactions from context menu for now
adzialocha Jan 9, 2024
badcdd7
Bring back metadata to previous position
adzialocha Jan 10, 2024
0740ca9
Give methods the same name as event handlers
adzialocha Jan 10, 2024
8adbb73
Show shortcut menu on hover on the right or left side of message
adzialocha Jan 10, 2024
d2ff905
Increase min-width of messages now when we take space with the shortc…
adzialocha Jan 10, 2024
bf943f6
Remove unused media query for message
adzialocha Jan 10, 2024
be92b20
Change order, that's easier to read
adzialocha Jan 10, 2024
bf6dba9
Introduce helper for absolute positioning and test it with a prelimin…
adzialocha Jan 10, 2024
2ad8ecd
Position always centered and above element, add doc-strings
adzialocha Jan 10, 2024
5279049
Handle bar state in own context
adzialocha Jan 10, 2024
3a505de
Show bar always centered above button
adzialocha Jan 10, 2024
712f239
Hide bar on scroll
adzialocha Jan 10, 2024
6194967
Re-export shortcut bar methods in index.ts file
adzialocha Jan 10, 2024
70354c8
Better naming
adzialocha Jan 10, 2024
0b49881
Close bar when detecting click outside of it
adzialocha Jan 10, 2024
d6733cd
Improve click handler, do not propagate event to it when switching to…
adzialocha Jan 10, 2024
82998dd
Click emoji to send reaction in bar
adzialocha Jan 10, 2024
f848960
Simplify ShortcutMenu
adzialocha Jan 10, 2024
0c2070d
Show context menu button in shortcut menu
adzialocha Jan 10, 2024
27e3338
Fix type
adzialocha Jan 16, 2024
1c550a0
Round values
adzialocha Jan 16, 2024
6dff20c
Prevent flickering when showing reactions bar
adzialocha Jan 16, 2024
931b0a4
Limit max number of emojis to show in reactions
adzialocha Jan 16, 2024
ea13007
Show dialog with all reactions on click
adzialocha Jan 16, 2024
ab5720f
Same emojis than on Android
adzialocha Jan 16, 2024
5f1d7c8
Give user option to pick from all emojis
adzialocha Jan 16, 2024
ba25527
Do not show reactions on setup messages
adzialocha Jan 17, 2024
d6cdee7
Make sure metadata is always aligned to the right
adzialocha Jan 17, 2024
b93362d
Styling for reactions in message
adzialocha Jan 17, 2024
32cebcb
Move ShortcutMenu into own folder, start styling
adzialocha Jan 17, 2024
efb0c16
Move helper method into hook
adzialocha Jan 17, 2024
0c2e2f9
WIP styling of reactions bar
adzialocha Jan 17, 2024
4298e41
Styling for reactions dialog
adzialocha Jan 18, 2024
55ee9f2
Do not touch unrelated components for now, less margin for dialog
adzialocha Jan 18, 2024
bc63405
Use different icons, improve css transition
adzialocha Jan 18, 2024
f90f932
Do not wrap emoji picker around reactions bar
adzialocha Jan 18, 2024
4693492
Every reactions bar is different
adzialocha Jan 18, 2024
3ab2b69
Less strong shadow
adzialocha Jan 18, 2024
d2c1484
Reactions should be layered above top bar
adzialocha Jan 18, 2024
662c467
Smoother animation for bar, make sure no flickering occurs
adzialocha Jan 18, 2024
eb649df
Adjust animation curve for emojis
adzialocha Jan 18, 2024
8c9c365
Less space between emojis
adzialocha Jan 18, 2024
6a52555
Position emojis over button in reactions dialog
adzialocha Jan 18, 2024
2496101
Styling for reactions under only-media messages
adzialocha Jan 18, 2024
4af9809
Move EmojiPicker into own component and reuse it in reaction bar
adzialocha Jan 18, 2024
2073668
Less intense animation when hovering over emojis
adzialocha Jan 22, 2024
8315016
Make sure to not cut icons
adzialocha Jan 22, 2024
26a255b
Give emojis a little shadow under message
adzialocha Jan 22, 2024
543ae1a
Reduce width of reactions dialog
adzialocha Jan 22, 2024
c330c3d
Use theme vars for reactions bar, give emoji picker some shadow
adzialocha Jan 22, 2024
20ea2ae
Show background in shortcut menu buttons
adzialocha Jan 22, 2024
bd80a4b
Handle positioning cases as well where element is too low or screen i…
adzialocha Jan 22, 2024
bbfb43d
Minor comment change
adzialocha Jan 22, 2024
6de954b
Always show button to emoji picker
adzialocha Jan 22, 2024
853fb88
Context-menu action handler passes over event now
adzialocha Jan 22, 2024
bb3c9d7
Revert "Context-menu action handler passes over event now"
adzialocha Jan 22, 2024
0753bcf
Add item to context menu to open reactions bar
adzialocha Jan 22, 2024
92d320a
Slightly less shadow for reactions
adzialocha Jan 22, 2024
3692633
Larger font instead of bold for reactions count
adzialocha Jan 22, 2024
c912b38
Flat style for reactions
adzialocha Jan 22, 2024
373e58b
Capture hover events globally, constantly show shortcut menu when ove…
adzialocha Jan 23, 2024
cdf833a
Reduce moving-up animation slightly
adzialocha Jan 23, 2024
e1de0ad
Remove redundant styles in emoji picker
adzialocha Jan 23, 2024
5277ceb
Fix positioning logic
adzialocha Jan 23, 2024
43ab49e
Less flickering
adzialocha Jan 23, 2024
70dcb84
Add some comments
adzialocha Jan 23, 2024
0956447
Add more comments
adzialocha Jan 23, 2024
7d1092a
Bring back event into context menu item action
adzialocha Jan 23, 2024
7164b81
Fix issue when switching to another message reactions bar via context…
adzialocha Jan 23, 2024
675fa3c
Correct comment
adzialocha Jan 23, 2024
dbed088
Use correct string (not pulled yet from tx)
adzialocha Jan 23, 2024
3124d2c
Revert "Use correct string (not pulled yet from tx)"
adzialocha Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@
- Hovering outside of the menu closes last opened sublevel
- ChatListContextMenu mute option now opens a submenu with duration options instead of a dialog
- Add shortcut to scan qr code to "New Chat" dialog #3623
- Message reactions with emojis #2964

### Changed
- Update `deltachat-node` and `deltachat/jsonrpc-client` to `v1.132.1`
Expand Down
1 change: 1 addition & 0 deletions images/icons/more.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions images/icons/reaction.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 1 addition & 32 deletions scss/composer/_emoji-sticker-picker.scss
Expand Up @@ -46,42 +46,11 @@ $emojimart-search-height: 31px + 6px;
}
}
}
div.emoji-picker,

div.sticker-picker {
height: calc(#{$esp-height} - #{$esp-header-height});
}

div.emoji-picker {
& > div {
width: 100%;
height: 100%;
em-emoji-picker {
width: 100%;
height: 100%;
--border-radius: 0px;

// unknown effect?
--background-rgb: var(--emoji-picker-background-rgb);
// background
--rgb-background: var(--emoji-picker-background-rgb);
// base color for text, seach placeholder an icons
--rgb-color: var(--emoji-picker-text-rgb);
--rgb-accent: var(--emoji-picker-accent-rgb);
// selected input field bg
--rgb-input: var(--emoji-picker-selected-input-bg-rgb);
// border, scrollbar, in-active search input, emoji hover background
--color-border: var(--emoji-picker-border-and-hover-color);

--font-family: $roboto;

// --category-icon-size: 24px;
// --font-size: 20px;
--color-border-over: rgba(0, 0, 0, 0);
--shadow: none;
}
}
}

div.sticker-picker {
background: var(--emojiMartBg);
width: 100%;
Expand Down
2 changes: 1 addition & 1 deletion scss/message/_message-attachment.scss
Expand Up @@ -7,7 +7,7 @@
margin-left: -12px;
margin-right: -12px;
margin-top: -10px;
margin-bottom: -11px;
margin-bottom: -12px;

border-radius: 16px;
overflow: hidden;
Expand Down
23 changes: 4 additions & 19 deletions scss/message/_message.scss
Expand Up @@ -149,7 +149,6 @@
.metadata {
margin-top: 10px;
margin-bottom: -7px;
float: right;
}

.module-message__img-attachment {
Expand Down Expand Up @@ -223,9 +222,10 @@
}

.message.outgoing {
flex-direction: row-reverse;
float: right;
margin-right: 0;
margin-left: 32px;
margin-right: 0;

.metadata:not(.with-image-no-caption) {
& > .date {
Expand Down Expand Up @@ -370,25 +370,10 @@
}
}

/* Spec: container > 438px and container < 593px*/
@media (min-width: 800px) and (max-width: 925px) {
.message {
max-width: 374px;

&.incoming {
margin-right: auto;
}

&.outgoing {
margin-left: auto;
}
}
}

// Spec: container > 593px
@media (min-width: 926px) {
.message {
max-width: 66%;
max-width: 75%;

&.incoming {
margin-right: auto;
Expand All @@ -402,7 +387,7 @@

@media (max-width: 925px) {
.message {
max-width: 90%;
max-width: 100%;

&.incoming {
margin-right: auto;
Expand Down
129 changes: 129 additions & 0 deletions src/renderer/components/AbsolutePositioningHelper/index.tsx
@@ -0,0 +1,129 @@
import React, { useState, useLayoutEffect, useRef } from 'react'

import styles from './styles.module.scss'

import type { ReactNode } from 'react'

type Props = {
/** Desired x position of element, usually determined by a user click event */
x: number

/** Desired y position of element, usually determined by a user click event */
y: number

/** Element which will be absolutely positioned and automatically adjusted */
children: ReactNode
}

/**
* Helper component which fixes it's wrapped components absolutely on the screen,
* if possible, centered and above the desired position. The position is usually
* determined by an user mouse event.
*
* Additionally it makes sure the wrapped components are not crossing the window's
* boundaries. In that case the position will be automatically adjusted.
*
* Note: This does not adjust the element after window size changes. Usually you
* want to cancel what you're doing with this component after a window resize
* or scroll.
*/
export default function AbsolutePositioningHelper(props: Props) {
const ref = useRef<HTMLDivElement>(null)
const [dimensions, setDimensions] = useState({
refWidth: 0,
refHeight: 0,
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
})

// Observe size & position changes of the wrapped "ref" element.
useLayoutEffect(() => {
if (!ref.current) {
return
}

const refElem = ref.current

const observer = new ResizeObserver(entries => {
if (entries.length === 0) {
return
}

const { width: refWidth, height: refHeight } = entries[0].contentRect

if (refWidth === 0 || refHeight === 0) {
return
}

setDimensions({
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
refWidth,
refHeight,
})
})

observer.observe(refElem)

return () => {
observer.unobserve(refElem)
}
}, [])

let x

// Adjust element position to be centered
x = props.x - dimensions.refWidth / 2

// Change the x position when it is too far to the right and leaving the window
if (x + dimensions.refWidth > dimensions.windowWidth) {
x = dimensions.windowWidth - dimensions.refWidth
}

// .. and when it is too far to the left
if (x < 0) {
x = 0
}

let y

// Align element to be above the initial y position
y = props.y - dimensions.refHeight

// Align the element under the initial y position if it would otherwise cut the top
if (y < dimensions.refHeight) {
y = props.y
}

// .. and when it is too far to the bottom
if (y + dimensions.refHeight > dimensions.windowHeight) {
y = dimensions.windowHeight - dimensions.refHeight
}

// Finally adjust when the screen is just too small
if (y < 0) {
y = 0
}

// Hide wrapper if observed element is not rendered yet. Do not remove
// it from DOM as we need to learn about it's size as soon as it comes in.
// This helps us to prevent weird flickering when React is not ready yet.
const visibility =
dimensions.refWidth === 0 || dimensions.refHeight === 0
? 'hidden'
: 'visible'

return (
<div
style={{
left: `${x}px`,
top: `${y}px`,
visibility,
}}
className={styles.absolutePositioningHelper}
ref={ref}
>
{props.children}
</div>
)
}
@@ -0,0 +1,4 @@
.absolutePositioningHelper {
position: fixed;
z-index: 1000;
}
23 changes: 23 additions & 0 deletions src/renderer/components/ContactName/index.tsx
@@ -0,0 +1,23 @@
import React from 'react'

import { InlineVerifiedIcon } from '../VerifiedIcon'

import styles from './styles.module.scss'

type Props = {
displayName: string
address: string
isVerified?: boolean
}

export default function ContactName(props: Props) {
return (
<div className={styles.contactName}>
<div className={styles.contactNameDisplay}>
<span className={styles.contactNameTruncated}>{props.displayName}</span>
{props.isVerified && <InlineVerifiedIcon />}
</div>
<div className={styles.contactNameAddress}>{props.address}</div>
</div>
)
}
23 changes: 23 additions & 0 deletions src/renderer/components/ContactName/styles.module.scss
@@ -0,0 +1,23 @@
.contactName {
display: flex;
flex-direction: column;
justify-content: center;
}

.contactNameDisplay {
font-weight: bold;
white-space: nowrap;
}

.contactNameTruncated {
text-overflow: ellipsis;
overflow: hidden;
}

.contactNameAddress {
color: var(--contactEmailColor);
margin-top: 3px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
7 changes: 5 additions & 2 deletions src/renderer/components/ContextMenu.tsx
Expand Up @@ -9,7 +9,10 @@ import classNames from 'classnames'

import useContextMenu from '../hooks/useContextMenu'

type ContextMenuItemActionable = { action: () => void; subitems?: never }
type ContextMenuItemActionable = {
action: (event: React.MouseEvent<Element, MouseEvent>) => void
subitems?: never
}

type ContextMenuItemExpandable = {
action?: never
Expand Down Expand Up @@ -340,7 +343,7 @@ export function ContextMenu(props: {
keyboardFocus.current = -1
didOpen.current = false
closeCallback()
item.action()
item.action(ev)
}
}}
onMouseOver={() => {
Expand Down
67 changes: 67 additions & 0 deletions src/renderer/components/EmojiPicker/index.tsx
@@ -0,0 +1,67 @@
import Picker from '@emoji-mart/react'
import React from 'react'
import classNames from 'classnames'
import emojiData from '@emoji-mart/data'

import useTranslationFunction from '../../hooks/useTranslationFunction'
import { useThemeCssVar } from '../../ThemeManager'

import styles from './styles.module.scss'

import type { BaseEmoji } from 'emoji-mart/index'

type Props = {
className?: string
onSelect: (emoji: BaseEmoji) => void
full?: boolean
}

export default function EmojiPicker({
className,
onSelect,
full = false,
}: Props) {
const tx = useTranslationFunction()

let iconsTheme = useThemeCssVar('--SPECIAL-emoji-picker-category-icon-style')
if (iconsTheme !== 'solid' && iconsTheme !== 'outline') {
iconsTheme = 'solid'
}

return (
<div
className={classNames(styles.emojiPicker, className, {
[styles.full]: full,
})}
>
<Picker
data={emojiData}
i18n={{
search: tx('search'),
notfound: tx('emoji_not_found'),
categories: {
search: tx('emoji_search_results'),
recent: tx('emoji_recent'),
people: tx('emoji_people'),
nature: tx('emoji_nature'),
foods: tx('emoji_foods'),
activity: tx('emoji_activity'),
places: tx('emoji_places'),
objects: tx('emoji_objects'),
symbols: tx('emoji_symbols'),
flags: tx('emoji_flags'),
},
}}
native
onEmojiSelect={onSelect}
navPosition='bottom'
previewPosition='none'
searchPosition='sticky'
skinTonePosition='none'
autoFocus
dynamicWidth={full}
icons={iconsTheme}
/>
</div>
)
}