diff --git a/package.json b/package.json index 596007d..cc6fc44 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emoji-mart/data": "^1.1.2", + "@emoji-mart/react": "^1.1.1", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4", "@react-hook/resize-observer": "^1.2.6", @@ -13,6 +15,7 @@ "@types/webscopeio__react-textarea-autocomplete": "^4.7.2", "@webscopeio/react-textarea-autocomplete": "^4.9.2", "buffer": "^6.0.3", + "emoji-mart": "^5.5.2", "hls.js": "^1.4.6", "lodash": "^4.17.21", "moment": "^2.29.4", @@ -21,6 +24,7 @@ "react-dom": "^18.2.0", "react-intersection-observer": "^9.5.1", "react-router-dom": "^6.13.0", + "resize-observer-polyfill": "^1.5.1", "semantic-sdp": "^3.26.2", "usehooks-ts": "^2.9.1", "web-vitals": "^2.1.0", diff --git a/src/element/live-chat.css b/src/element/live-chat.css index 1a982bc..8b814c0 100644 --- a/src/element/live-chat.css +++ b/src/element/live-chat.css @@ -192,7 +192,7 @@ position: relative; border-radius: 12px; border: 1px solid transparent; - background: black; + background: #0A0A0A; background-clip: padding-box; padding: 8px 12px; } @@ -236,6 +236,50 @@ color: #FF8D2B; } +.message-zap-container { + display: flex; + padding: 8px; + justify-content: center; + align-items: flex-start; + gap: 12px; + border-radius: 12px; + border: 1px solid #303030; + background: #111; + box-shadow: 0px 7px 4px 0px rgba(0, 0, 0, 0.25); + margin-top: 4px; + width: fit-content; + z-index: 1; + transition: opacity .3s ease-out; +} + +@media (min-width: 1020px) { + .message-zap-container { + flex-direction: column; + } +} + +.message-zap-button { + border: none; + cursor: pointer; + height: 24px; + padding: 0px 4px; + justify-content: center; + align-items: center; + gap: 2px; + border-radius: 100px; + background: rgba(255, 255, 255, 0.05); + color: #FFFFFF66; +} + +.message-zap-button:hover { + color: white; +} + +.message-zap-button-icon { + width: 16px; + height: 16px; +} + .message-reactions { display: flex; align-items: flex-end; @@ -268,26 +312,3 @@ line-height: 18px; text-transform: lowercase; } - -.message-zap-button { - cursor: pointer; - position: absolute; - left: 12px; - top: -6px; - background: transparent; - border: none; - display: flex; - background: #434343; - border-radius: 8px; - width: 16px; - height: 16px; - justify-content: center; - align-items: center; -} - -.message-zap-button-icon { - color: #FF8D2B; - width: 12px; - height: 12px; - flex-shrink: 0; -} diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx index 04f34a5..ae9b98c 100644 --- a/src/element/live-chat.tsx +++ b/src/element/live-chat.tsx @@ -16,8 +16,10 @@ import { type ChangeEvent, type LegacyRef, } from "react"; -import { useHover } from "usehooks-ts"; +import { useHover, useOnClickOutside, useMediaQuery } from "usehooks-ts"; +import data from "@emoji-mart/data"; +import Picker from "@emoji-mart/react"; import useEmoji from "hooks/emoji"; import { System } from "index"; import { useLiveChatFeed } from "hooks/live-chat"; @@ -141,6 +143,10 @@ function emojifyReaction(reaction: string) { return reaction; } +interface Emoji { + native: string; +} + function ChatMessage({ streamer, ev, @@ -153,7 +159,10 @@ function ChatMessage({ reactions: readonly TaggedRawEvent[]; }) { const ref = useRef(null); - const isHovering = useHover(ref); + const emojiRef = useRef(null); + const isTablet = useMediaQuery("(max-width: 1020px)"); + const [showZapDialog, setShowZapDialog] = useState(false); + const [showEmojiPicker, setShowEmojiPicker] = useState(false); const profile = useUserProfile(System, ev.pubkey); const zapTarget = profile?.lud16 ?? profile?.lud06; const zaps = reactions @@ -166,45 +175,53 @@ function ChatMessage({ .map((ev) => emojifyReaction(ev.content)); return [...new Set(emojified)]; }, [ev, reactions]); + const hasReactions = emojis.length > 0; const totalZaps = useMemo(() => { const messageZaps = zaps.filter((z) => z.event === ev.id); return messageZaps.reduce((acc, z) => acc + z.amount, 0); }, [reactions, ev]); const hasZaps = totalZaps > 0; + + useOnClickOutside(ref, () => { + setShowZapDialog(false); + }); + + useOnClickOutside(emojiRef, () => { + setShowEmojiPicker(false); + }); + + async function onEmojiSelect(emoji: Emoji) { + setShowEmojiPicker(false); + setShowZapDialog(false); + try { + const pub = await EventPublisher.nip7(); + const reply = await pub?.generic((eb) => { + eb.kind(EventKind.Reaction) + .content(emoji.native) + .tag(["e", ev.id]) + .tag(["p", ev.pubkey]); + return eb; + }); + if (reply) { + console.debug(reply); + System.BroadcastEvent(reply); + } + } catch (error) {} + } + + // @ts-expect-error + const topOffset = ref.current?.getBoundingClientRect().top; + // @ts-expect-error + const leftOffset = ref.current?.getBoundingClientRect().left; + return ( <>