feat: upgraded emoji-mart and added theme support
This commit is contained in:
parent
dd58d29450
commit
c6af1a7468
|
@ -83,7 +83,8 @@
|
|||
"rebuild-curve25519-js": "cd node_modules/curve25519-js && yarn install && yarn build && cd ../../"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emoji-mart/data": "1.0.2",
|
||||
"@emoji-mart/data": "^1.0.6",
|
||||
"@emoji-mart/react": "^1.0.1",
|
||||
"@reduxjs/toolkit": "^1.4.0",
|
||||
"abort-controller": "3.0.0",
|
||||
"auto-bind": "^4.0.0",
|
||||
|
@ -104,7 +105,7 @@
|
|||
"electron-is-dev": "^1.1.0",
|
||||
"electron-localshortcut": "^3.2.1",
|
||||
"electron-updater": "^4.2.2",
|
||||
"emoji-mart": "5.1.0",
|
||||
"emoji-mart": "^5.2.2",
|
||||
"filesize": "3.6.1",
|
||||
"firstline": "1.2.1",
|
||||
"fs-extra": "9.0.0",
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -170,77 +170,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.session-emoji-panel {
|
||||
position: absolute;
|
||||
bottom: 68px;
|
||||
right: 0px;
|
||||
padding: var(--margins-lg);
|
||||
|
||||
z-index: 5;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: var(--default-duration);
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
& > section.emoji-mart {
|
||||
font-family: $session-font-default;
|
||||
font-size: $session-font-sm;
|
||||
background-color: var(--color-cell-background);
|
||||
border: 1px solid var(--color-session-border);
|
||||
border-radius: 8px;
|
||||
padding-bottom: var(--margins-sm);
|
||||
|
||||
.emoji-mart-category-label {
|
||||
top: -2px;
|
||||
|
||||
span {
|
||||
font-family: $session-font-default;
|
||||
padding-top: var(--margins-sm);
|
||||
background-color: var(--color-cell-background);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-scroll {
|
||||
height: 340px;
|
||||
}
|
||||
|
||||
.emoji-mart-category .emoji-mart-emoji span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.emoji-mart-bar:last-child {
|
||||
border: none;
|
||||
|
||||
.emoji-mart-preview {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: calc(100% - 40px);
|
||||
left: calc(100% - 79px);
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background-color: var(--color-cell-background);
|
||||
transform: rotate(45deg);
|
||||
border-radius: 3px;
|
||||
transform: scaleY(1.4) rotate(45deg);
|
||||
border: 0.7px solid var(--color-session-border);
|
||||
clip-path: polygon(100% 100%, 7.2px 100%, 100% 7.2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.send-message-button {
|
||||
animation: fadein var(--default-duration);
|
||||
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
import React, { forwardRef, MutableRefObject, useEffect } from 'react';
|
||||
import React, { forwardRef, useEffect, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
import data from '@emoji-mart/data';
|
||||
// @ts-ignore
|
||||
import { Picker } from '../../../node_modules/emoji-mart/dist/index.cjs';
|
||||
import Picker from '@emoji-mart/react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getTheme } from '../../state/selectors/theme';
|
||||
import { noop } from 'lodash';
|
||||
import { loadEmojiPanelI18n } from '../../util/i18n';
|
||||
import { FixedBaseEmoji, FixedPickerProps } from '../../types/Reaction';
|
||||
import { ThemeStateType } from '../../themes/colors.js';
|
||||
import { COLORS, PrimaryColorStateType, THEMES, ThemeStateType } from '../../themes/colors.js';
|
||||
import { hexColorToRGB } from '../../util/hexColorToRGB';
|
||||
import { getPrimaryColor } from '../../state/selectors/primaryColor';
|
||||
import { i18nEmojiData } from '../../util/emoji';
|
||||
|
||||
export const StyledEmojiPanel = styled.div<{ isModal: boolean; theme: ThemeStateType }>`
|
||||
export const StyledEmojiPanel = styled.div<{
|
||||
isModal: boolean;
|
||||
primaryColor: PrimaryColorStateType;
|
||||
theme: ThemeStateType;
|
||||
panelBackgroundRGB: string;
|
||||
panelTextRGB: string;
|
||||
}>`
|
||||
padding: var(--margins-lg);
|
||||
z-index: 5;
|
||||
opacity: 0;
|
||||
|
@ -29,41 +35,25 @@ export const StyledEmojiPanel = styled.div<{ isModal: boolean; theme: ThemeState
|
|||
}
|
||||
|
||||
em-emoji-picker {
|
||||
background-color: var(--color-cell-background);
|
||||
border: 1px solid var(--color-session-border);
|
||||
${props => props.panelBackgroundRGB && `background-color: rgb(${props.panelBackgroundRGB})`};
|
||||
border: 1px solid var(--border-color);
|
||||
padding-bottom: var(--margins-sm);
|
||||
--shadow: none;
|
||||
--border-radius: 8px;
|
||||
--color-border: var(--color-session-border);
|
||||
--font-family: var(--font-default);
|
||||
--font-size: var(--font-size-sm);
|
||||
--rgb-accent: 0, 247, 130; // Constants.UI.COLORS.GREEN
|
||||
|
||||
${props => {
|
||||
switch (props.theme) {
|
||||
case 'ocean-dark':
|
||||
// TODO Theming
|
||||
return ``;
|
||||
case 'ocean-light':
|
||||
// TODO Theming
|
||||
return ``;
|
||||
case 'classic-dark':
|
||||
return `
|
||||
--background-rgb: 27, 27, 27; // var(--color-cell-background)
|
||||
--rgb-background: 27, 27, 27;
|
||||
--rgb-color: 255, 255, 255; // var(--color-text)
|
||||
--rgb-input: 27, 27, 27;
|
||||
`;
|
||||
case 'classic-light':
|
||||
default:
|
||||
return `
|
||||
--background-rgb: 249, 249, 249; // var(--color-cell-background)
|
||||
--rgb-background: 249, 249, 249;
|
||||
--rgb-color: 0, 0, 0; // var(--color-text)
|
||||
--rgb-input: 249, 249, 249;
|
||||
`;
|
||||
}
|
||||
}}
|
||||
--shadow: none;
|
||||
--border-radius: 8px;
|
||||
--color-border: var(--border-color);
|
||||
--color-border-over: var(--border-color);
|
||||
--background-rgb: ${props => props.panelBackgroundRGB};
|
||||
--rgb-background: ${props => props.panelBackgroundRGB};
|
||||
--rgb-color: ${props => props.panelTextRGB};
|
||||
--rgb-input: ${props => props.panelBackgroundRGB};
|
||||
--rgb-accent: ${props =>
|
||||
hexColorToRGB(
|
||||
props.primaryColor
|
||||
? (COLORS.PRIMARY as any)[`${props.primaryColor.toUpperCase()}`]
|
||||
: COLORS.PRIMARY.GREEN
|
||||
)};
|
||||
|
||||
${props =>
|
||||
!props.isModal &&
|
||||
|
@ -75,14 +65,14 @@ export const StyledEmojiPanel = styled.div<{ isModal: boolean; theme: ThemeState
|
|||
left: calc(100% - 79px);
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background-color: var(--color-cell-background);
|
||||
transform: rotate(45deg);
|
||||
border-radius: 3px;
|
||||
transform: scaleY(1.4) rotate(45deg);
|
||||
border: 0.7px solid var(--color-session-border);
|
||||
border: 0.7px solid var(--border-color);
|
||||
clip-path: polygon(100% 100%, 7.2px 100%, 100% 7.2px);
|
||||
${props.panelBackgroundRGB && `background-color: rgb(${props.panelBackgroundRGB})`};
|
||||
}
|
||||
`}
|
||||
`};
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -90,6 +80,7 @@ type Props = {
|
|||
onEmojiClicked: (emoji: FixedBaseEmoji) => void;
|
||||
show: boolean;
|
||||
isModal?: boolean;
|
||||
// NOTE Currently this doesn't work but we have a PR waiting to be merged to resolve this. William Grant 30/09/2022
|
||||
onKeyDown?: (event: any) => void;
|
||||
};
|
||||
|
||||
|
@ -102,45 +93,51 @@ const pickerProps: FixedPickerProps = {
|
|||
|
||||
export const SessionEmojiPanel = forwardRef<HTMLDivElement, Props>((props: Props, ref) => {
|
||||
const { onEmojiClicked, show, isModal = false, onKeyDown } = props;
|
||||
const primaryColor = useSelector(getPrimaryColor);
|
||||
const theme = useSelector(getTheme);
|
||||
const emojiPanelTheme = theme.includes('light') ? 'light' : 'dark';
|
||||
const pickerRef = ref as MutableRefObject<HTMLDivElement>;
|
||||
|
||||
const [panelBackgroundRGB, setPanelBackgroundRGB] = useState('');
|
||||
const [panelTextRGB, setPanelTextRGB] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
let isCancelled = false;
|
||||
if (pickerRef.current !== null) {
|
||||
if (pickerRef.current.children.length === 0) {
|
||||
loadEmojiPanelI18n()
|
||||
.then(async i18n => {
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
new Picker({
|
||||
data,
|
||||
ref,
|
||||
i18n,
|
||||
theme: emojiPanelTheme,
|
||||
onEmojiSelect: onEmojiClicked,
|
||||
onKeyDown,
|
||||
...pickerProps,
|
||||
});
|
||||
})
|
||||
.catch(noop);
|
||||
}
|
||||
switch (theme) {
|
||||
case 'ocean-dark':
|
||||
setPanelBackgroundRGB(hexColorToRGB(THEMES.OCEAN_DARK.COLOR1));
|
||||
setPanelTextRGB(hexColorToRGB(THEMES.OCEAN_DARK.COLOR6));
|
||||
break;
|
||||
case 'ocean-light':
|
||||
setPanelBackgroundRGB(hexColorToRGB(THEMES.OCEAN_LIGHT.COLOR7!));
|
||||
setPanelTextRGB(hexColorToRGB(THEMES.OCEAN_LIGHT.COLOR1));
|
||||
break;
|
||||
case 'classic-dark':
|
||||
setPanelBackgroundRGB(hexColorToRGB(THEMES.CLASSIC_DARK.COLOR1));
|
||||
setPanelTextRGB(hexColorToRGB(THEMES.CLASSIC_DARK.COLOR6));
|
||||
break;
|
||||
case 'classic-light':
|
||||
default:
|
||||
setPanelBackgroundRGB(hexColorToRGB(THEMES.CLASSIC_LIGHT.COLOR6));
|
||||
setPanelTextRGB(hexColorToRGB(THEMES.CLASSIC_LIGHT.COLOR0));
|
||||
break;
|
||||
}
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [data, pickerProps]);
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<StyledEmojiPanel
|
||||
isModal={isModal}
|
||||
primaryColor={primaryColor}
|
||||
theme={theme}
|
||||
panelBackgroundRGB={panelBackgroundRGB}
|
||||
panelTextRGB={panelTextRGB}
|
||||
className={classNames(show && 'show')}
|
||||
ref={ref}
|
||||
/>
|
||||
>
|
||||
<Picker
|
||||
theme={theme.includes('light') ? 'light' : 'dark'}
|
||||
i18n={i18nEmojiData}
|
||||
onEmojiSelect={onEmojiClicked}
|
||||
onKeyDown={onKeyDown}
|
||||
{...pickerProps}
|
||||
/>
|
||||
</StyledEmojiPanel>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { SuggestionDataItem } from 'react-mentions';
|
||||
import styled from 'styled-components';
|
||||
// @ts-ignore
|
||||
import { SearchIndex } from '../../../../node_modules/emoji-mart/dist/index.cjs';
|
||||
import { SearchIndex } from 'emoji-mart';
|
||||
import { searchSync } from '../../../util/emoji.js';
|
||||
|
||||
const EmojiQuickResult = styled.span`
|
||||
|
|
|
@ -109,7 +109,7 @@ export const MessageContextMenu = (props: Props) => {
|
|||
|
||||
const emojiPanelRef = useRef<HTMLDivElement>(null);
|
||||
const [showEmojiPanel, setShowEmojiPanel] = useState(false);
|
||||
// emoji-mart v5.1 default dimensions
|
||||
// emoji-mart v5.2.2 default dimensions
|
||||
const emojiPanelWidth = 354;
|
||||
const emojiPanelHeight = 435;
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import { OpenGroupData } from '../data/opengroups';
|
|||
import { loadKnownBlindedKeys } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys';
|
||||
import nativeEmojiData from '@emoji-mart/data';
|
||||
import { initialiseEmojiData } from '../util/emoji';
|
||||
import { loadEmojiPanelI18n } from '../util/i18n';
|
||||
// tslint:disable: max-classes-per-file
|
||||
|
||||
// Globally disable drag and drop
|
||||
|
@ -176,7 +175,7 @@ Storage.onready(async () => {
|
|||
await window.Events.setThemeSetting(newThemeSetting);
|
||||
|
||||
try {
|
||||
initialiseEmojiData(nativeEmojiData);
|
||||
await initialiseEmojiData(nativeEmojiData);
|
||||
await AttachmentDownloads.initAttachmentPaths();
|
||||
|
||||
await Promise.all([
|
||||
|
@ -184,7 +183,6 @@ Storage.onready(async () => {
|
|||
BlockedNumberController.load(),
|
||||
OpenGroupData.opengroupRoomsLoad(),
|
||||
loadKnownBlindedKeys(),
|
||||
loadEmojiPanelI18n(),
|
||||
]);
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import { AbortSignal } from 'abort-controller';
|
||||
import { getEmojiDataFromNative } from 'emoji-mart';
|
||||
import { Data } from '../../../../data/data';
|
||||
import { ConversationModel } from '../../../../models/conversation';
|
||||
import { Action, OpenGroupReactionResponse, Reaction } from '../../../../types/Reaction';
|
||||
import { getEmojiDataFromNative } from '../../../../util/emoji';
|
||||
import {
|
||||
Action,
|
||||
FixedBaseEmoji,
|
||||
OpenGroupReactionResponse,
|
||||
Reaction,
|
||||
} from '../../../../types/Reaction';
|
||||
import { Reactions } from '../../../../util/reactions';
|
||||
import { OnionSending } from '../../../onions/onionSend';
|
||||
import { UserUtils } from '../../../utils';
|
||||
|
@ -68,7 +73,9 @@ export const sendSogsReactionOnionV4 = async (
|
|||
|
||||
// The SOGS endpoint supports any text input so we need to make sure we are sending a valid unicode emoji
|
||||
// for an invalid input we use https://emojipedia.org/frame-with-an-x/ as a replacement since it cannot rendered as an emoji but is valid unicode
|
||||
const emoji = getEmojiDataFromNative(reaction.emoji) ? reaction.emoji : '🖾';
|
||||
// NOTE emoji-mart v5.2.2 types for getEmojiDataFromNative are broken
|
||||
// @ts-ignore
|
||||
const emoji = (getEmojiDataFromNative(reaction.emoji) as FixedBaseEmoji) ? reaction.emoji : '🖾';
|
||||
const endpoint = `/room/${room}/reaction/${reaction.id}/${emoji}`;
|
||||
const method = reaction.action === Action.REACT ? 'PUT' : 'DELETE';
|
||||
const serverPubkey = allValidRoomInfos[0].serverPublicKey;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EmojiSet } from 'emoji-mart';
|
||||
import { EmojiSet, PartialI18n } from 'emoji-mart';
|
||||
|
||||
export const reactionLimit: number = 6;
|
||||
|
||||
|
@ -76,6 +76,7 @@ export interface FixedPickerProps {
|
|||
noResultsEmoji?: string | undefined;
|
||||
previewPosition?: 'bottom' | 'top' | 'none' | undefined;
|
||||
skinTonePosition?: 'preview' | 'search' | 'none';
|
||||
i18n?: PartialI18n | undefined;
|
||||
onEmojiSelect?: (emoji: FixedBaseEmoji) => void;
|
||||
onClickOutside?: () => void;
|
||||
onKeyDown?: (event: any) => void;
|
||||
|
@ -83,7 +84,6 @@ export interface FixedPickerProps {
|
|||
getImageURL?: () => void;
|
||||
getSpritesheetURL?: () => void;
|
||||
// Below here I'm currently unsure of usage
|
||||
// i18n?: PartialI18n | undefined;
|
||||
// style?: React.CSSProperties | undefined;
|
||||
// color?: string | undefined;
|
||||
// skin?: EmojiSkin | undefined;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { FixedBaseEmoji, NativeEmojiData } from '../types/Reaction';
|
||||
// @ts-ignore
|
||||
import { init, PartialI18n } from 'emoji-mart';
|
||||
import { loadEmojiPanelI18n } from './i18n';
|
||||
|
||||
export type SizeClassType = 'default' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||
|
||||
|
@ -40,8 +43,9 @@ export function getEmojiSizeClass(str: string): SizeClassType {
|
|||
}
|
||||
|
||||
export let nativeEmojiData: NativeEmojiData | null = null;
|
||||
export let i18nEmojiData: PartialI18n | null = null;
|
||||
|
||||
export function initialiseEmojiData(data: any) {
|
||||
export async function initialiseEmojiData(data: any): Promise<void> {
|
||||
const ariaLabels: Record<string, string> = {};
|
||||
Object.entries(data.emojis).forEach(([key, value]: [string, any]) => {
|
||||
value.search = `,${[
|
||||
|
@ -73,6 +77,11 @@ export function initialiseEmojiData(data: any) {
|
|||
|
||||
data.ariaLabels = ariaLabels;
|
||||
nativeEmojiData = data;
|
||||
|
||||
i18nEmojiData = await loadEmojiPanelI18n();
|
||||
// Data needs to be initialised once per page load for the emoji components
|
||||
// See https://github.com/missive/emoji-mart#%EF%B8%8F%EF%B8%8F-headless-search
|
||||
init({ data, i18n: i18nEmojiData });
|
||||
}
|
||||
|
||||
// Synchronous version of Emoji Mart's SearchIndex.search()
|
||||
|
@ -148,23 +157,3 @@ export function searchSync(query: string, args?: any): Array<any> {
|
|||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// No longer exists on emoji-mart v5.1
|
||||
export function getEmojiDataFromNative(nativeString: string): FixedBaseEmoji | null {
|
||||
if (!nativeEmojiData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matches = Object.values(nativeEmojiData.emojis).filter((emoji: any) => {
|
||||
const skinMatches = (emoji as FixedBaseEmoji).skins.filter((skin: any) => {
|
||||
return skin.native === nativeString;
|
||||
});
|
||||
return skinMatches.length > 0;
|
||||
});
|
||||
|
||||
if (matches.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return matches[0] as FixedBaseEmoji;
|
||||
}
|
||||
|
|
21
yarn.lock
21
yarn.lock
|
@ -645,10 +645,15 @@
|
|||
global-agent "^3.0.0"
|
||||
global-tunnel-ng "^2.7.1"
|
||||
|
||||
"@emoji-mart/data@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.0.2.tgz#2b94c5b5f2c79611c12238438dad9516576a09ab"
|
||||
integrity sha512-+ZdzBM4llDJJvjuCEsdOYVoSlNA16MMmxKG3oF5LARkwhx6N5clr6phzneWV1qIwJsywqwG7NaBjH8DV6yzjcA==
|
||||
"@emoji-mart/data@^1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.0.6.tgz#68f71be5e023653a3f9f73f4beadfd50848e2131"
|
||||
integrity sha512-8wu3ec/kLCB0Y3K+pOKyY6Ob+xtQu3XhZvntdrpOTUQZ/PO6FW5PpFw7RE1kQ/up1fsVSJBl5mZ8Gs4SPwTYeg==
|
||||
|
||||
"@emoji-mart/react@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@emoji-mart/react/-/react-1.0.1.tgz#46b6a2e92faf16fa9b7f9471f137fa2e3d1e8ac3"
|
||||
integrity sha512-ALhLD96BOL5w+a4NI5NpmfqfF1aVjjj2qJE0dLst/OhjBfVmpteWNgn/h8LZy9ulU6AnbeS+13KnPFzDjCvRRw==
|
||||
|
||||
"@emotion/is-prop-valid@^0.8.8":
|
||||
version "0.8.8"
|
||||
|
@ -3823,10 +3828,10 @@ elliptic@^6.5.3:
|
|||
minimalistic-assert "^1.0.1"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
emoji-mart@5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.1.0.tgz#8a36a872e1297747342d1385bd7b7141ac2f4365"
|
||||
integrity sha512-ytXgeemyw4FormPQqWd35Vh06ZSnQFhVUqW51kASZzzjhQOPSGtiN3VCC7vDq94Pkxmsbet+Gps/qj5N90mEnw==
|
||||
emoji-mart@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.2.2.tgz#1c093ffc19554dd6edfcfeec9aca43ff38bcc16e"
|
||||
integrity sha512-BvcrX+Ps9MxSVEjnvxupclU3MBD6WVC4WZOY26csfC6oFdaWpFhdrzeVNVBmCLPOmzY1SE0aAsqZJRNVbZ1yhQ==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
|
|
Loading…
Reference in New Issue