mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-14 11:47:33 +00:00
Emoji picker
This commit is contained in:
parent
f2d4af04e3
commit
4eba641ec3
3 changed files with 89 additions and 13 deletions
|
@ -1,8 +1,13 @@
|
|||
import * as React from 'react';
|
||||
import {useRef, useState} from 'react';
|
||||
import Popover from '@mui/material/Popover';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {rawEmojis} from '../app/emojis';
|
||||
import Box from "@mui/material/Box";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {InputAdornment} from "@mui/material";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import {Close} from "@mui/icons-material";
|
||||
|
||||
const emojisByCategory = {};
|
||||
rawEmojis.forEach(emoji => {
|
||||
|
@ -14,22 +19,69 @@ rawEmojis.forEach(emoji => {
|
|||
|
||||
const EmojiPicker = (props) => {
|
||||
const open = Boolean(props.anchorEl);
|
||||
const [search, setSearch] = useState("");
|
||||
const searchRef = useRef(null);
|
||||
|
||||
/*
|
||||
FIXME Search is inefficient, somehow make it faster
|
||||
|
||||
useEffect(() => {
|
||||
const matching = rawEmojis.filter(e => {
|
||||
const searchLower = search.toLowerCase();
|
||||
return e.description.toLowerCase().indexOf(searchLower) !== -1
|
||||
|| matchInArray(e.aliases, searchLower)
|
||||
|| matchInArray(e.tags, searchLower);
|
||||
});
|
||||
console.log("matching", matching.length);
|
||||
}, [search]);
|
||||
*/
|
||||
const handleSearchClear = () => {
|
||||
setSearch("");
|
||||
searchRef.current?.focus();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={props.anchorEl}
|
||||
elevation={3}
|
||||
onClose={props.onClose}
|
||||
anchorEl={props.anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ padding: 2, paddingRight: 0, width: "370px", maxHeight: "300px" }}>
|
||||
{Object.keys(emojisByCategory).map(category =>
|
||||
<Category title={category} emojis={emojisByCategory[category]} onPick={props.onEmojiPick}/>
|
||||
)}
|
||||
<TextField
|
||||
inputRef={searchRef}
|
||||
margin="dense"
|
||||
size="small"
|
||||
placeholder="Search emoji"
|
||||
value={search}
|
||||
onChange={ev => setSearch(ev.target.value)}
|
||||
type="text"
|
||||
variant="standard"
|
||||
fullWidth
|
||||
sx={{ marginTop: 0, paddingRight: 2 }}
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end" sx={{ display: (search) ? '' : 'none' }}>
|
||||
<IconButton size="small" onClick={handleSearchClear} edge="end"><Close/></IconButton>
|
||||
</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", paddingRight: 0, marginTop: 1 }}>
|
||||
{Object.keys(emojisByCategory).map(category =>
|
||||
<Category
|
||||
key={category}
|
||||
title={category}
|
||||
emojis={emojisByCategory[category]}
|
||||
search={search.toLowerCase()}
|
||||
onPick={props.onEmojiPick}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Popover>
|
||||
</>
|
||||
|
@ -37,18 +89,36 @@ const EmojiPicker = (props) => {
|
|||
};
|
||||
|
||||
const Category = (props) => {
|
||||
const showTitle = !props.search;
|
||||
return (
|
||||
<>
|
||||
<Typography variant="body2">{props.title}</Typography>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", paddingRight: 0, marginBottom: 1 }}>
|
||||
{props.emojis.map(emoji => <Emoji emoji={emoji} onClick={() => props.onPick(emoji.aliases[0])}/>)}
|
||||
</Box>
|
||||
{showTitle &&
|
||||
<Typography variant="body1" sx={{ width: "100%", marginTop: 1, marginBottom: 1 }}>
|
||||
{props.title}
|
||||
</Typography>
|
||||
}
|
||||
{props.emojis.map(emoji =>
|
||||
<Emoji
|
||||
key={emoji.aliases[0]}
|
||||
emoji={emoji}
|
||||
search={props.search}
|
||||
onClick={() => props.onPick(emoji.aliases[0])}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Emoji = (props) => {
|
||||
const emoji = props.emoji;
|
||||
const search = props.search;
|
||||
const matches = search === ""
|
||||
|| emoji.description.toLowerCase().indexOf(search) !== -1
|
||||
|| matchInArray(emoji.aliases, search)
|
||||
|| matchInArray(emoji.tags, search);
|
||||
if (!matches) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
|
@ -69,4 +139,11 @@ const Emoji = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
const matchInArray = (arr, search) => {
|
||||
if (!arr || !search) {
|
||||
return false;
|
||||
}
|
||||
return arr.filter(s => s.indexOf(search) !== -1).length > 0;
|
||||
}
|
||||
|
||||
export default EmojiPicker;
|
||||
|
|
|
@ -213,11 +213,11 @@ const SendDialog = (props) => {
|
|||
onDragLeave={handleAttachFileDragLeave}/>
|
||||
}
|
||||
<Dialog maxWidth="md" open={open} onClose={props.onCancel} fullScreen={fullScreen}>
|
||||
<DialogTitle>Publish to {shortUrl(topicUrl)}</DialogTitle>
|
||||
<DialogTitle>{topicUrl ? `Publish to ${shortUrl(topicUrl)}` : "Publish message"}</DialogTitle>
|
||||
<DialogContent>
|
||||
{dropZone && <DropBox/>}
|
||||
{showTopicUrl &&
|
||||
<ClosableRow disabled={disabled} onClose={() => {
|
||||
<ClosableRow closable={!!props.topicUrl} disabled={disabled} onClose={() => {
|
||||
setTopicUrl(props.topicUrl);
|
||||
setShowTopicUrl(false);
|
||||
}}>
|
||||
|
@ -468,10 +468,11 @@ const Row = (props) => {
|
|||
};
|
||||
|
||||
const ClosableRow = (props) => {
|
||||
const closable = (props.hasOwnProperty("closable")) ? props.closable : true;
|
||||
return (
|
||||
<Row>
|
||||
{props.children}
|
||||
<DialogIconButton disabled={props.disabled} onClick={props.onClose} sx={{marginLeft: "6px"}}><Close/></DialogIconButton>
|
||||
{closable && <DialogIconButton disabled={props.disabled} onClick={props.onClose} sx={{marginLeft: "6px"}}><Close/></DialogIconButton>}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@ import {useState} from 'react';
|
|||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
|
@ -11,7 +10,6 @@ import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/mate
|
|||
import theme from "./theme";
|
||||
import api from "../app/Api";
|
||||
import {topicUrl, validTopic, validUrl} from "../app/utils";
|
||||
import Box from "@mui/material/Box";
|
||||
import userManager from "../app/UserManager";
|
||||
import subscriptionManager from "../app/SubscriptionManager";
|
||||
import poller from "../app/Poller";
|
||||
|
|
Loading…
Reference in a new issue