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:
parent
d5e6b54c78
commit
dd7d13f41b
1 changed files with 77 additions and 67 deletions
|
|
@ -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>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue