2022-04-05 02:04:01 +12:00
|
|
|
import * as React from "react";
|
2022-04-05 11:56:21 +12:00
|
|
|
import { useRef, useState } from "react";
|
2023-05-24 22:25:20 +12:00
|
|
|
import { Typography, Box, TextField, ClickAwayListener, Fade, InputAdornment, styled, IconButton, Popper } from "@mui/material";
|
2022-04-05 11:56:21 +12:00
|
|
|
import { Close } from "@mui/icons-material";
|
2022-04-08 13:46:33 +12:00
|
|
|
import { useTranslation } from "react-i18next";
|
2022-04-06 11:40:34 +12:00
|
|
|
import { splitNoEmpty } from "../app/utils";
|
2022-04-05 02:04:01 +12:00
|
|
|
import { rawEmojis } from "../app/emojis";
|
2022-04-06 11:40:34 +12:00
|
|
|
|
|
|
|
// Create emoji list by category and create a search base (string with all search words)
|
|
|
|
//
|
|
|
|
// This also filters emojis that are not supported by Desktop Chrome.
|
|
|
|
// This is a hack, but on Ubuntu 18.04, with Chrome 99, only Emoji <= 11 are supported.
|
2022-04-05 02:04:01 +12:00
|
|
|
|
|
|
|
const emojisByCategory = {};
|
2022-04-05 12:44:40 +12:00
|
|
|
const isDesktopChrome = /Chrome/.test(navigator.userAgent) && !/Mobile/.test(navigator.userAgent);
|
|
|
|
const maxSupportedVersionForDesktopChrome = 11;
|
2022-04-05 02:04:01 +12:00
|
|
|
rawEmojis.forEach((emoji) => {
|
|
|
|
if (!emojisByCategory[emoji.category]) {
|
|
|
|
emojisByCategory[emoji.category] = [];
|
|
|
|
}
|
2022-04-05 12:44:40 +12:00
|
|
|
try {
|
|
|
|
const unicodeVersion = parseFloat(emoji.unicode_version);
|
|
|
|
const supportedEmoji = unicodeVersion <= maxSupportedVersionForDesktopChrome || !isDesktopChrome;
|
|
|
|
if (supportedEmoji) {
|
2022-04-06 11:40:34 +12:00
|
|
|
const searchBase = `${emoji.description.toLowerCase()} ${emoji.aliases.join(" ")} ${emoji.tags.join(" ")}`;
|
|
|
|
const emojiWithSearchBase = { ...emoji, searchBase };
|
|
|
|
emojisByCategory[emoji.category].push(emojiWithSearchBase);
|
2022-04-05 12:44:40 +12:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Nothing. Ignore.
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2022-04-05 02:04:01 +12:00
|
|
|
});
|
|
|
|
|
|
|
|
const EmojiPicker = (props) => {
|
2022-04-08 13:46:33 +12:00
|
|
|
const { t } = useTranslation();
|
2022-04-05 02:04:01 +12:00
|
|
|
const open = Boolean(props.anchorEl);
|
2022-04-05 11:56:21 +12:00
|
|
|
const [search, setSearch] = useState("");
|
|
|
|
const searchRef = useRef(null);
|
2022-04-06 11:40:34 +12:00
|
|
|
const searchFields = splitNoEmpty(search.toLowerCase(), " ");
|
2022-04-05 11:56:21 +12:00
|
|
|
|
|
|
|
const handleSearchClear = () => {
|
|
|
|
setSearch("");
|
|
|
|
searchRef.current?.focus();
|
|
|
|
};
|
2022-04-05 02:04:01 +12:00
|
|
|
|
|
|
|
return (
|
2022-04-06 11:40:34 +12:00
|
|
|
<Popper open={open} anchorEl={props.anchorEl} placement="bottom-start" sx={{ zIndex: 10005 }} transition>
|
|
|
|
{({ TransitionProps }) => (
|
|
|
|
<ClickAwayListener onClickAway={props.onClose}>
|
|
|
|
<Fade {...TransitionProps} timeout={350}>
|
|
|
|
<Box
|
|
|
|
sx={{
|
|
|
|
boxShadow: 3,
|
|
|
|
padding: 2,
|
|
|
|
paddingRight: 0,
|
|
|
|
paddingBottom: 1,
|
|
|
|
width: "380px",
|
|
|
|
maxHeight: "300px",
|
|
|
|
backgroundColor: "background.paper",
|
|
|
|
overflowY: "scroll",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<TextField
|
|
|
|
inputRef={searchRef}
|
|
|
|
margin="dense"
|
|
|
|
size="small"
|
2022-04-08 13:46:33 +12:00
|
|
|
placeholder={t("emoji_picker_search_placeholder")}
|
2022-04-06 11:40:34 +12:00
|
|
|
value={search}
|
|
|
|
onChange={(ev) => setSearch(ev.target.value)}
|
|
|
|
type="text"
|
|
|
|
variant="standard"
|
|
|
|
fullWidth
|
|
|
|
sx={{ marginTop: 0, marginBottom: "12px", paddingRight: 2 }}
|
2022-05-03 12:02:21 +12:00
|
|
|
inputProps={{
|
|
|
|
role: "searchbox",
|
|
|
|
"aria-label": t("emoji_picker_search_placeholder"),
|
2023-05-25 13:32:15 +12:00
|
|
|
}}
|
2023-05-25 07:08:33 +12:00
|
|
|
InputProps={{
|
2022-04-06 11:40:34 +12:00
|
|
|
endAdornment: (
|
|
|
|
<InputAdornment position="end" sx={{ display: search ? "" : "none" }}>
|
2022-05-03 11:30:29 +12:00
|
|
|
<IconButton size="small" onClick={handleSearchClear} edge="end" aria-label={t("emoji_picker_search_clear")}>
|
|
|
|
<Close />
|
|
|
|
</IconButton>
|
2022-04-06 11:40:34 +12:00
|
|
|
</InputAdornment>
|
2023-05-24 07:13:01 +12:00
|
|
|
),
|
2022-04-06 11:40:34 +12:00
|
|
|
}}
|
2022-04-05 11:56:21 +12:00
|
|
|
/>
|
2022-04-06 11:40:34 +12:00
|
|
|
<Box
|
|
|
|
sx={{
|
|
|
|
display: "flex",
|
|
|
|
flexWrap: "wrap",
|
|
|
|
paddingRight: 0,
|
|
|
|
marginTop: 1,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{Object.keys(emojisByCategory).map((category) => (
|
|
|
|
<Category
|
|
|
|
key={category}
|
|
|
|
title={category}
|
|
|
|
emojis={emojisByCategory[category]}
|
|
|
|
search={searchFields}
|
|
|
|
onPick={props.onEmojiPick}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</Box>
|
2023-05-24 07:13:01 +12:00
|
|
|
</Box>
|
2022-04-06 11:40:34 +12:00
|
|
|
</Fade>
|
|
|
|
</ClickAwayListener>
|
|
|
|
)}
|
|
|
|
</Popper>
|
2022-04-05 02:04:01 +12:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const Category = (props) => {
|
2022-04-06 11:40:34 +12:00
|
|
|
const showTitle = props.search.length === 0;
|
2022-04-05 02:04:01 +12:00
|
|
|
return (
|
|
|
|
<>
|
2022-04-05 11:56:21 +12:00
|
|
|
{showTitle && (
|
2022-04-06 11:40:34 +12:00
|
|
|
<Typography variant="body1" sx={{ width: "100%", marginBottom: 1 }}>
|
2022-04-05 11:56:21 +12:00
|
|
|
{props.title}
|
|
|
|
</Typography>
|
|
|
|
)}
|
|
|
|
{props.emojis.map((emoji) => (
|
|
|
|
<Emoji key={emoji.aliases[0]} emoji={emoji} search={props.search} onClick={() => props.onPick(emoji.aliases[0])} />
|
|
|
|
))}
|
2022-04-05 02:04:01 +12:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-05-24 20:20:15 +12:00
|
|
|
const emojiMatches = (emoji, words) => {
|
|
|
|
if (words.length === 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
for (const word of words) {
|
|
|
|
if (emoji.searchBase.indexOf(word) === -1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2022-04-05 02:04:01 +12:00
|
|
|
const Emoji = (props) => {
|
|
|
|
const { emoji } = props;
|
2022-04-06 11:40:34 +12:00
|
|
|
const matches = emojiMatches(emoji, props.search);
|
2022-05-03 11:30:29 +12:00
|
|
|
const title = `${emoji.description} (${emoji.aliases[0]})`;
|
2022-04-05 02:04:01 +12:00
|
|
|
return (
|
2022-04-06 11:40:34 +12:00
|
|
|
<EmojiDiv onClick={props.onClick} title={title} aria-label={title} style={{ display: matches ? "" : "none" }}>
|
2022-04-05 02:04:01 +12:00
|
|
|
{props.emoji.emoji}
|
2022-04-06 11:40:34 +12:00
|
|
|
</EmojiDiv>
|
2022-04-05 02:04:01 +12:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-04-06 11:40:34 +12:00
|
|
|
const EmojiDiv = styled("div")({
|
|
|
|
fontSize: "30px",
|
|
|
|
width: "30px",
|
|
|
|
height: "30px",
|
|
|
|
marginTop: "8px",
|
|
|
|
marginBottom: "8px",
|
|
|
|
marginRight: "8px",
|
|
|
|
lineHeight: "30px",
|
|
|
|
cursor: "pointer",
|
|
|
|
opacity: 0.85,
|
|
|
|
"&:hover": {
|
|
|
|
opacity: 1,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2022-04-05 02:04:01 +12:00
|
|
|
export default EmojiPicker;
|