Compare commits
14 Commits
v0.3.0-rc.
...
refactor
| Author | SHA1 | Date | |
|---|---|---|---|
| ca8728c90c | |||
| 0dd9efb9fb | |||
| 3c885edc46 | |||
| 12ae0ae839 | |||
| d06892e990 | |||
| 5d45c9674f | |||
| 1ca73e786c | |||
| e4ed785911 | |||
| abf4d41c23 | |||
| edef0a70d2 | |||
| 6a2111092b | |||
| 0b148c6a7d | |||
| f845777bac | |||
| ad477ee813 |
@@ -17,12 +17,6 @@ jobs:
|
||||
run: |
|
||||
ssh-keyscan git.koptilnya.xyz >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Set up secret file
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
run: |
|
||||
echo "${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}" | sed 's/./& /g'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
54
client/.zed/settings.json
Normal 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 },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import chadApi from '#shared/chad-api'
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
|
||||
export interface ChatClientMessage {
|
||||
text: string
|
||||
replyTo?: {
|
||||
messageId: string
|
||||
}
|
||||
// replyTo?: {
|
||||
// messageId: string
|
||||
// }
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string
|
||||
sender: string
|
||||
senderId: string
|
||||
text: string
|
||||
createdAt: string
|
||||
replyTo?: {
|
||||
messageId: string
|
||||
sender: string
|
||||
text: string
|
||||
}
|
||||
updatedAt: string
|
||||
attachments: string[]
|
||||
// replyTo?: {
|
||||
// messageId: string
|
||||
// sender: string
|
||||
// text: string
|
||||
// }
|
||||
}
|
||||
|
||||
export const useChat = createGlobalState(() => {
|
||||
@@ -41,16 +44,16 @@ export const useChat = createGlobalState(() => {
|
||||
})
|
||||
}, { immediate: true, flush: 'sync' })
|
||||
|
||||
function sendMessage(message: ChatClientMessage) {
|
||||
if (!signaling.connected.value)
|
||||
return
|
||||
|
||||
async function sendMessage(message: ChatClientMessage) {
|
||||
message.text = message.text.trim()
|
||||
|
||||
if (!message.text.length)
|
||||
return
|
||||
|
||||
signaling.socket.value!.emit('chat:message', message)
|
||||
await chadApi<ChatMessage>('/chat/send', {
|
||||
method: 'POST',
|
||||
body: message,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -13,10 +13,8 @@ export const useDevices = createGlobalState(() => {
|
||||
return navigator.mediaDevices.getDisplayMedia({
|
||||
audio: false,
|
||||
video: {
|
||||
width: { max: 2560 },
|
||||
height: { max: 1080 },
|
||||
displaySurface: 'monitor',
|
||||
frameRate: { ideal: fps, max: fps },
|
||||
frameRate: { max: fps },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,34 +326,33 @@ 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', () => {
|
||||
disableProducer(producers.value[producer.id]!)
|
||||
})
|
||||
|
||||
return producer
|
||||
}
|
||||
|
||||
async function disableProducer(producer: Producer) {
|
||||
@@ -354,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,
|
||||
})
|
||||
}
|
||||
@@ -388,10 +396,8 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
streamId: 'mic-video',
|
||||
codecOptions: {
|
||||
opusStereo: true,
|
||||
opusMaxPlaybackRate: 48000,
|
||||
opusMaxAverageBitrate: 192000,
|
||||
opusDtx: false,
|
||||
opusFec: false,
|
||||
opusDtx: true, // Меньше пакетов летит когда тишина
|
||||
opusFec: false, // Фиксит пакет лос
|
||||
},
|
||||
appData: {
|
||||
source: 'mic-video',
|
||||
@@ -453,46 +459,17 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
|
||||
const track = stream.getVideoTracks()[0]
|
||||
|
||||
console.log('settings', track.getSettings())
|
||||
|
||||
if (!track)
|
||||
return
|
||||
|
||||
await createProducer({
|
||||
track,
|
||||
streamId: 'share',
|
||||
codec: device.value.rtpCapabilities.codecs?.find(
|
||||
c => c.mimeType.toLowerCase() === 'video/h264',
|
||||
codec: device.value.sendRtpCapabilities.codecs?.find(
|
||||
c => c.mimeType.toLowerCase() === 'video/AV1',
|
||||
),
|
||||
// codec: device.value.rtpCapabilities.codecs?.find(
|
||||
// c => c.mimeType.toLowerCase() === 'video/av1',
|
||||
// ),
|
||||
// codec: device.value.rtpCapabilities.codecs?.find(
|
||||
// c => c.mimeType.toLowerCase() === 'video/vp9',
|
||||
// ),
|
||||
// codec: {
|
||||
// kind: 'video',
|
||||
// mimeType: 'video/AV1',
|
||||
// clockRate: 90000,
|
||||
// parameters: {
|
||||
// 'level-idx': 13, // Level 4.1 — 1080p60
|
||||
// 'profile': 0, // Main Profile
|
||||
// 'tier': 0, // Main tier (0) vs High tier (1)
|
||||
// 'x-google-start-bitrate': 8000,
|
||||
// },
|
||||
// },
|
||||
encodings: [
|
||||
{
|
||||
maxBitrate: 120_000_000,
|
||||
maxFramerate: 60,
|
||||
scalabilityMode: 'L1T1',
|
||||
networkPriority: 'high',
|
||||
},
|
||||
],
|
||||
codecOptions: {
|
||||
// videoGoogleStartBitrate: 8000,
|
||||
videoGoogleMaxBitrate: 120000,
|
||||
videoGoogleMinBitrate: 2000,
|
||||
videoGoogleStartBitrate: 1000,
|
||||
},
|
||||
zeroRtpOnPause: true,
|
||||
appData: {
|
||||
@@ -511,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,
|
||||
})
|
||||
}
|
||||
@@ -527,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,
|
||||
})
|
||||
}
|
||||
@@ -559,7 +536,7 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
speakingClients,
|
||||
sendTransport,
|
||||
recvTransport,
|
||||
rtpCapabilities,
|
||||
rtpCapabilities: routerRtpCapabilities,
|
||||
device,
|
||||
micProducer,
|
||||
videoProducer,
|
||||
@@ -569,5 +546,7 @@ export const useMediasoup = createSharedComposable(() => {
|
||||
enableVideo,
|
||||
enableShare,
|
||||
disableProducer,
|
||||
consumersArray,
|
||||
producersArray,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -42,7 +42,7 @@ export const usePreferences = createGlobalState(() => {
|
||||
async ([toggleInputHotkey, toggleOutputHotkey]) => {
|
||||
try {
|
||||
await chadApi(
|
||||
'/preferences',
|
||||
'/user/preferences',
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
|
||||
@@ -52,11 +52,39 @@
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
@@ -65,6 +93,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import chadApi from '#shared/chad-api'
|
||||
import {
|
||||
Camera,
|
||||
CameraOff,
|
||||
@@ -80,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,
|
||||
@@ -93,7 +129,8 @@ const {
|
||||
toggleVideo,
|
||||
toggleShare,
|
||||
} = useApp()
|
||||
const { connect, connected } = useSignaling()
|
||||
const { connect, connected, socket } = useSignaling()
|
||||
const { consumersArray, producersArray } = useMediasoup()
|
||||
|
||||
interface Tab {
|
||||
id: string
|
||||
@@ -150,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>
|
||||
|
||||
@@ -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' })
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,25 +10,35 @@
|
||||
:key="message.id"
|
||||
class="w-fit max-w-[60%]"
|
||||
:class="{
|
||||
'ml-auto': message.sender === me?.username,
|
||||
'ml-auto': message.senderId === me?.userId,
|
||||
}"
|
||||
>
|
||||
<p
|
||||
v-if="message.sender !== me?.username"
|
||||
v-if="message.senderId !== me?.userId"
|
||||
class="text-sm text-muted-color mb-1"
|
||||
>
|
||||
{{ message.sender }}
|
||||
{{ message.senderId }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="px-3 py-2 rounded-lg"
|
||||
:class="{
|
||||
'bg-surface-800': message.sender !== me?.username,
|
||||
'bg-surface-700': message.sender === me?.username,
|
||||
'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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "Chad",
|
||||
"version": "0.3.0-rc.5",
|
||||
"version": "0.3.0-rc.4",
|
||||
"identifier": "xyz.koptilnya.chad",
|
||||
"build": {
|
||||
"frontendDist": "../.output/public",
|
||||
|
||||
@@ -3004,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:
|
||||
@@ -3013,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"
|
||||
@@ -4082,7 +4091,7 @@ __metadata:
|
||||
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"
|
||||
@@ -7355,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"
|
||||
@@ -7368,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
|
||||
|
||||
|
||||
7
new-client/.claude/settings.local.json
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
54
new-client/.zed/settings.json
Normal 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
@@ -0,0 +1 @@
|
||||
use context7
|
||||
17
new-client/eslint.config.mjs
Normal 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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
1
new-client/public/favicon.svg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
24
new-client/public/icons.svg
Normal 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 |
BIN
new-client/public/sad-pepe.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
4
new-client/src-tauri/.gitignore
vendored
Normal 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
33
new-client/src-tauri/Cargo.toml
Normal 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"
|
||||
10
new-client/src-tauri/Info.plist
Normal 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>
|
||||
3
new-client/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
11
new-client/src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
20
new-client/src-tauri/capabilities/desktop.json
Normal 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"
|
||||
]
|
||||
}
|
||||
BIN
new-client/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
new-client/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
new-client/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 403 B |
BIN
new-client/src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 758 B |
BIN
new-client/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
new-client/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
new-client/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
new-client/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
new-client/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 391 B |
BIN
new-client/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
new-client/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 540 B |
BIN
new-client/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
new-client/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 1009 B |
BIN
new-client/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 561 B |
BIN
new-client/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 570 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 570 B |
BIN
new-client/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 584 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 584 B |
BIN
new-client/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
BIN
new-client/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
BIN
new-client/src-tauri/icons/icon.icns
Normal file
BIN
new-client/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
new-client/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
new-client/src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 332 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 805 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 461 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
new-client/src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 997 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 997 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
new-client/src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
new-client/src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
new-client/src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
new-client/src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 979 B |
BIN
new-client/src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
new-client/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
3
new-client/src-tauri/icons/original.svg
Normal 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 |
21
new-client/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
#[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(|_, _, _| {}))
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
21
new-client/src-tauri/src/main.rs
Normal file
@@ -0,0 +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();
|
||||
}
|
||||
1
new-client/src-tauri/target/.rustc_info.json
Normal file
@@ -0,0 +1 @@
|
||||
{"rustc_fingerprint":1074701663444030447,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.95.0 (59807616e 2026-04-14)\nbinary: rustc\ncommit-hash: 59807616e1fa2540724bfbac14d7976d7e4a3860\ncommit-date: 2026-04-14\nhost: x86_64-pc-windows-msvc\nrelease: 1.95.0\nLLVM version: 22.1.2\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\opti\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":8891013984288978370,"features":"[]","declared_features":"[]","target":12687594469746301899,"profile":8731458305071235362,"path":4942398508502643691,"deps":[[1322478694103194923,"app_lib",false,8070585750093435046],[1322478694103194923,"build_script_build",false,16691984141835454783],[2700615264719386388,"tauri_plugin_opener",false,7350390124577390804],[4736393929136623228,"tauri_plugin_global_shortcut",false,15267933955632970549],[5673630358394410545,"tauri_plugin_updater",false,14019643380268120922],[8695090098768911186,"windows",false,17298248671909505349],[10630857666389190470,"log",false,5016507518680283320],[12653526805862679340,"tauri_plugin_log",false,15855325777720298125],[13548984313718623784,"serde",false,7298479236282139191],[13795362694956882968,"serde_json",false,4974751760879812175],[13905323280135927163,"tauri_plugin_single_instance",false,10523578089830730283],[14261240476176424521,"tauri",false,8738078672178348289],[18393575805749125025,"tauri_plugin_process",false,17190748950968290852]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\app-4e7dc2d6835c5d74\\dep-bin-app","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
BIN
new-client/src-tauri/target/debug/app_lib.dll.lib
Normal file
60
new-client/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "Chad",
|
||||
"version": "0.3.0-rc.3",
|
||||
"identifier": "xyz.koptilnya.chad",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"beforeBuildCommand": "yarn generate"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"maximizable": true,
|
||||
"label": "main",
|
||||
"title": "Chad",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"minWidth": 800,
|
||||
"minHeight": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"center": true,
|
||||
"theme": "Dark",
|
||||
"additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --autoplay-policy=no-user-gesture-required --lang=en",
|
||||
"incognito": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null,
|
||||
"capabilities": []
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"createUpdaterArtifacts": true,
|
||||
"active": true,
|
||||
"targets": ["nsis"],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"installerIcon": "icons/icon.ico"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3MzkxMzM3RkQ3NTg4QUQKUldTdGlIWDlOeE01NStIak9VbmZTTm9HY2NyNUQrVXB5ZEdIN1BkK2lhYW9zWkNCQnZQSjRmelIK",
|
||||
"endpoints": [
|
||||
"https://git.koptilnya.xyz/opti1337/chad/releases/download/latest/updater.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
23
new-client/src/app/App.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<Component :is="layoutComponent">
|
||||
<RouterView />
|
||||
</Component>
|
||||
|
||||
<ChadDialogContainer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ChadDialogContainer from '@shared/components/ui/DialogContainer.vue'
|
||||
import DefaultLayout from '@shared/layouts/Default.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const layoutComponent = computed(() => {
|
||||
return route.meta.layout ?? DefaultLayout
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
42
new-client/src/app/Error.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="error-app">
|
||||
<img class="error-app__image" src="/sad-pepe.png" alt="Oops!" draggable="false">
|
||||
|
||||
<p class="error-app__message">
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
message: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.error-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
&__image {
|
||||
width: 120px;
|
||||
margin-bottom: var(--space-6);
|
||||
user-select: none;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&__message {
|
||||
@include font-body-bold;
|
||||
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background-color: var(--paper);
|
||||
outline: var(--border-w) solid var(--ink);
|
||||
outline-offset: calc(var(--border-w) * -1);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
new-client/src/app/Preloader.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="preloader-app">
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.preloader-app {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
> p {
|
||||
@include font-body-bold;
|
||||
|
||||
margin: auto;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background-color: var(--paper);
|
||||
outline: var(--border-w) solid var(--ink);
|
||||
outline-offset: calc(var(--border-w) * -1);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
34
new-client/src/app/Updater.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="updater-app">
|
||||
<p v-if="checking">
|
||||
Checking updates...
|
||||
</p>
|
||||
<p v-else-if="!!lastUpdate">
|
||||
Update available: {{ lastUpdate.version }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUpdater } from '@shared/composables/use-updater'
|
||||
|
||||
const { checking, lastUpdate } = useUpdater()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.updater-app {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
> p {
|
||||
@include font-body-bold;
|
||||
|
||||
margin: auto;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background-color: var(--paper);
|
||||
outline: var(--border-w) solid var(--ink);
|
||||
outline-offset: calc(var(--border-w) * -1);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
}
|
||||
</style>
|
||||