brutalism design
This commit is contained in:
@@ -4,6 +4,7 @@ import { useClients } from '@shared/composables/use-clients'
|
||||
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
||||
import { useSignaling } from '@shared/composables/use-signaling'
|
||||
import { watch, watchEffect } from 'vue'
|
||||
import { queryClient } from '../plugins/tanstack-query'
|
||||
|
||||
export default function () {
|
||||
const { authorized } = useAuth()
|
||||
@@ -33,8 +34,9 @@ export default function () {
|
||||
removeClient(socketId)
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
socket.on('disconnect', async () => {
|
||||
clearClients()
|
||||
await queryClient.resetQueries()
|
||||
})
|
||||
|
||||
socket.on('chat:new-message', (message: ChatMessage) => {
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import type { FunctionPlugin } from 'vue'
|
||||
import { VueQueryPlugin } from '@tanstack/vue-query'
|
||||
import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
app.use(VueQueryPlugin, {})
|
||||
app.use(VueQueryPlugin, {
|
||||
queryClient,
|
||||
})
|
||||
},
|
||||
} as FunctionPlugin
|
||||
|
||||
@@ -35,7 +35,7 @@ export function useChatScroll(target: TemplateRef<HTMLElement>, options?: MaybeR
|
||||
getArrivedState()
|
||||
}, {
|
||||
childList: true,
|
||||
// subtree: true,
|
||||
subtree: true,
|
||||
})
|
||||
|
||||
useEventListener(el, 'scroll', () => {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
|
||||
import { useSignaling } from '@shared/composables/use-signaling'
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
import { shallowRef, triggerRef, watch } from 'vue'
|
||||
import { computed, shallowRef, triggerRef, watch } from 'vue'
|
||||
import { qChatMessages } from '../api/qChatMessages.ts'
|
||||
|
||||
export interface MessageGroup {
|
||||
senderUsername: string
|
||||
messages: ChatMessage[]
|
||||
}
|
||||
|
||||
export const useChat = createGlobalState(() => {
|
||||
const { data: messages, hasNextPage: hasMoreMessages, fetchNextPage, isFetching } = qChatMessages()
|
||||
|
||||
const { socket, connected } = useSignaling()
|
||||
const feedItems = shallowRef<ChatMessage[]>([])
|
||||
|
||||
watch(connected, (isConnected) => {
|
||||
watch(connected, async (isConnected) => {
|
||||
if (!isConnected)
|
||||
return
|
||||
|
||||
@@ -20,5 +25,17 @@ export const useChat = createGlobalState(() => {
|
||||
})
|
||||
})
|
||||
|
||||
return { messages, feedItems, hasMoreMessages, fetchNextPage, isFetching }
|
||||
const messageGroups = computed<MessageGroup[]>(() => {
|
||||
const all = [...(messages.value ?? []), ...feedItems.value]
|
||||
return all.reduce((groups, msg) => {
|
||||
const last = groups.at(-1)
|
||||
if (last?.senderUsername === msg.senderUsername)
|
||||
last.messages.push(msg)
|
||||
else
|
||||
groups.push({ senderUsername: msg.senderUsername, messages: [msg] })
|
||||
return groups
|
||||
}, [] as MessageGroup[])
|
||||
})
|
||||
|
||||
return { messageGroups, hasMoreMessages, fetchNextPage, isFetching }
|
||||
})
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div class="chat-message">
|
||||
<ChadAvatar class="chat-message__avatar" src="" :fallback="initials" :highlighted="isMyMessage" />
|
||||
<div>
|
||||
<div class="chat-message__top">
|
||||
<p class="chat-message__sender">
|
||||
{{ !sender ? message.senderUsername : sender.displayName }}
|
||||
@@ -18,13 +16,10 @@
|
||||
|
||||
<ChatMessageAttachments v-if="message.attachments.length > 0" :attachments="message.attachments" class="chat-message__attachments" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
|
||||
import ChadAvatar from '@shared/components/ui/Avatar.vue'
|
||||
import { useAuth } from '@shared/composables/use-auth.ts'
|
||||
import { useUserDetails } from '@shared/composables/use-user-details.ts'
|
||||
import ChatMessageAttachments from '@widgets/chat/ui/ChatMessageAttachments.vue'
|
||||
import { format, isThisYear, isToday } from 'date-fns'
|
||||
@@ -34,16 +29,8 @@ const props = defineProps<{
|
||||
message: ChatMessage
|
||||
}>()
|
||||
|
||||
const { me } = useAuth()
|
||||
|
||||
const isMyMessage = computed(() => {
|
||||
return props.message.senderUsername === me.value?.username
|
||||
})
|
||||
|
||||
const { user: sender } = useUserDetails(toRef(() => props.message.senderUsername))
|
||||
|
||||
const initials = computed(() => props.message.senderUsername.slice(props.message.senderUsername.length - 2))
|
||||
|
||||
const datetime = computed(() => {
|
||||
let formatStr = 'd MMMM yyyy, HH:mm'
|
||||
|
||||
@@ -62,27 +49,23 @@ const datetime = computed(() => {
|
||||
.chat-message {
|
||||
$self: &;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-areas: 'avatar top' 'avatar body';
|
||||
column-gap: var(--space-3);
|
||||
padding-block: var(--space-3);
|
||||
padding-inline: var(--space-6);
|
||||
|
||||
&__avatar {
|
||||
grid-area: avatar;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
&__top {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
grid-area: top;
|
||||
//margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
&__sender {
|
||||
@include font-body-bold;
|
||||
|
||||
#{$self}:not(:first-child) & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__datetime {
|
||||
@@ -93,7 +76,6 @@ const datetime = computed(() => {
|
||||
}
|
||||
|
||||
&__body {
|
||||
grid-area: body;
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,21 +31,17 @@ const notImages = computed(() => {
|
||||
|
||||
<style lang="scss">
|
||||
.chat-message-attachments {
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
overflow-x: auto;
|
||||
|
||||
&__images {
|
||||
&__images,
|
||||
&__list {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
&__list {
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,7 +41,6 @@ const isMyMessage = computed(() => {
|
||||
padding-inline: var(--space-6);
|
||||
|
||||
&__avatar {
|
||||
align-self: flex-start;
|
||||
position: sticky;
|
||||
top: var(--space-3);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div class="chat-messages">
|
||||
<div v-if="messages" ref="scroll" class="chat-messages__list">
|
||||
<ChatMessage v-for="message in messages" :key="message.id" :message="message" />
|
||||
<ChatMessage v-for="item in feedItems" :key="item.id" :message="item" />
|
||||
<div v-if="messageGroups.length" ref="scroll" class="chat-messages__list">
|
||||
<ChatMessageGroup
|
||||
v-for="group in messageGroups"
|
||||
:key="`group-${group.messages[0].id}`"
|
||||
:group="group"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ChadSpinner v-if="isFetching" class="chat-messages__spinner" />
|
||||
@@ -18,7 +21,7 @@ import { ArrowDown } from '@lucide/vue'
|
||||
import ChadSpinner from '@shared/components/ui/Spinner.vue'
|
||||
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
||||
import { useChatScroll } from '@widgets/chat/composables/use-chat-scroll.ts'
|
||||
import ChatMessage from '@widgets/chat/ui/ChatMessage.vue'
|
||||
import ChatMessageGroup from '@widgets/chat/ui/ChatMessageGroup.vue'
|
||||
import { onUnmounted, ref, useTemplateRef, watch } from 'vue'
|
||||
import { useChat } from '../composables/use-chat'
|
||||
|
||||
@@ -26,7 +29,7 @@ const eventBus = useEventBus()
|
||||
|
||||
const scrollRef = useTemplateRef('scroll')
|
||||
|
||||
const { messages, feedItems, hasMoreMessages, fetchNextPage, isFetching } = useChat()
|
||||
const { messageGroups, hasMoreMessages, fetchNextPage, isFetching } = useChat()
|
||||
|
||||
const hasUnreadMessages = ref(false)
|
||||
|
||||
|
||||
2
server/.gitignore
vendored
2
server/.gitignore
vendored
@@ -18,3 +18,5 @@ node_modules
|
||||
|
||||
.env*
|
||||
*.db
|
||||
|
||||
uploads
|
||||
|
||||
Reference in New Issue
Block a user