165 lines
5.3 KiB
TypeScript
165 lines
5.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import Box from '@mui/material/Box';
|
|
import Tooltip from '@mui/material/Tooltip';
|
|
import Chip from '@mui/material/Chip';
|
|
import IconButton from '@mui/material/IconButton';
|
|
import Popover from '@mui/material/Popover';
|
|
import Divider from '@mui/material/Divider';
|
|
import Typography from '@mui/material/Typography';
|
|
import AddReactionOutlinedIcon from '@mui/icons-material/AddReactionOutlined';
|
|
import { nextcloudApi } from '../../services/nextcloud';
|
|
|
|
const QUICK_REACTIONS = ['\u{1F44D}', '\u2764\uFE0F', '\u{1F602}', '\u{1F62E}', '\u{1F622}', '\u{1F389}'];
|
|
|
|
const EXTENDED_EMOJI_CATEGORIES: { label: string; emojis: string[] }[] = [
|
|
{
|
|
label: 'Smileys',
|
|
emojis: [
|
|
'\u{1F600}', '\u{1F603}', '\u{1F604}', '\u{1F601}', '\u{1F605}', '\u{1F606}', '\u{1F923}', '\u{1F60A}',
|
|
'\u{1F607}', '\u{1F970}', '\u{1F60D}', '\u{1F618}', '\u{1F917}', '\u{1F914}', '\u{1F644}', '\u{1F612}',
|
|
],
|
|
},
|
|
{
|
|
label: 'Gesten',
|
|
emojis: [
|
|
'\u{1F44D}', '\u{1F44E}', '\u{1F44F}', '\u{1F64C}', '\u{1F4AA}', '\u{1F91D}', '\u{1F64F}', '\u270C\uFE0F',
|
|
],
|
|
},
|
|
{
|
|
label: 'Objekte',
|
|
emojis: [
|
|
'\u{1F525}', '\u2B50', '\u{1F4AF}', '\u2705', '\u274C', '\u26A0\uFE0F', '\u{1F680}', '\u{1F3AF}',
|
|
'\u{1F4A1}', '\u{1F389}', '\u{1F381}', '\u2764\uFE0F', '\u{1F494}', '\u{1F48E}', '\u{1F3C6}', '\u{1F4DD}',
|
|
],
|
|
},
|
|
];
|
|
|
|
interface MessageReactionsProps {
|
|
token: string;
|
|
messageId: number;
|
|
reactions: Record<string, number>;
|
|
reactionsSelf: string[];
|
|
onReactionToggled: () => void;
|
|
visible?: boolean;
|
|
}
|
|
|
|
const MessageReactions: React.FC<MessageReactionsProps> = ({
|
|
token,
|
|
messageId,
|
|
reactions,
|
|
reactionsSelf,
|
|
onReactionToggled,
|
|
visible = true,
|
|
}) => {
|
|
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
const [loading, setLoading] = useState<string | null>(null);
|
|
|
|
const handleToggle = async (emoji: string) => {
|
|
setLoading(emoji);
|
|
try {
|
|
if (reactionsSelf.includes(emoji)) {
|
|
await nextcloudApi.removeReaction(token, messageId, emoji);
|
|
} else {
|
|
await nextcloudApi.addReaction(token, messageId, emoji);
|
|
}
|
|
onReactionToggled();
|
|
} catch {
|
|
// ignore
|
|
} finally {
|
|
setLoading(null);
|
|
}
|
|
};
|
|
|
|
const hasReactions = Object.keys(reactions).length > 0;
|
|
|
|
return (
|
|
<Box sx={{
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
gap: 0.25,
|
|
mt: 0.25,
|
|
alignItems: 'center',
|
|
minHeight: 24,
|
|
visibility: visible ? 'visible' : 'hidden',
|
|
opacity: visible ? 1 : 0,
|
|
transition: 'opacity 0.15s, visibility 0.15s',
|
|
}}>
|
|
{hasReactions && Object.entries(reactions).map(([emoji, count]) => (
|
|
<Chip
|
|
key={emoji}
|
|
label={`${emoji} ${count}`}
|
|
size="small"
|
|
onClick={() => handleToggle(emoji)}
|
|
disabled={loading === emoji}
|
|
sx={{
|
|
height: 20,
|
|
fontSize: '0.75rem',
|
|
cursor: 'pointer',
|
|
border: '1px solid',
|
|
borderColor: reactionsSelf.includes(emoji) ? 'primary.main' : 'transparent',
|
|
bgcolor: reactionsSelf.includes(emoji) ? 'primary.light' : 'action.hover',
|
|
'& .MuiChip-label': { px: 0.5 },
|
|
}}
|
|
/>
|
|
))}
|
|
<Tooltip title="Reaktion hinzuf\u00FCgen">
|
|
<IconButton
|
|
size="small"
|
|
onClick={(e) => setAnchorEl(e.currentTarget)}
|
|
sx={{ width: 20, height: 20, opacity: 0.6, '&:hover': { opacity: 1 } }}
|
|
>
|
|
<AddReactionOutlinedIcon sx={{ fontSize: '0.85rem' }} />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Popover
|
|
open={Boolean(anchorEl)}
|
|
anchorEl={anchorEl}
|
|
onClose={() => setAnchorEl(null)}
|
|
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
transformOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
>
|
|
<Box sx={{ p: 0.5, maxWidth: 280 }}>
|
|
{/* Quick reactions row */}
|
|
<Box sx={{ display: 'flex', gap: 0.25 }}>
|
|
{QUICK_REACTIONS.map((emoji) => (
|
|
<IconButton
|
|
key={emoji}
|
|
size="small"
|
|
onClick={() => { handleToggle(emoji); setAnchorEl(null); }}
|
|
sx={{ fontSize: '1rem', minWidth: 32, minHeight: 32 }}
|
|
>
|
|
{emoji}
|
|
</IconButton>
|
|
))}
|
|
</Box>
|
|
<Divider sx={{ my: 0.5 }} />
|
|
{/* Extended emoji grid */}
|
|
<Box sx={{ maxHeight: 200, overflowY: 'auto' }}>
|
|
{EXTENDED_EMOJI_CATEGORIES.map((cat) => (
|
|
<Box key={cat.label}>
|
|
<Typography variant="caption" color="text.secondary" sx={{ px: 0.5, display: 'block', mt: 0.5 }}>
|
|
{cat.label}
|
|
</Typography>
|
|
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(8, 1fr)' }}>
|
|
{cat.emojis.map((emoji) => (
|
|
<IconButton
|
|
key={emoji}
|
|
size="small"
|
|
onClick={() => { handleToggle(emoji); setAnchorEl(null); }}
|
|
sx={{ fontSize: '1rem', minWidth: 28, minHeight: 28, p: 0.25 }}
|
|
>
|
|
{emoji}
|
|
</IconButton>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
</Popover>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default MessageReactions;
|