This commit is contained in:
2026-03-11 17:56:17 +03:00
parent 24c935df5f
commit 5a188b80e3
32 changed files with 225 additions and 828 deletions

View File

@@ -0,0 +1,77 @@
<script setup lang="ts"></script>
<template>
<div class="d-flex mb-4 ga-2">
<v-avatar size="36" class="" color="blue-lighten-4">
<span class="text-blue-darken-2">А</span>
</v-avatar>
<div class="message-wrapper">
<div class="d-flex align-center ga-2">
<span class="text-subtitle-2 font-weight-medium">Анна</span>
<span class="text-caption text-disabled">10:30</span>
</div>
<v-card class="pa-4" rounded="lg" flat>
<span>Привет! Как дела? Что нового?</span>
</v-card>
</div>
</div>
<div class="d-flex mb-4 ga-2">
<v-avatar size="36" color="deep-purple-lighten-4">
<span class="text-deep-purple-darken-2">М</span>
</v-avatar>
<div class="message-wrapper">
<div class="d-flex align-center ga-2">
<span class="text-subtitle-2 font-weight-medium">Максим</span>
<span class="text-caption text-disabled">10:30</span>
</div>
<v-card class="pa-4" rounded="lg" flat>
<span>
Всем привет! Смотрели новый фильм? Там такие спецэффекты, просто космос! Очень советую
сходить в кино.
</span>
</v-card>
</div>
</div>
<div class="d-flex mb-4 ga-2 flex-row-reverse">
<v-avatar size="36" color="primary">
<span class="text-white">Я</span>
</v-avatar>
<div style="align-items: flex-end">
<div class="d-flex align-center flex-row-reverse ga-2">
<span class="text-caption text-disabled">10:35</span>
<span class="text-subtitle-2 font-weight-medium">Вы</span>
</div>
<v-card class="pa-4" rounded="lg" flat color="primary">
<span class="text-white"
>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum
has been the industry's standard dummy text ever since the 1500s, when an unknown printer
took a galley of type and scrambled it to make a type specimen book.</span
>
</v-card>
</div>
</div>
<div class="d-flex mb-4 ga-2">
<v-avatar size="36" color="orange-lighten-4">
<span class="text-orange-darken-2">К</span>
</v-avatar>
<div class="message-wrapper">
<div class="d-flex align-center ga-2">
<span class="text-subtitle-2 font-weight-medium">Катя</span>
<span class="text-caption text-disabled">10:40</span>
</div>
<v-card class="message-bubble friend-message pa-4" rounded="lg" flat>
<span>Смотрите какой милый котик</span>
<v-img
src="https://cdn.vuetifyjs.com/images/cards/kitchen.png"
max-width="200"
class="mt-2 rounded"
></v-img>
</v-card>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = withDefaults(
defineProps<{ createdAt?: string; username?: string; my?: boolean; text?: string }>(),
{
my: false,
text: 'foobar',
username: 'robot',
},
)
const createdAt = computed(() => {
return props.createdAt
? new Date(props.createdAt).toLocaleTimeString('ru-RU', { timeStyle: 'short' })
: new Date().toLocaleTimeString('ru-RU', { timeStyle: 'short' })
})
const avatarLetter = computed(() => {
return props.username.slice(0, 1).toUpperCase()
})
</script>
<template>
<!-- <div class="d-flex ga-2" :class="{ 'flex-row-reverse': props.side === 'right' }">-->
<!-- <v-avatar size="36" color="primary" :text="avatarLetter" />-->
<!-- <div>-->
<!-- <div class="d-flex align-center ga-2" :class="{ 'justify-end': props.side === 'right' }">-->
<!-- <span class="text-subtitle-2 font-weight-medium">{{ props.username }}</span>-->
<!-- <span class="text-caption text-disabled">{{ createdAt }}</span>-->
<!-- </div>-->
<!-- <v-card class="pa-2" rounded="lg" flat :color="props.side === 'right' ? 'primary' : 'white'">-->
<!-- <span>{{ props.text }}</span>-->
<!-- </v-card>-->
<!-- </div>-->
<!-- </div>-->
<div class="flex gap-2 border" :class="{ 'flex-row-reverse': props.my }">
<!-- <v-avatar size="36" color="deep-purple-lighten-4">-->
<!-- <span class="text-deep-purple-darken-2">{{ avatarLetter }}</span>-->
<!-- </v-avatar>-->
<!-- :class="props.my ? 'message-shaped-right' : 'message-shaped'"-->
<div class="pa-4" :class="props.my ? 'bg-blue-400' : 'bg-white'">
<span class="">{{ props.text }}</span>
<span class="">{{ createdAt }}</span>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { SocketDataReq, useSocketsStore } from '@/stores/sockets.ts'
import { useChatsStore } from '@/stores/chats.ts'
const socketsStore = useSocketsStore()
const chatsStore = useChatsStore()
const text = ref('')
const sendMessage = () => {
if (text.value.trim()) {
socketsStore.send({
type: SocketDataReq.CREATE_MESSAGE,
data: {
chat_id: chatsStore.selected,
text: text.value,
},
})
}
text.value = ''
}
const isEmptyText = computed(() => {
return !text.value
})
</script>
<template>
<div class="d-flex ga-2 border pa-4">
<input v-model="text" placeholder="message" @keyup.enter="sendMessage" />
<button><span class="mdi mdi-emoticon-outline"></span></button>
<button><span class="mdi mdi-paperclip"></span></button>
<button :disabled="isEmptyText" @click="sendMessage">
<span class="mdi mdi-send"></span>
</button>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,10 @@
<script setup lang="ts"></script>
<template>
<VToolbar class="px-2">
<VAvatar text="A" color="primary" />
<VToolbarTitle text="Message title" />
</VToolbar>
</template>
<style scoped></style>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed, nextTick, useTemplateRef, watch } from 'vue'
import { useScroll } from '@vueuse/core'
const area = useTemplateRef('messageArea')
const { y, arrivedState } = useScroll(area)
const messages = computed(() => {
// return [...messagesStore.messages]
})
async function scrollToBottom() {
await nextTick()
if (area.value) y.value = area.value?.scrollHeight
}
</script>
<template>
<div class="flex h-full flex-col overflow-hidden">
<div class="grow-0">
<slot name="toolbar" />
</div>
<div class="px-8 gap-2 grow flex flex-col-reverse overflow-y-auto" ref="messageArea">
<slot />
</div>
<div class="grow-0">
<slot name="input" />
</div>
</div>
</template>
<style scoped></style>