diff --git a/src/components/NoteStats/Likes.tsx b/src/components/NoteStats/Likes.tsx index 5395c5cd..8b14d3cd 100644 --- a/src/components/NoteStats/Likes.tsx +++ b/src/components/NoteStats/Likes.tsx @@ -7,13 +7,17 @@ import noteStatsService from '@/services/note-stats.service' import { TEmoji } from '@/types' import { Loader } from 'lucide-react' import { Event } from 'nostr-tools' -import { useMemo, useState } from 'react' +import { useMemo, useRef, useState } from 'react' import Emoji from '../Emoji' export default function Likes({ event }: { event: Event }) { const { pubkey, checkLogin, publish } = useNostr() const noteStats = useNoteStatsById(event.id) const [liking, setLiking] = useState(null) + const longPressTimerRef = useRef(null) + const [isLongPressing, setIsLongPressing] = useState(null) + const [isCompleted, setIsCompleted] = useState(null) + const likes = useMemo(() => { const _likes = noteStats?.likes if (!_likes) return [] @@ -51,6 +55,59 @@ export default function Likes({ event }: { event: Event }) { }) } + const handleMouseDown = (key: string) => { + if (pubkey && likes.find((l) => l.key === key)?.pubkeys.has(pubkey)) { + return + } + + setIsLongPressing(key) + longPressTimerRef.current = setTimeout(() => { + setIsCompleted(key) + setIsLongPressing(null) + }, 1000) + } + + const handleMouseUp = () => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current) + longPressTimerRef.current = null + } + + if (isCompleted) { + const completedKey = isCompleted + const completedEmoji = likes.find((l) => l.key === completedKey)?.emoji + if (completedEmoji) { + like(completedKey, completedEmoji) + } + } + + setIsLongPressing(null) + setIsCompleted(null) + } + + const handleMouseLeave = () => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current) + longPressTimerRef.current = null + } + setIsLongPressing(null) + setIsCompleted(null) + } + + const handleTouchMove = (e: React.TouchEvent) => { + const touch = e.touches[0] + const rect = (e.currentTarget as HTMLElement).getBoundingClientRect() + const isInside = + touch.clientX >= rect.left && + touch.clientX <= rect.right && + touch.clientY >= rect.top && + touch.clientY <= rect.bottom + + if (!isInside) { + handleMouseLeave() + } + } + return (
@@ -58,25 +115,46 @@ export default function Likes({ event }: { event: Event }) {
{ - e.stopPropagation() - if (pubkey && pubkeys.has(pubkey)) { - return - } - like(key, emoji) - }} + onMouseDown={() => handleMouseDown(key)} + onMouseUp={handleMouseUp} + onMouseLeave={handleMouseLeave} + onTouchStart={() => handleMouseDown(key)} + onTouchMove={handleTouchMove} + onTouchEnd={handleMouseUp} + onTouchCancel={handleMouseLeave} > - {liking === key ? ( - - ) : ( - + {(isLongPressing === key || isCompleted === key) && ( +
+
+
)} -
{pubkeys.size}
+
+ {liking === key ? ( + + ) : ( +
+ +
+ )} +
{pubkeys.size}
+
))}
diff --git a/src/index.css b/src/index.css index d9171c83..9adc64c2 100644 --- a/src/index.css +++ b/src/index.css @@ -129,3 +129,46 @@ filter: invert(1) brightness(1.5); } } + +@keyframes progressFill { + 0% { + width: 0%; + } + 100% { + width: 100%; + } +} + +@keyframes shake { + 0%, + 100% { + transform: translate(0, 0) rotate(0deg); + } + 10% { + transform: translate(-1px, -1px) rotate(-1deg); + } + 20% { + transform: translate(1px, -1px) rotate(1deg); + } + 30% { + transform: translate(-1px, 1px) rotate(-1deg); + } + 40% { + transform: translate(1px, 1px) rotate(1deg); + } + 50% { + transform: translate(-1px, -1px) rotate(-1deg); + } + 60% { + transform: translate(1px, -1px) rotate(1deg); + } + 70% { + transform: translate(-1px, 1px) rotate(-1deg); + } + 80% { + transform: translate(1px, 1px) rotate(1deg); + } + 90% { + transform: translate(-1px, -1px) rotate(-1deg); + } +}