vip
This commit is contained in:
BIN
public/img.png
Normal file
BIN
public/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 406 KiB |
142
src/App.vue
142
src/App.vue
@@ -1,143 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import WebSocketComponent from '@/components/WebSocketComponent.vue'
|
import ChatView from '@/views/ChatView.vue'
|
||||||
import { ref } from 'vue'
|
import LoginView from '@/views/LoginView.vue'
|
||||||
import ChatMessageText from '@/components/ChatMessageText.vue'
|
import { useAuthStore } from '@/stores/auth.ts'
|
||||||
import ChatMessage from '@/components/ChatMessage.vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const email = ref('vadim.olonin@gmail.com')
|
const authStore = useAuthStore()
|
||||||
const search = ref('foobar')
|
|
||||||
|
|
||||||
async function onSubmit() {
|
const isAuth = computed(() => !!authStore.token)
|
||||||
console.log('onSubmit')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ email: email.value }),
|
|
||||||
}
|
|
||||||
const res = await fetch('http://localhost:5173/login', options)
|
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json()
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-app>
|
<v-app>
|
||||||
<v-main class="chat-fullscreen">
|
<v-main class="chat-fullscreen">
|
||||||
<v-container fluid class="h-100 pa-0">
|
<v-container fluid class="h-100 pa-0">
|
||||||
<v-row no-gutters class="h-100">
|
<chat-view v-if="isAuth" />
|
||||||
<v-col cols="12" md="3">
|
<login-view v-else />
|
||||||
<v-sheet class="pa-4 d-flex flex-column h-100" theme="dark">
|
|
||||||
<div class="d-flex align-center ga-4">
|
|
||||||
<v-btn icon="mdi-menu" variant="text"></v-btn>
|
|
||||||
<v-text-field
|
|
||||||
v-model="search"
|
|
||||||
rounded="xl"
|
|
||||||
density="compact"
|
|
||||||
hide-details
|
|
||||||
variant="outlined"
|
|
||||||
bg-color="black"
|
|
||||||
base-color="black"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-list theme="dark" density="compact" lines="two">
|
|
||||||
<v-list-subheader>Чаты</v-list-subheader>
|
|
||||||
|
|
||||||
<v-list-item
|
|
||||||
v-for="n in 5"
|
|
||||||
:key="n"
|
|
||||||
:title="'Контакт ' + n"
|
|
||||||
:subtitle="'Последнее сообщение...'"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<v-avatar color="grey-lighten-1" text="V"> </v-avatar>
|
|
||||||
</template>
|
|
||||||
<template v-slot:append>
|
|
||||||
<div class="d-flex flex-column align-end">
|
|
||||||
<v-chip density="compact" size="small">2</v-chip>
|
|
||||||
<div class="text-caption">22:22</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-sheet>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="9" class="d-flex flex-column h-100">
|
|
||||||
<v-toolbar color="white" class="flex-grow-0 px-4">
|
|
||||||
<v-avatar color="grey-lighten-1" text="V"></v-avatar>
|
|
||||||
<v-toolbar-title class="">name</v-toolbar-title>
|
|
||||||
</v-toolbar>
|
|
||||||
|
|
||||||
<v-card
|
|
||||||
rounded="0"
|
|
||||||
variant="flat"
|
|
||||||
color="grey-lighten-3"
|
|
||||||
class="flex-grow-1 d-flex flex-column"
|
|
||||||
style="overflow: hidden"
|
|
||||||
>
|
|
||||||
<v-card-text
|
|
||||||
class="flex-grow-1 d-flex ga-4 flex-column flex-column-reverse"
|
|
||||||
style="overflow-y: auto"
|
|
||||||
>
|
|
||||||
<!-- <div class="d-flex justify-center">-->
|
|
||||||
<!-- <v-chip size="small">Сегодня</v-chip>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
<chat-message />
|
|
||||||
<chat-message username="Vadim" text="Привет! Как дела? Что нового?" />
|
|
||||||
<chat-message
|
|
||||||
username="Vadim"
|
|
||||||
text="Привет! Как дела? Что нового?"
|
|
||||||
created-at="2020-02-04T05:45:00.000Z"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
|
|
||||||
<v-card-actions class="flex-grow-0 bg-white pa-4">
|
|
||||||
<v-row dense align="center">
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
rounded="xl"
|
|
||||||
bg-color="white"
|
|
||||||
variant="outlined"
|
|
||||||
hide-details
|
|
||||||
label="message"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend-inner>
|
|
||||||
<v-btn icon variant="text" density="compact" color="grey-darken-1">
|
|
||||||
<v-icon>mdi-emoticon-outline</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-btn icon variant="text" color="grey-darken-1">
|
|
||||||
<v-icon>mdi-paperclip</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-btn icon variant="flat" color="primary">
|
|
||||||
<v-icon>mdi-send</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-main>
|
</v-main>
|
||||||
</v-app>
|
</v-app>
|
||||||
@@ -147,4 +24,7 @@ async function onSubmit() {
|
|||||||
.chat-fullscreen {
|
.chat-fullscreen {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
.message-shaped {
|
||||||
|
border-radius: 0 24px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
31
src/components/ChatList.vue
Normal file
31
src/components/ChatList.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSharedWebSocket } from '@/composables/useSharedWebSocket.ts'
|
||||||
|
|
||||||
|
const { messages, chats, isConnected, error, send } = useSharedWebSocket()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-list theme="dark" density="compact" lines="two">
|
||||||
|
<v-list-subheader>Чаты</v-list-subheader>
|
||||||
|
<div>{{ chats }}</div>
|
||||||
|
|
||||||
|
<v-list-item
|
||||||
|
v-for="n in 5"
|
||||||
|
:key="n"
|
||||||
|
:title="'Контакт ' + n"
|
||||||
|
:subtitle="'Последнее сообщение...'"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-avatar color="grey-lighten-1" text="V"> </v-avatar>
|
||||||
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<div class="d-flex flex-column align-end">
|
||||||
|
<v-chip density="compact" size="small">2</v-chip>
|
||||||
|
<div class="text-caption">22:22</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{ createdAt?: string; username?: string; side?: 'left' | 'right'; text?: string }>(),
|
|
||||||
{
|
|
||||||
text: 'foobar',
|
|
||||||
side: 'left',
|
|
||||||
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">
|
|
||||||
<v-avatar size="36" color="blue-lighten-4">
|
|
||||||
<span class="text-blue-darken-2">{{ avatarLetter }}</span>
|
|
||||||
</v-avatar>
|
|
||||||
<div class="message-wrapper">
|
|
||||||
<div class="d-flex align-center ga-2">
|
|
||||||
<span class="text-subtitle-2 font-weight-medium">{{ props.username }}</span>
|
|
||||||
<span class="text-caption text-disabled">{{ createdAt }}</span>
|
|
||||||
</div>
|
|
||||||
<v-card class="pa-3" rounded="lg" flat>
|
|
||||||
<span>{{ props.text }}</span>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex mb-4 ga-2">
|
<div class="d-flex mb-4 ga-2">
|
||||||
<v-avatar size="36" class="" color="deep-purple-lighten-4">
|
<v-avatar size="36" color="deep-purple-lighten-4">
|
||||||
<span class="text-deep-purple-darken-2">М</span>
|
<span class="text-deep-purple-darken-2">М</span>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
<div class="message-wrapper">
|
<div class="message-wrapper">
|
||||||
@@ -38,13 +38,17 @@
|
|||||||
<v-avatar size="36" color="primary">
|
<v-avatar size="36" color="primary">
|
||||||
<span class="text-white">Я</span>
|
<span class="text-white">Я</span>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
<div class="message-wrapper" style="align-items: flex-end">
|
<div style="align-items: flex-end">
|
||||||
<div class="d-flex align-center flex-row-reverse ga-2">
|
<div class="d-flex align-center flex-row-reverse ga-2">
|
||||||
<span class="text-caption text-disabled">10:35</span>
|
<span class="text-caption text-disabled">10:35</span>
|
||||||
<span class="text-subtitle-2 font-weight-medium">Вы</span>
|
<span class="text-subtitle-2 font-weight-medium">Вы</span>
|
||||||
</div>
|
</div>
|
||||||
<v-card class="message-bubble my-message pa-4" rounded="lg" flat color="primary">
|
<v-card class="pa-4" rounded="lg" flat color="primary">
|
||||||
<span class="text-white">Отлично! Тоже хочу сходить.</span>
|
<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>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
24
src/components/ChatToolbar.vue
Normal file
24
src/components/ChatToolbar.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const search = ref('')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-flex align-center ga-4">
|
||||||
|
<v-btn icon="mdi-menu" variant="text"></v-btn>
|
||||||
|
<v-text-field
|
||||||
|
v-model="search"
|
||||||
|
rounded="xl"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
variant="outlined"
|
||||||
|
bg-color="black"
|
||||||
|
base-color="black"
|
||||||
|
color="primary"
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
13
src/components/ChatsPane.vue
Normal file
13
src/components/ChatsPane.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ChatToolbar from '@/components/ChatToolbar.vue'
|
||||||
|
import ChatList from '@/components/ChatList.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-sheet class="pa-4 d-flex flex-column h-100" theme="dark">
|
||||||
|
<chat-toolbar />
|
||||||
|
<chat-list />
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
68
src/components/MessageData.vue
Normal file
68
src/components/MessageData.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{ createdAt?: string; username?: string; side?: 'right'; text?: string }>(),
|
||||||
|
{
|
||||||
|
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>-->
|
||||||
|
|
||||||
|
<v-sheet
|
||||||
|
color="transparent"
|
||||||
|
class="d-flex ga-2"
|
||||||
|
:class="{ 'flex-row-reverse': props.side === 'right' }"
|
||||||
|
>
|
||||||
|
<v-avatar size="36" color="deep-purple-lighten-4">
|
||||||
|
<span class="text-deep-purple-darken-2">{{ avatarLetter }}</span>
|
||||||
|
</v-avatar>
|
||||||
|
<v-sheet
|
||||||
|
class="pa-4 message-width"
|
||||||
|
:color="props.side === 'right' ? 'primary' : 'white'"
|
||||||
|
:class="props.side === 'right' ? 'message-shaped-right' : 'message-shaped'"
|
||||||
|
>
|
||||||
|
<span class="text-body-1">{{ props.text }}</span>
|
||||||
|
<span class="text-caption ml-2">{{ createdAt }}</span>
|
||||||
|
</v-sheet>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.message-shaped {
|
||||||
|
border-radius: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-shaped-right {
|
||||||
|
border-radius: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-width {
|
||||||
|
max-width: 66%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
29
src/components/MessageForm.vue
Normal file
29
src/components/MessageForm.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-row dense align="center">
|
||||||
|
<v-col>
|
||||||
|
<v-text-field rounded="xl" bg-color="white" variant="outlined" hide-details label="Message">
|
||||||
|
<template v-slot:prepend-inner>
|
||||||
|
<v-btn icon variant="text" density="compact" color="grey-darken-1">
|
||||||
|
<v-icon>mdi-emoticon-outline</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn icon variant="text" color="grey-darken-1">
|
||||||
|
<v-icon>mdi-paperclip</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn icon variant="flat" color="primary">
|
||||||
|
<v-icon>mdi-send</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
10
src/components/MessageToolbar.vue
Normal file
10
src/components/MessageToolbar.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-toolbar color="white" class="flex-grow-0 px-4">
|
||||||
|
<v-avatar color="grey-lighten-1" text="V"></v-avatar>
|
||||||
|
<v-toolbar-title class="">name</v-toolbar-title>
|
||||||
|
</v-toolbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
28
src/components/MessagesPane.vue
Normal file
28
src/components/MessagesPane.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MessageToolbar from '@/components/MessageToolbar.vue'
|
||||||
|
import MessageForm from '@/components/MessageForm.vue'
|
||||||
|
import MessageData from '@/components/MessageData.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="h-100 d-flex flex-column">
|
||||||
|
<message-toolbar />
|
||||||
|
|
||||||
|
<v-card
|
||||||
|
rounded="0"
|
||||||
|
variant="flat"
|
||||||
|
color="grey-lighten-3"
|
||||||
|
class="flex-grow-1 d-flex flex-column overflow-hidden"
|
||||||
|
>
|
||||||
|
<v-card-text class="flex-grow-1 d-flex ga-4 flex-column flex-column-reverse overflow-y-auto">
|
||||||
|
<message-data />
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions class="flex-grow-0 bg-white pa-4">
|
||||||
|
<message-form />
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -5,29 +5,29 @@ import { useSharedWebSocket, WsDataType } from '../composables/useSharedWebSocke
|
|||||||
const message = ref('')
|
const message = ref('')
|
||||||
const selectedChat = ref('')
|
const selectedChat = ref('')
|
||||||
|
|
||||||
const { messages, chats, isConnected, error, send } = useSharedWebSocket({
|
// const { messages, chats, isConnected, error, send } = useSharedWebSocket({
|
||||||
// onMessage: (data) => {
|
// onMessage: (data) => {
|
||||||
// console.log('Received message:', data)
|
// console.log('Received message:', data)
|
||||||
// },
|
// },
|
||||||
})
|
// })
|
||||||
|
|
||||||
const onChatClick = (id: string) => {
|
const onChatClick = (id: string) => {
|
||||||
console.log(id)
|
console.log(id)
|
||||||
selectedChat.value = id
|
selectedChat.value = id
|
||||||
send({
|
// send({
|
||||||
type: WsDataType.GET_MESSAGES,
|
// type: WsDataType.GET_MESSAGES,
|
||||||
data: { chat_id: id },
|
// data: { chat_id: id },
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (message.value.trim()) {
|
if (message.value.trim()) {
|
||||||
send({
|
// send({
|
||||||
type: WsDataType.CREATE_MESSAGE,
|
// type: WsDataType.CREATE_MESSAGE,
|
||||||
data: {
|
// data: {
|
||||||
chat_id: selectedChat.value,
|
// chat_id: selectedChat.value,
|
||||||
text: message.value,
|
// text: message.value,
|
||||||
},
|
// },
|
||||||
})
|
})
|
||||||
message.value = ''
|
message.value = ''
|
||||||
}
|
}
|
||||||
@@ -37,29 +37,29 @@ const sendMessage = () => {
|
|||||||
<template>
|
<template>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<div class="status" :class="{ isConnected, disconnected: !isConnected }">
|
<!-- <div class="status" :class="{ isConnected, disconnected: !isConnected }">-->
|
||||||
status: {{ isConnected ? 'connected' : 'disconnected' }}
|
<!-- status: {{ isConnected ? 'connected' : 'disconnected' }}-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<div>selected chat {{ selectedChat }}</div>
|
<div>selected chat {{ selectedChat }}</div>
|
||||||
<div class="error">Error: {{ error }}</div>
|
<!-- <div class="error">Error: {{ error }}</div>-->
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<div>
|
<div>
|
||||||
<div v-for="chat in chats" :key="chat.id" @click="onChatClick(chat.id)">
|
<!-- <div v-for="chat in chats" :key="chat.id" @click="onChatClick(chat.id)">-->
|
||||||
<div>{{ chat.id }}</div>
|
<!-- <div>{{ chat.id }}</div>-->
|
||||||
<div>{{ chat.users }}</div>
|
<!-- <div>{{ chat.users }}</div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-sheet border class="pa-4 border-opacity-50">
|
<v-sheet border class="pa-4 border-opacity-50">
|
||||||
<div v-if="selectedChat">
|
<div v-if="selectedChat">
|
||||||
<v-sheet height="400px" class="overflow-y-auto">
|
<v-sheet height="400px" class="overflow-y-auto">
|
||||||
<div v-for="message in messages" :key="message.id">
|
<!-- <div v-for="message in messages" :key="message.id">-->
|
||||||
<div>{{ message }}</div>
|
<!-- <div>{{ message }}</div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
|
|
||||||
<div class="d-flex align-center ga-1">
|
<div class="d-flex align-center ga-1">
|
||||||
@@ -72,13 +72,13 @@ const sendMessage = () => {
|
|||||||
placeholder="Type message..."
|
placeholder="Type message..."
|
||||||
>
|
>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
<v-btn
|
<!-- <v-btn-->
|
||||||
:disabled="!isConnected"
|
<!-- :disabled="!isConnected"-->
|
||||||
variant="elevated"
|
<!-- variant="elevated"-->
|
||||||
color="primary"
|
<!-- color="primary"-->
|
||||||
icon="mdi-send"
|
<!-- icon="mdi-send"-->
|
||||||
@click="sendMessage"
|
<!-- @click="sendMessage"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
|
|||||||
@@ -44,10 +44,7 @@ export enum COMMAND {
|
|||||||
|
|
||||||
export function useSharedWebSocket(options?: { url?: string; autoConnect?: true }) {
|
export function useSharedWebSocket(options?: { url?: string; autoConnect?: true }) {
|
||||||
const url = options?.url || 'ws://localhost:3000/ws'
|
const url = options?.url || 'ws://localhost:3000/ws'
|
||||||
const autoConnect = options?.autoConnect ?? true
|
const autoConnect = options?.autoConnect ?? false
|
||||||
|
|
||||||
console.log('useSharedWebSocket', url, autoConnect)
|
|
||||||
|
|
||||||
const messages = ref<Message[]>([])
|
const messages = ref<Message[]>([])
|
||||||
const chats = ref<Chat[]>([])
|
const chats = ref<Chat[]>([])
|
||||||
const users = ref<unknown[]>([])
|
const users = ref<unknown[]>([])
|
||||||
@@ -55,9 +52,11 @@ export function useSharedWebSocket(options?: { url?: string; autoConnect?: true
|
|||||||
const error = ref<string>()
|
const error = ref<string>()
|
||||||
const worker = ref<SharedWorker>()
|
const worker = ref<SharedWorker>()
|
||||||
|
|
||||||
const initWorker = () => {
|
const init = () => {
|
||||||
|
console.log('INIT WORKER')
|
||||||
|
|
||||||
if (!window.SharedWorker) {
|
if (!window.SharedWorker) {
|
||||||
console.warn('SharedWorker not supported')
|
console.log('SharedWorker not supported')
|
||||||
error.value = 'SharedWorker not supported'
|
error.value = 'SharedWorker not supported'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,13 +67,10 @@ export function useSharedWebSocket(options?: { url?: string; autoConnect?: true
|
|||||||
worker.value.port.onmessage = (event) => {
|
worker.value.port.onmessage = (event) => {
|
||||||
const { type, data, connected, message } = event.data
|
const { type, data, connected, message } = event.data
|
||||||
|
|
||||||
console.log(event.data)
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WsDataType.USER:
|
case WsDataType.USER:
|
||||||
console.log('USER')
|
|
||||||
break
|
break
|
||||||
case WsDataType.CHATS:
|
case WsDataType.CHATS:
|
||||||
console.log('chat')
|
|
||||||
chats.value = data
|
chats.value = data
|
||||||
break
|
break
|
||||||
case WsDataType.MESSAGES:
|
case WsDataType.MESSAGES:
|
||||||
@@ -91,16 +87,25 @@ export function useSharedWebSocket(options?: { url?: string; autoConnect?: true
|
|||||||
break
|
break
|
||||||
case WsDataType.ERROR:
|
case WsDataType.ERROR:
|
||||||
error.value = message
|
error.value = message
|
||||||
|
logout()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.value.port.postMessage({
|
worker.value.port.postMessage({
|
||||||
command: COMMAND.CONNECT,
|
command: COMMAND.CONNECT,
|
||||||
data: { url },
|
data: { url: url, token: getToken() },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
return localStorage.removeItem('token')
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToken() {
|
||||||
|
return localStorage.getItem('token')
|
||||||
|
}
|
||||||
|
|
||||||
const send = (data: WsData) => {
|
const send = (data: WsData) => {
|
||||||
if (worker.value) {
|
if (worker.value) {
|
||||||
worker.value.port.postMessage({
|
worker.value.port.postMessage({
|
||||||
@@ -117,7 +122,7 @@ export function useSharedWebSocket(options?: { url?: string; autoConnect?: true
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (autoConnect) initWorker()
|
if (autoConnect) init()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -131,6 +136,6 @@ export function useSharedWebSocket(options?: { url?: string; autoConnect?: true
|
|||||||
error,
|
error,
|
||||||
send,
|
send,
|
||||||
close,
|
close,
|
||||||
initWorker,
|
init,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/stores/auth.ts
Normal file
29
src/stores/auth.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
const token = ref(localStorage.getItem('token'))
|
||||||
|
|
||||||
|
async function login(email: string) {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email: email }),
|
||||||
|
}
|
||||||
|
const res = await fetch('http://localhost:5173/login', options)
|
||||||
|
if (res.ok) {
|
||||||
|
const data: { accessToken: string; refreshToken: string } = await res.json()
|
||||||
|
|
||||||
|
token.value = data.accessToken
|
||||||
|
localStorage.setItem('token', token.value)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { token, login }
|
||||||
|
})
|
||||||
0
src/stores/chats.ts
Normal file
0
src/stores/chats.ts
Normal file
0
src/stores/messages.ts
Normal file
0
src/stores/messages.ts
Normal file
5
src/stores/sockets.ts
Normal file
5
src/stores/sockets.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useSockets = defineStore('sockets', () => {
|
||||||
|
return {}
|
||||||
|
})
|
||||||
0
src/stores/users.ts
Normal file
0
src/stores/users.ts
Normal file
49
src/views/ChatView.vue
Normal file
49
src/views/ChatView.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useSharedWebSocket, WsDataType } from '@/composables/useSharedWebSocket.ts'
|
||||||
|
import ChatsPane from '@/components/ChatsPane.vue'
|
||||||
|
import MessagesPane from '@/components/MessagesPane.vue'
|
||||||
|
|
||||||
|
const { messages, chats, isConnected, error, send } = useSharedWebSocket({ autoConnect: true })
|
||||||
|
|
||||||
|
const message = ref('')
|
||||||
|
const selectedChat = ref('')
|
||||||
|
|
||||||
|
const onChatClick = (id: string) => {
|
||||||
|
console.log(id)
|
||||||
|
selectedChat.value = id
|
||||||
|
send({
|
||||||
|
type: WsDataType.GET_MESSAGES,
|
||||||
|
data: { chat_id: id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendMessage = () => {
|
||||||
|
if (message.value.trim()) {
|
||||||
|
send({
|
||||||
|
type: WsDataType.CREATE_MESSAGE,
|
||||||
|
data: {
|
||||||
|
chat_id: selectedChat.value,
|
||||||
|
text: message.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
message.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-row no-gutters class="h-100">
|
||||||
|
<v-col cols="12" md="4" lg="3">
|
||||||
|
<chats-pane />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="8" lg="9" class="h-100">
|
||||||
|
<!-- <messages-pane />-->
|
||||||
|
<div>error:{{ error }}</div>
|
||||||
|
<div>is connected:{{ isConnected }}</div>
|
||||||
|
<div>chats {{ chats }}</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
44
src/views/LoginView.vue
Normal file
44
src/views/LoginView.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useAuthStore } from '@/stores/auth.ts'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const email = ref('vadim.olonin@gmail.com')
|
||||||
|
async function onSubmit() {
|
||||||
|
console.log('onSubmit')
|
||||||
|
await authStore.login(email.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-row justify="center" class="h-100 mt-10">
|
||||||
|
<v-col cols="12" md="4" lg="4">
|
||||||
|
<v-card elevation="0">
|
||||||
|
<v-card-item class="justify-center mb-4">
|
||||||
|
<v-img rounded="lg" src="img.png" width="160" height="160" />
|
||||||
|
</v-card-item>
|
||||||
|
<v-card-text>
|
||||||
|
<form @submit.prevent="onSubmit">
|
||||||
|
<div class="d-flex flex-column ga-4">
|
||||||
|
<v-text-field
|
||||||
|
hide-details
|
||||||
|
variant="outlined"
|
||||||
|
v-model="email"
|
||||||
|
placeholder="email"
|
||||||
|
type="email"
|
||||||
|
rounded="xl"
|
||||||
|
/>
|
||||||
|
<div class="d-flex ga-4 align-center justify-space-between">
|
||||||
|
<v-switch color="black" label="Keep me signed in" hide-details></v-switch>
|
||||||
|
<v-btn variant="flat" rounded="xl" type="submit" color="black">login</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -9,7 +9,6 @@ function connectWebSocket(url) {
|
|||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
console.log('[Shared Worker] WebSocket connected')
|
console.log('[Shared Worker] WebSocket connected')
|
||||||
reconnectAttempts = 0
|
|
||||||
broadcast({ type: 'STATUS', connected: true })
|
broadcast({ type: 'STATUS', connected: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,10 +33,10 @@ function connectWebSocket(url) {
|
|||||||
|
|
||||||
socket.onerror = (error) => {
|
socket.onerror = (error) => {
|
||||||
console.error('[Shared Worker] WebSocket error:', error)
|
console.error('[Shared Worker] WebSocket error:', error)
|
||||||
broadcast({ type: 'ERROR', message: error.message })
|
broadcast({ type: 'ERROR', message: 'error' })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Shared Worker] Failed to create WebSocket:', error)
|
console.error('[Shared Worker] Failed to create WebSocket:', error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +56,8 @@ onconnect = function (event) {
|
|||||||
switch (command) {
|
switch (command) {
|
||||||
case 'CONNECT':
|
case 'CONNECT':
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||||
connectWebSocket(data.url)
|
console.log()
|
||||||
|
connectWebSocket(data.url + '?token=' + data.token)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'SEND':
|
case 'SEND':
|
||||||
|
|||||||
Reference in New Issue
Block a user