Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reactions on mobile app #689

Merged
70 changes: 35 additions & 35 deletions mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,58 +10,58 @@
"copy-html-entry": "cp ../raven/public/raven_mobile/index.html ../raven/www/raven_mobile.html"
},
"dependencies": {
"@capacitor/app": "^5.0.6",
"@capacitor/core": "^5.0.0",
"@capacitor/haptics": "^5.0.0",
"@ionic/react": "^7.5.2",
"@ionic/react-router": "^7.3.2",
"@capacitor/app": "^5.0.7",
"@capacitor/core": "^5.7.0",
"@capacitor/haptics": "^5.0.7",
"@ionic/react": "^7.7.3",
"@ionic/react-router": "^7.7.3",
"@ionic/storage": "^4.0.0",
"@tiptap/extension-code-block-lowlight": "^2.1.12",
"@tiptap/extension-highlight": "^2.1.12",
"@tiptap/extension-link": "^2.1.12",
"@tiptap/extension-mention": "^2.1.12",
"@tiptap/extension-placeholder": "^2.1.12",
"@tiptap/extension-typography": "^2.1.12",
"@tiptap/extension-underline": "^2.1.12",
"@tiptap/pm": "^2.1.12",
"@tiptap/react": "^2.1.12",
"@tiptap/starter-kit": "^2.1.12",
"@tiptap/suggestion": "^2.1.12",
"@tiptap/extension-code-block-lowlight": "^2.2.3",
"@tiptap/extension-highlight": "^2.2.3",
"@tiptap/extension-link": "^2.2.3",
"@tiptap/extension-mention": "^2.2.3",
"@tiptap/extension-placeholder": "^2.2.3",
"@tiptap/extension-typography": "^2.2.3",
"@tiptap/extension-underline": "^2.2.3",
"@tiptap/pm": "^2.2.3",
"@tiptap/react": "^2.2.3",
"@tiptap/starter-kit": "^2.2.3",
"@tiptap/suggestion": "^2.2.3",
"cal-sans": "^1.0.1",
"emoji-picker-element": "^1.18.4",
"frappe-react-sdk": "^1.3.8",
"emoji-picker-element": "^1.21.1",
"frappe-react-sdk": "^1.3.11",
"highlight.js": "^11.9.0",
"ionicons": "^7.1.2",
"ionicons": "^7.2.2",
"lowlight": "^3.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.4",
"react-icons": "^4.10.1",
"react-intersection-observer": "^9.5.2",
"react-markdown": "^8.0.7",
"react-hook-form": "^7.50.1",
"react-icons": "^5.0.1",
"react-intersection-observer": "^9.8.0",
"react-markdown": "^9.0.1",
"react-remark": "^2.1.0",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^3.0.1",
"swiper": "^10.2.0",
"remark-gfm": "^4.0.0",
"tippy.js": "^6.3.7",
"turndown": "^7.1.2"
},
"devDependencies": {
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@capacitor/cli": "^5.7.0",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@types/react-router": "5.1.11",
"@types/react-router-dom": "^5.3.3",
"@types/turndown": "^5.0.3",
"@vitejs/plugin-react": "^4.0.4",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.29",
"tailwindcss": "^3.2.7",
"@types/turndown": "^5.0.4",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^4.9.3",
"vite": "^4.4.9",
"vite-plugin-pwa": "^0.16.5"
"vite": "^5.1.4",
"vite-plugin-pwa": "^0.19.0"
}
}
}
28 changes: 28 additions & 0 deletions mobile/src/components/common/EmojiPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createElement, useEffect, useRef } from "react"
import 'emoji-picker-element'
import './emojiPicker.styles.css'

const EmojiPicker = ({ onSelect }: { onSelect: (emoji: string) => void }) => {

const ref = useRef<any>(null)

useEffect(() => {
const handler = (event: any) => {
onSelect(event.detail.unicode)
}
ref.current?.addEventListener('emoji-click', handler)
ref.current.skinToneEmoji = '👍'

const style = document.createElement('style');
style.textContent = `.picker { border-radius: 8px; box-shadow: var(--shadow-6); } input.search{ color: 'rgb(24 24 27)' } }`
ref.current.shadowRoot.appendChild(style);

return () => {
ref.current?.removeEventListener('emoji-click', handler)
}
}, [])

return createElement('emoji-picker', { ref })
}

export default EmojiPicker
23 changes: 23 additions & 0 deletions mobile/src/components/common/emojiPicker.styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
emoji-picker {
--indicator-color: var(--ion-color-primary);
--input-padding: 0.6rem;
width: 100%;
/* --emoji-size: 1.8rem; */
/* --num-columns: 8; */
height: 100%;
--input-font-size: 0.8rem;
--background: rgb(24 24 27);
--indicator-height: 4px;
--border-color: transparent;
--input-border-color: rgb(63 63 70);
--input-border-size: 2px;
--input-font-color: rgb(244 244 245);
--button-active-background: color: rgb(39 39 42);
--button-hover-background: rgb(39 39 42);
--input-placeholder-color: rgb(82 82 91);
--outline-color: #5958B1;
}

.picker {
--border-size: 2px;
}
2 changes: 1 addition & 1 deletion mobile/src/components/features/chat-input/Tiptap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export const Tiptap = ({ onMessageSend, messageSending, defaultText = '' }: Tipt
</div>
<div className='mb-1'>
<button
className='p-1.5 text-white rounded-full bg-blue-500 hover:bg-blue-600 focus:outline-none disabled:opacity-30 disabled:cursor-not-allowed'
className='p-1.5 text-white rounded-full bg-[var(--ion-color-primary)] hover:bg-[var(--ion-color-primary-shade)] focus:outline-none disabled:opacity-30 disabled:cursor-not-allowed'
aria-disabled={messageSending || isEditorEmpty}
disabled={messageSending || isEditorEmpty}
onClick={onSubmit}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,90 @@
import { BiBookAdd } from "react-icons/bi"
import { useFrappePostCall } from "frappe-react-sdk"
import { BiSmile } from "react-icons/bi"
import { ActionProps } from "./common"
import { useCallback } from "react"
import { IonButton, IonContent, IonModal } from "@ionic/react"
import { Haptics, ImpactStyle } from "@capacitor/haptics"
import EmojiPicker from "@/components/common/EmojiPicker"
import './emojiAction.styles.css'

type Props = {}

export const EmojiAction = (props: Props) => {
const STANDARD_EMOJIS = ['👍', '✅', '❤️', '🎉', '👀']

interface EmojiActionProps extends ActionProps {
presentingElement: any
}
export const EmojiAction = ({ message, onSuccess, presentingElement }: EmojiActionProps) => {

const { data: { name: messageID } } = message
const { call: reactToMessage } = useFrappePostCall('raven.api.reactions.react')

const saveReaction = useCallback(async (emoji: string) => {
Haptics.impact({
style: ImpactStyle.Light
})
return reactToMessage({
message_id: messageID,
reaction: emoji
})
.then(() => onSuccess())
}, [messageID, reactToMessage])

return (
<div className="p-4 text-center grid grid-cols-6">
<QuickEmojiAction emoji="👍" />
<QuickEmojiAction emoji="✅" />
<QuickEmojiAction emoji="❤️" />
<QuickEmojiAction emoji="🎉" />
<QuickEmojiAction emoji="👀" />
<QuickEmojiAction emoji="😂" />
<div className="px-1 pb-2 text-center grid grid-cols-6 gap-2">
{STANDARD_EMOJIS.map(emoji => <QuickEmojiAction
key={emoji}
emoji={emoji} onClick={() => saveReaction(emoji)}
/>)
}
<OtherEmojiAction
presentingElement={presentingElement}
onClick={saveReaction}
/>
</div>
)
}


const QuickEmojiAction = ({ emoji }: { emoji: string }) => {
const QuickEmojiAction = ({ emoji, onClick }: { emoji: string, onClick: VoidFunction }) => {
return <div>
<IonButton
slot='icon-only'
shape="round"
fill='clear'
className={"flex items-center flex-col bg-zinc-800 aspect-square justify-center rounded-full"}
onClick={onClick}>
<span className="text-lg block rounded-md w-9">{emoji}</span>
</IonButton>
</div>
}

const OtherEmojiAction = ({ presentingElement, onClick }: { presentingElement: any, onClick: (emoji: string) => void }) => {
return <div>
<span className="text-2xl">{emoji}</span>
<IonButton
slot='icon-only'
shape="round"
id='emoji-picker'
fill='clear'
className={"flex items-center flex-col bg-zinc-800 text-zinc-300 aspect-square justify-center rounded-full"}
>
<span>
<BiSmile size='24' />
</span>
</IonButton>
<IonModal
trigger="emoji-picker"
id='emoji-picker-modal'
handle
presentingElement={presentingElement}
// handleBehavior="cycle"
canDismiss
>
<IonContent className="ion-padding">
<div>
<EmojiPicker onSelect={onClick} />
</div>
</IonContent>
</IonModal>
</div>
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react'
import { useContext, useEffect, useRef, useState } from 'react'
import { MessageBlock } from '../../../../../../types/Messaging/Message'
import {
IonModal,
Expand All @@ -10,9 +10,10 @@ import { DeleteAction } from './DeleteAction';
import { UserContext } from '@/utils/auth/UserProvider';
import { CopyAction } from './CopyAction';
import { SaveMessageAction } from './SaveMessageAction';
import { NonContinuationMessageBlock } from '../chat-view/MessageBlock';
import { useGetUser } from '@/hooks/useGetUser';
import { ShareAction } from './ShareAction';
import { EmojiAction } from './EmojiAction';
import MessagePreview from './MessagePreview';

interface MessageActionModalProps {
selectedMessage?: MessageBlock,
Expand All @@ -23,6 +24,7 @@ export const MessageActionModal = ({ selectedMessage, onDismiss }: MessageAction
const { currentUser } = useContext(UserContext)
const isOwnMessage = currentUser === selectedMessage?.data?.owner

const modalRef = useRef<HTMLIonModalElement>(null)


/**
Expand All @@ -36,7 +38,7 @@ export const MessageActionModal = ({ selectedMessage, onDismiss }: MessageAction
useEffect(() => {
const timeout = setTimeout(() => {
setEnablePointerEvents(true)
}, 1000)
}, 800)
return () => {
clearTimeout(timeout)
setEnablePointerEvents(false)
Expand All @@ -50,6 +52,7 @@ export const MessageActionModal = ({ selectedMessage, onDismiss }: MessageAction
isOpen={!!selectedMessage}
breakpoints={[0, 0.75, 0.9]}
id='message-action-modal'
ref={modalRef}
initialBreakpoint={0.75}
onWillDismiss={onDismiss}>
<IonContent className="ion-padding" style={{
Expand All @@ -58,14 +61,15 @@ export const MessageActionModal = ({ selectedMessage, onDismiss }: MessageAction
{selectedMessage &&
<IonList lines='none' className='bg-zinc-900'>
<div className='rounded-md pb-2 flex bg-zinc-800'>
<NonContinuationMessageBlock message={selectedMessage} user={user} />
<MessagePreview message={selectedMessage} user={user} />
</div>

<div className='h-[2px] bg-zinc-800 my-2'>

</div>

{/* <EmojiAction /> */}
<EmojiAction message={selectedMessage} onSuccess={onDismiss}
presentingElement={modalRef.current} />
{/* <IonItem className='py-1'>
<IonIcon slot="start" icon={createOutline} />
<IonLabel className='font-semibold'>Edit</IonLabel>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MessageBlock } from '../../../../../../types/Messaging/Message'
import { UserFields } from '@/utils/users/UserListProvider'
import { UserAvatarBlock } from '../chat-view/MessageBlock'
import { IonText } from '@ionic/react'
import { DateObjectToTimeString } from '@/utils/operations/operations'

type MessagePreview = { message: MessageBlock, user?: UserFields }

const MessagePreview = ({ message, user }: MessagePreview) => {
return (
<div className='px-2 mt-3 py-1 rounded-md flex'>
<UserAvatarBlock message={message} user={user} />
<div>
<div className='flex items-end pb-1.5'>
<IonText className='font-bold text-zinc-50 text-sm'>{user?.full_name ?? message.data.owner}</IonText>
<IonText className='text-xs pl-1.5 text-zinc-500'>{DateObjectToTimeString(message.data.creation)}</IonText>
</div>
{message.data.message_type === 'Text' && <div className='text-base line-clamp-3'>{message.data.content}</div>}
{message.data.message_type === 'Image' && <div className='flex items-center space-x-2'>
<img src={message.data.file} alt={`Image`} className='inline-block w-10 h-10 rounded-md' />
<p className='text-sm font-semibold'>📸 &nbsp;Image</p>
</div>}
{message.data.message_type === 'File' && <p
className='text-sm font-semibold'>📎 &nbsp;{message.data.file?.split('/')[3]}</p>}
</div>
</div>
)
}

export default MessagePreview
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ion-modal#emoji-picker-modal {
/* --height: 50%; */
--border-radius: 8px;
/* --background: rgb(250 250 250); */
--ion-background-color: rgb(24 24 27);
--height: 90vh;
/* --backdrop-opacity: 1; */
/* --box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); */
}
Loading
Loading