refactor: Improve chat component layout and message handling

- Ensure the chat header is always present to maintain layout consistency, even when no peer is selected.
- Update message display logic to only scroll to the bottom when new messages arrive, enhancing user experience.
- Refactor message input and display sections for better responsiveness and usability across devices.
This commit is contained in:
padreug 2025-08-06 00:03:11 +02:00
parent d5e6b54c78
commit dd7d13f41b

View file

@ -192,66 +192,66 @@
<!-- Chat Area --> <!-- Chat Area -->
<div class="flex-1 flex flex-col"> <div class="flex-1 flex flex-col">
<div v-if="selectedPeer" class="flex-1 flex flex-col"> <!-- Chat Header - Always present to maintain layout -->
<!-- Chat Header --> <div class="p-4 border-b" :class="{ 'h-16': !selectedPeer }">
<div class="p-4 border-b"> <div v-if="selectedPeer" class="flex items-center space-x-3">
<div class="flex items-center space-x-3"> <Avatar class="h-8 w-8">
<Avatar class="h-8 w-8"> <AvatarImage v-if="getPeerAvatar(selectedPeer)" :src="getPeerAvatar(selectedPeer)!" />
<AvatarImage v-if="getPeerAvatar(selectedPeer)" :src="getPeerAvatar(selectedPeer)!" /> <AvatarFallback>{{ getPeerInitials(selectedPeer) }}</AvatarFallback>
<AvatarFallback>{{ getPeerInitials(selectedPeer) }}</AvatarFallback> </Avatar>
</Avatar> <div>
<div> <h3 class="font-medium">{{ selectedPeer.username || 'Unknown User' }}</h3>
<h3 class="font-medium">{{ selectedPeer.username || 'Unknown User' }}</h3> <p class="text-sm text-muted-foreground">
<p class="text-sm text-muted-foreground"> {{ formatPubkey(selectedPeer.pubkey) }}
{{ formatPubkey(selectedPeer.pubkey) }} </p>
</div>
</div>
<div v-else class="h-8"></div>
</div>
<!-- Messages -->
<ScrollArea v-if="selectedPeer" class="flex-1 p-4" ref="messagesScrollArea">
<div class="space-y-4" ref="messagesContainer">
<div
v-for="message in currentMessages"
:key="message.id"
:class="[
'flex',
message.sent ? 'justify-end' : 'justify-start'
]"
>
<div
:class="[
'max-w-xs lg:max-w-md px-4 py-2 rounded-lg',
message.sent
? 'bg-primary text-primary-foreground'
: 'bg-muted'
]"
>
<p class="text-sm">{{ message.content }}</p>
<p class="text-xs opacity-70 mt-1">
{{ formatTime(message.created_at) }}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<!-- Hidden element at bottom for scrolling -->
<div ref="scrollTarget" class="h-1" />
</ScrollArea>
<!-- Messages --> <!-- Message Input -->
<ScrollArea class="flex-1 p-4"> <div v-if="selectedPeer" class="p-4 border-t">
<div class="space-y-4"> <form @submit.prevent="sendMessage" class="flex space-x-2">
<div <Input
v-for="message in currentMessages" v-model="messageInput"
:key="message.id" placeholder="Type a message..."
:class="[ :disabled="!isConnected || !selectedPeer"
'flex', class="flex-1"
message.sent ? 'justify-end' : 'justify-start' />
]" <Button type="submit" :disabled="!isConnected || !selectedPeer || !messageInput.trim()">
> <Send class="h-4 w-4" />
<div </Button>
:class="[ </form>
'max-w-xs lg:max-w-md px-4 py-2 rounded-lg',
message.sent
? 'bg-primary text-primary-foreground'
: 'bg-muted'
]"
>
<p class="text-sm">{{ message.content }}</p>
<p class="text-xs opacity-70 mt-1">
{{ formatTime(message.created_at) }}
</p>
</div>
</div>
</div>
<div ref="messagesEndRef" />
</ScrollArea>
<!-- Message Input -->
<div class="p-4 border-t">
<form @submit.prevent="sendMessage" class="flex space-x-2">
<Input
v-model="messageInput"
placeholder="Type a message..."
:disabled="!isConnected || !selectedPeer"
class="flex-1"
/>
<Button type="submit" :disabled="!isConnected || !selectedPeer || !messageInput.trim()">
<Send class="h-4 w-4" />
</Button>
</form>
</div>
</div> </div>
<!-- No Peer Selected --> <!-- No Peer Selected -->
@ -300,9 +300,12 @@ interface ChatMessage {
const peers = ref<Peer[]>([]) const peers = ref<Peer[]>([])
const selectedPeer = ref<Peer | null>(null) const selectedPeer = ref<Peer | null>(null)
const messageInput = ref('') const messageInput = ref('')
const messagesEndRef = ref<HTMLDivElement | null>(null)
const isLoading = ref(false) const isLoading = ref(false)
const showChat = ref(false) const showChat = ref(false)
const messagesScrollArea = ref<HTMLElement | null>(null)
const messagesContainer = ref<HTMLElement | null>(null)
const scrollTarget = ref<HTMLElement | null>(null)
// Mobile detection // Mobile detection
const isMobile = ref(false) const isMobile = ref(false)
@ -400,10 +403,8 @@ const selectPeer = async (peer: Peer) => {
// Subscribe to messages from this peer // Subscribe to messages from this peer
await subscribeToPeer(peer.pubkey) await subscribeToPeer(peer.pubkey)
// Scroll to bottom after messages load // Don't scroll when selecting a peer - let user see existing messages
nextTick(() => { // Only scroll when new messages arrive
scrollToBottom()
})
} }
const sendMessage = async () => { const sendMessage = async () => {
@ -423,9 +424,15 @@ const sendMessage = async () => {
} }
const scrollToBottom = () => { const scrollToBottom = () => {
if (messagesEndRef.value) { nextTick(() => {
messagesEndRef.value.scrollIntoView({ behavior: 'smooth' }) if (scrollTarget.value) {
} // Scroll to the hidden target element at the bottom
scrollTarget.value.scrollIntoView({
behavior: 'smooth',
block: 'end'
})
}
})
} }
const formatPubkey = (pubkey: string) => { const formatPubkey = (pubkey: string) => {
@ -466,9 +473,12 @@ onUnmounted(() => {
}) })
// Watch for new messages and scroll to bottom // Watch for new messages and scroll to bottom
watch(currentMessages, () => { watch(currentMessages, (newMessages, oldMessages) => {
nextTick(() => { // Only scroll if messages were added (not just peer selection)
scrollToBottom() if (newMessages.length > 0 && oldMessages && newMessages.length > oldMessages.length) {
}) nextTick(() => {
scrollToBottom()
})
}
}) })
</script> </script>