This commit is contained in:
Binary file not shown.
@@ -7,7 +7,5 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
console.group('Build Info')
|
const route = useRoute()
|
||||||
console.log(`COMMIT_SHA: ${__COMMIT_SHA__}`)
|
|
||||||
console.groupEnd()
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,8 +6,25 @@ body {
|
|||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-image: radial-gradient(var(--p-surface-700), var(--p-surface-800));
|
||||||
|
background-size: 200% 200%;
|
||||||
|
background-position: left -100% top -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#__nuxt {
|
#__nuxt {
|
||||||
|
--p-scrollpanel-bar-size: 5px;
|
||||||
|
--p-scrollpanel-bar-background: var(--p-surface-950);
|
||||||
|
--p-divider-horizontal-margin: 2rem 0 1rem;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-divider {
|
||||||
|
&:first-child {
|
||||||
|
--p-divider-horizontal-margin: 1rem 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-scrollpanel-bar-y {
|
||||||
|
translate: -0.25rem;
|
||||||
|
}
|
||||||
2
client/app/components.d.ts
vendored
2
client/app/components.d.ts
vendored
@@ -13,7 +13,9 @@ declare module 'vue' {
|
|||||||
PrimeButton: typeof import('primevue/button')['default']
|
PrimeButton: typeof import('primevue/button')['default']
|
||||||
PrimeButtonGroup: typeof import('primevue/buttongroup')['default']
|
PrimeButtonGroup: typeof import('primevue/buttongroup')['default']
|
||||||
PrimeCard: typeof import('primevue/card')['default']
|
PrimeCard: typeof import('primevue/card')['default']
|
||||||
|
PrimeChip: typeof import('primevue/chip')['default']
|
||||||
PrimeDivider: typeof import('primevue/divider')['default']
|
PrimeDivider: typeof import('primevue/divider')['default']
|
||||||
|
PrimeFieldset: typeof import('primevue/fieldset')['default']
|
||||||
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
||||||
PrimeInputText: typeof import('primevue/inputtext')['default']
|
PrimeInputText: typeof import('primevue/inputtext')['default']
|
||||||
PrimeMenu: typeof import('primevue/menu')['default']
|
PrimeMenu: typeof import('primevue/menu')['default']
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between gap-2 border-b-2 border-surface-800 px-3 py-3"
|
|
||||||
:class="{
|
|
||||||
'bg-surface-950': !secondary,
|
|
||||||
'bg-surface-900': secondary,
|
|
||||||
}"
|
|
||||||
style="height: 75px;"
|
|
||||||
>
|
|
||||||
<slot name="left">
|
|
||||||
<h1 v-if="!!title">
|
|
||||||
{{ title }}
|
|
||||||
</h1>
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<slot name="right" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
title?: string
|
|
||||||
secondary?: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
defineSlots<{
|
|
||||||
left: () => unknown
|
|
||||||
right: () => unknown
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ChadClient } from '#shared/types'
|
import type { ChadClient } from '#shared/types'
|
||||||
import type { MenuItem } from 'primevue/menuitem'
|
import type { MenuItem } from 'primevue/menuitem'
|
||||||
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
import { User } from 'lucide-vue-next'
|
import { User } from 'lucide-vue-next'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -52,9 +53,9 @@ const { outputMuted } = useApp()
|
|||||||
const { getClientConsumers, micProducer } = useMediasoup()
|
const { getClientConsumers, micProducer } = useMediasoup()
|
||||||
const { me } = useClients()
|
const { me } = useClients()
|
||||||
|
|
||||||
const menuRef = useTemplateRef<HTMLAudioElement>('menu')
|
const volume = useLocalStorage<number>(computed(() => `CLIENT_VOLUME_${props.client.userId}`), 100, { writeDefaults: false })
|
||||||
|
|
||||||
const volume = ref(100)
|
const menuRef = useTemplateRef<HTMLAudioElement>('menu')
|
||||||
|
|
||||||
const menuItems: MenuItem[] = [
|
const menuItems: MenuItem[] = [
|
||||||
{
|
{
|
||||||
@@ -101,7 +102,7 @@ watch(volume, (volume) => {
|
|||||||
// return
|
// return
|
||||||
|
|
||||||
setGain(volume * 0.01)
|
setGain(volume * 0.01)
|
||||||
})
|
}, { immediate: true })
|
||||||
|
|
||||||
// watch(outputMuted, (outputMuted) => {
|
// watch(outputMuted, (outputMuted) => {
|
||||||
// setGain(outputMuted ? 0 : (volume.value * 0.01))
|
// setGain(outputMuted ? 0 : (volume.value * 0.01))
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export const useApp = createGlobalState(() => {
|
|||||||
const mediasoup = useMediasoup()
|
const mediasoup = useMediasoup()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const ready = ref(false)
|
||||||
|
|
||||||
const inputMuted = ref(false)
|
const inputMuted = ref(false)
|
||||||
const outputMuted = ref(false)
|
const outputMuted = ref(false)
|
||||||
|
|
||||||
@@ -15,8 +17,38 @@ export const useApp = createGlobalState(() => {
|
|||||||
|
|
||||||
const isTauri = computed(() => '__TAURI_INTERNALS__' in window)
|
const isTauri = computed(() => '__TAURI_INTERNALS__' in window)
|
||||||
|
|
||||||
|
const commitSha = __COMMIT_SHA__
|
||||||
|
|
||||||
const version = computedAsync(() => isTauri.value ? getVersion() : 'web', '-')
|
const version = computedAsync(() => isTauri.value ? getVersion() : 'web', '-')
|
||||||
|
|
||||||
|
watch(inputMuted, async (inputMuted) => {
|
||||||
|
if (inputMuted) {
|
||||||
|
await mediasoup.pauseProducer('microphone')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (outputMuted.value) {
|
||||||
|
outputMuted.value = false
|
||||||
|
}
|
||||||
|
await mediasoup.resumeProducer('microphone')
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastText = inputMuted ? 'Microphone muted' : 'Microphone activated'
|
||||||
|
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(outputMuted, (outputMuted) => {
|
||||||
|
if (outputMuted) {
|
||||||
|
previousInputMuted.value = inputMuted.value
|
||||||
|
muteInput()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inputMuted.value = previousInputMuted.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastText = outputMuted ? 'Sound muted' : 'Sound resumed'
|
||||||
|
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
|
||||||
|
})
|
||||||
|
|
||||||
function muteInput() {
|
function muteInput() {
|
||||||
inputMuted.value = true
|
inputMuted.value = true
|
||||||
}
|
}
|
||||||
@@ -47,35 +79,8 @@ export const useApp = createGlobalState(() => {
|
|||||||
muteOutput()
|
muteOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(inputMuted, async (inputMuted) => {
|
|
||||||
if (inputMuted) {
|
|
||||||
await mediasoup.pauseProducer('microphone')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (outputMuted.value) {
|
|
||||||
outputMuted.value = false
|
|
||||||
}
|
|
||||||
await mediasoup.resumeProducer('microphone')
|
|
||||||
}
|
|
||||||
|
|
||||||
const toastText = inputMuted ? 'Microphone muted' : 'Microphone activated'
|
|
||||||
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(outputMuted, (outputMuted) => {
|
|
||||||
if (outputMuted) {
|
|
||||||
previousInputMuted.value = inputMuted.value
|
|
||||||
muteInput()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
inputMuted.value = previousInputMuted.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const toastText = outputMuted ? 'Sound muted' : 'Sound resumed'
|
|
||||||
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
ready,
|
||||||
clients,
|
clients,
|
||||||
inputMuted,
|
inputMuted,
|
||||||
muteInput,
|
muteInput,
|
||||||
@@ -87,5 +92,6 @@ export const useApp = createGlobalState(() => {
|
|||||||
toggleOutput,
|
toggleOutput,
|
||||||
version,
|
version,
|
||||||
isTauri,
|
isTauri,
|
||||||
|
commitSha,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { createGlobalState, useDevicesList, useLocalStorage } from '@vueuse/core'
|
import chadApi from '#shared/chad-api'
|
||||||
|
import { createGlobalState, useDevicesList, useLocalStorage, watchDebounced } from '@vueuse/core'
|
||||||
|
|
||||||
|
export interface SyncedPreferences {
|
||||||
|
toggleInputHotkey: string
|
||||||
|
toggleOutputHotkey: string
|
||||||
|
volumes: Record<Client['id'], number>
|
||||||
|
}
|
||||||
|
|
||||||
export const usePreferences = createGlobalState(() => {
|
export const usePreferences = createGlobalState(() => {
|
||||||
|
const synced = ref(false)
|
||||||
|
|
||||||
const inputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('INPUT_DEVICE_ID', 'default')
|
const inputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('INPUT_DEVICE_ID', 'default')
|
||||||
const outputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('OUTPUT_DEVICE_ID', 'default')
|
const outputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('OUTPUT_DEVICE_ID', 'default')
|
||||||
|
|
||||||
@@ -8,6 +17,9 @@ export const usePreferences = createGlobalState(() => {
|
|||||||
const noiseSuppression = useLocalStorage('NOISE_SUPPRESSION', true)
|
const noiseSuppression = useLocalStorage('NOISE_SUPPRESSION', true)
|
||||||
const echoCancellation = useLocalStorage('ECHO_CANCELLATION', true)
|
const echoCancellation = useLocalStorage('ECHO_CANCELLATION', true)
|
||||||
|
|
||||||
|
const toggleInputHotkey = ref<SyncedPreferences['toggleInputHotkey']>('')
|
||||||
|
const toggleOutputHotkey = ref<SyncedPreferences['toggleOutputHotkey']>('')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ensurePermissions,
|
ensurePermissions,
|
||||||
permissionGranted,
|
permissionGranted,
|
||||||
@@ -24,12 +36,35 @@ export const usePreferences = createGlobalState(() => {
|
|||||||
return audioOutputs.value.some(device => device.deviceId === outputDeviceId.value)
|
return audioOutputs.value.some(device => device.deviceId === outputDeviceId.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watchDebounced(
|
||||||
|
[toggleInputHotkey, toggleOutputHotkey],
|
||||||
|
async ([toggleInputHotkey, toggleOutputHotkey]) => {
|
||||||
|
try {
|
||||||
|
await chadApi(
|
||||||
|
'/preferences',
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
body: {
|
||||||
|
toggleInputHotkey,
|
||||||
|
toggleOutputHotkey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
},
|
||||||
|
{ debounce: 1000 },
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
synced,
|
||||||
inputDeviceId,
|
inputDeviceId,
|
||||||
outputDeviceId,
|
outputDeviceId,
|
||||||
autoGainControl,
|
autoGainControl,
|
||||||
noiseSuppression,
|
noiseSuppression,
|
||||||
echoCancellation,
|
echoCancellation,
|
||||||
|
toggleInputHotkey,
|
||||||
|
toggleOutputHotkey,
|
||||||
inputDeviceExist,
|
inputDeviceExist,
|
||||||
outputDeviceExist,
|
outputDeviceExist,
|
||||||
videoInputs: computed(() => JSON.parse(JSON.stringify(videoInputs.value))),
|
videoInputs: computed(() => JSON.parse(JSON.stringify(videoInputs.value))),
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import chadApi from '#shared/chad-api'
|
|||||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||||
const { me, setMe } = useAuth()
|
const { me, setMe } = useAuth()
|
||||||
|
|
||||||
if (!me.value && !from?.name) {
|
if (!me.value) {
|
||||||
try {
|
try {
|
||||||
setMe(await chadApi('/me'))
|
setMe(await chadApi('/me', { method: 'GET' }))
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
if (to.meta.auth !== 'guest') {
|
if (to.meta.auth !== 'guest') {
|
||||||
|
|||||||
26
client/app/middleware/02.user-preferences.global.ts
Normal file
26
client/app/middleware/02.user-preferences.global.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { SyncedPreferences } from '~/composables/use-preferences'
|
||||||
|
import chadApi from '#shared/chad-api'
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware(async () => {
|
||||||
|
const { me } = useAuth()
|
||||||
|
|
||||||
|
if (!me.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
const { synced, toggleInputHotkey, toggleOutputHotkey } = usePreferences()
|
||||||
|
|
||||||
|
if (synced.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const preferences = await chadApi<SyncedPreferences>('/preferences', { method: 'GET' })
|
||||||
|
|
||||||
|
if (!preferences)
|
||||||
|
return
|
||||||
|
|
||||||
|
toggleInputHotkey.value = preferences.toggleInputHotkey ?? toggleInputHotkey.value
|
||||||
|
toggleOutputHotkey.value = preferences.toggleOutputHotkey ?? toggleOutputHotkey.value
|
||||||
|
synced.value = true
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
})
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<PrimeDivider align="left">
|
||||||
|
Audio
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
<PrimeFloatLabel variant="on">
|
<PrimeFloatLabel variant="on">
|
||||||
<PrimeSelect
|
<PrimeSelect
|
||||||
v-model="inputDeviceId"
|
v-model="inputDeviceId"
|
||||||
:options="audioInputs"
|
:options="audioInputs"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
option-value="deviceId"
|
option-value="deviceId"
|
||||||
fluid
|
|
||||||
input-id="inputDevice"
|
input-id="inputDevice"
|
||||||
|
fluid
|
||||||
:invalid="!inputDeviceExist"
|
:invalid="!inputDeviceExist"
|
||||||
/>
|
/>
|
||||||
<label for="inputDevice">Input device</label>
|
<label for="inputDevice">Input device</label>
|
||||||
@@ -43,10 +47,34 @@
|
|||||||
<!-- <label for="outputDevice">Output device</label> -->
|
<!-- <label for="outputDevice">Output device</label> -->
|
||||||
<!-- </PrimeFloatLabel> -->
|
<!-- </PrimeFloatLabel> -->
|
||||||
|
|
||||||
<template v-if="isTauri">
|
<PrimeDivider align="left">
|
||||||
<PrimeDivider />
|
Hotkeys
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
|
<PrimeFloatLabel variant="on">
|
||||||
|
<PrimeInputText id="microphoneToggle" :model-value="toggleInputHotkey" fluid @keydown="setupToggleInputHotkey" />
|
||||||
|
<label for="microphoneToggle">Toggle microphone</label>
|
||||||
|
</PrimeFloatLabel>
|
||||||
|
|
||||||
|
<PrimeFloatLabel variant="on" class="mt-3">
|
||||||
|
<PrimeInputText id="soundToggle" :model-value="toggleOutputHotkey" fluid @keydown="setupToggleOutputHotkey" />
|
||||||
|
<label for="soundToggle">Toggle sound</label>
|
||||||
|
</PrimeFloatLabel>
|
||||||
|
|
||||||
|
<PrimeDivider align="left">
|
||||||
|
About
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
|
<p v-if="version" class="text-muted-color text-sm">
|
||||||
|
VERSION: {{ version }}
|
||||||
|
</p>
|
||||||
|
<p class="text-muted-color text-sm mt-2">
|
||||||
|
COMMIT_SHA: {{ commitSha }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<PrimeButton
|
<PrimeButton
|
||||||
|
v-if="isTauri"
|
||||||
|
class="mt-3"
|
||||||
size="small"
|
size="small"
|
||||||
label="Check for Updates"
|
label="Check for Updates"
|
||||||
fluid
|
fluid
|
||||||
@@ -54,7 +82,6 @@
|
|||||||
:loading="checking"
|
:loading="checking"
|
||||||
@click="onCheckForUpdates"
|
@click="onCheckForUpdates"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PrimeToast position="bottom-center" group="updater">
|
<PrimeToast position="bottom-center" group="updater">
|
||||||
@@ -73,10 +100,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: 'Preferences',
|
name: 'Preferences',
|
||||||
})
|
})
|
||||||
const { isTauri } = useApp()
|
const { isTauri, version, commitSha } = useApp()
|
||||||
const { checking, checkForUpdates } = useUpdater()
|
const { checking, checkForUpdates } = useUpdater()
|
||||||
const {
|
const {
|
||||||
inputDeviceId,
|
inputDeviceId,
|
||||||
@@ -84,6 +113,8 @@ const {
|
|||||||
autoGainControl,
|
autoGainControl,
|
||||||
noiseSuppression,
|
noiseSuppression,
|
||||||
echoCancellation,
|
echoCancellation,
|
||||||
|
toggleInputHotkey,
|
||||||
|
toggleOutputHotkey,
|
||||||
inputDeviceExist,
|
inputDeviceExist,
|
||||||
outputDeviceExist,
|
outputDeviceExist,
|
||||||
audioInputs,
|
audioInputs,
|
||||||
@@ -92,6 +123,46 @@ const {
|
|||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const setupToggleInputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleInputHotkey)
|
||||||
|
const setupToggleOutputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleOutputHotkey)
|
||||||
|
|
||||||
|
function setupHotkey(event: KeyboardEvent, model: RemovableRef<string>) {
|
||||||
|
if (event.key === 'Tab' || event.key === 'Enter') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const hotkey = []
|
||||||
|
|
||||||
|
if (event.ctrlKey || event.metaKey)
|
||||||
|
hotkey.push('CommandOrControl')
|
||||||
|
if (event.altKey)
|
||||||
|
hotkey.push('Alt')
|
||||||
|
if (event.shiftKey)
|
||||||
|
hotkey.push('Shift')
|
||||||
|
|
||||||
|
const modifierApplied = hotkey.length > 0
|
||||||
|
|
||||||
|
if (!modifierApplied && ['Escape', 'Backspace', 'Delete'].includes(event.key)) {
|
||||||
|
model.value = ''
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!['Control', 'Shift', 'Alt'].includes(event.key)) {
|
||||||
|
hotkey.push(event.key.toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifierApplied && hotkey.length === 1) {
|
||||||
|
model.value = ''
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
model.value = hotkey.join('+')
|
||||||
|
}
|
||||||
|
|
||||||
async function onCheckForUpdates() {
|
async function onCheckForUpdates() {
|
||||||
const update = await checkForUpdates()
|
const update = await checkForUpdates()
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="save()">
|
<form @submit.prevent="save()">
|
||||||
|
<PrimeDivider align="left">
|
||||||
|
General
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
<PrimeFloatLabel variant="on">
|
<PrimeFloatLabel variant="on">
|
||||||
<PrimeInputText id="displayName" v-model="displayName" fluid autocomplete="off" />
|
<PrimeInputText id="displayName" v-model="displayName" fluid autocomplete="off" />
|
||||||
<label for="displayName">Display name</label>
|
<label for="displayName">Display name</label>
|
||||||
</PrimeFloatLabel>
|
</PrimeFloatLabel>
|
||||||
|
|
||||||
<PrimeDivider />
|
<div class="flex items-center gap-3 mt-6">
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<PrimeButton label="Save" :disabled="!valid" :loading="saving" fluid type="submit" />
|
<PrimeButton label="Save" :disabled="!valid" :loading="saving" fluid type="submit" />
|
||||||
<PrimeButton severity="danger" class="shrink-0" @click="logout()">
|
<PrimeButton severity="danger" class="shrink-0" @click="logout()">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|||||||
7
client/app/plugins/00.build-info.ts
Normal file
7
client/app/plugins/00.build-info.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default defineNuxtPlugin({
|
||||||
|
setup() {
|
||||||
|
console.group('Build Info')
|
||||||
|
console.log(`COMMIT_SHA: ${__COMMIT_SHA__}`)
|
||||||
|
console.groupEnd()
|
||||||
|
},
|
||||||
|
})
|
||||||
45
client/app/plugins/01.register-hotkeys.ts
Normal file
45
client/app/plugins/01.register-hotkeys.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { isRegistered, register, unregister, unregisterAll } from '@tauri-apps/plugin-global-shortcut'
|
||||||
|
|
||||||
|
export default defineNuxtPlugin({
|
||||||
|
async setup() {
|
||||||
|
const { isTauri, toggleInput, toggleOutput } = useApp()
|
||||||
|
const preferences = usePreferences()
|
||||||
|
|
||||||
|
if (!isTauri.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
await unregisterAll()
|
||||||
|
|
||||||
|
watch(preferences.toggleInputHotkey, async (shortcut, prevShortcut) => {
|
||||||
|
await registerHotkey(shortcut, prevShortcut, toggleInput)
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(preferences.toggleOutputHotkey, async (shortcut, prevShortcut) => {
|
||||||
|
await registerHotkey(shortcut, prevShortcut, toggleOutput)
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
async function registerHotkey(shortcut: string, prevShortcut: string | undefined, cb: () => void) {
|
||||||
|
if (!!prevShortcut && await isRegistered(prevShortcut)) {
|
||||||
|
await unregister(prevShortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shortcut)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (await isRegistered(shortcut)) {
|
||||||
|
await unregister(shortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
await register(shortcut, ({ state }) => {
|
||||||
|
if (state !== 'Released')
|
||||||
|
return
|
||||||
|
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -14,9 +14,11 @@
|
|||||||
"@nuxt/fonts": "^0.11.4",
|
"@nuxt/fonts": "^0.11.4",
|
||||||
"@primeuix/themes": "^1.2.5",
|
"@primeuix/themes": "^1.2.5",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
|
"@tauri-apps/plugin-global-shortcut": "~2",
|
||||||
"@tauri-apps/plugin-process": "~2",
|
"@tauri-apps/plugin-process": "~2",
|
||||||
"@tauri-apps/plugin-updater": "~2",
|
"@tauri-apps/plugin-updater": "~2",
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
|
"hotkeys-js": "^4.0.0",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
"mediasoup-client": "^3.16.7",
|
"mediasoup-client": "^3.16.7",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ const instance = $fetch.create({
|
|||||||
baseURL: __API_BASE_URL__,
|
baseURL: __API_BASE_URL__,
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
retry: false,
|
retry: false,
|
||||||
onResponseError({ response }) {
|
onResponseError({ request, response }) {
|
||||||
if (!import.meta.client)
|
if (!import.meta.client)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (typeof request === 'string' && request.endsWith('/me'))
|
||||||
|
return
|
||||||
|
|
||||||
const message = response._data.error || 'Something went wrong'
|
const message = response._data.error || 'Something went wrong'
|
||||||
|
|
||||||
ToastEventBus.emit('add', { severity: 'error', summary: 'Error', detail: message, closable: false, life: 3000 })
|
ToastEventBus.emit('add', { severity: 'error', summary: 'Error', detail: message, closable: false, life: 3000 })
|
||||||
|
|||||||
87
client/src-tauri/Cargo.lock
generated
87
client/src-tauri/Cargo.lock
generated
@@ -93,6 +93,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-global-shortcut",
|
||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
@@ -292,7 +293,7 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -603,7 +604,7 @@ dependencies = [
|
|||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1375,6 +1376,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gethostname"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@@ -1504,6 +1515,24 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "global-hotkey"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"keyboard-types",
|
||||||
|
"objc2 0.6.2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"once_cell",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
"x11rb",
|
||||||
|
"xkeysym",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -4180,6 +4209,21 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-global-shortcut"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405"
|
||||||
|
dependencies = [
|
||||||
|
"global-hotkey",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-log"
|
name = "tauri-plugin-log"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
@@ -5227,7 +5271,7 @@ checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
"windows-result 0.4.0",
|
"windows-result 0.4.0",
|
||||||
"windows-strings 0.5.0",
|
"windows-strings 0.5.0",
|
||||||
]
|
]
|
||||||
@@ -5273,9 +5317,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-numerics"
|
name = "windows-numerics"
|
||||||
@@ -5302,7 +5346,7 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5320,7 +5364,7 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5365,7 +5409,7 @@ version = "0.61.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5405,7 +5449,7 @@ version = "0.53.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
|
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
"windows_aarch64_gnullvm 0.53.0",
|
"windows_aarch64_gnullvm 0.53.0",
|
||||||
"windows_aarch64_msvc 0.53.0",
|
"windows_aarch64_msvc 0.53.0",
|
||||||
"windows_i686_gnu 0.53.0",
|
"windows_i686_gnu 0.53.0",
|
||||||
@@ -5431,7 +5475,7 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "700dad7c058606087f6fdc1f88da5841e06da40334413c6cd4367b25ef26d24e"
|
checksum = "700dad7c058606087f6fdc1f88da5841e06da40334413c6cd4367b25ef26d24e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5687,6 +5731,23 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||||
|
dependencies = [
|
||||||
|
"gethostname",
|
||||||
|
"rustix",
|
||||||
|
"x11rb-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb-protocol"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xattr"
|
name = "xattr"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
@@ -5697,6 +5758,12 @@ dependencies = [
|
|||||||
"rustix",
|
"rustix",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xkeysym"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ tauri-plugin-log = "2"
|
|||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
|
tauri-plugin-global-shortcut = "2"
|
||||||
tauri-plugin-single-instance = "2"
|
tauri-plugin-single-instance = "2"
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"updater:default"
|
"updater:default",
|
||||||
|
"global-shortcut:allow-is-registered",
|
||||||
|
"global-shortcut:allow-register",
|
||||||
|
"global-shortcut:allow-unregister",
|
||||||
|
"global-shortcut:allow-unregister-all"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_single_instance::init(|_, _, _| {
|
.plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
|
||||||
}))
|
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
app.handle().plugin(
|
app.handle().plugin(
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ model Session {
|
|||||||
|
|
||||||
model UserPreferences {
|
model UserPreferences {
|
||||||
userId String @id
|
userId String @id
|
||||||
toggleInputHotkey String?
|
toggleInputHotkey String? @default("")
|
||||||
toggleOutputHotkey String?
|
toggleOutputHotkey String? @default("")
|
||||||
volumes Json?
|
volumes Json? @default("{}")
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user