Compare commits

...

19 Commits

Author SHA1 Message Date
ca8728c90c модалочки оп-оп 2026-06-01 05:07:42 +06:00
0dd9efb9fb обновОЧКИ 2026-05-29 04:28:09 +06:00
3c885edc46 brutalism design 2026-05-24 16:26:52 +06:00
12ae0ae839 brutalism design 2026-05-23 22:49:52 +06:00
d06892e990 brutalism design 2026-05-22 06:01:27 +06:00
5d45c9674f brutalism design 2026-05-22 05:27:41 +06:00
1ca73e786c brutalism design 2026-05-22 05:08:41 +06:00
e4ed785911 brutalism design 2026-05-22 05:08:02 +06:00
abf4d41c23 chat wip 2026-05-14 07:09:52 +06:00
edef0a70d2 brutalism design 2026-05-14 01:05:01 +06:00
6a2111092b working hard 2026-05-09 17:39:42 +06:00
0b148c6a7d работаем бля работаем 2026-05-09 03:21:44 +06:00
f845777bac последовательность запуска плагинов 2026-04-25 00:53:57 +06:00
ad477ee813 вложения, канальчики, бим-бим + бам-бам 2026-04-25 00:51:12 +06:00
0b75148a3f навалил фокуса 2026-04-16 15:24:49 +06:00
c966aa9c4b продолжаю чат 2026-04-16 14:02:59 +06:00
0915d3c64d начало чата
All checks were successful
Deploy / deploy (push) Successful in 3m47s
2026-04-16 02:21:54 +06:00
9f39ee6430 убрал из панели задач, скрыл от шаринга
All checks were successful
Deploy / publish-web (push) Successful in 1m28s
2026-04-15 15:43:43 +06:00
3658975b93 cringe sfx
All checks were successful
Deploy / publish-web (push) Successful in 1m33s
2026-04-12 22:38:48 +06:00
268 changed files with 34885 additions and 2148 deletions

Binary file not shown.

54
client/.zed/settings.json Normal file
View File

@@ -0,0 +1,54 @@
{
// Use ESLint's --fix:
"code_actions_on_format": {
"source.fixAll.eslint": true,
},
"formatter": [],
// Enable eslint for all supported languages
// Defaults only include https://github.com/search?q=repo%3Azed-industries%2Fzed%20eslint_languages&type=code
"languages": {
"HTML": {
"language_servers": ["...", "eslint"],
},
"Markdown": {
"language_servers": ["...", "eslint"],
},
"Markdown-Inline": {
"language_servers": ["...", "eslint"],
},
"JSON": {
"language_servers": ["...", "eslint"],
},
"JSONC": {
"language_servers": ["...", "eslint"],
},
"YAML": {
"language_servers": ["...", "eslint"],
},
"CSS": {
"language_servers": ["...", "eslint"],
},
// Add other languages as needed
},
"lsp": {
"eslint": {
"settings": {
"workingDirectories": ["./"],
// Silent the stylistic rules in your IDE, but still auto fix them
"rulesCustomizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true },
],
},
},
},
}

View File

@@ -15,6 +15,7 @@ declare module 'vue' {
PrimeCard: typeof import('primevue/card')['default']
PrimeDivider: typeof import('primevue/divider')['default']
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
PrimeInputGroup: typeof import('primevue/inputgroup')['default']
PrimeInputText: typeof import('primevue/inputtext')['default']
PrimePassword: typeof import('primevue/password')['default']
PrimeProgressBar: typeof import('primevue/progressbar')['default']

View File

@@ -21,7 +21,7 @@
</PrimeAvatar>
<p class="flex-1 text-sm leading-5 font-medium text-color truncate w-0">
{{ client.displayName || client.username }}
{{ client.displayName || client.username || client.socketId }}
</p>
<Component :is="expanded ? ChevronUp : ChevronDown" v-if="!isMe" :size="20" class="text-muted-color" />

View File

@@ -1,5 +1,5 @@
import { getVersion } from '@tauri-apps/api/app'
import { openUrl as tauriOpenUrl } from '@tauri-apps/plugin-opener'
import { computedAsync, createGlobalState } from '@vueuse/core'
import { useClients } from '~/composables/use-clients'
@@ -85,7 +85,7 @@ export const useApp = createGlobalState(() => {
await muteInput()
await signaling.socket.value?.emitWithAck('updateClient', {
await signaling.socket.value?.emitWithAck('update-client', {
outputMuted: true,
})
@@ -98,7 +98,7 @@ export const useApp = createGlobalState(() => {
if (!previousInputMuted.value)
await unmuteInput()
await signaling.socket.value?.emitWithAck('updateClient', {
await signaling.socket.value?.emitWithAck('update-client', {
outputMuted: false,
})
@@ -134,6 +134,15 @@ export const useApp = createGlobalState(() => {
}
}
async function openUrl(href: string) {
if (isTauri.value) {
await tauriOpenUrl(href)
}
else {
window.open(href, '_blank', 'noopener noreferrer')
}
}
return {
ready,
clients,
@@ -153,5 +162,6 @@ export const useApp = createGlobalState(() => {
videoEnabled,
sharingEnabled,
somebodyStreamingVideo,
openUrl,
}
})

View File

@@ -16,7 +16,7 @@ export const useAuth = createGlobalState(() => {
async function login(username: string, password: string): Promise<void> {
try {
const result = await chadApi<Me>('/login', {
const result = await chadApi<Me>('/auth/login', {
method: 'POST',
body: {
username,
@@ -33,7 +33,7 @@ export const useAuth = createGlobalState(() => {
async function register(username: string, password: string): Promise<void> {
try {
const result = await chadApi<Me>('/register', {
const result = await chadApi<Me>('/auth/register', {
method: 'POST',
body: {
username,
@@ -50,7 +50,7 @@ export const useAuth = createGlobalState(() => {
async function logout(): Promise<void> {
try {
await chadApi('/logout', { method: 'POST' })
await chadApi('/auth/logout', { method: 'POST' })
setMe(undefined)

View File

@@ -0,0 +1,63 @@
import chadApi from '#shared/chad-api'
import { createGlobalState } from '@vueuse/core'
export interface ChatClientMessage {
text: string
// replyTo?: {
// messageId: string
// }
}
export interface ChatMessage {
id: string
senderId: string
text: string
createdAt: string
updatedAt: string
attachments: string[]
// replyTo?: {
// messageId: string
// sender: string
// text: string
// }
}
export const useChat = createGlobalState(() => {
const signaling = useSignaling()
const { emit } = useEventBus()
const messages = shallowRef<ChatMessage[]>([])
watch(signaling.socket, (socket) => {
if (!socket)
return
socket.on('chat:new-message', (message: ChatMessage) => {
messages.value.push(message)
triggerRef(messages)
emit('chat:new-message')
})
socket.on('disconnect', () => {
messages.value = []
})
}, { immediate: true, flush: 'sync' })
async function sendMessage(message: ChatClientMessage) {
message.text = message.text.trim()
if (!message.text.length)
return
await chadApi<ChatMessage>('/chat/send', {
method: 'POST',
body: message,
})
}
return {
messages,
sendMessage,
}
})

View File

@@ -14,7 +14,7 @@ export const useClients = createGlobalState(() => {
if (!socket)
return
socket.on('clientChanged', (clientId: ChadClient['socketId'], updatedClient: UpdatedClient) => {
socket.on('client-updated', (clientId: ChadClient['socketId'], updatedClient: UpdatedClient) => {
const client = getClient(clientId)
if (!client)

View File

@@ -29,6 +29,8 @@ export interface AppEvents extends Record<EventType, unknown> {
'video:disabled': void
'share:enabled': void
'share:disabled': void
'chat:new-message': void
}
const emitter = mitt<AppEvents>()

View File

@@ -26,15 +26,15 @@ const ICE_SERVERS: RTCIceServer[] = [
]
export const useMediasoup = createSharedComposable(() => {
const { emit } = useEventBus()
const eventBus = useEventBus()
const signaling = useSignaling()
const { addClient, removeClient, me } = useClients()
const { addClient, removeClient, me, clients, updateClient } = useClients()
const preferences = usePreferences()
const { getShareStream } = useDevices()
const device = shallowRef<mediasoupClient.Device>()
const rtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
const routerRtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
const sendTransport = shallowRef<mediasoupClient.types.Transport>()
const recvTransport = shallowRef<mediasoupClient.types.Transport>()
@@ -79,18 +79,30 @@ export const useMediasoup = createSharedComposable(() => {
if (!socket)
return
socket.on('authenticated', async () => {
socket.on('new-client', (client) => {
addClient(client)
eventBus.emit('client:added', client)
})
socket.on('client-switched-channel', (client) => {
updateClient(client.socketId, client)
})
socket.on('initialized', async (initData) => {
if (!signaling.socket.value)
return
device.value = new mediasoupClient.Device()
rtpCapabilities.value = await signaling.socket.value.emitWithAck('getRtpCapabilities')
routerRtpCapabilities.value = initData.rtpCapabilities
await device.value.load({ routerRtpCapabilities: rtpCapabilities.value! })
clients.value = initData.clients
await device.value.load({ routerRtpCapabilities: routerRtpCapabilities.value! })
// Send transport
{
const transportInfo = await signaling.socket.value.emitWithAck('createTransport', { producing: true, consuming: false })
const transportInfo = await signaling.socket.value.emitWithAck('create-transport', { producing: true, consuming: false })
sendTransport.value = device.value.createSendTransport({
...transportInfo,
iceServers: [
@@ -101,7 +113,7 @@ export const useMediasoup = createSharedComposable(() => {
sendTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await signaling.socket.value!.emitWithAck('connectTransport', {
await signaling.socket.value!.emitWithAck('connect-transport', {
transportId: sendTransport.value!.id,
dtlsParameters,
})
@@ -135,7 +147,7 @@ export const useMediasoup = createSharedComposable(() => {
// Recv Transport
{
const transportInfo = await signaling.socket.value.emitWithAck('createTransport', { producing: false, consuming: true })
const transportInfo = await signaling.socket.value.emitWithAck('create-transport', { producing: false, consuming: true })
recvTransport.value = device.value.createRecvTransport({
...transportInfo,
iceServers: [
@@ -146,7 +158,7 @@ export const useMediasoup = createSharedComposable(() => {
recvTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await signaling.socket.value!.emitWithAck('connectTransport', {
await signaling.socket.value!.emitWithAck('connect-transport', {
transportId: recvTransport.value!.id,
dtlsParameters,
})
@@ -160,36 +172,33 @@ export const useMediasoup = createSharedComposable(() => {
}
})
}
//
// const joinedClients = (await signaling.socket.value.emitWithAck('join', {
// rtpCapabilities: routerRtpCapabilities.value,
// }))
//
// addClient(...joinedClients)
//
// if (me.value)
// eventBus.emit('socket:authenticated', { socketId: me.value.socketId })
//
const joinedClients = (await signaling.socket.value.emitWithAck('join', {
rtpCapabilities: rtpCapabilities.value,
}))
addClient(...joinedClients)
if (me.value)
emit('socket:authenticated', { socketId: me.value.socketId })
// TODO: при переподключении проверять inputMuted
await enableMic()
})
socket.on('newPeer', (client) => {
addClient(client)
emit('client:added', client)
})
socket.on('peerClosed', (id) => {
socket.on('client-disconnected', (id) => {
const { getClient } = useClients()
const client = getClient(id)
removeClient(id)
if (client)
emit('client:removed', client)
eventBus.emit('client:removed', client)
})
socket.on(
'newConsumer',
'new-consumer',
async (
{ id, producerId, kind, rtpParameters, socketId, appData, producerPaused },
cb,
@@ -216,18 +225,18 @@ export const useMediasoup = createSharedComposable(() => {
raw: markRaw(consumer),
}
emit('consumer:added', consumers.value[consumer.id]!)
eventBus.emit('consumer:added', consumers.value[consumer.id]!)
consumer.observer.on('resume', () => {
consumers.value[consumer.id]!.paused = false
emit('consumer:resumed', consumers.value[consumer.id]!)
eventBus.emit('consumer:resumed', consumers.value[consumer.id]!)
})
consumer.observer.on('pause', () => {
consumers.value[consumer.id]!.paused = true
emit('consumer:paused', consumers.value[consumer.id]!)
eventBus.emit('consumer:paused', consumers.value[consumer.id]!)
})
consumer.observer.on('close', () => {
@@ -236,7 +245,7 @@ export const useMediasoup = createSharedComposable(() => {
delete consumers.value[consumer.id]
if (consumerData)
emit('consumer:removed', consumerData)
eventBus.emit('consumer:removed', consumerData)
})
consumer.on('trackended', () => {
@@ -248,7 +257,7 @@ export const useMediasoup = createSharedComposable(() => {
)
socket.on(
'consumerClosed',
'consumer-closed',
async (
{ consumerId },
) => {
@@ -261,7 +270,7 @@ export const useMediasoup = createSharedComposable(() => {
},
)
socket.on('consumerPaused', ({ consumerId }) => {
socket.on('consumer-paused', ({ consumerId }) => {
const consumer = consumers.value[consumerId]
if (!consumer)
@@ -270,7 +279,7 @@ export const useMediasoup = createSharedComposable(() => {
consumer.raw.pause()
})
socket.on('consumerResumed', ({ consumerId }) => {
socket.on('consumer-resumed', ({ consumerId }) => {
const consumer = consumers.value[consumerId]
if (!consumer)
@@ -279,13 +288,13 @@ export const useMediasoup = createSharedComposable(() => {
consumer.raw.resume()
})
socket.on('speakingPeers', (value: SpeakingClient[]) => {
socket.on('speaking-clients', (value: SpeakingClient[]) => {
speakingClients.value = value
})
socket.on('disconnect', () => {
device.value = undefined
rtpCapabilities.value = undefined
routerRtpCapabilities.value = undefined
sendTransport.value?.close()
sendTransport.value = undefined
@@ -317,27 +326,28 @@ export const useMediasoup = createSharedComposable(() => {
raw: markRaw(producer),
}
emit('producer:added', producers.value[producer.id]!)
eventBus.emit('producer:added', producers.value[producer.id]!)
producer.observer.on('pause', () => {
producers.value[producer.id]!.paused = true
emit('producer:paused', producers.value[producer.id]!)
eventBus.emit('producer:paused', producers.value[producer.id]!)
})
producer.observer.on('resume', () => {
producers.value[producer.id]!.paused = false
emit('producer:resumed', producers.value[producer.id]!)
eventBus.emit('producer:resumed', producers.value[producer.id]!)
})
producer.observer.on('close', () => {
console.log('producer closed')
const producerData = producers.value[producer.id]
delete producers.value[producer.id]
if (producerData)
emit('producer:removed', producerData)
eventBus.emit('producer:removed', producerData)
})
producer.on('trackended', () => {
@@ -352,7 +362,7 @@ export const useMediasoup = createSharedComposable(() => {
try {
producer.raw.close()
await signaling.socket.value.emitWithAck('closeProducer', {
await signaling.socket.value.emitWithAck('close-producer', {
producerId: producer.id,
})
}
@@ -455,7 +465,7 @@ export const useMediasoup = createSharedComposable(() => {
await createProducer({
track,
streamId: 'share',
codec: device.value.rtpCapabilities.codecs?.find(
codec: device.value.sendRtpCapabilities.codecs?.find(
c => c.mimeType.toLowerCase() === 'video/AV1',
),
codecOptions: {
@@ -478,7 +488,7 @@ export const useMediasoup = createSharedComposable(() => {
try {
producer.raw.pause()
await signaling.socket.value.emitWithAck('pauseProducer', {
await signaling.socket.value.emitWithAck('pause-producer', {
producerId: producer.id,
})
}
@@ -494,7 +504,7 @@ export const useMediasoup = createSharedComposable(() => {
try {
producer.raw.resume()
await signaling.socket.value.emitWithAck('resumeProducer', {
await signaling.socket.value.emitWithAck('resume-producer', {
producerId: producer.id,
})
}
@@ -526,7 +536,7 @@ export const useMediasoup = createSharedComposable(() => {
speakingClients,
sendTransport,
recvTransport,
rtpCapabilities,
rtpCapabilities: routerRtpCapabilities,
device,
micProducer,
videoProducer,
@@ -536,5 +546,7 @@ export const useMediasoup = createSharedComposable(() => {
enableVideo,
enableShare,
disableProducer,
consumersArray,
producersArray,
}
})

View File

@@ -42,7 +42,7 @@ export const usePreferences = createGlobalState(() => {
async ([toggleInputHotkey, toggleOutputHotkey]) => {
try {
await chadApi(
'/preferences',
'/user/preferences',
{
method: 'PATCH',
body: {

View File

@@ -15,7 +15,7 @@ function hashStringToNumber(str: string, cap: number): number {
const oneShots: Howl[] = []
type SfxEvent = 'mic-on' | 'mic-off' | 'stream-on' | 'stream-off' | 'connection'
type SfxEvent = 'mic-on' | 'mic-off' | 'stream-on' | 'stream-off' | 'connection' | 'message'
const EVENT_VOLUME: Record<SfxEvent, number> = {
'mic-on': 0.2,

View File

@@ -66,7 +66,7 @@ export const useSignaling = createSharedComposable(() => {
const uri = host ? `${protocol}//${host}` : ``
socket.value = io(`${uri}/webrtc`, {
socket.value = io(uri, {
path: `${pathname}/ws`,
transports: ['websocket'],
withCredentials: true,

View File

@@ -52,21 +52,48 @@
<PrimeScrollPanel class="bg-surface-900 rounded-xl overflow-hidden" style="min-height: 0">
<div v-auto-animate class="p-3 space-y-1">
<ClientRow v-for="client of clients" :key="client.userId" :client="client" />
<template v-for="channel in channels" :key="channel.id">
<PrimeDivider>
<PrimeButton size="small" variant="text" @click="joinChannel(channel)">
{{ channel.name }}
</PrimeButton>
</PrimeDivider>
<ClientRow v-for="client in clients.filter(_client => _client.channelId === channel.id)" :key="client.socketId" :client="client">
{{ client.userId }}
</ClientRow>
</template>
<!-- <ClientRow v-for="client of clients" :key="client.userId" :client="client" /> -->
</div>
</PrimeScrollPanel>
<PrimeScrollPanel class="bg-surface-900 rounded-xl overflow-hidden" style="min-height: 0">
<div class="p-3">
<slot />
</div>
</PrimeScrollPanel>
<div class="bg-surface-900 rounded-xl overflow-hidden p-3 flex flex-col min-h-full">
<dl>
<dt>Socket ID</dt>
<dd>{{ socket?.id }}</dd>
<br>
<dt>Producers</dt>
<dd v-for="producer in producersArray" :key="producer.id">
{{ producer.id }}
{{ producer.appData }}
</dd>
<br>
<dl>Consumers</dl>
<dd v-for="consumer in consumersArray" :key="consumer.id">
{{ consumer.id }}
{{ consumer.appData }}
</dd>
</dl>
<slot />
</div>
</div>
<FullscreenGallery />
</template>
<script setup lang="ts">
import chadApi from '#shared/chad-api'
import {
Camera,
CameraOff,
@@ -82,6 +109,13 @@ import {
VolumeOff,
} from 'lucide-vue-next'
const channels = shallowRef<any[]>([])
;(async () => {
channels.value = await chadApi<any[]>('/channels', { method: 'GET' })
})()
const { me } = useClients()
const {
version,
clients,
@@ -95,7 +129,8 @@ const {
toggleVideo,
toggleShare,
} = useApp()
const { connect, connected } = useSignaling()
const { connect, connected, socket } = useSignaling()
const { consumersArray, producersArray } = useMediasoup()
interface Tab {
id: string
@@ -152,4 +187,30 @@ watch(activeTab, (activeTab) => {
})
connect()
async function joinChannel(channel) {
socket.value?.emit('join-channel', { channelId: channel.id })
}
watch(socket, (socket) => {
if (!socket)
return
socket.on('channel-removed', (channelId) => {
const idx = channels.value.findIndex(channel => channel.id === channelId)
if (idx === -1)
return
channels.value.splice(idx, 1)
triggerRef(channels)
})
socket.on('channel-created', (channel) => {
channels.value.push(channel)
triggerRef(channels)
})
}, { immediate: true })
</script>

View File

@@ -5,7 +5,7 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
if (!me.value) {
try {
setMe(await chadApi('/me', { method: 'GET' }))
setMe(await chadApi('/auth/me', { method: 'GET' }))
if (to.meta.auth !== false)
return navigateTo({ name: 'Index' })

View File

@@ -13,7 +13,7 @@ export default defineNuxtRouteMiddleware(async () => {
return
try {
const preferences = await chadApi<SyncedPreferences>('/preferences', { method: 'GET' })
const preferences = await chadApi<SyncedPreferences>('/user/preferences', { method: 'GET' })
if (!preferences)
return

View File

@@ -1,5 +1,5 @@
<template>
<div class="grid grid-cols-[1fr_1fr] gap-2">
<PrimeScrollPanel class="grid grid-cols-[1fr_1fr] gap-2 min-h-0">
<GalleryCard
v-for="producer in producers"
:key="`producer-${producer.id}`"
@@ -13,7 +13,7 @@
:client="consumer.client"
:consumer="consumer.consumer"
/>
</div>
</PrimeScrollPanel>
</template>
<script setup lang="ts">

View File

@@ -1,17 +1,182 @@
<template>
<div>
<div class="flex items-center justify-center">
<PrimeCard>
<template #content>
The chat is under development.
</template>
</PrimeCard>
<p v-if="!messages.length" class="text-muted-color text-center m-auto">
Chat is empty
</p>
<PrimeScrollPanel v-else ref="scroll" class="flex-1 min-h-0 overflow-x-hidden">
<div class="flex flex-col gap-3">
<div
v-for="message in messages"
:key="message.id"
class="w-fit max-w-[60%]"
:class="{
'ml-auto': message.senderId === me?.userId,
}"
>
<p
v-if="message.senderId !== me?.userId"
class="text-sm text-muted-color mb-1"
>
{{ message.senderId }}
</p>
<div
class="px-3 py-2 rounded-lg"
:class="{
'bg-surface-800 rounded-tl': message.senderId !== me?.userId,
'bg-surface-700 rounded-tr': message.senderId === me?.userId,
}"
>
<p class="[&>a]:break-all" @click="handleMessageClick" v-html="parseMessageText(message.text)" />
<div v-if="message.attachments.length > 0" class="flex flex-col gap-2 mt-2">
<img
v-for="attachmentId in message.attachments"
:key="attachmentId"
class="rounded-xl max-w-60"
:src="`http://localhost:4000/chad/attachment/${attachmentId}`"
:alt="attachmentId"
>
</div>
<p class="mt-1 text-right text-sm text-muted-color" :title="formatDate(message.createdAt, 'dd.MM.yyyy, HH:mm')">
{{ formatDate(message.createdAt) }}
</p>
</div>
</div>
</div>
</PrimeScrollPanel>
<div class="mt-3 shrink-0">
<PrimeInputGroup>
<!-- <PrimeInputGroupAddon> -->
<!-- <PrimeButton severity="secondary" class="shrink-0" disabled> -->
<!-- <template #icon> -->
<!-- <Paperclip /> -->
<!-- </template> -->
<!-- </PrimeButton> -->
<!-- </PrimeInputGroupAddon> -->
<PrimeInputText
ref="input"
v-model="text"
placeholder="Write a message..."
fluid
autocomplete="off"
@keydown.enter.exact="sendMessage"
@vue:mounted="onInputMounted"
/>
<PrimeButton class="shrink-0" label="Send" severity="contrast" :disabled="!text" @click="sendMessage" />
</PrimeInputGroup>
</div>
</template>
<script setup lang="ts">
import { useEventBus } from '#imports'
import { onStartTyping, unrefElement, useEventListener } from '@vueuse/core'
import { format } from 'date-fns'
import linkifyStr from 'linkify-string'
import { useChat } from '~/composables/use-chat'
definePageMeta({
name: 'Index',
})
const { openUrl } = useApp()
const { me } = useClients()
const chat = useChat()
const eventBus = useEventBus()
const { messages } = chat
const scrollRef = useTemplateRef('scroll')
const inputRef = useTemplateRef('input')
const contentRef = computed(() => scrollRef.value?.$refs.content)
const text = ref('')
eventBus.on('chat:new-message', onNewMessage)
onScopeDispose(() => {
eventBus.off('chat:new-message', onNewMessage)
})
function parseMessageText(text: string) {
return linkifyStr(
text,
{
className: 'underline',
rel: 'noopener noreferrer',
target: '_blank',
},
).replaceAll('\n', '<br>')
}
function formatDate(date: string, formatStr = 'HH:mm') {
return format(date, formatStr)
}
function sendMessage() {
if (!text.value)
return
chat.sendMessage({
text: text.value,
})
text.value = ''
}
function onInputMounted(ref: VNode) {
ref.el?.focus()
}
useEventListener(window, 'focus', async (evt) => {
unrefElement(inputRef.value)?.focus()
})
onStartTyping(() => {
unrefElement(inputRef.value)?.focus()
})
const ARRIVED_STATE_THRESHOLD_PIXELS = 1
async function onNewMessage() {
if (!contentRef.value)
return
const arrivedBottom = contentRef.value.scrollTop + contentRef.value.clientHeight
>= contentRef.value.scrollHeight - ARRIVED_STATE_THRESHOLD_PIXELS
const scrollable = contentRef.value.scrollHeight > contentRef.value.clientHeight
await nextTick()
if (scrollable && !arrivedBottom)
return
contentRef.value.scrollTo({
behavior: 'smooth',
top: contentRef.value.scrollHeight,
})
}
function handleMessageClick({ target }: MouseEvent) {
if (!target)
return
if (target instanceof HTMLElement) {
if (target.tagName === 'A') {
target.addEventListener('click', onAnchorClick)
}
}
}
function onAnchorClick(event: MouseEvent) {
event.preventDefault()
const target = event.target as HTMLAnchorElement
console.log('yo')
openUrl(target.href)
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div>
<PrimeScrollPanel class="min-h-0">
<PrimeDivider align="left">
Audio
</PrimeDivider>
@@ -132,7 +132,7 @@
@click="checkForUpdates"
/>
</template>
</div>
</PrimeScrollPanel>
</template>
<script setup lang="ts">

View File

@@ -52,7 +52,7 @@ async function save() {
saving.value = true
const updatedMe = await chadApi('/profile', {
const updatedMe = await chadApi('/user/profile', {
method: 'PATCH',
body: {
displayName: displayName.value,

View File

@@ -4,12 +4,12 @@ export default defineNuxtPlugin(() => {
// Connection sounds
on('socket:authenticated', ({ socketId }) => {
sfx.playRandomConnectionSound(socketId)
sfx.playEvent('stream-on')
})
// Client events
on('client:added', (client) => {
sfx.playRandomConnectionSound(client.socketId)
sfx.playEvent('stream-on')
})
on('client:removed', () => {
@@ -54,4 +54,8 @@ export default defineNuxtPlugin(() => {
sfx.playEvent('stream-off')
}
})
on('chat:new-message', () => {
sfx.playEvent('message')
})
})

View File

@@ -14,14 +14,18 @@
"@nuxt/fonts": "^0.11.4",
"@primeuix/themes": "^1.2.5",
"@tailwindcss/vite": "^4.1.14",
"@tauri-apps/plugin-global-shortcut": "~2",
"@tauri-apps/plugin-process": "~2",
"@tauri-apps/plugin-updater": "~2",
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
"@tauri-apps/plugin-opener": "~2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"@vueuse/core": "^13.9.0",
"date-fns": "^4.1.0",
"hotkeys-js": "^4.0.0",
"howler": "^2.2.4",
"linkify-string": "^4.3.2",
"linkifyjs": "^4.3.2",
"lucide-vue-next": "^0.562.0",
"mediasoup-client": "^3.18.6",
"mediasoup-client": "^3.19.0",
"mitt": "^3.0.1",
"nuxt": "^4.2.2",
"postcss": "^8.5.6",

View File

@@ -3,8 +3,7 @@ import type { Consumer as MediasoupConsumer, Producer as MediasoupProducer } fro
export interface ChadClient {
socketId: string
userId: string
username: string
displayName: string
channelId: string
inputMuted?: boolean
outputMuted?: boolean

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,8 @@ log = "0.4"
tauri = { version = "2.8.5", features = [] }
tauri-plugin-log = "2"
tauri-plugin-process = "2"
windows = { version = "0.52", features = ["Win32_UI_Shell"] }
tauri-plugin-opener = "2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-global-shortcut = "2"

View File

@@ -13,6 +13,8 @@
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister",
"global-shortcut:allow-unregister-all"
"global-shortcut:allow-unregister-all",
"opener:allow-default-urls",
"opener:allow-open-url"
]
}

View File

@@ -1,10 +1,11 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
// .plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(

View File

@@ -1,6 +1,21 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[cfg(target_os = "windows")]
fn set_app_user_model_id() {
use windows::core::HSTRING;
use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
unsafe {
SetCurrentProcessExplicitAppUserModelID(&HSTRING::from("xyz.koptilnya.chad"))
.ok()
.expect("Failed to set AppUserModelID");
}
}
fn main() {
#[cfg(target_os = "windows")]
set_app_user_model_id();
app_lib::run();
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Chad",
"version": "0.2.33",
"version": "0.3.0-rc.4",
"identifier": "xyz.koptilnya.chad",
"build": {
"frontendDist": "../.output/public",
@@ -23,7 +23,7 @@
"fullscreen": false,
"center": true,
"theme": "Dark",
"additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --autoplay-policy=no-user-gesture-required",
"additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --autoplay-policy=no-user-gesture-required --lang=en",
"incognito": false
}
],

View File

@@ -2824,10 +2824,10 @@ __metadata:
languageName: node
linkType: hard
"@tauri-apps/api@npm:^2.6.0":
version: 2.8.0
resolution: "@tauri-apps/api@npm:2.8.0"
checksum: 10c0/fb111e4d7572372997b440ebe6879543fa8c4765151878e3fddfbfe809b18da29eed142ce83061d14a9ca6d896b3266dc8a4927c642d71cdc0b4277dc7e3aabf
"@tauri-apps/api@npm:^2.10.1":
version: 2.10.1
resolution: "@tauri-apps/api@npm:2.10.1"
checksum: 10c0/f3c0b2ba67a0b887440a7faa1e0589e847760ee30ec29b964f22573a46b817cb3af2199d6f5f7dfdda54d65b465ebaaa280454c610a5c53d808a0911fa15e45d
languageName: node
linkType: hard
@@ -2959,7 +2959,7 @@ __metadata:
languageName: node
linkType: hard
"@tauri-apps/plugin-global-shortcut@npm:~2":
"@tauri-apps/plugin-global-shortcut@npm:^2.3.1":
version: 2.3.1
resolution: "@tauri-apps/plugin-global-shortcut@npm:2.3.1"
dependencies:
@@ -2968,21 +2968,30 @@ __metadata:
languageName: node
linkType: hard
"@tauri-apps/plugin-process@npm:~2":
version: 2.3.0
resolution: "@tauri-apps/plugin-process@npm:2.3.0"
"@tauri-apps/plugin-opener@npm:~2":
version: 2.5.3
resolution: "@tauri-apps/plugin-opener@npm:2.5.3"
dependencies:
"@tauri-apps/api": "npm:^2.6.0"
checksum: 10c0/ef50344a7436d92278c2ef4526f72daaf3171c4d65743bbc1f7a00fa581644a8583bb8680f637a34af5c7e6a0e8722c22189290e903584fef70ed83b64b6e9c0
"@tauri-apps/api": "npm:^2.8.0"
checksum: 10c0/9ef2fae01e03f3bb16d8e55bfd921cf7c1d284e6459bd5b45777806304eb70ab0b50cbf03be76fc05e64ef70a37493e0cd90b0acc16eaee4a4fc2cfff7e43b71
languageName: node
linkType: hard
"@tauri-apps/plugin-updater@npm:~2":
version: 2.9.0
resolution: "@tauri-apps/plugin-updater@npm:2.9.0"
"@tauri-apps/plugin-process@npm:^2.3.1":
version: 2.3.1
resolution: "@tauri-apps/plugin-process@npm:2.3.1"
dependencies:
"@tauri-apps/api": "npm:^2.6.0"
checksum: 10c0/72ce83d1c241308a13b9929f0900e4d33453875877009166e3998e3e75a1003ac48c3641086b4d3230f0f18c64f475ad6c3556d1603fc641ca50dc9c18d61866
"@tauri-apps/api": "npm:^2.8.0"
checksum: 10c0/2e5086898f1c9f25f6426a752404c788727237142bbb7c8f418b97c76c5360874d06203150d136e51114df9e720022e4fa3681fd1d4cb6f777dc83c3553f8670
languageName: node
linkType: hard
"@tauri-apps/plugin-updater@npm:^2.10.1":
version: 2.10.1
resolution: "@tauri-apps/plugin-updater@npm:2.10.1"
dependencies:
"@tauri-apps/api": "npm:^2.10.1"
checksum: 10c0/5d3813851ccbbf90253ad4647dbd97501c2bb75db864175693322fa6eb062b6e1bae03890810ad0bbe7f6da4e020af35e8c787e6999f8c7c645121164587dc29
languageName: node
linkType: hard
@@ -2995,7 +3004,7 @@ __metadata:
languageName: node
linkType: hard
"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.12":
"@types/debug@npm:^4.0.0":
version: 4.1.12
resolution: "@types/debug@npm:4.1.12"
dependencies:
@@ -3004,6 +3013,15 @@ __metadata:
languageName: node
linkType: hard
"@types/debug@npm:^4.1.13":
version: 4.1.13
resolution: "@types/debug@npm:4.1.13"
dependencies:
"@types/ms": "npm:*"
checksum: 10c0/e5e124021bbdb23a82727eee0a726ae0fc8a3ae1f57253cbcc47497f259afb357de7f6941375e773e1abbfa1604c1555b901a409d762ec2bb4c1612131d4afb7
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8":
version: 1.0.8
resolution: "@types/estree@npm:1.0.8"
@@ -4059,17 +4077,21 @@ __metadata:
"@primevue/nuxt-module": "npm:^4.4.0"
"@tailwindcss/vite": "npm:^4.1.14"
"@tauri-apps/cli": "npm:^2.8.4"
"@tauri-apps/plugin-global-shortcut": "npm:~2"
"@tauri-apps/plugin-process": "npm:~2"
"@tauri-apps/plugin-updater": "npm:~2"
"@tauri-apps/plugin-global-shortcut": "npm:^2.3.1"
"@tauri-apps/plugin-opener": "npm:~2"
"@tauri-apps/plugin-process": "npm:^2.3.1"
"@tauri-apps/plugin-updater": "npm:^2.10.1"
"@types/howler": "npm:^2"
"@vueuse/core": "npm:^13.9.0"
date-fns: "npm:^4.1.0"
eslint: "npm:^9.36.0"
eslint-plugin-format: "npm:^1.0.2"
hotkeys-js: "npm:^4.0.0"
howler: "npm:^2.2.4"
linkify-string: "npm:^4.3.2"
linkifyjs: "npm:^4.3.2"
lucide-vue-next: "npm:^0.562.0"
mediasoup-client: "npm:^3.18.6"
mediasoup-client: "npm:^3.19.0"
mitt: "npm:^3.0.1"
nuxt: "npm:^4.2.2"
postcss: "npm:^8.5.6"
@@ -4566,6 +4588,13 @@ __metadata:
languageName: node
linkType: hard
"date-fns@npm:^4.1.0":
version: 4.1.0
resolution: "date-fns@npm:4.1.0"
checksum: 10c0/b79ff32830e6b7faa009590af6ae0fb8c3fd9ffad46d930548fbb5acf473773b4712ae887e156ba91a7b3dc30591ce0f517d69fd83bd9c38650fdc03b4e0bac8
languageName: node
linkType: hard
"db0@npm:^0.3.4":
version: 0.3.4
resolution: "db0@npm:0.3.4"
@@ -6953,6 +6982,22 @@ __metadata:
languageName: node
linkType: hard
"linkify-string@npm:^4.3.2":
version: 4.3.2
resolution: "linkify-string@npm:4.3.2"
peerDependencies:
linkifyjs: ^4.0.0
checksum: 10c0/674e908b46aa6da3ee7e5c0749464d8de55f4d44933d7e9dea4d2f9bb5af0137d45142494288cd120335e21d8298c76ec4524ab6e67edede4613adb5f17f7dc6
languageName: node
linkType: hard
"linkifyjs@npm:^4.3.2":
version: 4.3.2
resolution: "linkifyjs@npm:4.3.2"
checksum: 10c0/1a85e6b368304a4417567fe5e38651681e3e82465590836942d1b4f3c834cc35532898eb1e2479f6337d9144b297d418eb708b6be8ed0b3dc3954a3588e07971
languageName: node
linkType: hard
"listhen@npm:^1.9.0":
version: 1.9.0
resolution: "listhen@npm:1.9.0"
@@ -7319,11 +7364,11 @@ __metadata:
languageName: node
linkType: hard
"mediasoup-client@npm:^3.18.6":
version: 3.18.6
resolution: "mediasoup-client@npm:3.18.6"
"mediasoup-client@npm:^3.19.0":
version: 3.19.0
resolution: "mediasoup-client@npm:3.19.0"
dependencies:
"@types/debug": "npm:^4.1.12"
"@types/debug": "npm:^4.1.13"
"@types/events-alias": "npm:@types/events@^3.0.3"
awaitqueue: "npm:^3.3.0"
debug: "npm:^4.4.3"
@@ -7332,7 +7377,7 @@ __metadata:
h264-profile-level-id: "npm:^2.3.2"
sdp-transform: "npm:^3.0.0"
supports-color: "npm:^10.2.2"
checksum: 10c0/f5baff9139afccf88de5db767c1139efa5cdd68f4871e2fa9d6ff94d2e71d2365dc40e9ba6e903cde5fbb51a2d82972e738da656be9f6fc7006640fdd82dd5da
checksum: 10c0/9fde5ec5daec91d43a88796f49e2b1b7a018c8100a3f99786966678a0e0b5328e88f6e6af36d50f9eed93889b84f23a164865c7177c0767ee805c7a8c7a51eb2
languageName: node
linkType: hard

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(python -c \"import sys,json; p=json.load\\(sys.stdin\\); deps={**p.get\\('dependencies',{}\\), **p.get\\('devDependencies',{}\\)}; [print\\(k,v\\) for k in sorted\\(deps\\) if any\\(x in k for x in ['mediasoup','socket','vueuse','vue']\\)]\")"
]
}
}

3
new-client/.env Normal file
View File

@@ -0,0 +1,3 @@
; API_BASE_URL=/api
; API_BASE_URL=https://api.koptilnya.xyz/chad
API_BASE_URL=http://127.0.0.1:4000

24
new-client/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
new-client/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

View File

@@ -0,0 +1,54 @@
{
// Use ESLint's --fix:
"code_actions_on_format": {
"source.fixAll.eslint": true,
},
"formatter": [],
// Enable eslint for all supported languages
// Defaults only include https://github.com/search?q=repo%3Azed-industries%2Fzed%20eslint_languages&type=code
"languages": {
"HTML": {
"language_servers": ["...", "eslint"],
},
"Markdown": {
"language_servers": ["...", "eslint"],
},
"Markdown-Inline": {
"language_servers": ["...", "eslint"],
},
"JSON": {
"language_servers": ["...", "eslint"],
},
"JSONC": {
"language_servers": ["...", "eslint"],
},
"YAML": {
"language_servers": ["...", "eslint"],
},
"CSS": {
"language_servers": ["...", "eslint"],
},
// Add other languages as needed
},
"lsp": {
"eslint": {
"settings": {
"workingDirectories": ["./"],
// Silent the stylistic rules in your IDE, but still auto fix them
"rulesCustomizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true },
],
},
},
},
}

1
new-client/AGENTS.md Normal file
View File

@@ -0,0 +1 @@
use context7

View File

@@ -0,0 +1,17 @@
import antfu from '@antfu/eslint-config'
export default antfu({
formatters: {
css: true,
},
overrides: {
typescript: {
'no-console': 'off',
},
vue: {
'vue/block-order': ['error', {
order: ['template', 'script', 'style'],
}],
},
},
})

24
new-client/index.html Normal file
View File

@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<title>Chad</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&family=Unbounded:wght@200..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/src/shared/styles/reset.css" />
<link rel="stylesheet" href="/src/shared/styles/sanitize.css" />
<link rel="stylesheet" href="/src/shared/styles/main.scss" />
</head>
<body>
<div data-mount-point id="app"></div>
<div data-mount-point id="updater"></div>
<div data-mount-point id="preloader"></div>
<script type="module" src="/src/app/entry.ts"></script>
</body>
</html>

50
new-client/package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "new-client",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"typegen": "npx swagger-typescript-api generate --path http://localhost:4000/reference/openapi.yaml --output ./src/shared/api --name generated-chad-api.ts"
},
"dependencies": {
"@lucide/vue": "^1.14.0",
"@tanstack/query-persist-client-core": "^5.100.10",
"@tanstack/vue-query": "^5.100.10",
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
"@tauri-apps/plugin-opener": "~2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"@vueuse/core": "^14.3.0",
"@zag-js/avatar": "^1.40.0",
"@zag-js/collapsible": "^1.40.0",
"@zag-js/dialog": "^1.41.1",
"@zag-js/file-upload": "^1.41.0",
"@zag-js/file-utils": "^1.40.0",
"@zag-js/password-input": "^1.40.0",
"@zag-js/toggle": "^1.40.0",
"@zag-js/vue": "^1.40.0",
"date-fns": "^4.1.0",
"mediasoup-client": "^3.20.0",
"mitt": "^3.0.1",
"primevue": "^4.5.5",
"socket.io-client": "^4.8.3",
"vue": "^3.5.32",
"vue-router": "^5.0.6"
},
"devDependencies": {
"@antfu/eslint-config": "^8.2.0",
"@tauri-apps/cli": "^2.8.4",
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.6",
"@vue/tsconfig": "^0.9.1",
"eslint": "^10.3.0",
"eslint-plugin-format": "^2.0.1",
"sass-embedded": "^1.99.0",
"typescript": "~6.0.2",
"vite": "^8.0.10",
"vue-tsc": "^3.2.7"
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

4
new-client/src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/gen/schemas

6228
new-client/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
[package]
name = "app"
version = "0.1.0"
description = "WW додепчик"
authors = ["KPTL"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.4.1", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.8.5", features = [] }
tauri-plugin-log = "2"
tauri-plugin-process = "2"
windows = { version = "0.52", features = ["Win32_UI_Shell"] }
tauri-plugin-opener = "2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-global-shortcut = "2"
tauri-plugin-single-instance = "2"
tauri-plugin-updater = "2"

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Request camera access for WebRTC</string>
<key>NSMicrophoneUsageDescription</key>
<string>Request microphone access for WebRTC</string>
</dict>
</plist>

View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}

View File

@@ -0,0 +1,20 @@
{
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"windows": [
"main"
],
"permissions": [
"updater:default",
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister",
"global-shortcut:allow-unregister-all",
"opener:allow-default-urls",
"opener:allow-open-url"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M232.899 20C237.318 20.0002 240.899 23.5819 240.899 28V227.644C240.899 234.599 232.635 238.241 227.501 233.548L202.03 210.261C200.556 208.913 198.63 208.165 196.632 208.165H23.1006C18.6825 208.165 15.1006 204.583 15.1006 200.165V28C15.1006 23.5819 18.6825 20.0002 23.1006 20H232.899ZM129.758 58.3818C120.029 58.3818 111.244 60.5556 103.403 64.9033C95.5633 69.251 89.3442 75.6118 84.7471 83.9863C80.1499 92.3611 77.8516 102.572 77.8516 114.617C77.8516 126.627 80.1147 136.82 84.6406 145.194C89.1665 153.569 95.3323 159.948 103.137 164.331C110.977 168.679 119.851 170.853 129.758 170.853C137.277 170.852 143.941 169.712 149.75 167.432C155.594 165.151 160.548 162.086 164.61 158.237C168.673 154.353 171.863 150.059 174.18 145.354C175.83 142.054 177.015 138.719 177.735 135.349C178.227 133.051 176.378 131.016 174.028 131.002L155.054 130.888C153.103 130.876 151.468 132.29 150.861 134.145C150.415 135.508 149.849 136.786 149.162 137.978C147.986 140.044 146.471 141.808 144.618 143.27C142.801 144.695 140.68 145.782 138.257 146.53C135.869 147.279 133.214 147.653 130.292 147.653C125.089 147.653 120.581 146.424 116.768 143.965C112.99 141.47 110.05 137.782 107.947 132.899C105.88 127.981 104.847 121.887 104.847 114.617C104.847 107.632 105.862 101.681 107.894 96.7627C109.961 91.8448 112.901 88.0849 116.714 85.4834C120.563 82.8819 125.142 81.5811 130.452 81.5811C133.446 81.5811 136.172 82.009 138.631 82.8643C141.125 83.6839 143.282 84.8773 145.1 86.4453C146.917 88.0134 148.378 89.9028 149.482 92.1123C150.104 93.3561 150.607 94.6845 150.992 96.0977C151.52 98.0371 153.178 99.542 155.188 99.542H173.992C176.355 99.5418 178.218 97.5003 177.806 95.1738C176.953 90.3679 175.494 85.9973 173.431 82.0625C170.758 76.9664 167.283 72.6721 163.007 69.1797C158.73 65.6516 153.777 62.9786 148.146 61.1611C142.516 59.308 136.386 58.3819 129.758 58.3818Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Some files were not shown because too many files have changed in this diff Show More